@sema-agent/core
Version:
Stateless, task-oriented AI agent core
260 lines (259 loc) • 15 kB
JavaScript
import { types } from "node:util";
import { ASK_CLASSES, ASK_ORIGINS, isAskClass, isAskOrigin, isPersistedRuleMandate, PERSISTED_RULE_MANDATES } from "./ask-origin.js";
import { AUTO_MODE_FALLBACK_CAUSES, isAutoModeFallbackCause } from "./auto-mode.js";
import { thrownText } from "./thrown-value.js";
const ENGINE_REFUSALS = new WeakSet();
export const refusedOutOfContract = (message) => {
const refused = Object.freeze({ action: "deny", message, decisionReason: "rule" });
ENGINE_REFUSALS.add(refused);
return refused;
};
export const isEngineRefusal = (d) => typeof d === "object" && d !== null && ENGINE_REFUSALS.has(d);
const DECISION_REASONS = Object.freeze(["rule", "mode", "hook", "safety", "classifier", "persisted_rule", "sandbox", "org_rule", "org_unavailable", "read_only", "skill", "deployment_rule", "request_rule"]);
const DECISION_ACTIONS = Object.keys({ allow: 0, deny: 0, ask: 0 });
const GUARDS = Object.keys({ dangerous_delete: 0, unverifiable_delete: 0, unverifiable_shell_text: 0, transcript_write: 0, artifact_shared_read: 0 });
const RULE_UNREADABLE_WORDS = Object.keys({ content_deny: 0, content_ask: 0, persisted_call: 0, persisted_rows: 0 });
const AUTHORED_BY_WORDS = Object.keys({ deployment: 0 });
const memberOf = (words) => {
const set = new Set(words);
return (v) => set.has(v);
};
const CLOSED_MEMBERS = [
{ member: "action", words: DECISION_ACTIONS, has: memberOf(DECISION_ACTIONS), required: true },
{ member: "decisionReason", words: DECISION_REASONS, has: memberOf(DECISION_REASONS) },
{ member: "origin", words: ASK_ORIGINS, has: isAskOrigin },
{ member: "askClass", words: ASK_CLASSES, has: isAskClass },
{ member: "classes", words: ASK_CLASSES, has: isAskClass, list: true },
{ member: "foldedAskClasses", words: ASK_CLASSES, has: isAskClass, list: true },
{ member: "mandate", words: PERSISTED_RULE_MANDATES, has: isPersistedRuleMandate },
{ member: "guards", words: GUARDS, has: memberOf(GUARDS), list: true },
{ member: "ruleUnreadable", words: RULE_UNREADABLE_WORDS, has: memberOf(RULE_UNREADABLE_WORDS) },
{ member: "authoredBy", words: AUTHORED_BY_WORDS, has: memberOf(AUTHORED_BY_WORDS) },
{ member: "classifierNoRuling", words: AUTO_MODE_FALLBACK_CAUSES, has: isAutoModeFallbackCause },
{ member: "requiresRealApproval", words: [true, false], has: (v) => v === true || v === false },
{ member: "probeMandated", words: [true], has: (v) => v === true },
{ member: "mandated", words: [true], has: (v) => v === true },
];
const RETIRED_WINDOW_MEMBER = "autoDenyAfterMs";
export const RETIRED_WINDOW_MEMBER_DENY_MESSAGE = `a permission decision's "denialLimitFallback" carries "${RETIRED_WINDOW_MEMBER}" — the decision names the counts only; ` +
`the auto-deny window is decided by the ask's route (a live approver arms it, every other route runs none) and cannot be ` +
`declared by a policy; denied fail-closed rather than silently replacing the declared number`;
export const carriesRetiredWindow = (fallback) => typeof fallback === "object" && fallback !== null && fallback[RETIRED_WINDOW_MEMBER] !== undefined;
const OPERAND_MEMBERS = Object.keys({ shown: 0, total: 0 });
const NESTED_CONTRACTS = [
{
holder: "",
member: "denialLimitFallback",
members: Object.keys({ consecutive: 0, total: 0, limit: 0, autoDenyAfterMs: 0 }),
required: Object.keys({ consecutive: 0, total: 0, limit: 0 }),
},
{ holder: "", member: "probeCause", members: Object.keys({ code: 0, roots: 0, further: 0 }), required: Object.keys({ code: 0, roots: 0 }) },
{ holder: "probeCause.", member: "roots", members: OPERAND_MEMBERS, required: OPERAND_MEMBERS },
{ holder: "probeCause.", member: "further", members: OPERAND_MEMBERS, required: OPERAND_MEMBERS },
];
const nonNegativeInteger = (v) => typeof v === "number" && Number.isInteger(v) && v >= 0;
const NESTED_VALUE_FENCES = {
denialLimitFallback: { consecutive: nonNegativeInteger, total: nonNegativeInteger, limit: (v) => v === "consecutive" || v === "total" },
probeCause: { code: (v) => typeof v === "string" && v.length > 0 },
"probeCause.roots": { total: nonNegativeInteger },
"probeCause.further": { total: nonNegativeInteger },
};
const NESTED_LIST_MEMBERS = {
"probeCause.roots": { shown: { words: ["a string"], has: (x) => typeof x === "string" } },
"probeCause.further": { shown: { words: ["a string"], has: (x) => typeof x === "string" } },
};
function renderForeignValue(v) {
try {
return JSON.stringify(String(v).slice(0, 64));
}
catch {
return `[unprintable ${typeof v}]`;
}
}
export function screenContractValues(contract, opts) {
for (const { member, words, has, list, required } of CLOSED_MEMBERS) {
if (opts?.skip?.has(member) === true)
continue;
const v = contract[member];
if (v === undefined) {
if (required === true && opts?.optional?.has(member) !== true)
return refusedOutOfContract(`a permission decision's "${member}" is absent — the closed set is ${words.join("/")}; denied fail-closed rather than reading a word outside it as an allow`);
continue;
}
if (list === true) {
const received = receiveList(v, member, words, has);
if (received.kind === "refused")
return received.refused;
contract[member] = received.held;
continue;
}
if (has(v))
continue;
return unrecognized(member, v, words);
}
if (carriesRetiredWindow(contract.denialLimitFallback))
return refusedOutOfContract(RETIRED_WINDOW_MEMBER_DENY_MESSAGE);
return undefined;
}
const unrecognized = (member, v, words) => refusedOutOfContract(`a permission decision carries an unrecognized "${member}" value (${renderForeignValue(v)}) — ` +
`the closed set is ${words.join("/")}; denied fail-closed rather than letting consumers branch on a value this layer cannot classify`);
function receiveList(v, member, words, has) {
if (typeof v !== "object" || v === null)
return { kind: "refused", refused: unrecognized(member, v, words) };
const ordinary = ordinaryChain(v);
if (ordinary.kind === "not")
return { kind: "refused", refused: refusedOutOfContract(`a permission decision's "${member}" ${ordinary.why} — the engine reads a list off an ordinary array's own property descriptors and runs none of the carrier's code; denied fail-closed`) };
if (!Array.isArray(v))
return { kind: "refused", refused: unrecognized(member, v, words) };
let own;
try {
own = Object.getOwnPropertyDescriptors(v);
}
catch (e) {
return { kind: "refused", refused: refusedOutOfContract(`a permission decision's "${member}" threw while its property descriptors were looked at (${thrownText(e)}) — not an ordinary array; denied fail-closed`) };
}
const length = Object.hasOwn(own, "length") ? own.length : undefined;
if (length === undefined || !Object.hasOwn(length, "value") || typeof length.value !== "number")
return { kind: "refused", refused: refusedOutOfContract(`a permission decision's "${member}" has no plain length — not an ordinary array; denied fail-closed`) };
const held = [];
for (let i = 0; i < length.value; i++) {
const d = Object.hasOwn(own, String(i)) ? own[String(i)] : undefined;
if (d === undefined)
return { kind: "refused", refused: refusedOutOfContract(`a permission decision's "${member}" has no element at index ${i} (a hole) — the closed set is ${words.join("/")}; denied fail-closed rather than reading a hole as a word`) };
if (!Object.hasOwn(d, "value"))
return { kind: "refused", refused: refusedOutOfContract(`a permission decision's "${member}" element ${i} is held by an accessor — list elements must be own data properties, not values supplied through an accessor; denied fail-closed`) };
if (!has(d.value))
return { kind: "refused", refused: unrecognized(member, d.value, words) };
held.push(d.value);
}
return { kind: "held", held };
}
export function receiveNestedRecords(contract) {
const records = Object.assign(Object.create(null), { "": contract });
for (const { holder, member, members, required } of NESTED_CONTRACTS) {
const held = records[holder];
if (held === undefined)
continue;
const v = held[member];
if (v === undefined)
continue;
const path = `${holder}${member}`;
const shape = `a nested contract record is a plain object holding ${required.map((r) => `"${r}"`).join(", ")}`;
if (typeof v !== "object" || v === null) {
return refusedOutOfContract(`a permission decision's "${path}" is ${renderForeignValue(v)} (not an object) — ${shape}; denied fail-closed rather than reading a value outside the contract as a record`);
}
const nested = receiveContract(v, members, `${path}.`);
if (nested.kind === "refused")
return nested.refused;
if (Array.isArray(v))
return refusedOutOfContract(`a permission decision's "${path}" is an array — ${shape}; denied fail-closed rather than reading a list's properties as a record`);
const missing = required.find((r) => nested.held[r] === undefined);
if (missing !== undefined)
return refusedOutOfContract(`a permission decision's "${path}" record is missing its required member "${missing}" — ${shape}; denied fail-closed rather than reading a partial record`);
for (const [m, { words, has }] of Object.entries(NESTED_LIST_MEMBERS[path] ?? {})) {
const value = nested.held[m];
if (value === undefined)
continue;
const received = receiveList(value, `${path}.${m}`, words, has);
if (received.kind === "refused")
return received.refused;
nested.held[m] = received.held;
}
for (const [m, ok] of Object.entries(NESTED_VALUE_FENCES[path] ?? {})) {
const value = nested.held[m];
if (value !== undefined && !ok(value))
return refusedOutOfContract(`a permission decision's "${path}.${m}" is ${renderForeignValue(value)} — outside what the record's type admits; denied fail-closed rather than reading a value this layer cannot classify`);
}
held[member] = records[`${path}.`] = nested.held;
}
return undefined;
}
export function nestedRecordsOf(receipt) {
const ownRecord = (o, k) => (typeof o === "object" && o !== null && Object.hasOwn(o, k) ? o[k] : undefined);
const out = [];
for (const { holder, member } of NESTED_CONTRACTS) {
const record = ownRecord(holder.split(".").filter(Boolean).reduce(ownRecord, receipt), member);
if (typeof record === "object" && record !== null)
out.push(record);
}
return out;
}
const spellsAbsence = (d) => Object.hasOwn(d, "value") && d.value === undefined;
function ordinaryChain(carrier) {
const chain = [];
const seen = new Set();
try {
for (let o = carrier; o !== null; o = Object.getPrototypeOf(o)) {
if (types.isProxy(o))
return { kind: "not", why: o === carrier ? "is a Proxy" : "has a Proxy on its prototype chain" };
if (seen.has(o))
return { kind: "not", why: "has a cyclic prototype chain" };
seen.add(o);
chain.push(o);
}
}
catch (e) {
return { kind: "not", why: `threw while its prototype chain was looked at (${thrownText(e)})` };
}
return { kind: "chain", chain };
}
export function receiveContract(carrier, members, path, retired) {
const what = path === "" ? "a permission decision" : `a permission decision's "${path.slice(0, -1)}" record`;
const ordinary = ordinaryChain(carrier);
if (ordinary.kind === "not") {
return { kind: "refused", refused: refusedOutOfContract(`${what} ${ordinary.why} — the engine reads a decision off an ordinary object's own property descriptors and runs none of the carrier's code; denied fail-closed`) };
}
let own;
try {
own = Object.getOwnPropertyDescriptors(carrier);
}
catch (e) {
return { kind: "refused", refused: refusedOutOfContract(`${what} threw while its property descriptors were looked at (${thrownText(e)}) — not an ordinary object; denied fail-closed`) };
}
const nearest = (m) => {
try {
if (Object.hasOwn(own, m))
return { kind: "found", d: own[m] };
for (let i = 1; i < ordinary.chain.length; i++) {
const d = Object.getOwnPropertyDescriptor(ordinary.chain[i], m);
if (d !== undefined)
return { kind: "found", d };
}
return { kind: "found", d: undefined };
}
catch (e) {
return { kind: "refused", refused: refusedOutOfContract(`${what} threw while "${path}${m}" was looked for on its prototype chain (${thrownText(e)}) — not an ordinary object; denied fail-closed`) };
}
};
const held = Object.create(null);
for (const m of members) {
const d = Object.hasOwn(own, m) ? own[m] : undefined;
if (d !== undefined && !Object.hasOwn(d, "value")) {
return { kind: "refused", refused: refusedOutOfContract(`${what}'s contract member "${path}${m}" is held by an accessor — contract members must be own data properties, not values supplied through an accessor; denied fail-closed`) };
}
if (d !== undefined && d.enumerable === true) {
held[m] = d.value;
continue;
}
const found = nearest(m);
if (found.kind === "refused")
return found;
const reached = found.d;
if (reached !== undefined && !spellsAbsence(reached)) {
return {
kind: "refused",
refused: refusedOutOfContract(`${what} supplies "${path}${m}" other than as its own enumerable property (its prototype or a non-enumerable property) — ` +
`contract fields must be own properties, so every reader sees the one value the engine read; denied fail-closed`),
};
}
}
for (const [m, replacement] of Object.entries(retired ?? {})) {
const found = nearest(m);
if (found.kind === "refused")
return found;
if (found.d !== undefined && !spellsAbsence(found.d)) {
return { kind: "refused", refused: refusedOutOfContract(`a permission decision carries the retired "${m}" member — ${replacement}; denied fail-closed rather than executing a decision with a member this layer does not read`) };
}
}
return { kind: "held", held };
}