UNPKG

@sema-agent/core

Version:

Stateless, task-oriented AI agent core

288 lines (287 loc) • 15.9 kB
import { escapeForDisclosure, parseRuleText, programRunReachOf, ruleReachesProgramRun } from "./permission-rule-model.js"; import { writerOf } from "./permission-rule-store.js"; export const ORG_UNAVAILABLE_DECISION_REASON = "org_unavailable"; export const ORG_ADJUDICATION_TIMEOUT_MS = 15_000; export function settleOrgVerdictWithin(p, fallback, opts) { return new Promise((resolve) => { let settled = false; const finish = (v, cause) => { if (settled) return; settled = true; clearTimeout(timer); opts.signal?.removeEventListener("abort", onAbort); if (cause !== undefined) { try { opts.onFallback?.(cause); } catch { } } resolve(v); }; const onAbort = () => finish(fallback, "aborted"); const timer = setTimeout(() => finish(fallback, "timeout"), opts.timeoutMs); if (opts.signal?.aborted === true) { finish(fallback, "aborted"); return; } opts.signal?.addEventListener("abort", onAbort); p.then((v) => finish(v), () => finish(fallback)); }); } export const ORG_RULE_DECISION_REASON = "org_rule"; export const ORG_FETCHED_AT_SKEW_ALLOWANCE_MS = 5 * 60_000; export const UNGOVERNED_ORG_RESOLUTION = Object.freeze({ source: "org", status: "ungoverned", rules: [], disclosures: [] }); export function createOrgRulePartition(cfg) { if (cfg === null || typeof cfg !== "object") { throw new Error("the org partition config must be an object naming a snapshot source and a staleness bound"); } if (cfg.source === undefined || typeof cfg.source.current !== "function") { throw new Error("org-governed is declared but no OrgRuleSnapshotProvider is wired as `org.source` — a governed deployment without an org source is a configuration contradiction; refusing to boot rather than running silently ungoverned"); } if (typeof cfg.stalenessBoundMs !== "number" || !Number.isFinite(cfg.stalenessBoundMs) || cfg.stalenessBoundMs <= 0) { throw new Error(`org stalenessBoundMs must be a finite positive number of milliseconds (got ${escapeForDisclosure(cfg.stalenessBoundMs)})`); } const provider = cfg.source; const now = cfg.now ?? Date.now; let memoryState; const readState = async () => { const state = cfg.persistence !== undefined ? await cfg.persistence.readOrgState() : memoryState; return state === undefined ? undefined : structuredClone(state); }; const writeState = async (state) => { if (cfg.persistence !== undefined) await cfg.persistence.installOrgState(structuredClone(state)); else memoryState = structuredClone(state); }; let chain = Promise.resolve(); const serialize = (fn) => { const run = chain.then(fn, fn); chain = run.then(() => undefined, () => undefined); return run; }; const resolve = async () => { const disclosures = []; if (cfg.persistence === undefined) { disclosures.push("org rule state has no durable persistence wired — the last-known-good snapshot and the anti-rollback mark do not survive a restart"); } let fetched; let fetchFailed = false; const settled = await settleOrgVerdictWithin(provider.current().then((v) => ({ v }), (e) => ({ e })), { timedOut: true }, { timeoutMs: ORG_ADJUDICATION_TIMEOUT_MS }); try { if ("timedOut" in settled) throw new Error(`the org snapshot source did not answer within ${ORG_ADJUDICATION_TIMEOUT_MS}ms`); if ("e" in settled) throw settled.e; fetched = structuredClone(settled.v); } catch (err) { fetchFailed = true; disclosures.push(`org snapshot source failed: ${escapeForDisclosure(err instanceof Error ? err.message : err)}`); } return await settleOrgVerdictWithin(serialize(async () => { const nowMs = now(); const guardedRead = async (context) => { try { return await readState(); } catch (err) { disclosures.push(`org state ${context} read failed: ${escapeForDisclosure(err instanceof Error ? err.message : err)}`); return undefined; } }; const installed = await (async () => { const persisted = await guardedRead("persistence"); if (fetched === undefined) return persisted; const invalid = validateOrgSnapshot(fetched, nowMs); if (invalid !== undefined) { disclosures.push(`org snapshot refused: ${invalid}; the previously installed snapshot (if any) remains in force`); return persisted; } const persistedFloor = persisted === undefined ? undefined : antiRollbackFloor(persisted); if (persisted !== undefined && !persistedMarkIsCoherent(persisted)) { disclosures.push(`the durable org state carries a high-water mark (${escapeForDisclosure(persisted.revisionHighWater)}) that is not a finite number at or above its own snapshot revision (${escapeForDisclosure(persisted.snapshot?.revision)}) — the anti-rollback fence is being held at ${escapeForDisclosure(persistedFloor)} instead, and the durable state should be repaired`); } if (persistedFloor !== undefined && fetched.revision < persistedFloor) { disclosures.push(`org snapshot revision ${fetched.revision} is below the installed high-water mark ${escapeForDisclosure(persistedFloor)} — refusing the rollback; the current deny set is unchanged`); return persisted; } if (persisted !== undefined && fetched.revision === persisted.snapshot.revision && sameOrgPolicyContent(fetched, persisted.snapshot) && fetched.fetchedAtMs <= persisted.snapshot.fetchedAtMs) { return persisted; } if (persisted !== undefined && fetched.revision === persisted.snapshot.revision && !sameOrgPolicyContent(fetched, persisted.snapshot)) { disclosures.push(`org snapshot revision ${fetched.revision} equals the installed revision but carries DIFFERENT policy content — refusing the swap; the current deny set is unchanged`); return persisted; } const state = { revisionHighWater: Math.max(persistedFloor !== undefined && Number.isFinite(persistedFloor) ? persistedFloor : 0, fetched.revision), snapshot: fetched, installedAtMs: nowMs, }; try { await writeState(state); } catch (err) { const reread = await guardedRead("reconciliation"); if (reread !== undefined && antiRollbackFloor(reread) >= state.revisionHighWater) { disclosures.push(`org snapshot install was superseded by a concurrent install at revision ${escapeForDisclosure(reread.snapshot.revision)} — continuing on the newer installed state`); return reread; } disclosures.push(`org snapshot install failed (${escapeForDisclosure(err instanceof Error ? err.message : err)}) and no superseding installed state exists — org adjudication is UNAVAILABLE`); return undefined; } return state; })(); const decisionMs = now(); const withinBound = (snap) => decisionMs - snap.fetchedAtMs <= cfg.stalenessBoundMs && snap.fetchedAtMs <= decisionMs + ORG_FETCHED_AT_SKEW_ALLOWANCE_MS; if (installed !== undefined) { if (installed.snapshot.fetchedAtMs > decisionMs + ORG_FETCHED_AT_SKEW_ALLOWANCE_MS) { disclosures.push(`the installed org snapshot claims a FUTURE observation time (${escapeForDisclosure(installed.snapshot.fetchedAtMs)} vs now ${decisionMs}) — org adjudication is UNAVAILABLE; the consuming gate must tighten every terminal allow to a real-approval ask`); return { source: "org", status: "unavailable", rules: [], disclosures }; } const servingRefusal = validateOrgSnapshot(installed.snapshot, decisionMs); if (servingRefusal !== undefined) { disclosures.push(`the installed org snapshot (revision ${escapeForDisclosure(installed.snapshot.revision)}) does not meet this version's validator: ${servingRefusal} — org adjudication is UNAVAILABLE; the consuming gate must tighten every terminal allow to a real-approval ask`); return { source: "org", status: "unavailable", rules: [], disclosures }; } if (withinBound(installed.snapshot)) { const freshlyInstalled = fetched !== undefined && installed.snapshot === fetched; if (!freshlyInstalled && !fetchFailed && fetched === undefined) { disclosures.push("org snapshot source returned no snapshot — continuing on the last-known-good"); } return { source: "org", status: freshlyInstalled ? "fresh" : "last-known-good", rules: structuredClone(installed.snapshot.rules), revision: installed.snapshot.revision, disclosures, }; } disclosures.push(`the newest org snapshot was observed ${decisionMs - installed.snapshot.fetchedAtMs}ms ago (bound ${cfg.stalenessBoundMs}ms) — org adjudication is UNAVAILABLE; the consuming gate must tighten every terminal allow to a real-approval ask`); return { source: "org", status: "unavailable", rules: [], disclosures }; } disclosures.push("no org snapshot has ever been installed — org adjudication is UNAVAILABLE; the consuming gate must tighten every terminal allow to a real-approval ask"); return { source: "org", status: "unavailable", rules: [], disclosures }; }), { source: "org", status: "unavailable", rules: [], disclosures: [...disclosures, `org state persistence did not answer within ${ORG_ADJUDICATION_TIMEOUT_MS}ms — org adjudication is UNAVAILABLE; the consuming gate must tighten every terminal allow to a real-approval ask`], }, { timeoutMs: ORG_ADJUDICATION_TIMEOUT_MS }); }; return { resolve }; } function antiRollbackFloor(state) { const mark = typeof state.revisionHighWater === "number" && Number.isFinite(state.revisionHighWater) ? state.revisionHighWater : undefined; const own = typeof state.snapshot?.revision === "number" && Number.isFinite(state.snapshot.revision) ? state.snapshot.revision : undefined; if (mark === undefined) return own ?? Number.NEGATIVE_INFINITY; return own === undefined ? mark : Math.max(mark, own); } function persistedMarkIsCoherent(state) { return (typeof state.revisionHighWater === "number" && Number.isFinite(state.revisionHighWater) && typeof state.snapshot?.revision === "number" && Number.isFinite(state.snapshot.revision) && state.revisionHighWater >= state.snapshot.revision); } function sameOrgPolicyContent(a, b) { if (a.rules.length !== b.rules.length) return false; return a.rules.every((r, i) => b.rules[i]?.rule === r.rule && b.rules[i]?.behavior === r.behavior); } const DISCLOSED_UNENFORCEABLE_RULES = 20; export function unenforceableOrgRules(rules) { const out = []; for (const r of rules) { if (typeof r?.rule !== "string" || r.rule === "") continue; const parsed = parseRuleText(r.rule, r.behavior); if ("reject" in parsed) { out.push({ rule: r.rule, code: parsed.reject.code }); continue; } if (parsed.rule.tool !== "Bash") out.push({ rule: r.rule, code: "unsupported.tool" }); } return out; } function describeUnenforceable(unenforceable) { const named = unenforceable .slice(0, DISCLOSED_UNENFORCEABLE_RULES) .map((u) => `"${escapeForDisclosure(u.rule)}" (${u.code})`) .join(", "); const more = unenforceable.length - DISCLOSED_UNENFORCEABLE_RULES; return more > 0 ? `${named} and ${more} more` : named; } function validateOrgSnapshot(s, nowMs) { if (typeof s.revision !== "number" || !Number.isFinite(s.revision)) return `revision is not a finite number (${escapeForDisclosure(s.revision)})`; if (typeof s.fetchedAtMs !== "number" || !Number.isFinite(s.fetchedAtMs)) return `fetchedAtMs is not a finite number (${escapeForDisclosure(s.fetchedAtMs)})`; if (s.fetchedAtMs > nowMs + ORG_FETCHED_AT_SKEW_ALLOWANCE_MS) { return `fetchedAtMs is ${s.fetchedAtMs - nowMs}ms in the future (allowance ${ORG_FETCHED_AT_SKEW_ALLOWANCE_MS}ms) — a future-dated observation would satisfy the staleness bound indefinitely`; } if (!Array.isArray(s.rules)) return "rules is not an array"; for (const r of s.rules) { if (typeof r?.rule !== "string" || r.rule === "") return "a rule entry carries no rule text"; if (r.behavior !== "deny" && r.behavior !== "ask") { return `rule "${escapeForDisclosure(r.rule)}" carries behavior "${escapeForDisclosure(r.behavior)}" — the org layer has no allow bucket`; } } const unenforceable = unenforceableOrgRules(s.rules); const first = unenforceable[0]; if (first !== undefined) { const rest = unenforceable.length > 1 ? ` (${unenforceable.length} in all — ${describeUnenforceable(unenforceable)})` : ""; return `rule "${escapeForDisclosure(first.rule)}" does not parse (${first.code}) — an unenforceable deny/ask must not stand as policy${rest}`; } return undefined; } export function orgRuleVerdictFor(rules, call) { let ask; let unreadable; for (const r of rules) { const parsed = parseRuleText(r.rule, r.behavior); if ("reject" in parsed) continue; if (parsed.rule.tool !== call.tool) continue; const outcome = programRunReachOf(parsed.rule, call.command); if (outcome.reach === "not_reached") continue; if (outcome.reach === "unreadable") { unreadable ??= { behavior: "ask", rule: r.rule, unreadable: outcome.reason }; continue; } if (r.behavior === "deny") return { behavior: "deny", rule: r.rule }; ask ??= { behavior: "ask", rule: r.rule }; } return unreadable ?? ask; } export function orgRuleShadows(orgRules, rule) { return compileOrgShadowPredicate(orgRules)(rule); } export function compileOrgShadowPredicate(orgRules) { const denies = []; for (const d of orgRules) { if (d.behavior !== "deny") continue; const parsed = parseRuleText(d.rule, "deny"); if (!("reject" in parsed)) denies.push(parsed.rule); } return (rule) => { if (rule.tool !== "Bash" || denies.length === 0) return false; return denies.some((d) => d.tool === rule.tool && ruleReachesProgramRun(d, rule.command)); }; } export function isWritablePermissionRuleStore(store) { return writerOf(store) !== undefined; }