UNPKG

@sema-agent/core

Version:

Stateless, task-oriented AI agent core

176 lines (175 loc) • 9.84 kB
import { escapeForDisclosure } from "./permission-rule-model.js"; import { PERMISSION_RULE_WRITER, errText, isValidDurableScope, 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 = []; const illegal = durableRead.rules.filter((r) => !isValidDurableScope(r.scope)); const droppedRows = illegal.map((r) => ({ rule: String(r.rule), scopeRoot: scopeRootAsServed(r.scope.root) })); if (illegal.length > 0) disclosures.push(`the persisted rule partition served ${illegal.length} row(s) whose project scope root is not a complete absolute path — dropped, not adjudicated: ${illegal.slice(0, 3).map((r) => `${escapeForDisclosure(r.rule)} (${escapeForDisclosure(r.scope.root)})`).join(", ")}`); for (const r of [...sessionRead, ...durableRead.rules.filter((x) => !illegal.includes(x))]) { 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, ...(droppedRows.length > 0 ? { droppedRows } : {}) }; } 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 scopeRootAsServed(root) { return typeof root === "string" ? root : `(${root === null ? "null" : typeof root})`; } 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); }