UNPKG

@sema-agent/core

Version:

Stateless, task-oriented AI agent core

165 lines (164 loc) • 7.03 kB
import { describeThrown } from "./tool-policy.js"; import { ORG_ADJUDICATION_TIMEOUT_MS, settleOrgVerdictWithin } from "./permission-rule-org.js"; import { isRuleBehavior } from "./permission-rule-model.js"; function isPlainOwnRecord(x) { try { if (x === null || typeof x !== "object" || Array.isArray(x)) return false; const p = Object.getPrototypeOf(x); return p === Object.prototype || p === null; } catch { return false; } } function ownDataValue(obj, key) { try { const d = Object.getOwnPropertyDescriptor(obj, key); return d && "value" in d ? d.value : undefined; } catch { return undefined; } } export function normalizeOrgGateVerdict(answer, unreadable) { const clean = (v) => Object.assign(Object.create(null), v); const readable = (x) => { try { return x !== null && (typeof x === "object" || typeof x === "function") && !Array.isArray(x); } catch { return false; } }; if (!readable(answer)) return clean({ status: "unavailable", disclosures: [unreadable] }); const status = ownDataValue(answer, "status"); if (status === "unavailable") { const disclosures = ownDataValue(answer, "disclosures"); let lines = []; if (Array.isArray(disclosures)) { try { for (let i = 0; i < disclosures.length; i++) { const d = disclosures[i]; if (typeof d === "string") lines.push(d); } } catch { lines = []; } } return clean({ status: "unavailable", disclosures: lines.length > 0 ? lines : [unreadable] }); } if (status !== "available") return clean({ status: "unavailable", disclosures: [unreadable] }); const revision = ownDataValue(answer, "revision"); const revisionCell = typeof revision === "number" && Number.isFinite(revision) ? { revision } : {}; const verdict = ownDataValue(answer, "verdict"); if (verdict === undefined) return clean({ status: "available", ...revisionCell }); if (!readable(verdict)) return clean({ status: "unavailable", disclosures: [unreadable] }); const behavior = ownDataValue(verdict, "behavior"); const rule = ownDataValue(verdict, "rule"); if ((behavior !== "deny" && behavior !== "ask") || typeof rule !== "string") return clean({ status: "unavailable", disclosures: [unreadable] }); const reason = ownDataValue(verdict, "unreadable"); const unreadableCell = behavior === "ask" && typeof reason === "string" ? { unreadable: reason } : {}; return clean({ status: "available", verdict: clean({ behavior: behavior, rule, ...unreadableCell }), ...revisionCell }); } function copiedUsableDots(dots) { let copied; try { if (Array.isArray(dots)) { const walked = []; for (let i = 0; i < dots.length; i++) { const d = dots[i]; walked.push(isPlainOwnRecord(d) ? { actor: ownDataValue(d, "actor"), counter: ownDataValue(d, "counter") } : { actor: undefined, counter: undefined }); } copied = walked; } } catch { copied = undefined; } const usable = copied?.every((d) => typeof d.actor === "string" && typeof d.counter === "number" && Number.isFinite(d.counter)) === true && copied.length > 0; return usable ? { dots: copied } : {}; } export function normalizePersistedRuleHit(hit) { if (!isPlainOwnRecord(hit)) return {}; if (ownDataValue(hit, "unreadable") === true) { const reason = ownDataValue(hit, "reason"); return typeof reason === "string" ? { unreadable: true, reason } : { unreadable: true }; } const rulesRaw = ownDataValue(hit, "rules"); if (rulesRaw === undefined) { const coverageRaw = ownDataValue(hit, "segmentCoverage"); let rows; try { if (Array.isArray(coverageRaw)) { const walked = []; for (let i = 0; i < coverageRaw.length; i++) { const r = coverageRaw[i]; walked.push(isPlainOwnRecord(r) ? { segment: ownDataValue(r, "segment"), covered: ownDataValue(r, "covered") } : { segment: undefined, covered: undefined }); } rows = walked.every((r) => typeof r.segment === "string" && typeof r.covered === "boolean") ? walked : undefined; } } catch { rows = undefined; } return rows !== undefined && rows.length > 0 ? { coverage: rows.map((r) => ({ segment: r.segment, covered: r.covered })) } : {}; } let members; try { if (Array.isArray(rulesRaw)) { const walked = []; for (let i = 0; i < rulesRaw.length; i++) { const m = rulesRaw[i]; walked.push(isPlainOwnRecord(m) ? { rule: ownDataValue(m, "rule"), dots: ownDataValue(m, "dots") } : { rule: undefined, dots: undefined }); } members = walked; } } catch { members = undefined; } if (members === undefined || members.length === 0) return {}; if (!members.every((m) => typeof m.rule === "string")) return {}; const behavior = ownDataValue(hit, "behavior"); if (!isRuleBehavior(behavior)) return {}; return { hit: { behavior, rules: members.map((m) => ({ rule: m.rule, ...copiedUsableDots(m.dots) })) } }; } export async function readOrgRuleLane(org, req, signal, timeoutMs = ORG_ADJUDICATION_TIMEOUT_MS) { let pending; try { pending = Promise.resolve(org.adjudicate(req)); } catch (err) { pending = Promise.resolve({ status: "unavailable", disclosures: [`the org adjudication face threw before answering: ${describeThrown(err)}`] }); } const raw = await settleOrgVerdictWithin(pending.catch((err) => ({ status: "unavailable", disclosures: [`the org adjudication face threw: ${describeThrown(err)}`], })), { status: "unavailable", disclosures: [`the org adjudication face did not answer within ${timeoutMs}ms (or the task ended first)`], }, { ...(signal !== undefined ? { signal } : {}), timeoutMs }); return normalizeOrgGateVerdict(raw, "the org adjudication face answered something this gate cannot read as a verdict — treated as unreadable governance, never as an empty one"); } export async function readPersistedRuleLane(lane, req, signal, timeoutMs = ORG_ADJUDICATION_TIMEOUT_MS) { let pendingHit; try { pendingHit = Promise.resolve(lane?.adjudicate(req)); } catch { return { unreadable: true }; } return await settleOrgVerdictWithin(pendingHit.then(normalizePersistedRuleHit).catch(() => ({ unreadable: true })), { unreadable: true }, { ...(signal !== undefined ? { signal } : {}), timeoutMs }); }