@sema-agent/core
Version:
Stateless, task-oriented AI agent core
169 lines (168 loc) • 9.06 kB
JavaScript
import { escapeForDisclosure } from "./permission-rule-model.js";
import { PERMISSION_RULE_WRITER, errText, normalizePersistedRule, sameRuleIdentity, writerOf, } from "./permission-rule-store.js";
import { UNGOVERNED_ORG_RESOLUTION, compileOrgShadowPredicate, createOrgRulePartition } from "./permission-rule-org.js";
export function ruleSourceOf(scope) {
return scope.kind === "global" ? "user" : scope.kind;
}
export function ownerStoreOf(provider, principal, localOwnerDeclared) {
const anonymous = principal === undefined || principal === "";
return anonymous && localOwnerDeclared && provider.forLocalOwner !== undefined ? provider.forLocalOwner() : provider.forPrincipal(anonymous ? undefined : principal);
}
function usableSessionId(sessionId) {
return typeof sessionId === "string" && sessionId !== "" ? sessionId : undefined;
}
const EMPTY_DURABLE = { rules: [], tombstones: [], rev: 0 };
export function createPermissionRuleStoreProvider(cfg) {
if (cfg === null || typeof cfg !== "object") {
throw new Error("createPermissionRuleStoreProvider takes a config object naming the partitions to compose");
}
const durable = cfg.durable;
const org = cfg.org !== undefined ? createOrgRulePartition(cfg.org) : undefined;
const session = cfg.session;
if (session !== undefined && (typeof session.read !== "function" || typeof session.apply !== "function")) {
throw new Error("the session partition must implement read(sessionId) and apply(sessionId, add) — refusing a partial face rather than serving a session source that cannot answer");
}
const partitions = Object.freeze({ durable: durable !== undefined, org: org !== undefined, session: session !== undefined });
const storeOver = (partition) => new UnifiedPermissionRuleStore(partition, org, session);
const provider = {
partitions,
forPrincipal: (principal) => storeOver(durable?.forPrincipal(principal)),
...(durable?.forLocalOwner !== undefined ? { forLocalOwner: () => storeOver(durable.forLocalOwner()) } : {}),
};
return provider;
}
class UnifiedPermissionRuleStore {
durable;
org;
session;
durability;
fidelity;
constructor(durable, org, session) {
this.durable = durable;
this.org = org;
this.session = session;
this.durability = durable?.durability ?? "process-local";
this.fidelity = durable?.fidelity;
if (durable?.quarantined !== undefined) {
this.quarantined = () => durable.quarantined();
}
if (durable !== undefined) {
Object.defineProperty(this, PERMISSION_RULE_WRITER, {
get: () => {
const w = writerOf(durable);
return w === undefined ? undefined : routingWriter(w, session);
},
enumerable: false,
});
}
}
quarantined;
async effective(query) {
const disclosures = [];
const [orgSettled, durableSettled, sessionRows] = await Promise.all([
this.org !== undefined ? this.org.resolve() : Promise.resolve({ ...UNGOVERNED_ORG_RESOLUTION, rules: [], disclosures: [] }),
this.durable !== undefined ? this.durable.list().then((v) => ({ ok: true, v }), (e) => ({ ok: false, e })) : Promise.resolve({ ok: true, v: EMPTY_DURABLE }),
this.readSessionRows(usableSessionId(query?.sessionId), disclosures),
]);
const orgResolution = orgSettled;
const unreadable = [];
const unreadableSources = [];
if (!durableSettled.ok) {
unreadable.push(errText(durableSettled.e));
unreadableSources.push("user", "project");
}
if ("unreadable" in sessionRows) {
unreadable.push(sessionRows.unreadable);
unreadableSources.push("session");
}
const failed = unreadable.length > 0 ? { unreadable: unreadable.join("; "), unreadableSources } : {};
const durableRead = durableSettled.ok ? durableSettled.v : EMPTY_DURABLE;
const sessionRead = "unreadable" in sessionRows ? [] : sessionRows;
const shadowed = compileOrgShadowPredicate(orgResolution.rules);
const rules = [];
for (const r of [...sessionRead, ...durableRead.rules]) {
rules.push({ ...r, source: ruleSourceOf(r.scope), status: r.behavior === "allow" && shadowed(r) ? "shadowed-by-org" : "live" });
}
const removed = [];
for (const t of durableRead.tombstones) {
if (durableRead.rules.some((r) => sameRuleIdentity(r, t)))
continue;
if (removed.some((e) => sameRuleIdentity(e, t)))
continue;
if (t.scope.kind === "session")
continue;
removed.push({ behavior: t.behavior, rule: t.rule, scope: t.scope, source: ruleSourceOf(t.scope), status: "removed" });
}
return { rules, removed, org: orgResolution, rev: durableRead.rev, disclosures, ...failed };
}
async readSessionRows(sessionId, disclosures) {
if (this.session === undefined || sessionId === undefined)
return [];
try {
const served = structuredClone(await this.session.read(sessionId));
const rows = served.filter((r) => {
const scope = r?.scope;
return scope?.kind === "session" && scope.sessionId === sessionId && !("reject" in normalizePersistedRule(r));
});
if (rows.length < served.length) {
disclosures.push(`the session rule partition served ${served.length - rows.length} row(s) that are not canonical session rows of this session — dropped, not adjudicated`);
}
return rows;
}
catch (err) {
const why = `the session rule partition could not be read: ${escapeForDisclosure(errText(err))}`;
disclosures.push(why);
return { unreadable: why };
}
}
}
function routingWriter(durable, session) {
return {
nextDot: () => durable.nextDot(),
readRaw: () => durable.readRaw(),
apply: async (delta, opts) => {
const isAdd = delta.kind === "redemption-add" || delta.kind === "tighten-add";
const scope = isAdd ? structuredClone(delta.scope) : undefined;
if (!isAdd || scope?.kind !== "session")
return durable.apply(delta, opts);
if (session === undefined) {
const e = new Error(`a session-scope rule has nowhere to land — this deployment configured no session rule partition (createPermissionRuleStoreProvider({ session })); refusing the ${delta.kind}`);
e.code = "unsupported.session_scope_store";
throw e;
}
const outcome = await session.apply(scope.sessionId, { behavior: delta.behavior, rule: delta.rule, tool: delta.tool, match: delta.match, command: delta.command, add: delta.add });
const answered = outcome !== null && typeof outcome === "object" ? outcome : {};
const landed = answered.landed === true;
const sealed = answered.refused === "session_ended";
if (landed !== sealed)
return landed ? { rev: opts.expectedRev } : { refused: "session_ended", rev: opts.expectedRev };
throw new Error(`the session rule partition answered the ${delta.kind} with ${landed ? "both of its arms at once" : "neither of its two arms"} (landed / refused) — nothing here can say whether the authorization landed; reconcile by reading the session's rules`);
},
};
}
export async function effectiveOrThrow(store, query) {
const view = await store.effective(query);
if (view.unreadable !== undefined)
throw new Error(view.unreadable);
return view;
}
export async function effectivePermissionRules(opts) {
return await resolveOwnerStore(opts).effective(opts.sessionId !== undefined ? { sessionId: opts.sessionId } : undefined);
}
export function resolveOwnerStore(opts) {
if (opts.owner !== undefined && opts.owner.kind === "local-owner") {
if (opts.principal !== undefined) {
throw new Error("pass either a principal or a local-owner, not both — a bucket has one owner");
}
const store = opts.provider.forLocalOwner?.();
if (store === undefined) {
throw new Error("this provider has no local-owner bucket (forLocalOwner is not implemented)");
}
return store;
}
if (opts.owner !== undefined && opts.owner.kind === "principal" && opts.principal !== undefined && opts.owner.principal !== opts.principal) {
throw new Error(`contradictory identity: principal "${escapeForDisclosure(opts.principal)}" and owner principal "${escapeForDisclosure(opts.owner.principal)}" disagree`);
}
const principal = opts.owner?.kind === "principal" ? opts.owner.principal : opts.principal;
return opts.provider.forPrincipal(principal);
}