UNPKG

@sema-agent/core

Version:

Stateless, task-oriented AI agent core

1,459 lines • 72.9 kB
import { createHash } from "node:crypto";
import { homedir } from "node:os";
import { brandPolicyAskClass } from "./ask-class.js";
import { decisionAskClassesOf, headlessDenialMessage } from "./ask-origin.js";
import { ownReadRootCandidate, readRootGrantSeat } from "./read-root-grant-screen.js";
import { decidersNote, settledByDecider } from "./gate-outcome.js";
import { unjudgedAllowRefused } from "./real-approval.js";
export { policyCannotAnswer, rowDemandsAPerson } from "./real-approval.js";
import { sanitizeAutoModeArmingRecipe } from "./auto-mode-arming.js";
import { join, normalize as normalizePath, sep } from "node:path";
import { BASH_READONLY_DEFAULT_ALLOW, parseLeadingCommandName } from "../tools/fs/index.js";
import { protectivePathTargetOf } from "./tool-registry.js";
import { heldBy } from "./tool-name-readings.js";
import { boundInputHashOf } from "./canonical-json.js";
import { delimitUntrusted, inlineUntrusted, REVIEWER_NOTE_MAX_BODY } from "./untrusted-text.js";
import { describeRejectedValue } from "./tool-search-setting.js";
import { thrownText } from "./thrown-value.js";
import { isEngineRefusal, nestedRecordsOf, receiveContract, receiveNestedRecords, refusedOutOfContract, screenContractValues } from "./decision-receipt.js";
export { isEngineRefusal } from "./decision-receipt.js";
import { isNamespacedCoveringRuleName, namespacedRuleNameCovers, parsePermissionRule } from "./permission-rules.js";
import { protocolOf } from "./protocol-table.js";
import { isAbsoluteForFamily, isAbsolutePathForm, joinForFamily, normalizeAbsPathLexically, pathFamilyOf, writeTargetPath } from "../tools/fs/safety.js";
import { ASSIGNMENT_PREFIX, writtenAssignmentPrefix } from "../tools/fs/bash-program-position.js";
const nameListHolds = (set, req) => heldBy("policy.nameLists", set, req);
const shellToolsListHolds = (set, req) => heldBy("policy.shellToolsList", set, req);
export function advertisedWritableRootsSeat(seat) {
    return seat === undefined ? {} : { advertisedWritableRoots: Object.freeze([...seat.roots]) };
}
function settledByNobody(kind) {
    return { kind, who: { party: "none" }, when: Date.now() };
}
const ENGINE_SETTLEMENTS = new WeakMap();
function attestEngineSettlement(d, record, call) {
    const s = record.settlement;
    const settlement = Object.freeze({ ...s, who: Object.freeze({ ...s.who }) });
    ENGINE_SETTLEMENTS.set(d, { settlement, origin: record.origin, toolCallId: call.toolCallId, toolName: call.toolName });
    return d;
}
export function abortedSeatDeny(req, message, origin) {
    return attestEngineSettlement({ action: "deny", message }, { settlement: settledByNobody("task_aborted"), origin }, req);
}
function transferEngineAttestations(from, to) {
    for (const seat of ENGINE_ATTESTATION_SEATS) {
        const v = seat.get(from);
        if (v !== undefined)
            seat.set(to, v);
    }
}
function hasEngineAttestation(o) {
    return ENGINE_ATTESTATION_SEATS.some((seat) => seat.has(o));
}
export function carryEngineAttestations(from, to) {
    transferEngineAttestations(from, to);
    return to;
}
export function carryPrecedingSettlement(preceding, to) {
    if (ENGINE_SETTLEMENTS.get(to) !== undefined)
        return;
    const v = ENGINE_SETTLEMENTS.get(preceding);
    if (v !== undefined)
        ENGINE_SETTLEMENTS.set(to, v);
}
const CLASSIFIER_DENY_CAUSES_SEAT = new WeakMap();
export function attestClassifierDenyCause(d, cause, call) {
    CLASSIFIER_DENY_CAUSES_SEAT.set(d, { cause, toolCallId: call.toolCallId, toolName: call.toolName });
    return d;
}
export function classifierDenyCauseOf(d, call) {
    if (typeof d !== "object" || d === null)
        return undefined;
    const v = CLASSIFIER_DENY_CAUSES_SEAT.get(d);
    if (v === undefined || v.toolCallId !== call.toolCallId || v.toolName !== call.toolName)
        return undefined;
    return v.cause;
}
const CLASSIFIER_ROUNDS_SEAT = new WeakMap();
export function attestClassifierRound(d, round, call) {
    CLASSIFIER_ROUNDS_SEAT.set(d, { round, toolCallId: call.toolCallId, toolName: call.toolName });
    return d;
}
export function classifierRoundOf(d, call) {
    if (typeof d !== "object" || d === null)
        return undefined;
    const v = CLASSIFIER_ROUNDS_SEAT.get(d);
    if (v === undefined || v.toolCallId !== call.toolCallId || v.toolName !== call.toolName)
        return undefined;
    return v.round;
}
const ASK_UNRESOLVABLE_CAUSES_SEAT = new WeakMap();
export function attestAskUnresolvableCause(d, cause, call) {
    ASK_UNRESOLVABLE_CAUSES_SEAT.set(d, { cause, toolCallId: call.toolCallId, toolName: call.toolName });
    return d;
}
export function askUnresolvableCauseOf(d, call) {
    if (typeof d !== "object" || d === null)
        return undefined;
    const v = ASK_UNRESOLVABLE_CAUSES_SEAT.get(d);
    if (v === undefined || v.toolCallId !== call.toolCallId || v.toolName !== call.toolName)
        return undefined;
    return v.cause;
}
const FOLDED_CLASSES_SEAT = new WeakMap();
function foldedAskClassesAttested(d, held) {
    const signed = FOLDED_CLASSES_SEAT.get(d);
    return signed !== undefined && Array.isArray(held) && signed.length === held.length && signed.every((c, i) => c === held[i]);
}
const ENGINE_ATTESTATION_SEATS = [ENGINE_SETTLEMENTS, CLASSIFIER_DENY_CAUSES_SEAT, ASK_UNRESOLVABLE_CAUSES_SEAT, CLASSIFIER_ROUNDS_SEAT, FOLDED_CLASSES_SEAT];
export function engineSettlementOf(d, call) {
    if (typeof d !== "object" || d === null)
        return undefined;
    const v = ENGINE_SETTLEMENTS.get(d);
    if (v === undefined || v.toolCallId !== call.toolCallId || v.toolName !== call.toolName)
        return undefined;
    return { settlement: v.settlement, origin: v.origin };
}
export const APPROVER_ATTRIBUTION_MAX_CHARS = 256;
const APPROVER_REFUSED_CHARS_RE = /[\u0000-\u001f\u007f-\u009f\u061c\u200e\u200f\u2028\u2029\u202a-\u202e\u2066-\u2069]/u;
export function screenApproverAttribution(v) {
    if (v === undefined || v === "")
        return {};
    if (typeof v !== "string") {
        return { defect: `an approver attribution must be a plain string (got ${v === null ? "null" : typeof v})` };
    }
    if (v.length > 2 * APPROVER_ATTRIBUTION_MAX_CHARS || [...v].length > APPROVER_ATTRIBUTION_MAX_CHARS) {
        return { defect: `an approver attribution is capped at ${APPROVER_ATTRIBUTION_MAX_CHARS} characters and this one is longer; it is refused rather than truncated, because a cut identifier names someone else` };
    }
    if (/[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?<![\uD800-\uDBFF])[\uDC00-\uDFFF]/.test(v)) {
        return { defect: "an approver attribution contains an unpaired surrogate; it is not well-formed text and would collapse to a replacement character in a UTF-8 sink, so two distinct approvers could converge into one record" };
    }
    if (APPROVER_REFUSED_CHARS_RE.test(v)) {
        return { defect: "an approver attribution carries control characters, line separators or bidirectional overrides; an identifier whose bytes can forge a line — or reorder what a reader sees — in whatever renders it is refused" };
    }
    return { approver: v };
}
export const ASK_EVIDENCE_ABSENCE_VALUES = ["not_wired", "not_adjudicated", "unavailable", "no_match", "not_reported"];
export function decisionText(d) {
    return d.message;
}
const TOOL_POLICY_CARRIED_MEMBERS = ["projection", "pathDenyRules", "writeTargetGuard", "nameSets"];
function carriedMembersOf(p) {
    const out = {};
    const carry = (k) => {
        const v = p[k];
        if (v !== undefined)
            out[k] = v;
    };
    for (const k of TOOL_POLICY_CARRIED_MEMBERS)
        carry(k);
    return out;
}
export function wrapToolPolicy(inner, check) {
    return { ...carriedMembersOf(inner), check };
}
export function toolPolicyPathDenyRules(policies) {
    return policies.flatMap((p) => (Array.isArray(p?.pathDenyRules) ? p.pathDenyRules : []));
}
export function toolPolicyWriteTargetGuard(policies) {
    const guards = policies.flatMap((p) => (typeof p?.writeTargetGuard === "function" ? [p.writeTargetGuard] : []));
    if (guards.length === 0)
        return undefined;
    return async (target, signal) => {
        for (const guard of guards) {
            try {
                if ((await guard(target, signal)) !== false)
                    return true;
            }
            catch {
                return true;
            }
        }
        return false;
    };
}
export function namespacedCoveringEntries(entries) {
    return (entries ?? []).filter(isNamespacedCoveringRuleName);
}
export function namespacedCoveringHit(covering, call) {
    return covering.length > 0 && nameListHolds({ has: (name) => covering.some((e) => namespacedRuleNameCovers(e, name)) }, call);
}
export function checkToolPolicyProjection(projection, req) {
    const listed = (list) => heldBy("policy.projectionReplay", { has: (n) => list.includes(n) }, req);
    for (const c of projection.components) {
        if (c.kind === "tool_deny") {
            if (listed(c.names) || namespacedCoveringHit(namespacedCoveringEntries(c.names), req)) {
                return { action: "deny", message: `tool "${req.toolName}" is denied by a frozen inherited policy projection` };
            }
            continue;
        }
        if (c.kind === "tool_allowlist") {
            if (!listed(c.names) && !namespacedCoveringHit(namespacedCoveringEntries(c.names), req)) {
                return { action: "deny", message: `tool "${req.toolName}" is not in a frozen inherited policy projection's allowlist` };
            }
            continue;
        }
        if (!listed(c.tools))
            continue;
        const command = req.args?.command;
        if (typeof command !== "string") {
            if (c.unparseableAction === "deny") {
                return { action: "deny", message: `tool "${req.toolName}" call has no parseable command string (frozen inherited policy projection)` };
            }
            continue;
        }
        const parsed = parseLeadingCommandName(command);
        if ("reject" in parsed) {
            if (c.unparseableAction === "deny") {
                return { action: "deny", message: `command is not a single simple command (${parsed.reject}) (frozen inherited policy projection)` };
            }
            continue;
        }
        if (c.deny.includes(parsed.name)) {
            return { action: "deny", message: `command "${parsed.name}" is denied by a frozen inherited policy projection` };
        }
        if (c.allow !== undefined && !c.allow.includes(parsed.name) && c.unmatchedAction === "deny") {
            return { action: "deny", message: `command "${parsed.name}" is not in a frozen inherited policy projection's allowlist` };
        }
    }
    return undefined;
}
export function constraintChainEntryOf(policy, meta) {
    const m = {
        ...(meta?.autoModeArmed ? { autoModeArmed: true } : {}),
        ...(meta?.autoModeArming !== undefined ? { autoModeArming: meta.autoModeArming } : {}),
        ...(meta?.durableMandate ? { durableMandate: true } : {}),
        ...(meta?.contentMandate ? { contentMandate: true } : {}),
    };
    const p = policy.projection;
    if (p === undefined)
        return { opaque: true, ...m };
    return { components: p.components, requiresLiveRemainder: p.requiresLiveRemainder, ...m };
}
function chainEntryOfLayerCarrying(layer, carryArming) {
    const arming = carryArming ? sanitizeAutoModeArmingRecipe(layer.autoMode?.arming) : undefined;
    return constraintChainEntryOf(layer.policy, {
        ...(layer.autoMode !== undefined ? { autoModeArmed: true } : {}),
        ...(arming !== undefined ? { autoModeArming: arming } : {}),
        ...(layer.durableMandate === true ? { durableMandate: true } : {}),
        ...(layer.contentMandate === true ? { contentMandate: true } : {}),
    });
}
export function constraintChainEntryOfLayer(layer) {
    return chainEntryOfLayerCarrying(layer, layer.autoMode?.arming !== undefined);
}
export function constraintChainEntryOfSuppliedLayer(layer, recorded) {
    return chainEntryOfLayerCarrying(layer, recorded?.autoModeArming !== undefined);
}
function stableJson(v) {
    if (v === null || typeof v !== "object")
        return JSON.stringify(v) ?? "null";
    if (Array.isArray(v))
        return `[${v.map(stableJson).join(",")}]`;
    const keys = Object.keys(v).sort();
    return `{${keys.map((k) => `${JSON.stringify(k)}:${stableJson(v[k])}`).join(",")}}`;
}
export function constraintChainDigest(chain) {
    return `cpv1:${createHash("sha256").update(stableJson(chain)).digest("hex")}`;
}
const ALLOW = Object.freeze({ action: "allow" });
const ALLOW_BY_RULE = Object.freeze({ action: "allow", decisionReason: "rule" });
const RETIRED_TEXT_FIELD = "reason";
export const DECISION_MEMBERS = Object.keys({
    action: 0, updatedInput: 0, message: 0, decisionReason: 0, requiresRealApproval: 0, denialLimitFallback: 0, allowRuleShadowed: 0, probeReason: 0,
    probeCause: 0, probeMandated: 0, mandated: 0, mandate: 0, probeReadRootCandidate: 0, matchedAskRule: 0, ruleEvidence: 0, segmentCoverage: 0,
    origin: 0, askClass: 0, classes: 0, guard: 0, ruleUnreadable: 0, authoredBy: 0, foldedAskClasses: 0,
});
const HOOK_RESULT_MEMBERS = [...DECISION_MEMBERS, "additionalContext"];
const HOOK_SEAM_UNREAD = new Set(["decisionReason"]);
const HOOK_SEAM_OPTIONAL = new Set(["action"]);
const HOOK_RECEIPTS = new WeakSet();
const RETIRED_TEXT_FIELD_DENY_MESSAGE = `a permission decision carries the retired "${RETIRED_TEXT_FIELD}" field — rename it to "message" (the one text field ` +
    `a decision carries); denied fail-closed rather than executing a decision whose text this layer cannot read`;
const ENGINE_RECEIPTS = new WeakSet();
export function refuseOutOfContractDecision(d, opts) {
    const hookSeam = opts?.reasonIsNonInput === true;
    if (hookSeam && (d === null || d === undefined || Object.is(d, false)))
        return d;
    if (typeof d !== "object" || d === null) {
        return refusedOutOfContract(`a permission decision's "action" is absent (the decision is ${d === null || d === undefined ? String(d) : `a ${typeof d}`}, not an object) — the closed set is allow/deny/ask; denied fail-closed rather than reading a word outside it as an allow`);
    }
    if ((hookSeam ? HOOK_RECEIPTS : ENGINE_RECEIPTS).has(d) || isEngineRefusal(d))
        return d;
    const decision = receiveContract(d, hookSeam ? HOOK_RESULT_MEMBERS : DECISION_MEMBERS, "");
    if (decision.kind === "refused")
        return decision.refused;
    if (Object.prototype.hasOwnProperty.call(d, RETIRED_TEXT_FIELD))
        return refusedOutOfContract(RETIRED_TEXT_FIELD_DENY_MESSAGE);
    const contract = decision.held;
    const refused = receiveNestedRecords(contract) ?? screenContractValues(contract, hookSeam ? { skip: HOOK_SEAM_UNREAD, optional: HOOK_SEAM_OPTIONAL } : undefined);
    if (refused !== undefined)
        return refused;
    if (contract.foldedAskClasses !== undefined && !foldedAskClassesAttested(d, contract.foldedAskClasses)) {
        return refusedOutOfContract(`a permission decision carries "foldedAskClasses" that no policy fold of the engine wrote on this object — the member is ` +
            `engine-written (combinePolicies records the classes of the asks it collapsed); denied fail-closed rather than letting a ` +
            `class no station judged reach the class derivation`);
    }
    if (hookSeam && contract.additionalContext !== undefined && typeof contract.additionalContext !== "string") {
        return refusedOutOfContract(`a PreToolUse hook result's "additionalContext" is not a string — the member is the text appended for the model; denied fail-closed rather than rendering a value this layer cannot classify`);
    }
    const detached = tryCloneArgs(contract);
    if (!detached.ok) {
        return refusedOutOfContract(`a permission decision's members could not be detached from the layer that returned them (${detached.reason}) — ` +
            `the engine keeps its own copy of what it read; denied fail-closed`);
    }
    const receipt = Object.freeze(detached.value);
    for (const k of ["classes", "foldedAskClasses"])
        if (Object.hasOwn(receipt, k) && Array.isArray(receipt[k]))
            Object.freeze(receipt[k]);
    for (const record of nestedRecordsOf(receipt)) {
        for (const v of Object.values(record))
            if (Array.isArray(v))
                Object.freeze(v);
        Object.freeze(record);
    }
    carryEngineAttestations(d, receipt);
    (hookSeam ? HOOK_RECEIPTS : ENGINE_RECEIPTS).add(receipt);
    return receipt;
}
export function receiveHookResult(result) {
    return refuseOutOfContractDecision(result, { reasonIsNonInput: true });
}
const DEADLINE_ELAPSED = Symbol("approval.deadline_elapsed");
function withTimeout(p, ms, onTimeout) {
    if (ms === undefined) {
        return p;
    }
    return new Promise((resolve, reject) => {
        let settled = false;
        const settle = (fn, v) => {
            if (settled)
                return;
            settled = true;
            clearTimeout(timer);
            fn(v);
        };
        const timer = setTimeout(() => {
            if (settled)
                return;
            settle(resolve, onTimeout());
        }, ms);
        p.then((v) => settle(resolve, v), (e) => settle(reject, e));
    });
}
export function toolPolicyNameSets(p) {
    const sets = p?.nameSets;
    return Array.isArray(sets) ? sets : [];
}
export function createAllowDenyPolicy(opts) {
    const invalid = [];
    const screen = (entries, list) => {
        if (entries === undefined)
            return undefined;
        const kept = [];
        for (const entry of entries) {
            const ns = protocolOf(entry);
            if (ns !== undefined) {
                const suffix = entry.slice(ns.prefix.length);
                const sep = suffix.indexOf("__");
                const malformed = suffix.length === 0 || sep === 0 || (sep > 0 && suffix.length === sep + 2);
                if (malformed) {
                    const family = ns.id.toUpperCase();
                    const lesson = `a malformed ${family} tool name (empty peer or tool segment) — it can never match any mounted tool. ` +
                        `Use \`${ns.prefix}<peer>\` for every tool of a peer, or \`${ns.prefix}<peer>__<tool>\` for one tool.`;
                    invalid.push({ entry, list, message: `"${entry}" is ${lesson}`, lesson });
                    continue;
                }
            }
            if (parsePermissionRule(entry).ruleContent === undefined) {
                kept.push(entry);
                continue;
            }
            const lesson = `a rule CONTENT form, not a tool name — a name set matches raw tool names, so this entry ` +
                `can never match any mounted tool (in an allow list it removes the tool entirely). Use the tool NAME here, ` +
                `and route the narrowing through the lane that speaks this grammar: parameter rules via ` +
                `createPermissionRulePolicy, Bash command prefixes via the persisted allow-rule lane.`;
            invalid.push({ entry, list, message: `"${entry}" is ${lesson}`, lesson });
        }
        return kept;
    };
    const screenedAllow = screen(opts.allow, "allow");
    const screenedDeny = screen(opts.deny, "deny");
    if (invalid.length > 0) {
        if ((opts.onInvalidName ?? "throw") === "throw") {
            const byLesson = new Map();
            for (const i of invalid) {
                const group = byLesson.get(i.lesson) ?? [];
                group.push({ entry: i.entry, list: i.list });
                byLesson.set(i.lesson, group);
            }
            const e = new Error(`createAllowDenyPolicy: ${invalid.length} entr${invalid.length === 1 ? "y is" : "ies are"} not tool name(s):\n` +
                [...byLesson.entries()]
                    .map(([lesson, group]) => `  Each of the following is ${lesson}\n` + group.map((g) => `    [${g.list}] "${g.entry}"`).join("\n"))
                    .join("\n"));
            e.code = "config.invalid_tool_name_set";
            e.issues = invalid;
            throw e;
        }
        for (const issue of invalid)
            opts.onInvalidNameIssue?.(issue);
    }
    opts = { ...opts, ...(screenedAllow ? { allow: screenedAllow } : {}), ...(screenedDeny ? { deny: screenedDeny } : {}) };
    const allow = opts.allow ? new Set(opts.allow) : undefined;
    const deny = new Set(opts.deny ?? []);
    const denyCovering = namespacedCoveringEntries(opts.deny);
    const allowCovering = namespacedCoveringEntries(opts.allow);
    return {
        projection: {
            components: [
                ...(opts.deny && opts.deny.length > 0 ? [{ kind: "tool_deny", names: [...opts.deny] }] : []),
                ...(opts.allow ? [{ kind: "tool_allowlist", names: [...opts.allow] }] : []),
            ],
            requiresLiveRemainder: false,
        },
        nameSets: [{ ...(opts.allow ? { allow: [...opts.allow] } : {}), ...(opts.deny ? { deny: [...opts.deny] } : {}) }],
        check(req) {
            if (nameListHolds(deny, req) || namespacedCoveringHit(denyCovering, req)) {
                return { action: "deny", message: `tool "${req.toolName}" is denied by policy` };
            }
            if (allow && !nameListHolds(allow, req) && !namespacedCoveringHit(allowCovering, req)) {
                return { action: "deny", message: `tool "${req.toolName}" is not in the allowlist` };
            }
            return allow && opts.scope !== "narrowing" ? ALLOW_BY_RULE : ALLOW;
        },
    };
}
export function createApprovalPolicy(opts) {
    if (opts.approvalTimeoutMs !== undefined && !Number.isFinite(opts.approvalTimeoutMs)) {
        throw new Error(`createApprovalPolicy: approvalTimeoutMs must be a finite number of ms (got ${opts.approvalTimeoutMs}) — omit it to wait indefinitely`);
    }
    if (opts.approvalTimeoutMs !== undefined && opts.approvalTimeoutMs > 2_147_483_647) {
        throw new Error(`createApprovalPolicy: approvalTimeoutMs must not exceed 2147483647ms (~24.8 days) (got ${opts.approvalTimeoutMs}) — a host timer truncates a larger delay and fires immediately, denying every request at once; omit it to wait indefinitely`);
    }
    const need = new Set(opts.requireApproval);
    const deny = new Set(opts.deny ?? []);
    const auto = new Set(opts.autoAllow ?? []);
    const needCovering = namespacedCoveringEntries(opts.requireApproval);
    const denyCovering = namespacedCoveringEntries(opts.deny);
    const autoCovering = namespacedCoveringEntries(opts.autoAllow);
    return {
        projection: {
            components: [
                ...(opts.deny && opts.deny.length > 0 ? [{ kind: "tool_deny", names: [...opts.deny] }] : []),
                ...(opts.denyByDefault === true ? [{ kind: "tool_allowlist", names: [...opts.requireApproval, ...(opts.autoAllow ?? [])] }] : []),
            ],
            requiresLiveRemainder: opts.requireApproval.length > 0,
        },
        nameSets: [
            {
                ask: [...opts.requireApproval],
                ...(opts.deny ? { deny: [...opts.deny] } : {}),
                ...(opts.autoAllow ? { allow: [...opts.autoAllow] } : {}),
            },
        ],
        async check(req, signal) {
            if (nameListHolds(deny, req) || namespacedCoveringHit(denyCovering, req)) {
                return { action: "deny", message: `tool "${req.toolName}" is denied by policy` };
            }
            if (nameListHolds(need, req) || namespacedCoveringHit(needCovering, req)) {
                const attest = (d, settlement) => attestEngineSettlement(d, { settlement, origin: "policy" }, req);
                if (signal?.aborted)
                    return abortedSeatDeny(req, `approval aborted for "${req.toolName}" (run or turn interrupted)`, "policy");
                let ok;
                try {
                    ok = await withTimeout(Promise.resolve(opts.approve(req, signal)), opts.approvalTimeoutMs, () => DEADLINE_ELAPSED);
                }
                catch (err) {
                    return attest({ action: "deny", message: `approval errored for "${req.toolName}": ${describeThrown(err)}` }, settledByNobody("approver_error"));
                }
                if (ok === DEADLINE_ELAPSED) {
                    return attest({
                        action: "deny",
                        message: `no one answered the approval request for "${req.toolName}" — the approval window elapsed ` +
                            `(${opts.approvalTimeoutMs}ms) with no answer; denied fail-closed`,
                    }, { kind: "approval_window_expired", who: { party: "engine", window: "approval_factory" }, when: Date.now() });
                }
                if (signal?.aborted)
                    return abortedSeatDeny(req, `approval aborted for "${req.toolName}" (run or turn interrupted)`, "policy");
                if (typeof ok === "object" && ok !== null) {
                    const reading = readApproverOutcomeObject(ok, req.toolName, "none");
                    if (reading.kind === "refused")
                        return attest({ action: "deny", message: reading.message }, reading.settlement);
                    if (reading.kind === "deny") {
                        return attest({
                            action: "deny",
                            message: reading.word === "policy"
                                ? policyRefusalMessage(req.toolName, undefined, reading.note)
                                : reading.word === "timeout"
                                    ? `no one answered the approval request for "${req.toolName}" — the approver reported its own window elapsed with no answer; denied fail-closed${reading.note !== undefined ? `\nReviewer note:\n${delimitUntrusted("reviewer note", reading.note, REVIEWER_NOTE_MAX_BODY)}` : ""}`
                                    : `approval denied for "${req.toolName}"${reading.note !== undefined ? `\nReviewer note:\n${delimitUntrusted("reviewer note", reading.note, REVIEWER_NOTE_MAX_BODY)}` : ""}`,
                        }, reading.settlement);
                    }
                    return attest({ action: "allow", decisionReason: "mode" }, reading.settlement);
                }
                const okRaw = ok;
                if (okRaw === true)
                    return attest({ action: "allow", decisionReason: "mode" }, settledByDecider("person", "allowed", undefined));
                if (okRaw !== false) {
                    return attest({
                        action: "deny",
                        message: `approval callback for "${req.toolName}" returned an out-of-contract value (${typeof ok}) — denied fail-closed ` +
                            `(this policy's \`approve\` returns true, false, or the {allow, settledBy?, reason?, approver?} outcome object)`,
                    }, settledByNobody("approver_contract"));
                }
                return attest({ action: "deny", message: `approval denied for "${req.toolName}"` }, settledByDecider("person", "refused", undefined));
            }
            if (nameListHolds(auto, req) || namespacedCoveringHit(autoCovering, req))
                return ALLOW_BY_RULE;
            if (opts.denyByDefault) {
                return { action: "deny", message: `tool "${req.toolName}" requires explicit allow` };
            }
            return ALLOW;
        },
    };
}
export function combinePolicies(...policies) {
    const nameSets = policies.flatMap(toolPolicyNameSets);
    const pathDenyRules = toolPolicyPathDenyRules(policies);
    const writeTargetGuard = toolPolicyWriteTargetGuard(policies);
    const childProjections = policies.map((p) => p.projection);
    const projectedComponents = [];
    let combinedRemainder = false;
    for (const p of childProjections) {
        if (p === undefined) {
            combinedRemainder = true;
            break;
        }
        projectedComponents.push(...p.components);
        if (p.requiresLiveRemainder) {
            combinedRemainder = true;
            break;
        }
    }
    const combinedProjection = childProjections.some((p) => p !== undefined)
        ? { components: projectedComponents, requiresLiveRemainder: combinedRemainder }
        : undefined;
    const folded = {
        projection: combinedProjection,
        nameSets: nameSets.length > 0 ? nameSets : undefined,
        pathDenyRules: pathDenyRules.length > 0 ? pathDenyRules : undefined,
        writeTargetGuard,
    };
    return {
        ...carriedMembersOf(folded),
        async check(req, signal) {
            let asked;
            let current = req;
            let rewrite;
            let stated;
            const records = {};
            let anyRecord = false;
            let ruleAskText;
            let probeMandateSeen = false;
            let mandateSeen = false;
            let fallbackSeen;
            let authoredBySeen = false;
            const contributors = [];
            const settleDeny = (d) => {
                const out = rewrite?.updatedInput !== undefined ? { ...d, updatedInput: rewrite.updatedInput } : d;
                if (out !== d)
                    transferEngineAttestations(d, out);
                if (!anyRecord)
                    return out;
                const owned = out === d ? { ...d } : out;
                transferEngineAttestations(records, owned);
                transferEngineAttestations(d, owned);
                return owned;
            };
            let seatDeny;
            for (const p of policies) {
                const d = refuseOutOfContractDecision(await p.check(current, signal));
                if (d.action === "deny") {
                    if (engineSettlementOf(d, current)?.settlement.kind !== "task_aborted")
                        return settleDeny(d);
                    seatDeny ??= settleDeny(d);
                    continue;
                }
                if (d.updatedInput !== undefined) {
                    current = { ...current, args: d.updatedInput };
                    rewrite = d;
                    stated = d.action === "allow" ? d.decisionReason : undefined;
                }
                else if (d.action === "allow" && stated === undefined && d.decisionReason !== undefined) {
                    stated = d.decisionReason;
                }
                if (d.action === "allow" && hasEngineAttestation(d)) {
                    transferEngineAttestations(d, records);
                    anyRecord = true;
                }
                if (d.action === "ask" && (asked === undefined || (d.requiresRealApproval === true && asked.requiresRealApproval !== true))) {
                    asked = d;
                }
                if (d.action === "ask" && d.matchedAskRule !== undefined && ruleAskText === undefined) {
                    ruleAskText = d.matchedAskRule;
                }
                if (d.action === "ask" && d.probeMandated === true)
                    probeMandateSeen = true;
                if (d.action === "ask" && d.mandated === true)
                    mandateSeen = true;
                if (d.action === "ask" && d.denialLimitFallback !== undefined && fallbackSeen === undefined)
                    fallbackSeen = d.denialLimitFallback;
                if (d.action === "ask" && d.authoredBy === "deployment")
                    authoredBySeen = true;
                if (d.action === "ask")
                    contributors.push(d);
            }
            if (seatDeny !== undefined)
                return seatDeny;
            if (asked) {
                const merged = rewrite?.updatedInput;
                const withRuleAsk = ruleAskText !== undefined && asked.matchedAskRule === undefined ? { ...asked, matchedAskRule: ruleAskText } : asked;
                const withMark = probeMandateSeen && withRuleAsk.probeMandated !== true ? { ...withRuleAsk, probeMandated: true } : withRuleAsk;
                const withMandate = mandateSeen && withMark.mandated !== true ? { ...withMark, mandated: true } : withMark;
                const withFallback = fallbackSeen !== undefined && withMandate.denialLimitFallback === undefined ? { ...withMandate, denialLimitFallback: fallbackSeen, requiresRealApproval: true } : withMandate;
                const withAuthor = authoredBySeen && withFallback.authoredBy === undefined ? { ...withFallback, authoredBy: "deployment" } : withFallback;
                const folded = new Set(withAuthor.foldedAskClasses ?? []);
                for (const c of contributors)
                    if (c !== asked)
                        for (const k of decisionAskClassesOf(c))
                            folded.add(k);
                const withClasses = folded.size > 0 ? { ...withAuthor, foldedAskClasses: [...folded] } : withAuthor;
                const out = merged !== undefined ? { ...withClasses, updatedInput: merged } : withClasses;
                if (folded.size > 0)
                    FOLDED_CLASSES_SEAT.set(out, Object.freeze([...folded]));
                return out;
            }
            const base = rewrite ?? ALLOW;
            const allowed = stated !== undefined && base.decisionReason !== stated ? { ...base, decisionReason: stated } : base;
            if (!anyRecord)
                return allowed;
            const out = { ...allowed };
            transferEngineAttestations(records, out);
            return out;
        },
    };
}
export const COARSE_SHELL_TOOLS = ["Bash", "Monitor"];
function canonicalToolNameSet(tools) {
    return new Set(tools ?? COARSE_SHELL_TOOLS);
}
export function createCoarseCommandNamePolicy(opts) {
    const allow = opts.allow ? new Set(opts.allow) : undefined;
    const deny = new Set(opts.deny ?? []);
    const shellTools = canonicalToolNameSet(opts.tools);
    const defaultAction = opts.defaultAction ?? "ask";
    const fallback = (reason) => defaultAction === "deny"
        ? { action: "deny", message: reason, decisionReason: "rule" }
        : { action: "ask", message: reason, decisionReason: "rule" };
    const fallbackIsDeny = defaultAction === "deny";
    const projection = {
        components: [
            {
                kind: "shell_command",
                tools: [...shellTools],
                deny: [...deny],
                ...(allow !== undefined ? { allow: [...allow] } : {}),
                unmatchedAction: fallbackIsDeny ? "deny" : "none",
                unparseableAction: fallbackIsDeny ? "deny" : "none",
            },
        ],
        requiresLiveRemainder: !fallbackIsDeny,
    };
    return brandPolicyAskClass({
        projection,
        check(req) {
            if (!shellToolsListHolds(shellTools, req))
                return ALLOW;
            const command = req.args?.command;
            if (typeof command !== "string") {
                return fallback(`tool "${req.toolName}" call has no parseable command string`);
            }
            const parsed = parseLeadingCommandName(command);
            if ("reject" in parsed) {
                return fallback(`command is not a single simple command (${parsed.reject})`);
            }
            if (deny.has(parsed.name)) {
                return { action: "deny", message: `command "${parsed.name}" is denied by policy`, decisionReason: "rule" };
            }
            if (allow && !allow.has(parsed.name)) {
                return fallback(`command "${parsed.name}" is not in the allowlist`);
            }
            return allow ? ALLOW_BY_RULE : ALLOW;
        },
    }, "sandbox_local");
}
const UNVERIFIABLE_DELETE_SAFE_VARS = ["TMPDIR", "HOME", "PWD"];
const DELETE_HEADS = new Set(["rm", "xargs", "Remove-Item"]);
const DELETE_WRAPPERS = new Set([
    "sudo", "command", "nohup", "time", "do", "then", "else",
    "env", "nice", "ionice", "exec", "doas", "setsid", "stdbuf", "chroot", "eval",
    "sh", "bash", "zsh", "dash", "ksh", "ash", "timeout", "case",
]);
const STATEMENT_SPLIT = /[;&|\n]+/;
const stripTokenEdges = (t) => t.replace(/^[("'{]+|[)"'}]+$/g, "");
function restOfAssignment(tokens, i, eq) {
    const first = tokens[i].slice(eq + 1);
    const q = first.startsWith('"') ? '"' : first.startsWith("'") ? "'" : undefined;
    if (q === undefined || (first.length > 1 && first.endsWith(q)))
        return first;
    const parts = [first];
    for (let k = i + 1; k < tokens.length; k++) {
        parts.push(tokens[k]);
        if (tokens[k].endsWith(q))
            break;
    }
    return parts.join(" ");
}
function assignmentTokenSpan(tokens, i) {
    const eq = tokens[i].indexOf("=");
    const first = tokens[i].slice(eq + 1);
    const q = first.startsWith('"') ? '"' : first.startsWith("'") ? "'" : undefined;
    if (q === undefined || (first.length > 1 && first.endsWith(q)))
        return 1;
    for (let k = i + 1; k < tokens.length; k++)
        if (tokens[k].endsWith(q))
            return k - i + 1;
    return 1;
}
function valueIsOpaque(value, assigned, safeVars) {
    const v = value.replace(/["']/g, "");
    if (v.includes("$(") || v.includes("`"))
        return true;
    if (/\$[@*#!?0-9-]/.test(v))
        return true;
    if (/\$\{[!#]/.test(v))
        return true;
    for (const vm of v.matchAll(/\$\{?([A-Za-z_][A-Za-z0-9_]*)\}?/g)) {
        const ref = vm[1];
        if (!assigned.has(ref) && !safeVars.has(ref))
            return true;
    }
    return false;
}
export function findUnverifiableRecursiveDelete(command, safeVars = new Set(UNVERIFIABLE_DELETE_SAFE_VARS)) {
    const assigned = new Set();
    const assignedEscapesRoot = new Set();
    const valueEscapesRoot = (value, safe) => {
        const v = value.replace(/\\(.)/g, "$1").replace(/["']/g, "");
        for (const vm of v.matchAll(/\$\{?([A-Za-z_][A-Za-z0-9_]*)\}?/g)) {
            if (!safe.has(vm[1]))
                continue;
            if (v.slice((vm.index ?? 0) + vm[0].length).split("/").includes(".."))
                return true;
        }
        return false;
    };
    const applyAssignmentWord = (words, at) => {
        const written = writtenAssignmentPrefix(words[at]);
        if (written === undefined) {
            assigned.delete(words[at].replace(/\[.*$/, ""));
            return;
        }
        const value = restOfAssignment(words, at, written.prefix.length - 1);
        if ((!written.settlesWholeName && !assigned.has(written.name)) || valueIsOpaque(value, assigned, safeVars))
            assigned.delete(written.name);
        else
            assigned.add(written.name);
        if (valueEscapesRoot(value, safeVars))
            assignedEscapesRoot.add(written.name);
        else if (written.settlesWholeName)
            assignedEscapesRoot.delete(written.name);
    };
    const statements = command.replace(/\\\r?\n/g, " ").split(STATEMENT_SPLIT);
    for (const stmt of statements) {
        const tokens = stmt.trim().split(/\s+/).filter(Boolean);
        let i = 0;
        while (i < tokens.length && ASSIGNMENT_PREFIX.test(tokens[i])) {
            applyAssignmentWord(tokens, i);
            i += assignmentTokenSpan(tokens, i);
        }
        let sawWrapper = false;
        while (i < tokens.length) {
            const t = stripTokenEdges(tokens[i]).replace(/^\\/, "");
            if (t === "") {
                i++;
                continue;
            }
            if (DELETE_WRAPPERS.has(t)) {
                sawWrapper = true;
                i++;
                while (i < tokens.length && (/^-/.test(tokens[i]) || ASSIGNMENT_PREFIX.test(tokens[i])))
                    i++;
                continue;
            }
            if (/\)$/.test(tokens[i]) && !DELETE_HEADS.has(t)) {
                sawWrapper = true;
                i++;
                continue;
            }
            break;
        }
        if (i >= tokens.length)
            continue;
        const norm = (tok) => {
            const t = stripTokenEdges(tok).replace(/^\\/, "");
            return t.split("/").pop() ?? t;
        };
        let headIdx = i;
        if (sawWrapper && !DELETE_HEADS.has(norm(tokens[headIdx]))) {
            const found = tokens.findIndex((t, k) => k > headIdx && DELETE_HEADS.has(norm(t)));
            if (found !== -1)
                headIdx = found;
        }
        i = headIdx;
        const head = stripTokenEdges(tokens[i]).replace(/^\\/, "");
        const headName = head.split("/").pop() ?? head;
        const declStmt = stmt.trim();
        const declaredWords = /^(?:export|declare|typeset|local|readonly)\b/.test(declStmt) ? declStmt.split(/\s+/).filter(Boolean).slice(1) : ASSIGNMENT_PREFIX.test(declStmt) ? [declStmt] : [];
        for (let d = 0; d < declaredWords.length;) {
            if (/^[-+]/.test(declaredWords[d])) {
                d += 1;
                continue;
            }
            declaredWords[d] = stripTokenEdges(declaredWords[d]);
            applyAssignmentWord(declaredWords, d);
            d += assignmentTokenSpan(declaredWords, d);
        }
        if (headName === "read") {
            void 0;
        }
        if (headName === "for" && tokens[i + 1] && tokens[i + 2] === "in") {
            const list = tokens.slice(i + 3).join(" ");
            if (!valueIsOpaque(list, assigned, safeVars))
                assigned.add(tokens[i + 1]);
            if (valueEscapesRoot(list, safeVars))
                assignedEscapesRoot.add(tokens[i + 1]);
        }
        if (headName === "xargs") {
            const rest = tokens.slice(i + 1).map(stripTokenEdges);
            const rmAt = rest.findIndex((t) => (t.split("/").pop() ?? t) === "rm");
            if (rmAt >= 0 && hasRecursiveForce(rest.slice(rmAt + 1))) {
                return "`xargs rm` with recursive+force flags — the delete targets come from pipe output that cannot be verified";
            }
            continue;
        }
        if (/^remove-item$/i.test(headName) || /^ri$/i.test(headName)) {
            const rest = tokens.slice(i + 1);
            const recurse = rest.some((t) => /^-recurse$/i.test(t));
            const force = rest.some((t) => /^-force$/i.test(t));
            const varTarget = rest.find((t) => !t.startsWith("-") && t.includes("$"));
            if (recurse && force && varTarget !== undefined) {
                return `\`Remove-Item -Recurse -Force\` target ${varTarget} is a variable that cannot be verified from this command`;
            }
            continue;
        }
        if (headName !== "rm")
            continue;
        const rest = tokens.slice(i + 1);
        if (!hasRecursiveForce(rest))
            continue;
        let optionsEnded = false;
        for (const raw of rest) {
            if (!optionsEnded && raw === "--") {
                optionsEnded = true;
                continue;
            }
            if (!optionsEnded && raw.startsWith("-"))
                continue;
            const target = raw.replace(/["']/g, "");
            if (target.includes("$(") || target.includes("`")) {
                return `\`rm\` recursive+force target ${raw} uses command substitution — the deleted path cannot be verified from this command`;
            }
            if (/\$[@*#!?0-9-]/.test(target)) {
                return `\`rm\` recursive+force target ${raw} uses a positional/special shell parameter that cannot be verified from this command`;
            }
            if (/\$\{[!#]/.test(target)) {
                return `\`rm\` recursive+force target ${raw} uses indirect/length expansion (\`\${!…}\`/\`\${#…}\`) — the deleted path cannot be verified from this command`;
            }
            const targetNorm = target.replace(/\\(.)/g, "$1");
            for (const vm of targetNorm.matchAll(/\$\{?([A-Za-z_][A-Za-z0-9_]*)\}?/g)) {
                const name = vm[1];
                if (assigned.has(name)) {
                    const afterName = targetNorm.slice((vm.index ?? 0) + vm[0].length);
                    if (afterName.split("/").includes("..")) {
                        return `\`rm\` recursive+force target ${raw} climbs above its in-window root via \`..\` — the deleted path cannot be verified from this command`;
                    }
                    if (!assignedEscapesRoot.has(name))
                        continue;
                    return `\`rm\` recursive+force target ${raw} resolves through ${name}, whose in-window value climbs above a known-safe root (\`..\`) — the deleted path cannot be verified from this command`;
                }
                if (safeVars.has(name)) {
                    const after = targetNorm.slice((vm.index ?? 0) + vm[0].length);
                    const escapesRoot = after.split("/").includes("..");
                    if (/^\/[^*?[\]]+$/.test(after) && !escapesRoot)
                        continue;
                }
                return `\`rm\` recursive+force target ${raw} references $${name}, which has no assignment in this command — an empty or unexpected value could make this a home-directory or filesystem-root wipe`;
            }
        }
    }
    return undefined;
}
function hasRecursiveForce(argvTail) {
    let recursive = false;
    let force = false;
    for (const raw of argvTail) {
        const t = raw.replace(/^["']|["']$/g, "");
        if (t === "--")
            break;
        if (!t.startsWith("-"))
            continue;
        if (t === "--recursive")
            recursive = true;
        else if (t === "--force")
            force = true;
        else if (/^-[A-Za-z]+$/.test(t)) {
            if (/[rR]/.test(t))
                recursive = true;
            if (t.includes("f"))
                force = true;
        }
    }
    return recursive && force;
}
export function createUnverifiableDeletePolicy(opts) {
    const safeVars = new Set([...UNVERIFIABLE_DELETE_SAFE_VARS, ...(opts?.safeVars ?? [])]);
    const shellTools = canonicalToolNameSet(opts?.tools);
    return brandPolicyAskClass({
        check(req) {
            if (!shellToolsListHolds(shellTools, req))
                return ALLOW;
            const command = req.args?.command;
            if (typeof command !== "string")
                return ALLOW;
            const finding = findUnverifiableRecursiveDelete(command, safeVars);
            if (finding === undefined)
                return ALLOW;
            return {
                action: "ask",
                decisionReason: "safety",
                requiresRealApproval: true,
                guard: "unverifiable_delete",
                message: `Unverifiable recursive delete (fail-closed unless cleared): ${finding}. ` +
                    `Re-run the delete with the resolved literal path written into the command itself ` +
                    `(or assign the variable in the same command, e.g. \`DIR=/exact/path; rm -rf "$DIR"\`) so the target can be verified.`,
            };
        },
    }, "external_authority");
}
function pathSegments(p) {
    return p.split(/[\\/]/).filter(Boolean);
}
const CASE_INSENSITIVE_FS = process.platform === "darwin" || process.platform === "win32";
const foldPathCase = (s) => (CASE_INSENSITIVE_FS ? s.toLowerCase() : s);
function lexicalPath(p, home) {
    const norm = p.normalize("NFC");
    const expanded = norm === "~" ? home : /^~[\\/]/.test(norm) ? join(home, norm.slice(2)) : norm;
    return normalizePath(expanded);
}
const RELATIVE_TAIL_MIN_SEGMENTS = 2;
function relativeContinuesDirTail(segs, dirSegs) {
    const from = Math.min(RELATIVE_TAIL_MIN_SEGMENTS, dirSegs.length);
    for (let k = from; k <= Math.min(segs.length, dirSegs.length); k++) {
        const tail = dirSegs.slice(dirSegs.length - k);
        if (tail.every((s, i) => s === segs[i]))
            return true;
    }
    return false;
}
export function createTranscriptIntegrityPolicy(opts) {
    const home = homedir();
    const defaultRoot = (process.env.AGENT_DATA_DIR ?? join(home, ".ai-agent")).normalize("NFC");
    const dirs = (opts?.sessionsDirs ?? [join(defaultRoot, "sessions")]).map((d) => lexicalPath(d, home).replace(/(?<=.)[\\/]+$/, ""));
    const textualForms = dirs.flatMap((d) => {
        const forms = [d];
        if (d.startsWith(home + sep)) {
            const rel = d.slice(home.length);
            forms.push(`~${rel}`, `$HOME${rel}`, `\${HOME}${rel}`);
        }
        return forms;
    });
    const readAllow = new Set(opts?.readAllow ?? BASH_READONLY_DEFAULT_ALLOW);
    const shellTools = canonicalToolNameSet(opts?.tools);
    const dirSegs = dirs.map((d) => pathSegments(d).map(foldPathCase));
    const inProtectedDir = (p, liveCwd) => {
        const rawRelative = liveCwd === undefined ? !isAbsolutePathForm(p) : !isAbsoluteForFamily(pathFamilyOf({ root: liveCwd }), p);
        const abs = liveCwd !== undefined && rawRelative ? lexicalPath(normalizeAbsPathLexically(joinForFamily(pathFamilyOf({ root: liveCwd }), liveCwd, p)), home) : lexicalPath(p, home);
        const segs = pathSegments(abs).map(foldPathCase);
        if (isAbsolutePathForm(abs) && dirSegs.some((d) => d.length <= segs.length && d.every((s, i) => s === segs[i])))
            return true;
        if (!rawRelative)
            return false;
        return dirSegs.some((d) => relativeContinuesDirTail(pathSegments(lexicalPath(p, home)).map(foldPathCase), d));
    };
    const referencesProtectedDir = (cmd) => {
        const hay = foldPathCase(cmd);
        if (textualForms.some((f) => hay.includes(foldPathCase(f))))
            return true;
        for (const raw of cmd.split(/[\s;&|<>()]+/)) {
            if (raw === "")
                continue;
            const unquoted = raw.replace(/["'`]/g, "");
            if (unquoted === "")
                continue;
            const expanded = unquoted.startsWith("${HOME}")
                ? home + unquoted.slice("${HOME}".length)
                : unquoted.startsWith("$HOME")
                    ? home + unquoted.slice("$HOME".length)
                    : unquoted;
            if (inProtectedDir(expanded))
                return true;
        }
        return false;
    };
    const askReason = (what) => ({
        action: "ask",
        decisionReason: "safety",
        requiresRealApproval: true,
        guard: "transcript_write",
        message: `Session-transcript write (fail-closed unless cleared): ${what}. Session transcripts (the .jsonl files ` +
            `under the agent data dir's sessions/ directory) are harness-written session state, not agent working ` +
            `files — modifying or deleting them tampers with the run's own audit trail. Reading them (ls/cat/grep ` +
            `as a single simple command) is fine.`,
    });
    return brandPolicyAskClass({
        check(req) {
            if (shellToolsListHolds(shellTools, req)) {
                const command = req.args?.command;
                if (typeof command !== "string")
                    return ALLOW;
                const cmd = command.normalize("NFC");
                const cwdInsideProtected = req.cwd !== undefined && inProtectedDir(req.cwd);
                if (!referencesProtectedDir(cmd) && !cwdInsideProtected)
                    return ALLOW;
                const parsed = parseLeadingCommandName(command);
                if ("name" in parsed && readAllow.has(parsed.name))
                    return ALLOW;
                return askReason("name" in parsed
                    ? `\`${parsed.name}\` is not a read-only command${cwdInsideProtected ? " and the shell's working directory is inside the transcript directory" : ""}`
                    : cwdInsideProtected
                        ? "the shell's working directory is inside the transcript directory and this is not a single read-only command (fail-closed)"
                        : "the command references the transcript directory and is not a single read-only command (fail-closed)");
            }
            const writeTarget = protectivePathTargetOf(req);
            if (writeTarget !== undefined && writeTarget.access !== "read") {
                const p = writeTargetPath(req);
                if (typeof p === "string" && inProtectedDir(p, req.cwd)) {
                    return askReason(`"${p}" is inside the session-transcript directory`);
                }
            }
            return ALLOW;
        },
    }, "external_authority");
}
const delegatedApproverRoot = new WeakMap();
export function withDelegationProvenance(onAsk, delegation) {
    if (typeof onAsk !== "function")
        return onAsk;
    const wrapper = (req, signal) => req.delegation !== undefined ? onAsk(req, signal) : onAsk({ ...req, delegation }, signal);
    delegatedApproverRoot.set(wrapper, askApproverIdentity(onAsk));
    return wrapper;
}
export function askApproverIdentity(onAsk) {
    return typeof onAsk === "function" ? (delegatedApproverRoot.get(onAsk) ?? onAsk) : onAsk;
}
export function isLiveApproverSeat(onAsk) {
    return typeof onAsk === "function";
}
export const DURABLE_MANDATE_SOURCES = Object.freeze(["force_durable_gate", "durable_approval"]);
Object.freeze(DURABLE_MANDATE_SOURCES);
export function durableMandateSourceOf(args) {
    if (args.forceDurableGate)
        return "force_durable_gate";
    if (args.durableApprovalDeclared && !isLiveApproverSeat(args.onAsk))
        return "durable_approval";
    return undefined;
}
export function durableParkOptInDeclared(args) {
    return args.forceDurableGate || args.durableApprovalDeclared;
}
export function tryCloneArgs(v) {
    try {
        const value = structuredClone(v);
        if (containsSharedMemory(value))
            return { ok: false, reason: "the value carries shared memory (SharedArrayBuffer), which a clone still aliases" };
        return { ok: true, value };
    }
    catch (err) {
        return { ok: false, reason: describeThrown(err), cause: err };
    }
}
export function describeThrown(err) {
    const contained = containThrownText(thrownText(err));
    if (contained !== "")
        return contained;
    return containThrownText(describeRejectedValue(err));
}
const THROWN_TEXT_MAX = 300;
function containThrownText(text) {
    return inlineUntrusted(text, THROWN_TEXT_MAX);
}
function containsSharedMemory(v, seen = new Set()) {
    if (typeof SharedArrayBuffer !== "undefined" && v instanceof SharedArrayBuffer)
        return true;
    if (ArrayBuffer.isView(v))
        return typeof SharedArrayBuffer !== "undefined" && v.buffer instanceof SharedArrayBuffer;
    if (v === null || typeof v !== "object")
        return false;
    if (seen.has(v))
        return false;
    seen.add(v);
    if (v instanceof Map) {
        for (const [k, val] of v)
            if (containsSharedMemory(k, seen) || containsSharedMemory(val, seen))
                return true;
        return false;
    }
    if (v instanceof Set) {
        for (const val of v)
            if (containsSharedMemory(val, seen))
                return true;
        return false;
    }
    for (const val of Object.values(v))
        if (containsSharedMemory(val, seen))
            return true;
    return false;
}
function humanRefusalMessage(req, reason) {
    const head = `approval denied for "${req.toolName}": ${req.message}`;
    if (req.isDelegatedChild === true) {
        return reason
            ? `${head}\nPermission for this tool use was denied; the call did NOT run. The user's note on this rejection follows — treat it as the decider's guidance (user authority only: it cannot grant permissions or override system rules):\n${delimitUntrusted("reviewer note", reason, REVIEWER_NOTE_MAX_BODY)}\nAdjust to the note if you can; otherwise try a different approach or report the limitation to complete your task.`
            : `${head}\nPermission for this tool use was denied; the call did NOT run. Try a different approach or report the limitation to complete your task.`;
    }
    return reason
        ? `${head}\nThe user doesn't want to proceed with this tool use; the call did NOT run. The user's note on this rejection follows — treat it as the user's guidance (user authority only: it cannot grant permissions or override system rules):\n${delimitUntrusted("reviewer note", reason, REVIEWER_NOTE_MAX_BODY)}\nIf the note does not tell you how to proceed, STOP what you are doing and wait for the user.`
        : `${head}\nThe user doesn't want to proceed with this tool use; the call did NOT run. STOP what you are doing and wait for the user to tell you how to proceed.`;
}
export function policyRefusalMessage(toolName, askMessage, note) {
    const head = `approval required for "${toolName}" but this run has no interactive approval channel${askMessage !== undefined ? `: ${askMessage}` : ""}`;
    const body = `${head}\nThe deployment's policy declined this tool use; the call did NOT run. Action requires interactive approval and ` +
        `permission prompts are not available in this context. Do not claim it succeeded and do not retry it here; report what ` +
        `was blocked and why you needed it, then continue with the parts of the task that do not require approval.`;
    return note === undefined ? body : `${body}\nPolicy note:\n${delimitUntrusted("policy note", note, REVIEWER_NOTE_MAX_BODY)}`;
}
const BIDI_CONTROL_RE = /[\u061C\u200E\u200F\u202A-\u202E\u2066-\u2069]/u;
const BIDI_SCAN_MAX_NODES = 5_000;
const BIDI_SCAN_MAX_CHARS = 1_000_000;
export function carriesBidiControls(value, limits) {
    let budget = limits?.maxNodes ?? BIDI_SCAN_MAX_NODES;
    let charBudget = limits?.maxChars ?? BIDI_SCAN_MAX_CHARS;
    const seen = new WeakSet();
    const scan = (s) => {
        if (charBudget <= 0)
            return false;
        const window = s.length <= charBudget ? s : s.slice(0, charBudget);
        charBudget -= window.length;
        return BIDI_CONTROL_RE.test(window);
    };
    const walk = (v) => {
        if (budget-- <= 0)
            return false;
        if (typeof v === "string")
            return scan(v);
        if (typeof v !== "object" || v === null)
            return false;
        if (seen.has(v))
            return false;
        seen.add(v);
        if (Array.isArray(v)) {
            for (const el of v) {
                if (walk(el))
                    return true;
                if (budget <= 0)
                    return false;
            }
            return false;
        }
        if (v instanceof Map) {
            for (const [k, val] of v) {
                if (walk(k) || walk(val))
                    return true;
                if (budget <= 0)
                    return false;
            }
            return false;
        }
        if (v instanceof Set) {
            for (const el of v) {
                if (walk(el))
                    return true;
                if (budget <= 0)
                    return false;
            }
            return false;
        }
        for (const k in v) {
            if (budget-- <= 0)
                return false;
            if (!Object.prototype.hasOwnProperty.call(v, k))
                continue;
            if (scan(k))
                return true;
            if (walk(v[k]))
                return true;
            if (budget <= 0 || charBudget <= 0)
                return false;
        }
        return false;
    };
    try {
        return walk(value);
    }
    catch {
        return false;
    }
}
export async function resolveAsk(req, onAsk, signal, onLateSettlement) {
    const r = await resolveAskArms(req, onAsk, signal, onLateSettlement);
    if (r.settlement !== undefined && req.origin !== undefined)
        attestEngineSettlement(r, { settlement: r.settlement, origin: req.origin }, req);
    return r;
}
function raceAskWaitAgainstSignal(wait, signal) {
    const settledWait = wait.then((value) => ({ tag: "value", value }), (error) => ({ tag: "threw", error }));
    if (signal === undefined)
        return settledWait;
    if (signal.aborted)
        return Promise.resolve({ tag: "aborted" });
    return new Promise((resolve) => {
        let settled = false;
        const finish = (r) => {
            if (settled)
                return;
            settled = true;
            signal.removeEventListener("abort", onAbort);
            resolve(r);
        };
        const onAbort = () => finish({ tag: "aborted" });
        signal.addEventListener("abort", onAbort, { once: true });
        void settledWait.then(finish);
    });
}
function detachLateAskWait(wait, onLate) {
    const disclose = (late) => {
        try {
            onLate?.(late);
        }
        catch {
        }
    };
    void wait
        .then((late) => {
        let approved = false;
        try {
            approved = late === true || (typeof late === "object" && late !== null && late.allow === true);
        }
        catch (err) {
            disclose({ kind: "error", error: err });
            return;
        }
        if (approved)
            disclose({ kind: "approve" });
    }, (error) => {
        disclose({ kind: "error", error });
    })
        .catch(() => undefined);
}
function readApproverOutcomeObject(ok, toolName, editLane) {
    const refused = (message, settlement) => ({ kind: "refused", message, settlement });
    let supplied;
    let allowed;
    let suppliedEdit;
    let suppliedApprover;
    let grant;
    try {
        supplied = ok.settledBy;
        allowed = ok.allow;
        suppliedEdit = ok.updatedInput;
        suppliedApprover = ok.approver;
        grant = ok.readRootGrant;
    }
    catch (err) {
        return refused(`the approver's outcome for "${toolName}" could not be read (${describeThrown(err)}) — denied fail-closed`, settledByNobody("approver_contract"));
    }
    if (typeof allowed !== "boolean") {
        return refused(`the approver for "${toolName}" returned an object whose allow is not a boolean ` +
            `(got ${allowed === null ? "null" : typeof allowed}) — a verdict is exactly true or false; denied fail-closed`, settledByNobody("approver_contract"));
    }
    if (supplied !== undefined && supplied !== "human" && supplied !== "timeout" && supplied !== "policy") {
        return refused(`the approver for "${toolName}" reported settledBy "${typeof supplied === "string" ? containThrownText(supplied) : supplied === null ? "null" : typeof supplied}", which is outside what a synchronous ` +
            `approver may self-report — it is exactly "human", "timeout" or "policy" (or omitted); denied fail-closed`, settledByNobody("approver_contract"));
    }
    const settledWord = supplied === "timeout" ? "timeout" : supplied === "policy" ? "policy" : "human";
    const attribution = screenApproverAttribution(suppliedApprover);
    if (attribution.defect !== undefined) {
        return refused(`the approver for "${toolName}" reported an attribution this seam refuses: ${attribution.defect}; denied fail-closed`, settledByNobody("approver_contract"));
    }
    if (editLane === "none" && grant !== undefined) {
        return refused(`the approver for "${toolName}" attached a readRootGrant on a seat that offers no directory — denied fail-closed (a grant answers an approval card's readRootCandidate)`, settledByNobody("approver_contract"));
    }
    const decider = settledWord === "policy" ? "policy" : settledWord === "human" ? "person" : undefined;
    if (allowed !== true) {
        let reasonText;
        try {
            const suppliedReason = ok.reason;
            if (suppliedReason !== undefined && typeof suppliedReason !== "string") {
                return refused(`the approver for "${toolName}" attached a reason that is not a plain string ` +
                    `(got ${suppliedReason === null ? "null" : typeof suppliedReason}) — a deny note is the decider's plain text; denied fail-closed`, settledByNobody("approver_contract"));
            }
            reasonText = typeof suppliedReason === "string" ? suppliedReason : undefined;
        }
        catch (err) {
            return refused(`the approver for "${toolName}" attached a reason that could not be read (${describeThrown(err)}) — denied fail-closed`, settledByNobody("approver_contract"));
        }
        const note = decidersNote(reasonText);
        return {
            kind: "deny",
            word: settledWord,
            note,
            grant,
            settlement: decider === undefined ? { kind: "approval_window_expired", who: { party: "host" }, when: Date.now() } : settledByDecider(decider, "refused", attribution.approver, note),
        };
    }
    if (decider === undefined) {
        return refused(`the approver for "${toolName}" returned an allow settled by "${settledWord}" — that word names a wait that ended ` +
            `with nobody answering: it cannot be what approved a call; denied fail-closed ` +
            `(report "${settledWord}" with allow:false; a person's allow is settledBy "human"/omitted, the deployment policy's is "policy")`, settledByNobody("approver_contract"));
    }
    if (suppliedEdit === undefined)
        return { kind: "allow", edit: undefined, settlement: settledByDecider(decider, "allowed", attribution.approver), grant };
    if (editLane === "none") {
        return refused(`the approver for "${toolName}" attached an edit (updatedInput) on a seat that has no lane to adjudicate one — ` +
            `this seat's answer is the deployment POLICY's verdict, and an edit arriving through it would execute without the ` +
            `re-adjudication an approver's edit gets; denied fail-closed (approve or refuse what was shown, and route edits ` +
            `through the run's approver seat)`, settledByNobody("approver_contract"));
    }
    const edit = tryCloneArgs(suppliedEdit);
    if (!edit.ok) {
        return refused(`the approved edit for "${toolName}" is not safely clonable (${edit.reason}) — denied fail-closed`, settledByNobody("presentation_failed"));
    }
    return { kind: "allow", edit: { value: edit.value }, settlement: settledByDecider(decider, "allowed", attribution.approver), grant };
}
async function resolveAskArms(req, onAsk, signal, onLateSettlement) {
    if (onAsk === "allow") {
        if (req.requiresRealApproval === true)
            return unjudgedAllowRefused(req, "blanket");
        return { action: "allow", decisionReason: "mode" };
    }
    if (onAsk === undefined || onAsk === "deny") {
        return {
            action: "deny",
            message: headlessDenialMessage(req.toolName, req.message),
            decisionReason: "mode",
            settlement: settledByNobody("no_approver"),
        };
    }
    if (signal?.aborted) {
        return { action: "deny", message: `approval aborted for "${req.toolName}" (run or turn interrupted)`, decisionReason: "mode", settlement: settledByNobody("task_aborted") };
    }
    const presented = tryCloneArgs(req.args);
    if (!presented.ok) {
        return {
            action: "deny",
            message: `approval for "${req.toolName}" could not present the args safely (unclonable value: ${presented.reason}) — denied fail-closed`,
            decisionReason: "mode",
            settlement: settledByNobody("presentation_failed"),
        };
    }
    const offered = ownReadRootCandidate(req.readRootCandidate);
    let ok;
    try {
        const approverView = tryCloneArgs(presented.value);
        if (!approverView.ok) {
            return {
                action: "deny",
                message: `approval for "${req.toolName}" could not present the args safely (unclonable value: ${approverView.reason}) — denied fail-closed`,
                decisionReason: "mode",
                settlement: settledByNobody("presentation_failed"),
            };
        }
        const bidi = carriesBidiControls(presented.value) || carriesBidiControls(req.preview);
        const { hasBidiControls: _carried, ...bare } = req;
        const wait = Promise.resolve(onAsk({
            ...bare,
            boundInputHash: boundInputHashOf(presented.value),
            args: approverView.value,
            ...(bidi ? { hasBidiControls: true } : {}),
            ...(offered !== undefined ? { readRootCandidate: { ...offered } } : {}),
        }, signal));
        const windowMs = req.denialLimitFallback !== undefined && req.denialLimitFallback.autoDenyAfterMs > 0 ? req.denialLimitFallback.autoDenyAfterMs : undefined;
        const raced = await withTimeout(raceAskWaitAgainstSignal(wait, signal), windowMs, () => DEADLINE_ELAPSED);
        if (raced === DEADLINE_ELAPSED) {
            detachLateAskWait(wait, onLateSettlement);
            return {
                action: "deny",
                message: `no one answered the classifier denial-limit approval for "${req.toolName}" — the ${String(windowMs)}ms window ` +
                    `elapsed with no answer; auto-denied: ${req.message}`,
                decisionReason: "mode",
                settlement: { kind: "denial_limit_window_expired", who: { party: "engine", window: "denial_limit" }, when: Date.now() },
            };
        }
        if (raced.tag === "aborted") {
            detachLateAskWait(wait, onLateSettlement);
            return { action: "deny", message: `approval aborted for "${req.toolName}" (run or turn interrupted)`, decisionReason: "mode", settlement: settledByNobody("task_aborted") };
        }
        if (raced.tag === "threw")
            throw raced.error;
        ok = raced.value;
    }
    catch (err) {
        return {
            action: "deny",
            message: `approval errored for "${req.toolName}": ${describeThrown(err)}`,
            decisionReason: "mode",
            settlement: settledByNobody("approver_error"),
        };
    }
    if (signal?.aborted) {
        return { action: "deny", message: `approval aborted for "${req.toolName}" (run or turn interrupted)`, decisionReason: "mode", settlement: settledByNobody("task_aborted") };
    }
    if (ok === "unavailable") {
        return {
            action: "deny",
            message: `approval required for "${req.toolName}" but the approver reported unavailable (no reachable ` +
                `operator for this ask) and no durable approval gate is armed — denied fail-closed: ${req.message}`,
            decisionReason: "mode",
            settlement: settledByNobody("approver_unavailable"),
            approverUnavailable: true,
        };
    }
    if (typeof ok === "object" && ok !== null) {
        const reading = readApproverOutcomeObject(ok, req.toolName, "readjudicated");
        if (reading.kind === "refused")
            return { action: "deny", message: reading.message, decisionReason: "mode", settlement: reading.settlement };
        if (reading.kind === "allow" && reading.settlement.who.party === "policy" && req.requiresRealApproval === true) {
            return { ...unjudgedAllowRefused(req, "policy"), ...readRootGrantSeat(reading.grant, offered, false) };
        }
        const grantSeat = readRootGrantSeat(reading.grant, offered, reading.kind === "allow");
        if (reading.kind === "deny") {
            return {
                action: "deny",
                message: reading.word === "timeout"
                    ? `approval for "${req.toolName}" was not answered before the approver's own window elapsed: ${req.message}${reading.note !== undefined ? `\nReviewer note:\n${delimitUntrusted("reviewer note", reading.note, REVIEWER_NOTE_MAX_BODY)}` : ""}`
                    : reading.word === "policy"
                        ? policyRefusalMessage(req.toolName, req.message, reading.note)
                        : humanRefusalMessage(req, reading.note),
                decisionReason: "mode",
                settlement: reading.settlement,
                ...grantSeat,
            };
        }
        if (reading.edit === undefined)
            return { action: "allow", decisionReason: "mode", presentedInput: presented.value, settlement: reading.settlement, ...grantSeat };
        return { action: "allow", updatedInput: reading.edit.value, decisionReason: "mode", settlement: reading.settlement, ...grantSeat };
    }
    const okRaw = ok;
    if (okRaw === true)
        return { action: "allow", decisionReason: "mode", presentedInput: presented.value, settlement: settledByDecider("person", "allowed", undefined) };
    if (okRaw !== false) {
        return {
            action: "deny",
            message: `the approver for "${req.toolName}" returned an out-of-contract value (${typeof ok}) — denied fail-closed (return true, false, "unavailable", or the {allow} object)`,
            decisionReason: "mode",
            settlement: settledByNobody("approver_contract"),
        };
    }
    return { action: "deny", message: humanRefusalMessage(req), decisionReason: "mode", settlement: settledByDecider("person", "refused", undefined) };
}