@sema-agent/core
Version:
Stateless, task-oriented AI agent core
177 lines (176 loc) • 8.58 kB
JavaScript
import { isAskClass, isAskOrigin, isModeReleaseReason, isPermissionMode, RELEASABLE_ASK_CLASSES } from "./ask-origin.js";
import { isAutoModeFallbackCause } from "./auto-mode.js";
export const SETTLEMENT_KINDS = Object.freeze([
"human_allowed",
"human_refused",
"approval_window_expired",
"denial_limit_window_expired",
"park_sla_expired",
"no_approver",
"approver_unavailable",
"approver_error",
"approver_contract",
"presentation_failed",
"blanket_allow_refused",
"policy_allowed",
"policy_refused",
"task_aborted",
]);
const SETTLEMENT_KIND_SET = new Set(SETTLEMENT_KINDS);
export function isSettlementKind(v) {
return SETTLEMENT_KIND_SET.has(v);
}
export const SETTLEMENT_PARTIES = Object.freeze(["person", "policy", "engine", "host", "none"]);
const SETTLEMENT_PARTY_SET = new Set(SETTLEMENT_PARTIES);
export function isSettlementParty(v) {
return SETTLEMENT_PARTY_SET.has(v);
}
export function decidersNote(text) {
return text === "" ? undefined : text;
}
export function settledByDecider(party, verdict, approver, note) {
const attributed = approver !== undefined ? { approver } : {};
if (party === "policy")
return { kind: verdict === "allowed" ? "policy_allowed" : "policy_refused", who: { party: "policy", ...attributed }, when: Date.now() };
const given = decidersNote(note);
return { kind: verdict === "allowed" ? "human_allowed" : "human_refused", who: { party: "person", ...attributed }, when: Date.now(), ...(given !== undefined ? { note: given } : {}) };
}
export const SETTLEMENT_IS_REFUSAL = {
human_allowed: false,
human_refused: true,
approval_window_expired: true,
denial_limit_window_expired: true,
park_sla_expired: true,
no_approver: true,
approver_unavailable: true,
approver_error: true,
approver_contract: true,
presentation_failed: true,
blanket_allow_refused: true,
policy_allowed: false,
policy_refused: true,
task_aborted: true,
};
export const SETTLEMENT_NOBODY_JUDGED = {
human_allowed: false,
human_refused: false,
approval_window_expired: false,
denial_limit_window_expired: false,
park_sla_expired: false,
no_approver: true,
approver_unavailable: false,
approver_error: false,
approver_contract: false,
presentation_failed: false,
blanket_allow_refused: true,
policy_allowed: false,
policy_refused: true,
task_aborted: false,
};
export const DENIED_BY_VALUES = Object.freeze(["policy", "hook", "org", "persisted_rule", "classifier", "compliance", "write_protection", "ask_resolution", "read_boundary", "mode"]);
const DENIED_BY_SET = new Set(DENIED_BY_VALUES);
export function isDeniedBy(v) {
return DENIED_BY_SET.has(v);
}
export const DENIED_BY_MAY_VETO = {
policy: true,
hook: true,
org: true,
persisted_rule: true,
classifier: false,
compliance: false,
write_protection: true,
ask_resolution: false,
read_boundary: true,
mode: false,
};
export const CLASSIFIER_DENY_CAUSES = Object.freeze(["unavailable", "parse_error"]);
const CLASSIFIER_DENY_CAUSE_SET = new Set(CLASSIFIER_DENY_CAUSES);
export function isClassifierDenyCause(v) {
return CLASSIFIER_DENY_CAUSE_SET.has(v);
}
export const DENIED_BY_MAY_CARRY_CAUSE = {
policy: true,
hook: false,
org: false,
persisted_rule: false,
classifier: true,
compliance: false,
write_protection: false,
ask_resolution: false,
read_boundary: false,
mode: false,
};
export function screenGateOutcome(g) {
const defects = [];
const d = g.disposition;
if (d.kind === "denied" && !isDeniedBy(d.deniedBy))
defects.push(`disposition.deniedBy "${String(d.deniedBy)}" is outside the closed set`);
const s = g.settlement;
if (s !== undefined && !isSettlementKind(s.kind))
defects.push(`settlement.kind "${String(s.kind)}" is outside the closed set`);
const party = s === undefined ? undefined : s.who?.party;
if (s !== undefined && !isSettlementParty(party))
defects.push(`settlement.who.party "${String(party)}" is outside the closed set`);
if (g.origin !== undefined && !isAskOrigin(g.origin))
defects.push(`origin "${String(g.origin)}" is outside the closed set`);
if ((s !== undefined) !== (g.origin !== undefined))
defects.push("I1: settlement and origin must be present together — they describe the same ask");
if (d.kind === "denied" && d.deniedBy === "ask_resolution" && (s === undefined || !isSettlementKind(s.kind) || !SETTLEMENT_IS_REFUSAL[s.kind])) {
defects.push("I2: deniedBy ask_resolution requires a settlement whose kind is a refusal");
}
const approval = s !== undefined && isSettlementKind(s.kind) && !SETTLEMENT_IS_REFUSAL[s.kind];
if (approval && d.kind === "denied" && isDeniedBy(d.deniedBy) && !DENIED_BY_MAY_VETO[d.deniedBy]) {
defects.push(`I3: a ${s.kind} settlement beside a deny must name a veto layer, not "${d.deniedBy}"`);
}
if (d.kind === "allowed" && s !== undefined && !approval)
defects.push(`I4: an allowed disposition cannot carry a "${String(s.kind)}" settlement`);
if (d.kind === "denied" && d.cause !== undefined) {
if (!isClassifierDenyCause(d.cause))
defects.push(`disposition.cause "${String(d.cause)}" is outside the closed set`);
else if (isDeniedBy(d.deniedBy) && !DENIED_BY_MAY_CARRY_CAUSE[d.deniedBy])
defects.push(`I5: a classifier cause cannot stand beside deniedBy "${d.deniedBy}"`);
}
if (d.kind === "allowed" && d.released !== undefined) {
const r = d.released;
if (s !== undefined)
defects.push("I7: a mode release carries no settlement — nobody answered the ask it released");
if (r.mode !== "bypassPermissions")
defects.push(`I7: a mode release names mode "${String(r.mode)}" — only bypassPermissions releases`);
if (!isAskClass(r.askClass) || !RELEASABLE_ASK_CLASSES.includes(r.askClass))
defects.push(`I7: released.askClass "${String(r.askClass)}" is not a releasable class`);
if (!Array.isArray(r.classes) || r.classes.length === 0 || !r.classes.every((c) => isAskClass(c)))
defects.push("I7: released.classes must be a non-empty list of ask classes");
if (r.origin !== undefined && !isAskOrigin(r.origin))
defects.push(`I7: released.origin "${String(r.origin)}" is outside the closed set`);
if (r.reason !== undefined && !isModeReleaseReason(r.reason))
defects.push(`I7: released.reason "${String(r.reason)}" is outside the closed set`);
else if (r.reason !== undefined && Array.isArray(r.classes) && !r.classes.includes(r.reason))
defects.push(`I7: released.reason "${r.reason}" is not a member of released.classes`);
}
if (d.kind === "denied") {
const modeRefusal = d.deniedBy === "mode";
if (modeRefusal !== (d.mode !== undefined))
defects.push("I8: deniedBy mode and disposition.mode must be present together");
if (modeRefusal && (!isPermissionMode(d.mode) || (d.mode !== "bypassPermissions" && d.mode !== "dontAsk")))
defects.push(`I8: a mode refusal names mode "${String(d.mode)}" — only bypassPermissions, dontAsk and plan refuse`);
if (modeRefusal && (!isAskClass(d.askClass) || !Array.isArray(d.classes) || d.classes.length === 0 || !d.classes.every((c) => isAskClass(c))))
defects.push("I8: a mode refusal carries askClass and a non-empty classes list");
}
const attribution = d.classifier;
if (attribution !== undefined) {
const named = (v) => typeof v === "string" && v !== "";
if (!named(attribution.modelRequested) || !named(attribution.modelUsed))
defects.push("I6: disposition.classifier must name both the model the round was for and the model that answered");
const fb = attribution.fallback;
if (fb !== undefined) {
if (!named(fb.from) || !named(fb.to))
defects.push("I6: disposition.classifier.fallback must name the rung it left and the rung it arrived at");
else if (fb.to !== attribution.modelUsed)
defects.push(`I6: the fallback landed on "${fb.to}" but the record names "${attribution.modelUsed}" as the model that answered`);
if (!isAutoModeFallbackCause(fb.cause))
defects.push(`I6: fallback cause "${String(fb.cause)}" is outside the closed set`);
}
}
return defects;
}