@sema-agent/core
Version:
Stateless, task-oriented AI agent core
205 lines (204 loc) • 10.2 kB
JavaScript
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 };
};
}