UNPKG

@sema-agent/core

Version:

Stateless, task-oriented AI agent core

294 lines (293 loc) • 15.5 kB
import { isAskClass, isAskOrigin, isModeReleaseReason, isPermissionMode, isPersistedRuleMandate, ASK_FACES, releasableAskClassesFor } 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 const HOST_DECIDERS = Object.freeze(["person", "sla_timeout", "policy"]); export const isHostDecider = (word) => HOST_DECIDERS.some((w) => w === word); 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 function settlementOfHostDecision(facts, verdict, note) { if (facts.decidedBy === "sla_timeout") return { kind: "park_sla_expired", who: { party: "host", ...(facts.approver !== undefined ? { approver: facts.approver } : {}) }, when: Date.now() }; return settledByDecider(facts.decidedBy, verdict, facts.approver, verdict === "refused" ? note : undefined); } 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, }; const retired = (word, retiredIn) => Object.freeze({ word, retiredIn }); export const RETIRED_MEMBERS = Object.freeze({ askClass: Object.freeze([retired("write_protection_unresolved", "7.49.0"), retired("governance_unavailable", "7.51.0")]), modeReleaseReason: Object.freeze([retired("write_protection_unresolved", "7.49.0"), retired("rule_unreadable", "7.50.0")]), askOrigin: Object.freeze([retired("unresolvable", "7.14.0")]), persistedRuleMandate: Object.freeze([retired("write_protection_unresolved", "7.49.0")]), deniedBy: Object.freeze([retired("plan_mode", "7.34.0")]), settlementKind: Object.freeze([]), settlementParty: Object.freeze([]), classifierDenyCause: Object.freeze([]), permissionMode: Object.freeze([]), autoModeFallbackCause: Object.freeze([]), }); const LIVE_MEMBER = Object.freeze({ "askClass": isAskClass, "modeReleaseReason": isModeReleaseReason, "askOrigin": isAskOrigin, "persistedRuleMandate": isPersistedRuleMandate, "deniedBy": isDeniedBy, "settlementKind": isSettlementKind, "settlementParty": isSettlementParty, "classifierDenyCause": isClassifierDenyCause, "permissionMode": isPermissionMode, "autoModeFallbackCause": isAutoModeFallbackCause, }); function retiredInOf(set, v) { return RETIRED_MEMBERS[set].find((r) => r.word === v)?.retiredIn; } export function isRetiredMember(set, word) { if (typeof set !== "string" || !Object.hasOwn(RETIRED_MEMBERS, set)) throw new TypeError(`isRetiredMember: ${JSON.stringify(String(set))} is not a closed set a gate record carries (${Object.keys(RETIRED_MEMBERS).join(", ")})`); return retiredInOf(set, word) !== undefined; } export function releasePolicyDefects(g) { const d = objectOf(objectOf(g)?.disposition); const r = d?.kind === "allowed" ? objectOf(d.released) : undefined; const askClass = r?.askClass; const classes = r?.classes; if (!isAskClass(askClass) || !Array.isArray(classes) || !classes.every(isAskClass)) return []; if (!ASK_FACES.some((face) => releasableAskClassesFor(face).includes(askClass))) return [`I7: released.askClass "${askClass}" is not a releasable class under this engine's table`]; const held = [askClass, ...classes]; return ASK_FACES.some((face) => held.every((c) => releasableAskClassesFor(face).includes(c))) ? [] : [`I7: released.classes ${JSON.stringify(classes)} is not a set one face of this engine's table releases`]; } const DENY_ONLY_MEMBERS = Object.freeze(["deniedBy", "cause", "mode", "askClass", "classes"]); function objectOf(v) { return typeof v === "object" && v !== null && !Array.isArray(v) ? v : undefined; } function shapeOf(v) { return v === null ? "null" : Array.isArray(v) ? "an array" : typeof v === "object" ? "an object" : typeof v; } function spelled(v) { return v === null || (typeof v !== "object" && typeof v !== "function") ? `"${String(v)}"` : `(${shapeOf(v)})`; } function readingOf(opts) { if (opts === undefined) return "archive"; if (typeof opts !== "object" || opts === null) throw new TypeError(`screenGateOutcome: the options are an object ({ reading?: "archive" | "producer" }), not ${opts === null ? "null" : typeof opts}`); const reading = opts.reading; if (reading === undefined || reading === "archive" || reading === "producer") return reading ?? "archive"; throw new TypeError(`screenGateOutcome: reading ${typeof reading === "string" ? JSON.stringify(reading) : typeof reading} is not a reading (archive, producer)`); } export function screenGateOutcome(g, opts) { const producer = readingOf(opts) === "producer"; const member = (set, v) => LIVE_MEMBER[set](v) || (!producer && retiredInOf(set, v) !== undefined); const outside = (field, set, v) => { const was = retiredInOf(set, v); return `${field} ${spelled(v)} is outside the closed set${was !== undefined ? ` (retired in ${was})` : ""}`; }; const classList = (v) => Array.isArray(v) && v.length > 0 && v.every((c) => member("askClass", c)); const notRecord = (field, v) => `${field} is not an object (${shapeOf(v)})`; const record = objectOf(g); if (record === undefined) return [notRecord("the gate record", g)]; const defects = []; const d = objectOf(record.disposition); if (d === undefined) defects.push(notRecord("disposition", record.disposition)); else if (d.kind !== "allowed" && d.kind !== "denied") defects.push(`disposition.kind ${spelled(d.kind)} is neither allowed nor denied`); const denied = d?.kind === "denied" ? d : undefined; const allowed = d?.kind === "allowed" ? d : undefined; const layer = denied !== undefined && member("deniedBy", denied.deniedBy); if (denied !== undefined && !layer) defects.push(outside("disposition.deniedBy", "deniedBy", denied.deniedBy)); const settled = record.settlement !== undefined; const s = objectOf(record.settlement); if (settled && s === undefined) defects.push(notRecord("settlement", record.settlement)); if (s !== undefined && !member("settlementKind", s.kind)) defects.push(outside("settlement.kind", "settlementKind", s.kind)); const party = objectOf(s?.who)?.party; if (s !== undefined && !member("settlementParty", party)) defects.push(outside("settlement.who.party", "settlementParty", party)); if (record.origin !== undefined && !member("askOrigin", record.origin)) defects.push(outside("origin", "askOrigin", record.origin)); if (settled !== (record.origin !== undefined)) defects.push("I1: settlement and origin must be present together — they describe the same ask"); const kind = s !== undefined && isSettlementKind(s.kind) ? s.kind : undefined; if (denied?.deniedBy === "ask_resolution" && (kind === undefined || !SETTLEMENT_IS_REFUSAL[kind])) defects.push("I2: deniedBy ask_resolution requires a settlement whose kind is a refusal"); const approval = kind !== undefined && !SETTLEMENT_IS_REFUSAL[kind]; if (approval && denied !== undefined && layer && !(isDeniedBy(denied.deniedBy) && DENIED_BY_MAY_VETO[denied.deniedBy])) { defects.push(`I3: a ${kind} settlement beside a deny must name a veto layer, not "${String(denied.deniedBy)}"`); } if (allowed !== undefined && s !== undefined && !approval) defects.push(`I4: an allowed disposition cannot carry a ${spelled(s.kind)} settlement`); if (denied !== undefined && denied.cause !== undefined) { if (!member("classifierDenyCause", denied.cause)) defects.push(outside("disposition.cause", "classifierDenyCause", denied.cause)); else if (layer && !(isDeniedBy(denied.deniedBy) && DENIED_BY_MAY_CARRY_CAUSE[denied.deniedBy])) defects.push(`I5: a classifier cause cannot stand beside deniedBy "${String(denied.deniedBy)}"`); } for (const k of DENY_ONLY_MEMBERS) if (allowed !== undefined && allowed[k] !== undefined) defects.push(`disposition.${k} stands only on a denied disposition`); if (denied !== undefined && denied.released !== undefined) defects.push("I7: disposition.released stands only on an allowed disposition"); if (d !== undefined && d.released !== undefined) { const r = objectOf(d.released); if (r === undefined) defects.push(notRecord("I7: disposition.released", d.released)); else { if (settled) 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 ${spelled(r.mode)} — only bypassPermissions releases`); if (!member("askClass", r.askClass)) defects.push(outside("I7: released.askClass", "askClass", r.askClass)); if (!classList(r.classes)) defects.push("I7: released.classes must be a non-empty list of ask classes"); if (r.origin !== undefined && !member("askOrigin", r.origin)) defects.push(outside("I7: released.origin", "askOrigin", r.origin)); if (r.mandate !== undefined && !member("persistedRuleMandate", r.mandate)) defects.push(outside("I7: released.mandate", "persistedRuleMandate", r.mandate)); if (r.reason !== undefined && !member("modeReleaseReason", r.reason)) defects.push(outside("I7: released.reason", "modeReleaseReason", r.reason)); else if (r.reason !== undefined && Array.isArray(r.classes) && !r.classes.includes(r.reason)) defects.push(`I7: released.reason "${String(r.reason)}" is not a member of released.classes`); } } if (denied !== undefined) { const modeRefusal = denied.deniedBy === "mode"; if (modeRefusal !== (denied.mode !== undefined)) defects.push("I8: deniedBy mode and disposition.mode must be present together"); if (modeRefusal && denied.mode !== undefined && (!member("permissionMode", denied.mode) || (denied.mode !== "bypassPermissions" && denied.mode !== "dontAsk"))) { defects.push(`I8: a mode refusal names mode ${spelled(denied.mode)} — only bypassPermissions and dontAsk refuse`); } if (modeRefusal && (denied.askClass === undefined || denied.classes === undefined)) defects.push("I8: a mode refusal carries askClass and classes"); if (denied.askClass !== undefined && !member("askClass", denied.askClass)) defects.push(outside("disposition.askClass", "askClass", denied.askClass)); if (denied.classes !== undefined && !classList(denied.classes)) defects.push("disposition.classes must be a non-empty list of ask classes"); } if (d !== undefined && d.classifier !== undefined) { const attribution = objectOf(d.classifier); const named = (v) => typeof v === "string" && v !== ""; if (attribution === undefined) defects.push(notRecord("I6: disposition.classifier", d.classifier)); else { 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 = objectOf(attribution.fallback); if (attribution.fallback !== undefined && fb === undefined) defects.push(notRecord("I6: disposition.classifier.fallback", 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 ${spelled(fb.to)} but the record names ${spelled(attribution.modelUsed)} as the model that answered`); if (!member("autoModeFallbackCause", fb.cause)) defects.push(outside("I6: fallback cause", "autoModeFallbackCause", fb.cause)); } } } if (producer) defects.push(...releasePolicyDefects(g)); return defects; }