UNPKG

@sema-agent/core

Version:

Stateless, task-oriented AI agent core

205 lines (204 loc) • 10.2 kB
import { callTargetReadingOf } from "./effective-path-target.js"; import { advertisedWritableRootsSeat, attestationOf, describeThrown, exemptRootsSeat, refuseOutOfContractDecision } from "./tool-policy.js"; import { ownedClearanceFacts, ruleClearanceOf } from "./ask-origin.js"; import { skillRowRefusal } from "./skill-grant.js"; import { applyPersistedTightening } from "./persisted-rule-arms.js"; import { readOrgRuleLane, readPersistedRuleLane } from "./rule-lane-reads.js"; import { ORG_ADJUDICATION_TIMEOUT_MS, settleOrgVerdictWithin } from "./permission-rule-org.js"; import { deepJsonEqual } from "./runner/decide-continuation.js"; export const SYNTHETIC_READ_LAYERS = Object.freeze(["skill_rows", "narrowing", "caller_policy", "inherited_constraint", "persisted_rules", "org_rules"]); export const SYNTHETIC_READ_UNANSWERABLE_CAUSES = Object.freeze(["threw", "timed_out", "approval_seat", "call_unreadable", "org_unavailable", "rewritten", "store_unreadable"]); export function syntheticReadProbeId(face) { return `${probeFaceWord(face)}-visibility-probe`; } export function syntheticWriteProbeId(face) { return `${probeFaceWord(face)}-write-probe`; } function probeFaceWord(face) { if (!/^[a-z][a-z-]*$/.test(face)) throw new Error(`synthetic probe id: the probe face must be a lowercase word (letters and hyphens), got ${JSON.stringify(face)}`); return face; } function sameArguments(value, probed) { try { return deepJsonEqual(value, probed); } catch { return false; } } function layerWait(run) { const wait = new AbortController(); const runEnded = () => wait.abort(run?.reason); if (run?.aborted === true) runEnded(); else run?.addEventListener("abort", runEnded, { once: true }); return { signal: wait.signal, end: () => { run?.removeEventListener("abort", runEnded); if (!wait.signal.aborted) wait.abort(new DOMException("the judgment stopped waiting for this layer", "TimeoutError")); }, }; } export function createSyntheticReadJudge(layers) { const judge = createProbeJudge(layers, "read"); return async (call, face, budget) => { const probe = { toolName: "Read", args: { file_path: call.file_path, ...(call.offset !== undefined ? { offset: call.offset } : {}), ...(call.limit !== undefined ? { limit: call.limit } : {}) }, toolCallId: syntheticReadProbeId(face), ...(call.cwd !== undefined ? { cwd: call.cwd } : {}), ...(call.face !== undefined ? { face: call.face } : {}), }; const v = await judge(probe, budget); return v.verdict === "ask" ? { ...v, verdict: "mandated_ask" } : v; }; } export function createSyntheticWriteJudge(layers, facts) { const judge = createProbeJudge(layers, "write"); return async (call, face, budget) => { const f = facts(); const probe = { toolName: "Write", args: { file_path: call.file_path, content: "" }, toolCallId: syntheticWriteProbeId(face), ...(f.cwd !== undefined ? { cwd: f.cwd } : {}), ...(f.root !== undefined ? { root: f.root } : {}), ...(f.face !== undefined ? { face: f.face } : {}), ...advertisedWritableRootsSeat(f.advertisedWritableDirs), ...(f.exemptRoots !== undefined ? exemptRootsSeat(f.exemptRoots) : {}), }; return judge(probe, budget); }; } function createProbeJudge(layers, kind) { const everyAskHeld = kind === "write"; return async (unstated, budget) => { const deadlineAt = budget?.deadlineAt ?? Date.now() + ORG_ADJUDICATION_TIMEOUT_MS; const left = () => Math.max(0, Math.min(ORG_ADJUDICATION_TIMEOUT_MS, deadlineAt - Date.now())); const probe = { ...unstated, approvalReach: "none" }; const probedArgs = structuredClone(probe.args); const stamped = layers.stampCallTarget?.(probe, layers.signal); const req = stamped !== undefined ? await stamped : probe; const view = callTargetReadingOf(req)?.protective; const judged = view?.kind === "path" && view.canonical !== undefined ? { judged: view.canonical } : {}; let unanswered; let held; const cannot = (layer, cause, reason) => { unanswered ??= { verdict: "unanswerable", layer, cause, reason }; }; const ask = (layer, message, mandated) => { if (everyAskHeld || mandated) held ??= { verdict: "ask", layer, ...(message !== undefined ? { message } : {}), ...judged }; }; const spent = (layer) => { if (left() > 0) return false; cannot(layer, "timed_out", "the judgment's wait budget was spent before this layer was asked"); return true; }; const policyLayer = async (layer, policy, ancestorOwned = false) => { if (spent(layer)) return undefined; const waitMs = left(); let timedOut = false; let thrown = undefined; const TIMEOUT = Symbol("timeout"); let d; const wait = layerWait(layers.signal); try { d = await settleOrgVerdictWithin(Promise.resolve() .then(() => policy.check(req, wait.signal)) .catch((err) => { thrown = { err }; return TIMEOUT; }), TIMEOUT, { signal: wait.signal, timeoutMs: waitMs, onFallback: () => void (timedOut = true) }); } catch (err) { cannot(layer, "threw", describeThrown(err)); return undefined; } finally { wait.end(); } if (thrown !== undefined) { cannot(layer, "threw", describeThrown(thrown.err)); return undefined; } if (d === TIMEOUT || timedOut) { cannot(layer, "timed_out", `no answer within ${waitMs}ms (or the run ended first)`); return undefined; } let read; try { read = refuseOutOfContractDecision(d); } catch (err) { cannot(layer, "threw", describeThrown(err)); return undefined; } if (read.action !== "deny" && ((read.updatedInput !== undefined && !sameArguments(read.updatedInput, probedArgs)) || !sameArguments(req.args, probedArgs))) { cannot(layer, "rewritten", "the layer would rewrite the call's arguments, so its answer is not about this call"); } if (read.action === "ask") { ask(layer, read.message, ancestorOwned || ruleClearanceOf(ownedClearanceFacts(read), {}).mandated); return undefined; } if (read.action !== "deny") return undefined; if (attestationOf(read, req)?.settlement !== undefined) { cannot(layer, "approval_seat", "the policy would ask an approver, who is never consulted for a synthetic probe"); return undefined; } return { verdict: "deny", layer, ...(read.message !== undefined ? { message: read.message } : {}), ...judged }; }; try { const refusal = skillRowRefusal(layers.skillGrants, req); if (refusal !== undefined) return { verdict: "deny", layer: "skill_rows", ...(refusal.message !== undefined ? { message: refusal.message } : {}), ...judged }; } catch (err) { cannot("skill_rows", "threw", describeThrown(err)); } if (layers.denyNarrowingPolicy !== undefined) { const v = await policyLayer("narrowing", layers.denyNarrowingPolicy); if (v !== undefined) return v; } if (layers.callerPolicy !== undefined) { const v = await policyLayer("caller_policy", layers.callerPolicy, (layers.inheritedPolicies ?? []).includes(layers.callerPolicy)); if (v !== undefined) return v; } for (const parent of layers.inheritedPolicies ?? []) { if (parent === layers.callerPolicy) continue; const v = await policyLayer("inherited_constraint", parent, true); if (v !== undefined) return v; } if (layers.persistedRuleLane !== undefined && !spent("persisted_rules")) { const read = await readPersistedRuleLane(layers.persistedRuleLane, req, layers.signal, left()); const tightened = applyPersistedTightening({ action: "allow" }, read).tightened; if (tightened === "deny") return { verdict: "deny", layer: "persisted_rules", ...judged }; if (tightened === "ask") ask("persisted_rules", undefined, false); if (read.unreadable === true) cannot("persisted_rules", read.reason !== undefined ? "call_unreadable" : "store_unreadable", read.reason ?? "a partition of the person's rule store could not be read, so whether a standing rule forbids this call is unknown"); } if (layers.orgRuleLane !== undefined && !spent("org_rules")) { const org = await readOrgRuleLane(layers.orgRuleLane, req, layers.signal, left()); if (org.status === "unavailable") cannot("org_rules", left() > 0 ? "org_unavailable" : "timed_out", org.disclosures.join("; ") || "organization governance could not be read"); else if (org.verdict?.behavior === "deny") return { verdict: "deny", layer: "org_rules", message: `an organization policy rule (${org.verdict.rule}) denies it`, ...judged }; else if (org.verdict?.behavior === "ask") ask("org_rules", `an organization policy rule (${org.verdict.rule}) requires approval for it`, true); } return held ?? unanswered ?? { verdict: "no_objection", ...judged }; }; }