UNPKG

@sema-agent/core

Version:

Stateless, task-oriented AI agent core

141 lines (140 loc) • 8.3 kB
import { readRowBehavior } from "./permission-rule-model.js"; import { isValidConsentScope, normalizePersistedRule } from "./permission-rule-store.js"; function isValidOverlayAdd(add) { const a = add; if (typeof a?.dot?.actor !== "string" || a.dot.actor === "" || typeof a.dot.counter !== "number" || !Number.isFinite(a.dot.counter)) return false; if (a.origin !== "user" && a.origin !== "imported-cc" && a.origin !== "starter") return false; return typeof a.createdAt === "string"; } export class InMemorySessionRulePartition { rows = new Map(); sealed = new Set(); epochs = new Map(); bumpEpoch(sessionId) { this.epochs.set(sessionId, (this.epochs.get(sessionId) ?? 0) + 1); } async read(sessionId) { return structuredClone(this.rows.get(sessionId) ?? []); } async apply(sessionId, add) { if (this.sealed.has(sessionId)) return { refused: "session_ended" }; add = structuredClone(add); if (this.sealed.has(sessionId)) return { refused: "session_ended" }; if (!isValidOverlayAdd(add?.add)) { throw new Error("SessionRulePartition.apply was handed a malformed add (dot/origin/createdAt) — evidence this shape cannot vouch for must not install"); } const scope = { kind: "session", sessionId }; if (!isValidConsentScope(scope)) { throw new Error("SessionRulePartition.apply was addressed with an unusable session id — a session nobody can name is not a scope a grant can live under"); } const projected = { behavior: add.behavior, rule: add.rule, tool: add.tool, match: add.match, command: add.command, scope, adds: [add.add] }; if ("reject" in normalizePersistedRule(projected)) { throw new Error("SessionRulePartition.apply was handed a row whose text/metadata do not survive the canonical re-projection — a row the engine cannot re-project can never adjudicate, and would refuse its own restore"); } const rows = this.rows.get(sessionId) ?? []; if (!this.rows.has(sessionId)) this.rows.set(sessionId, rows); const sameDotAs = (a) => a.dot.actor === add.add.dot.actor && a.dot.counter === add.add.dot.counter; for (const [heldSession, heldRows] of this.rows) { const holder = heldRows.find((r) => r.adds.some(sameDotAs)); if (holder !== undefined && (heldSession !== sessionId || holder.rule !== add.rule || holder.behavior !== add.behavior)) { throw new Error(`SessionRulePartition.apply: dot ${add.add.dot.actor}#${add.add.dot.counter} already vouches for another grant — a dot identifies one add of one (behavior, rule, session), ever`); } } const existing = rows.find((r) => r.rule === add.rule && r.behavior === add.behavior); if (existing === undefined) { rows.push({ behavior: add.behavior, rule: add.rule, tool: add.tool, match: add.match, command: add.command, scope, adds: [structuredClone(add.add)] }); this.bumpEpoch(sessionId); return { landed: true }; } if (!existing.adds.some((a) => a.dot.actor === add.add.dot.actor && a.dot.counter === add.add.dot.counter)) { existing.adds.push(structuredClone(add.add)); this.bumpEpoch(sessionId); } return { landed: true }; } endSession(sessionId) { this.sealed.add(sessionId); this.rows.delete(sessionId); this.bumpEpoch(sessionId); } async snapshotSession(sessionId) { return this.read(sessionId); } async restoreSession(sessionId, rows) { if (this.sealed.has(sessionId)) { throw new Error(`session "${sessionId}" has ended — a sealed session's authorizations cannot be restored (termination is one-way)`); } const epochBeforeRead = this.epochs.get(sessionId) ?? 0; rows = structuredClone(rows); if (this.sealed.has(sessionId)) { throw new Error(`session "${sessionId}" has ended — a sealed session's authorizations cannot be restored (termination is one-way)`); } if ((this.epochs.get(sessionId) ?? 0) !== epochBeforeRead) { throw new Error(`restoreSession("${sessionId}") raced a landing on the same session — refusing the whole restore (this door replaces the session's rows, and a grant that already answered landed must not be erased by a resume)`); } for (const r of rows) { const behavior = readRowBehavior(r?.behavior); if (behavior !== undefined && r !== null && typeof r === "object") r.behavior = behavior; const scope = r?.scope; if (scope?.kind !== "session" || scope.sessionId !== sessionId || !isValidConsentScope(scope)) { throw new Error(`restoreSession("${sessionId}") was handed a row that is not a session row of that session — refusing the whole restore (a foreign-scope row through this door would reach adjudication)`); } if ("reject" in normalizePersistedRule(r)) { throw new Error(`restoreSession("${sessionId}") was handed a row whose text/metadata do not survive the canonical re-projection — refusing the whole restore (a row the engine cannot re-project must not reach adjudication)`); } } const identityOf = (r) => `${r.behavior}|${r.rule}`; const ownDotRule = new Map(); for (const held of this.rows.get(sessionId) ?? []) { for (const h of held.adds) ownDotRule.set(`${h.dot.actor}#${h.dot.counter}`, identityOf(held)); } const seenDots = new Map(); for (const r of rows) { if (!Array.isArray(r.adds) || r.adds.length === 0 || !r.adds.every(isValidOverlayAdd)) { throw new Error(`restoreSession("${sessionId}") was handed a row with malformed add evidence — refusing the whole restore (an add whose dot/origin cannot be read is not a consent anyone can audit)`); } for (const a of r.adds) { const key = `${a.dot.actor}#${a.dot.counter}`; const holder = seenDots.get(key); if (holder !== undefined && holder !== identityOf(r)) { throw new Error(`restoreSession("${sessionId}"): dot ${key} vouches for two different rules — refusing the whole restore`); } seenDots.set(key, identityOf(r)); const ownHolder = ownDotRule.get(key); if (ownHolder !== undefined && ownHolder !== identityOf(r)) { throw new Error(`restoreSession("${sessionId}"): dot ${key} is live in this session under a different rule — a restore may replay a grant, never reassign its dot; refusing the whole restore`); } for (const [heldSession, heldRows] of this.rows) { if (heldSession === sessionId) continue; if (heldRows.some((held) => held.adds.some((h) => h.dot.actor === a.dot.actor && h.dot.counter === a.dot.counter))) { throw new Error(`restoreSession("${sessionId}"): dot ${key} already vouches for another live session's grant — refusing the whole restore`); } } } } const restoredDots = new Set(rows.flatMap((r) => r.adds.map((a) => `${a.dot.actor}#${a.dot.counter}`))); const carried = []; for (const held of this.rows.get(sessionId) ?? []) { if (held.behavior === "allow") continue; const adds = held.adds.filter((a) => !restoredDots.has(`${a.dot.actor}#${a.dot.counter}`)); if (adds.length === 0) continue; const same = rows.find((r) => r.behavior === held.behavior && r.rule === held.rule); if (same !== undefined) same.adds.push(...structuredClone(adds)); else carried.push({ ...structuredClone(held), adds: structuredClone(adds) }); } this.rows.set(sessionId, [...rows, ...carried]); this.bumpEpoch(sessionId); } }