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