UNPKG

@sema-agent/core

Version:

Stateless, task-oriented AI agent core

196 lines (195 loc) • 13.3 kB
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" }; }; }