@sema-agent/core
Version:
Stateless, task-oriented AI agent core
174 lines (173 loc) • 7.6 kB
JavaScript
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 = typeof reason === "string" ? { unreadable: reason } : {};
return clean({ status: "available", verdict: clean({ behavior: behavior, rule, ...unreadableCell }), ...revisionCell });
}
function copiedDenial(raw, ruleTexts) {
if (!isPlainOwnRecord(raw))
return undefined;
const rule = ownDataValue(raw, "rule");
const view = ownDataValue(raw, "view");
const face = ownDataValue(raw, "face");
return typeof rule === "string" && typeof view === "string" && view !== "" && ruleTexts.includes(rule) && (face === "read" || face === "write") ? { rule, view, face } : undefined;
}
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 {};
const denial = behavior === "deny" ? copiedDenial(ownDataValue(hit, "denial"), members.map((m) => m.rule)) : undefined;
return { hit: { behavior, rules: members.map((m) => ({ rule: m.rule, ...copiedUsableDots(m.dots) })), ...(denial !== undefined ? { denial } : {}) } };
}
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 });
}