@sema-agent/core
Version:
Stateless, task-oriented AI agent core
196 lines (195 loc) • 13.3 kB
JavaScript
import { persistedRuleMandateOf, ruleClearanceOf } from "../ask-origin.js";
import {} from "../hooks.js";
import { adjudicatePersistedDomainRules, adjudicatePersistedNameRules, adjudicatePersistedPathRules, adjudicatePersistedRules, callPathFaceOf, domainRuleSubjectOf, escapeForDisclosure, faceRowsSpeakFor, laneToolNamesOf, nameRuleSubjectOf, ruleToolGrammarOf, segmentCoverageOf, suggestRuleForName, suggestRulesForCommand } from "../permission-rule-model.js";
import { callTargetViewsOf, validatePath } from "../effective-path-target.js";
import { bindPathRules, persistedRowAnchors, projectScopeIdentity } from "../path-rule-binding.js";
import { heldBy, seatOf, wireOf } from "../tool-name-readings.js";
import { describeUnreadableOrgRows, readOrgRules, settleOrgVerdictWithin } from "../permission-rule-org.js";
import { ASK_USER_QUESTION_TOOL_NAME } from "../ask-question.js";
import { ownerStoreOf } from "../permission-rule-provider.js";
import { thrownText } from "../thrown-value.js";
import { strongestAnswer } from "../persisted-rule-arms.js";
import { ORG_ADJUDICATION_TIMEOUT_MS } from "../permission-rule-org.js";
export const PERSISTED_RULE_TOOL = "Bash";
const NO_LEADS = new Map();
export function inheritedAskRuleEvidence(provider) {
const org = provider?.partitions.org === true ? "not_adjudicated" : "not_wired";
const personal = provider?.partitions.durable === true ? "not_adjudicated" : "not_wired";
return Object.freeze({ orgRevisionAbsent: org, orgRuleAbsent: org, personalRuleDotsAbsent: personal });
}
export function orgRevisionEvidenceOf(resolution, onDefect) {
let reported;
try {
reported = resolution.revision;
}
catch (err) {
onDefect(`the org rule partition's revision could not be read (${thrownText(err)}) — the adjudication stands, but the ask carries no revision evidence for this call`);
return {};
}
if (reported === undefined)
return {};
if (typeof reported === "number" && Number.isFinite(reported))
return { revision: reported };
onDefect(`the org rule partition reported revision ${typeof reported === "string" ? `"${escapeForDisclosure(reported)}"` : escapeForDisclosure(reported)} — a snapshot revision is a finite number; ` +
`the adjudication stands, but the ask carries no revision evidence for this call`);
return {};
}
export function persistedRuleHitOf(verdict) {
return verdict === undefined
? undefined
: { behavior: verdict.behavior, rules: verdict.rules.map((r) => ({ rule: r.rule, dots: r.adds.map((a) => ({ actor: a.dot.actor, counter: a.dot.counter })) })), ...(verdict.denial !== undefined ? { denial: { ...verdict.denial } } : {}) };
}
export function persistedRuleAnswerOf(verdict) {
if (verdict?.unreadable !== undefined)
return { unreadable: true, reason: verdict.unreadable };
return persistedRuleHitOf(verdict);
}
export function pathRuleLaneAnswer(table, req, ctx) {
const tighten = callTargetViewsOf(req, "tighten");
const allow = callTargetViewsOf(req, "allow");
if (tighten === undefined && allow === undefined)
return undefined;
return persistedRuleAnswerOf(adjudicatePersistedPathRules(table, { tool: req.toolName, cwd: ctx.root, sessionId: ctx.sessionId, face: callPathFaceOf(req), ...(ctx.scopeIdentity !== undefined ? { scopeIdentity: ctx.scopeIdentity } : {}) }, { ...(tighten !== undefined ? { tighten } : {}), ...(allow !== undefined ? { allow } : {}) }, ctx.anchorsOf, ctx.leads));
}
export function orgRuleLane(cfg) {
return {
adjudicate: async (req) => {
if (wireOf("offer.orgQuestionExemption", req) === cfg.questionToolName && cfg.questionToolMounted)
return { status: "available" };
let resolution;
try {
resolution = (await cfg.read(req.toolCallId)).org;
}
catch (err) {
return { status: "unavailable", disclosures: [`the permission-rule store threw while resolving the org partition: ${thrownText(err)}`] };
}
if (resolution.status === "unavailable" || resolution.status === "ungoverned") {
return { status: "unavailable", disclosures: resolution.status === "ungoverned" ? ["the store reports no org partition while the provider declares one — unreadable governance, never an empty one"] : resolution.disclosures };
}
const reading = readOrgRules(resolution.rules);
if ("refused" in reading)
return { status: "unavailable", disclosures: [`the store's org resolution cannot be read (${reading.refused}) — unreadable governance, never an empty one`] };
const command = req.args?.command;
const face = callPathFaceOf(req);
const verdict = reading.verdictFor({ tool: laneToolNamesOf(req), ...(typeof command === "string" ? { command } : {}), ...(face !== undefined ? { face } : {}) });
const revisionCell = orgRevisionEvidenceOf(resolution, cfg.onRevisionDefect);
const degraded = describeUnreadableOrgRows(reading.unreadable, revisionCell.revision);
if (degraded !== undefined)
cfg.onDegraded(degraded);
return verdict === undefined ? { status: "available", ...revisionCell } : { status: "available", verdict, ...revisionCell };
},
};
}
export function orgVerdictBeforeClaim(cfg, req) {
const unreadable = { status: "unavailable", disclosures: [] };
const read = () => {
const store = ownerStoreOf(cfg.provider, cfg.principal, cfg.localOwnerDeclared);
return orgRuleLane({ read: () => store.effective({ sessionId: cfg.sessionId }), questionToolMounted: true, questionToolName: ASK_USER_QUESTION_TOOL_NAME, onRevisionDefect: () => undefined, onDegraded: () => undefined }).adjudicate(req);
};
return settleOrgVerdictWithin(Promise.resolve().then(read).catch(() => unreadable), unreadable, { timeoutMs: ORG_ADJUDICATION_TIMEOUT_MS, ...(cfg.signal !== undefined ? { signal: cfg.signal } : {}) });
}
export function createPermissionRuleLanes(cfg) {
const provider = cfg.provider;
if (cfg.home !== undefined && validatePath(cfg.home, { intent: "validate-coordinate" }) === undefined) {
const e = new Error(`the execution environment declares homeDir ${JSON.stringify(cfg.home)}, which is not an absolute path — a \`~/\` permission rule could not be resolved against it; declare an absolute home directory or none`);
e.code = "config.execution_env_home_dir_invalid";
throw e;
}
if (cfg.localOwnerDeclared) {
if (provider === undefined || !provider.partitions.durable) {
throw new Error("RunnerDeps.localOwnerRules is declared but no durable permission-rule partition is wired — there is no bucket for the local owner to hold rules in; refusing rather than running as if the declaration were absent");
}
if (provider.forLocalOwner === undefined) {
throw new Error("RunnerDeps.localOwnerRules is declared but the wired permission-rule provider implements no forLocalOwner() face — a provider without a local-owner bucket cannot honor the declaration; refusing rather than silently resolving zero rules");
}
}
if (provider === undefined)
return { personal: undefined, org: undefined };
const anonymous = cfg.principal === undefined || cfg.principal === "";
const reader = cfg.rows;
const orgViewOf = async (toolCallId) => {
const read = await reader.pin(toolCallId);
if (read.org === undefined)
throw new Error(read.unreadable ?? "the store did not answer");
return { org: read.org };
};
const org = provider.partitions.org ? orgRuleLane({ read: orgViewOf, questionToolMounted: cfg.questionToolMounted, questionToolName: cfg.questionToolName, onRevisionDefect: cfg.onRevisionDefect, onDegraded: cfg.onDisclosure }) : undefined;
const personalAdjudicate = async (req) => {
if (anonymous && !cfg.localOwnerDeclared)
return undefined;
const judged = laneToolNamesOf(req).map((n) => [n, ruleToolGrammarOf(n)]);
if (!judged.some(([, g]) => g === "path") && faceRowsSpeakFor(callPathFaceOf(req)))
judged.push([req.toolName, "path"]);
if (judged.length === 0)
return undefined;
const view = await reader.pin(req.toolCallId);
try {
const table = view.rules.filter((r) => r.behavior !== "allow" || cfg.stripsAllow?.(r, req) !== true);
const leads = judged.some(([, g]) => g === "path") ? await cfg.leads.resolve(bindPathRules(table.filter((r) => r.match === "path" || r.match === "subpath").map((r) => ({ parsed: r, ruleText: r.rule, rule: r })), (spec) => anchorsOf(spec.rule))) : NO_LEADS;
const scopeIdentity = await projectScopeIdentity(table, cfg.rootCanonical, cfg.leads);
const answer = strongestAnswer(judged.map(([n, grammar]) => judgeAgainst(table, { ...req, toolName: n }, grammar, leads, scopeIdentity)));
const stands = answer !== undefined && (("behavior" in answer && answer.behavior === "deny") || "unreadable" in answer);
return view.unreadable !== undefined && !stands ? { unreadable: true } : answer;
}
catch (err) {
const why = `judging this call against the rows threw: ${thrownText(err)}`;
cfg.onDisclosure(`the persisted rule lane could not judge a ${req.toolName} call against the rows it read — ${why}`);
return { unreadable: true, reason: why };
}
};
const anchorsOf = (rule) => persistedRowAnchors(rule.scope, cfg.chainTop);
const judgeAgainst = (table, req, grammar, leads, scopeIdentity) => {
const liveCwd = cfg.liveCwd();
const scope = scopeIdentity !== undefined ? { scopeIdentity } : {};
if (grammar === "path")
return pathRuleLaneAnswer(table, req, { root: cfg.root, sessionId: cfg.sessionId, anchorsOf, leads, ...scope });
if (grammar === "name")
return persistedRuleAnswerOf(adjudicatePersistedNameRules(table, { tool: req.toolName, name: nameRuleSubjectOf(req.toolName, req.args), cwd: cfg.root, sessionId: cfg.sessionId, ...scope }));
if (grammar === "domain")
return persistedRuleAnswerOf(adjudicatePersistedDomainRules(table, { tool: req.toolName, host: domainRuleSubjectOf(req.toolName, req.args), cwd: cfg.root, sessionId: cfg.sessionId, ...scope }));
const command = req.args?.command;
if (typeof command !== "string")
return undefined;
const execCwd = liveCwd !== undefined ? { execCwd: liveCwd } : {};
const verdict = adjudicatePersistedRules(table, { tool: req.toolName, command, cwd: cfg.root, sessionId: cfg.sessionId, ...execCwd, ...scope });
if (verdict !== undefined)
return persistedRuleAnswerOf(verdict);
const coverage = segmentCoverageOf(command, { persisted: table }, { tool: req.toolName, cwd: cfg.root, sessionId: cfg.sessionId, ...execCwd, ...scope });
return coverage !== undefined ? { segmentCoverage: coverage } : undefined;
};
const personal = provider.partitions.durable ? { adjudicate: personalAdjudicate } : undefined;
return { personal, org };
}
export function createRuleOffersOf(cfg) {
return (call, args, ask) => {
const toolName = wireOf("offer.laneTool", call);
if (!cfg.laneArmed || (toolName !== PERSISTED_RULE_TOOL && ruleToolGrammarOf(toolName) !== "name"))
return {};
if ((cfg.principal === undefined || cfg.principal === "") && !cfg.localOwnerDeclared)
return {};
const tierSeat = seatOf("offer.marks", cfg.irreversibilityTier, call);
const closedDoor = ruleClearanceOf(ask, {
egress: heldBy("offer.marks.egress", cfg.egressTools, call),
irreversibility: tierSeat?.value,
shellGated: tierSeat?.shellGated(cfg.shellGatedTools, cfg.toolMarkSources) === true,
}).word;
if (closedDoor !== undefined)
return { ruleOffersAbsence: closedDoor };
if (ruleToolGrammarOf(toolName) === "name") {
const offer = suggestRuleForName(toolName, nameRuleSubjectOf(toolName, args) ?? "");
return offer !== undefined ? { ruleOffers: [offer] } : { ruleOffersAbsence: "lane_cannot_speak" };
}
const command = args?.command;
if (typeof command !== "string")
return { ruleOffersAbsence: "lane_cannot_speak" };
const offers = suggestRulesForCommand(command, {
...(ask?.segmentCoverage !== undefined ? { coverage: ask.segmentCoverage } : {}),
...(cfg.taskRoot !== undefined ? { scope: { kind: "project", root: cfg.taskRoot }, cwd: cfg.taskRoot } : {}),
...(cfg.cwdRef?.current !== undefined ? { execCwd: cfg.cwdRef.current } : {}),
...(cfg.deniesDirectoryRead !== undefined ? { deniesDirectoryRead: cfg.deniesDirectoryRead } : {}),
});
return offers.length > 0
? { ruleOffers: offers, ...(cfg.cwdRef?.current !== undefined ? { execCwd: cfg.cwdRef.current } : {}) }
: { ruleOffersAbsence: "lane_cannot_speak" };
};
}