UNPKG

@sema-agent/core

Version:

Stateless, task-oriented AI agent core

177 lines (176 loc) • 8.58 kB
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; }