@sema-agent/core 7.34.0 → 7.35.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +123 -3
- package/dist/brain/errors.d.ts +23 -1
- package/dist/brain/errors.js +19 -2
- package/dist/brain/status-sink.d.ts +7 -1
- package/dist/brain/stream-shared.d.ts +3 -1
- package/dist/brain/stream-shared.js +16 -4
- package/dist/core/ask-origin.d.ts +34 -6
- package/dist/core/ask-origin.js +24 -23
- package/dist/core/ask-question.js +2 -1
- package/dist/core/checkpoint-store.d.ts +4 -0
- package/dist/core/gate-fold.js +2 -2
- package/dist/core/gate-lanes.js +5 -4
- package/dist/core/governance-codes.d.ts +17 -1
- package/dist/core/governance-codes.js +11 -1
- package/dist/core/hooks.d.ts +3 -0
- package/dist/core/message-utils.d.ts +2 -1
- package/dist/core/message-utils.js +2 -1
- package/dist/core/mode-policy.d.ts +3 -0
- package/dist/core/mode-policy.js +5 -0
- package/dist/core/permission-rule-model.d.ts +4 -0
- package/dist/core/permission-rule-model.js +4 -0
- package/dist/core/permission-rules.js +21 -1
- package/dist/core/persisted-rule-arms.d.ts +4 -1
- package/dist/core/persisted-rule-arms.js +4 -0
- package/dist/core/retired-keys.js +1 -1
- package/dist/core/runner/active-skill-scope.js +5 -4
- package/dist/core/runner/assemble-result.js +2 -2
- package/dist/core/runner/contracts.d.ts +25 -3
- package/dist/core/runner/denial-limit-arms.d.ts +6 -1
- package/dist/core/runner/denial-limit-arms.js +4 -4
- package/dist/core/runner/file-history-request.d.ts +26 -0
- package/dist/core/runner/file-history-request.js +90 -0
- package/dist/core/runner/permission-rule-lanes.d.ts +2 -1
- package/dist/core/runner/permission-rule-lanes.js +14 -11
- package/dist/core/runner/prepare-ask-lane.d.ts +1 -1
- package/dist/core/runner/prepare-ask-lane.js +2 -2
- package/dist/core/runner/prepare-file-history.d.ts +9 -3
- package/dist/core/runner/prepare-file-history.js +4 -90
- package/dist/core/runner/prepare-gate-stations.d.ts +4 -3
- package/dist/core/runner/prepare-gate-stations.js +15 -11
- package/dist/core/runner/prepare-inherited-gate.d.ts +8 -1
- package/dist/core/runner/prepare-inherited-gate.js +10 -4
- package/dist/core/runner/prepare-park-ask.d.ts +4 -2
- package/dist/core/runner/prepare-park-ask.js +5 -4
- package/dist/core/runner/prepare-policy-chain.d.ts +3 -2
- package/dist/core/runner/prepare-policy-chain.js +171 -175
- package/dist/core/runner/prepare-task.js +7 -4
- package/dist/core/runner/run-brain-sinks.js +3 -1
- package/dist/core/runner/run-leg.js +5 -3
- package/dist/core/runner/run-notification-lane.d.ts +3 -0
- package/dist/core/runner/run-notification-lane.js +2 -1
- package/dist/core/runner/runtask.d.ts +1 -0
- package/dist/core/runner/runtask.js +5 -11
- package/dist/core/runner/session-mode-seats.d.ts +48 -0
- package/dist/core/runner/session-mode-seats.js +73 -0
- package/dist/core/runner/session-rule-policy.js +3 -2
- package/dist/core/secret-env.d.ts +30 -58
- package/dist/core/secret-env.js +149 -586
- package/dist/core/sensitive-path-policy.js +2 -1
- package/dist/core/store-contracts/checkpoint-store-contract.js +2 -1
- package/dist/core/tool-policy.d.ts +31 -6
- package/dist/core/tool-policy.js +35 -16
- package/dist/core/tool-registry.d.ts +36 -0
- package/dist/core/tool-registry.js +22 -1
- package/dist/core/tool-roster.d.ts +17 -2
- package/dist/core/tool-roster.js +11 -1
- package/dist/core/trace.d.ts +11 -0
- package/dist/engine/execution-env/node-execution-env.d.ts +3 -1
- package/dist/engine/execution-env/node-execution-env.js +5 -2
- package/dist/engine/harness/agent-harness.js +2 -1
- package/dist/engine/lsp/node-lsp-manager.js +1 -1
- package/dist/index.d.ts +4 -4
- package/dist/index.js +4 -4
- package/dist/stores/file/index.d.ts +5 -3
- package/dist/stores/file/session-store.d.ts +8 -2
- package/dist/stores/file/session-store.js +5 -5
- package/dist/tools/fs/file-read-check.d.ts +2 -1
- package/dist/tools/fs/file-read-check.js +15 -7
- package/dist/tools/fs/shell-read-check.d.ts +10 -2
- package/dist/tools/fs/shell-read-check.js +12 -8
- package/package.json +1 -1
- package/test/export-surface.snapshot.json +15 -1
- package/wire-changes/core@7.35.0.json +584 -0
package/dist/core/ask-origin.js
CHANGED
|
@@ -192,26 +192,26 @@ export function isAskClass(v) {
|
|
|
192
192
|
return ASK_CLASS_SET.has(v);
|
|
193
193
|
}
|
|
194
194
|
export const MODE_ARBITRATION = {
|
|
195
|
-
content_question: { bypassImmune: true, classifierEligible: false, sandboxAdmission: false, ruleMayClear: false },
|
|
196
|
-
hook: { bypassImmune: true, classifierEligible: false, sandboxAdmission: false, ruleMayClear: false },
|
|
197
|
-
ancestor: { bypassImmune: true, classifierEligible: false, sandboxAdmission: false, ruleMayClear: false },
|
|
198
|
-
unverifiable_delete: { bypassImmune: true, classifierEligible: true, sandboxAdmission: false, ruleMayClear: false },
|
|
199
|
-
transcript_write: { bypassImmune: false, classifierEligible: true, sandboxAdmission: false, ruleMayClear: false },
|
|
200
|
-
org_rule: { bypassImmune: true, classifierEligible: false, sandboxAdmission: false, ruleMayClear: false },
|
|
201
|
-
governance_unavailable: { bypassImmune: true, classifierEligible: false, sandboxAdmission: false, ruleMayClear: false },
|
|
202
|
-
rule_unreadable: { bypassImmune: false, classifierEligible: false, sandboxAdmission: false, ruleMayClear: false },
|
|
203
|
-
ask_rule: { bypassImmune: true, classifierEligible: false, sandboxAdmission: false, ruleMayClear: false },
|
|
204
|
-
denial_limit_fallback: { bypassImmune: true, classifierEligible: false, sandboxAdmission: false, ruleMayClear: false },
|
|
205
|
-
operator_forced: { bypassImmune: true, classifierEligible: true, sandboxAdmission: true, ruleMayClear: false },
|
|
206
|
-
write_protection_unresolved: { bypassImmune: true, classifierEligible: false, sandboxAdmission: true, ruleMayClear: false },
|
|
207
|
-
probe: { bypassImmune: false, classifierEligible: true, sandboxAdmission: false, ruleMayClear: false },
|
|
208
|
-
write_protection: { bypassImmune: false, classifierEligible: true, sandboxAdmission: true, ruleMayClear: false },
|
|
209
|
-
tool_marks_mcp: { bypassImmune: false, classifierEligible: true, sandboxAdmission: true, ruleMayClear: false },
|
|
210
|
-
tool_marks_a2a: { bypassImmune: false, classifierEligible: true, sandboxAdmission: true, ruleMayClear: false },
|
|
211
|
-
policy_rra: { bypassImmune: true, classifierEligible: true, sandboxAdmission: false, ruleMayClear: false },
|
|
212
|
-
artifact_shared_read: { bypassImmune: false, classifierEligible: false, sandboxAdmission: false, ruleMayClear: false },
|
|
213
|
-
mode: { bypassImmune: false, classifierEligible: false, sandboxAdmission: false, ruleMayClear: false },
|
|
214
|
-
ordinary: { bypassImmune: false, classifierEligible: true, sandboxAdmission: true, ruleMayClear: true },
|
|
195
|
+
content_question: { bypassImmune: true, classifierEligible: false, sandboxAdmission: false, ruleMayClear: false, hookAskFloor: true },
|
|
196
|
+
hook: { bypassImmune: true, classifierEligible: false, sandboxAdmission: false, ruleMayClear: false, hookAskFloor: false },
|
|
197
|
+
ancestor: { bypassImmune: true, classifierEligible: false, sandboxAdmission: false, ruleMayClear: false, hookAskFloor: false },
|
|
198
|
+
unverifiable_delete: { bypassImmune: true, classifierEligible: true, sandboxAdmission: false, ruleMayClear: false, hookAskFloor: true },
|
|
199
|
+
transcript_write: { bypassImmune: false, classifierEligible: true, sandboxAdmission: false, ruleMayClear: false, hookAskFloor: true },
|
|
200
|
+
org_rule: { bypassImmune: true, classifierEligible: false, sandboxAdmission: false, ruleMayClear: false, hookAskFloor: false },
|
|
201
|
+
governance_unavailable: { bypassImmune: true, classifierEligible: false, sandboxAdmission: false, ruleMayClear: false, hookAskFloor: false },
|
|
202
|
+
rule_unreadable: { bypassImmune: false, classifierEligible: false, sandboxAdmission: false, ruleMayClear: false, hookAskFloor: false },
|
|
203
|
+
ask_rule: { bypassImmune: true, classifierEligible: false, sandboxAdmission: false, ruleMayClear: false, hookAskFloor: true },
|
|
204
|
+
denial_limit_fallback: { bypassImmune: true, classifierEligible: false, sandboxAdmission: false, ruleMayClear: false, hookAskFloor: false },
|
|
205
|
+
operator_forced: { bypassImmune: true, classifierEligible: true, sandboxAdmission: true, ruleMayClear: false, hookAskFloor: false },
|
|
206
|
+
write_protection_unresolved: { bypassImmune: true, classifierEligible: false, sandboxAdmission: true, ruleMayClear: false, hookAskFloor: true },
|
|
207
|
+
probe: { bypassImmune: false, classifierEligible: true, sandboxAdmission: false, ruleMayClear: false, hookAskFloor: false },
|
|
208
|
+
write_protection: { bypassImmune: false, classifierEligible: true, sandboxAdmission: true, ruleMayClear: false, hookAskFloor: true },
|
|
209
|
+
tool_marks_mcp: { bypassImmune: false, classifierEligible: true, sandboxAdmission: true, ruleMayClear: false, hookAskFloor: false },
|
|
210
|
+
tool_marks_a2a: { bypassImmune: false, classifierEligible: true, sandboxAdmission: true, ruleMayClear: false, hookAskFloor: false },
|
|
211
|
+
policy_rra: { bypassImmune: true, classifierEligible: true, sandboxAdmission: false, ruleMayClear: false, hookAskFloor: false },
|
|
212
|
+
artifact_shared_read: { bypassImmune: false, classifierEligible: false, sandboxAdmission: false, ruleMayClear: false, hookAskFloor: true },
|
|
213
|
+
mode: { bypassImmune: false, classifierEligible: false, sandboxAdmission: false, ruleMayClear: false, hookAskFloor: false },
|
|
214
|
+
ordinary: { bypassImmune: false, classifierEligible: true, sandboxAdmission: true, ruleMayClear: true, hookAskFloor: false },
|
|
215
215
|
};
|
|
216
216
|
export const RRA_EXPLAINERS = new Set(["unverifiable_delete", "transcript_write", "org_rule", "governance_unavailable", "rule_unreadable", "denial_limit_fallback", "ancestor", "artifact_shared_read"]);
|
|
217
217
|
export const RELEASABLE_ASK_CLASSES = ASK_CLASSES.filter((c) => !MODE_ARBITRATION[c].bypassImmune);
|
|
@@ -248,15 +248,16 @@ export function decisionAskClassesOf(d) {
|
|
|
248
248
|
export function foldArbitrationRow(classes) {
|
|
249
249
|
if (classes.size === 0)
|
|
250
250
|
return MODE_ARBITRATION.ordinary;
|
|
251
|
-
let bypassImmune = false, classifierEligible = true, sandboxAdmission = true, ruleMayClear = true;
|
|
251
|
+
let bypassImmune = false, classifierEligible = true, sandboxAdmission = true, ruleMayClear = true, hookAskFloor = false;
|
|
252
252
|
for (const c of classes) {
|
|
253
253
|
const row = MODE_ARBITRATION[c];
|
|
254
254
|
bypassImmune ||= row.bypassImmune;
|
|
255
255
|
classifierEligible &&= row.classifierEligible;
|
|
256
256
|
sandboxAdmission &&= row.sandboxAdmission;
|
|
257
257
|
ruleMayClear &&= row.ruleMayClear;
|
|
258
|
+
hookAskFloor ||= row.hookAskFloor;
|
|
258
259
|
}
|
|
259
|
-
return { bypassImmune, classifierEligible, sandboxAdmission, ruleMayClear };
|
|
260
|
+
return { bypassImmune, classifierEligible, sandboxAdmission, ruleMayClear, hookAskFloor };
|
|
260
261
|
}
|
|
261
262
|
export function classifyAsk(d, facts) {
|
|
262
263
|
const classes = decisionAskClassesOf({ ...d, foldedAskClasses: undefined });
|
|
@@ -332,7 +333,7 @@ export function arbitrateByMode(mode, c, disclosure, call) {
|
|
|
332
333
|
const classes = ASK_CLASSES.filter((k) => c.classes.has(k));
|
|
333
334
|
switch (mode) {
|
|
334
335
|
case "dontAsk":
|
|
335
|
-
return has("hook") || has("org_rule") || has("operator_forced") || has("governance_unavailable") ? { kind: "ask" } : { kind: "denied_by_mode", mode, askClass: c.askClass, classes };
|
|
336
|
+
return (has("hook") && !c.row.hookAskFloor) || has("org_rule") || has("operator_forced") || has("governance_unavailable") ? { kind: "ask" } : { kind: "denied_by_mode", mode, askClass: c.askClass, classes };
|
|
336
337
|
case "bypassPermissions":
|
|
337
338
|
if (has("write_protection_unresolved"))
|
|
338
339
|
return { kind: "denied_by_mode", mode, askClass: c.askClass, classes };
|
|
@@ -5,6 +5,7 @@ import { defineTool, errorResult } from "./tools.js";
|
|
|
5
5
|
import { delimitUntrusted, inlineUntrusted } from "./untrusted-text.js";
|
|
6
6
|
import { toolFace } from "./tool-catalog-entries.js";
|
|
7
7
|
import { thrownText } from "./thrown-value.js";
|
|
8
|
+
import { callAnswersTo } from "./tool-roster.js";
|
|
8
9
|
export const ASK_USER_QUESTION_TOOL_NAME = "AskUserQuestion";
|
|
9
10
|
export function classifyQuestionOutcome(outcome) {
|
|
10
11
|
let unavailable;
|
|
@@ -134,7 +135,7 @@ export function validateAskQuestions(questions) {
|
|
|
134
135
|
export function createDurableQuestionPolicy() {
|
|
135
136
|
return {
|
|
136
137
|
check(req) {
|
|
137
|
-
if (req
|
|
138
|
+
if (!callAnswersTo(req, ASK_USER_QUESTION_TOOL_NAME)) {
|
|
138
139
|
return { action: "allow" };
|
|
139
140
|
}
|
|
140
141
|
return { action: "ask", message: "AskUserQuestion: awaiting a human answer (durable)" };
|
|
@@ -868,6 +868,10 @@ export type PendingAction = {
|
|
|
868
868
|
*/
|
|
869
869
|
family?: import("./tool-face.js").ToolFamily;
|
|
870
870
|
pathTarget?: import("./tool-face.js").ToolRosterPathTarget;
|
|
871
|
+
/** The names a rule may spell the parked tool by (`ToolCallFace.names`: the wire name, then the aliases that resolve
|
|
872
|
+
* to it) off the same roster row at the same mint — the pre-CAS recheck judges an edit against rules written on
|
|
873
|
+
* either name, as the live gate judged the original. Absent on a row minted before it ⇒ the row's `toolName` alone. */
|
|
874
|
+
names?: readonly string[];
|
|
871
875
|
/**
|
|
872
876
|
* #438 (additive; no checkpoint-version bump — the `reminderMark` precedent: an optional field an
|
|
873
877
|
* older reader ignores. Unlike that precedent seat — which resume DOES read, through its
|
package/dist/core/gate-fold.js
CHANGED
|
@@ -50,7 +50,7 @@ export async function runGateFold(pass) {
|
|
|
50
50
|
}
|
|
51
51
|
if ((pass.decision.action === "allow" || pass.decision.action === "ask") && input.shellReadCheck !== undefined) {
|
|
52
52
|
const station = input.shellReadCheck;
|
|
53
|
-
const judged = station.judge(
|
|
53
|
+
const judged = station.judge(pass.req, pass.policyRewrite !== undefined ? pass.policyRewrite : pass.currentInput, input.trackedCwd?.(), pass.callSignal ?? input.abortSignal, { toolCallId });
|
|
54
54
|
if (judged !== null) {
|
|
55
55
|
let verdict;
|
|
56
56
|
try {
|
|
@@ -61,7 +61,7 @@ export async function runGateFold(pass) {
|
|
|
61
61
|
if (kind !== "aborted" && input.abortSignal?.aborted !== true) {
|
|
62
62
|
traceHookCrash(input, new Error(`shell read station ${kind === "timeout" ? "timed out" : "threw"} for tool "${toolName}" (toolCallId ${toolCallId}) — where this call reads could not be established: ${thrownText(err)}`), notifier);
|
|
63
63
|
}
|
|
64
|
-
verdict = station.unanswered(
|
|
64
|
+
verdict = station.unanswered(pass.req, kind === "timeout" ? "the read station timed out" : "the read station could not answer");
|
|
65
65
|
}
|
|
66
66
|
applyShellReadVerdict(pass, verdict, toolName, input.shellGated === true);
|
|
67
67
|
}
|
package/dist/core/gate-lanes.js
CHANGED
|
@@ -11,6 +11,7 @@ import { CD_GIT_COMPOUND_SENTENCE, changesDirectoryBeforeGit, readOnlyShellVerdi
|
|
|
11
11
|
import { readShellCommand } from "./shell-lexer.js";
|
|
12
12
|
import { COMMAND_RULE_TOOL } from "./permission-rule-model.js";
|
|
13
13
|
import { catalogRuleFaceOf } from "./tool-registry.js";
|
|
14
|
+
import { callAnswersTo, callFaceSeat } from "./tool-roster.js";
|
|
14
15
|
import { modeAcceptsFileCommand } from "./mode-policy.js";
|
|
15
16
|
import { ORG_ADJUDICATION_TIMEOUT_MS, ORG_RULE_DECISION_REASON, ORG_UNAVAILABLE_DECISION_REASON, settleOrgVerdictWithin } from "./permission-rule-org.js";
|
|
16
17
|
import { exitGate } from "./runner/gate-exit.js";
|
|
@@ -169,7 +170,7 @@ export async function runGateLanes(pass) {
|
|
|
169
170
|
writeProtectionUnresolved: pass.writeProtectionUnresolved === true,
|
|
170
171
|
});
|
|
171
172
|
const askOriginFacts = (org, ruleCallUnreadable) => ({
|
|
172
|
-
contentQuestion:
|
|
173
|
+
contentQuestion: callAnswersTo(pass.req, ASK_USER_QUESTION_TOOL_NAME),
|
|
173
174
|
markedUnresolvable: markedUnresolvable(),
|
|
174
175
|
org,
|
|
175
176
|
...(ruleCallUnreadable ? { ruleCallUnreadable } : {}),
|
|
@@ -199,7 +200,7 @@ export async function runGateLanes(pass) {
|
|
|
199
200
|
let decided = current;
|
|
200
201
|
let pending;
|
|
201
202
|
try {
|
|
202
|
-
pending = Promise.resolve(input.orgRules.adjudicate({ toolName, args, toolCallId }));
|
|
203
|
+
pending = Promise.resolve(input.orgRules.adjudicate({ toolName, args, toolCallId, ...callFaceSeat(pass.req.face) }));
|
|
203
204
|
}
|
|
204
205
|
catch (err) {
|
|
205
206
|
pending = Promise.resolve({ status: "unavailable", disclosures: [`the org adjudication face threw before answering: ${describeThrown(err)}`] });
|
|
@@ -767,14 +768,14 @@ export async function runGateLanes(pass) {
|
|
|
767
768
|
}
|
|
768
769
|
if (editDenied === undefined && input.shellReadCheck !== undefined) {
|
|
769
770
|
const station = input.shellReadCheck;
|
|
770
|
-
const judged = station.judge(
|
|
771
|
+
const judged = station.judge(pass.req, editArgs, input.trackedCwd?.(), callSignal ?? input.abortSignal, { seat: "rejudge", toolCallId });
|
|
771
772
|
if (judged !== null) {
|
|
772
773
|
let verdict;
|
|
773
774
|
try {
|
|
774
775
|
verdict = await withGateTimeout(judged, boundedAwaitDeadlineMs(), callSignal ?? input.abortSignal, "shellReadStation");
|
|
775
776
|
}
|
|
776
777
|
catch {
|
|
777
|
-
verdict = station.unanswered(
|
|
778
|
+
verdict = station.unanswered(pass.req, "the read station could not answer");
|
|
778
779
|
}
|
|
779
780
|
if (verdict.disposition !== "vouch") {
|
|
780
781
|
editDenied = {
|
|
@@ -12,7 +12,6 @@ export declare const GOVERNANCE_CODES: {
|
|
|
12
12
|
readonly "config.compliance_required": "transient";
|
|
13
13
|
readonly "config.compliance_denied": "terminal";
|
|
14
14
|
readonly "config.locked_key": "terminal";
|
|
15
|
-
readonly "config.tool_mount_denied": "terminal";
|
|
16
15
|
readonly "memory.capture_optout_denied": "terminal";
|
|
17
16
|
};
|
|
18
17
|
export type GovernanceCode = keyof typeof GOVERNANCE_CODES;
|
|
@@ -42,6 +41,23 @@ export declare const NON_GOVERNANCE_MEMORY_CODES: ReadonlySet<string>;
|
|
|
42
41
|
* `undefined` (NOT governance — the caller falls through to its ordinary taxonomy).
|
|
43
42
|
*/
|
|
44
43
|
export declare function governanceRetryClass(code: string): GovernanceRetryClass | undefined;
|
|
44
|
+
/**
|
|
45
|
+
* The CLOSED set of codes the `spec.tools` DECLARATION door refuses a caller's tool entry with, at prepare,
|
|
46
|
+
* before anything mounts: the entry cannot be built from its own members (`config.tool_mount_denied`), a
|
|
47
|
+
* member over a roster row's wire bound (`config.tool_roster_bound` / `config.tool_name_too_long` /
|
|
48
|
+
* `config.tool_contract_too_long`), a schema over the row's key bounds (`config.tool_schema_bound`), a
|
|
49
|
+
* declared face or result card the schema / namespace cannot carry (`config.tool_face_invalid`), a name or
|
|
50
|
+
* alias in the engine's protocol namespace (`config.tool_name_invalid`), an egress tool declared other than
|
|
51
|
+
* `write` (`config.egress_requires_write_effect` — also minted, same meaning, for a protocol tool's resolved
|
|
52
|
+
* axes), a name the run reserves for a tool the engine injects (`config.reserved_tool_name` — structured output,
|
|
53
|
+
* the skill tool, tool search, the offload reader, an injected protocol tool; `runGoal`'s completion tool, which
|
|
54
|
+
* refuses before any run). Each ends the run `failed` with the code on `terminal.code`. A caller-shape refusal, not a policy
|
|
55
|
+
* verdict: none of these is a governance code ({@link GOVERNANCE_CODES}); re-issuing the same declaration
|
|
56
|
+
* changes nothing. Every mint site at the door types its code against this set.
|
|
57
|
+
*/
|
|
58
|
+
export declare const TOOL_DECLARATION_CODES: readonly ["config.tool_mount_denied", "config.tool_roster_bound", "config.tool_name_too_long", "config.tool_contract_too_long", "config.tool_schema_bound", "config.tool_face_invalid", "config.tool_name_invalid", "config.egress_requires_write_effect", "config.reserved_tool_name"];
|
|
59
|
+
/** A code in {@link TOOL_DECLARATION_CODES}. */
|
|
60
|
+
export type ToolDeclarationCode = (typeof TOOL_DECLARATION_CODES)[number];
|
|
45
61
|
/**
|
|
46
62
|
* The CLOSED registry of reasons a permission-rule sync round may refuse an inbound record or move a
|
|
47
63
|
* local row into the quarantine area (design/182 §4.3/§5.2/§8.1). Same doctrine as
|
|
@@ -5,7 +5,6 @@ export const GOVERNANCE_CODES = {
|
|
|
5
5
|
"config.compliance_required": "transient",
|
|
6
6
|
"config.compliance_denied": "terminal",
|
|
7
7
|
"config.locked_key": "terminal",
|
|
8
|
-
"config.tool_mount_denied": "terminal",
|
|
9
8
|
"memory.capture_optout_denied": "terminal",
|
|
10
9
|
};
|
|
11
10
|
export const NON_GOVERNANCE_MEMORY_CODES = new Set([
|
|
@@ -93,6 +92,17 @@ export function governanceRetryClass(code) {
|
|
|
93
92
|
return "terminal";
|
|
94
93
|
return undefined;
|
|
95
94
|
}
|
|
95
|
+
export const TOOL_DECLARATION_CODES = [
|
|
96
|
+
"config.tool_mount_denied",
|
|
97
|
+
"config.tool_roster_bound",
|
|
98
|
+
"config.tool_name_too_long",
|
|
99
|
+
"config.tool_contract_too_long",
|
|
100
|
+
"config.tool_schema_bound",
|
|
101
|
+
"config.tool_face_invalid",
|
|
102
|
+
"config.tool_name_invalid",
|
|
103
|
+
"config.egress_requires_write_effect",
|
|
104
|
+
"config.reserved_tool_name",
|
|
105
|
+
];
|
|
96
106
|
export const RULE_SYNC_DROP_CODES = {
|
|
97
107
|
invalid_rule_text: "inbound-refused-or-local-quarantined",
|
|
98
108
|
metadata_mismatch: "inbound-refused-or-local-quarantined",
|
package/dist/core/hooks.d.ts
CHANGED
|
@@ -1111,6 +1111,9 @@ export interface ToolGateInput {
|
|
|
1111
1111
|
* isolation scope (onHookError itself, the deny observer, the durable-park observer). The runner
|
|
1112
1112
|
* wires this to an `observer.notify_failed` trace event; absent = counted only (unchanged). */
|
|
1113
1113
|
onNotifyError?: (failure: import("./safe-notify.js").SafeNotifyFailure) => void;
|
|
1114
|
+
/** The call. `toolName` is the name every policy, hook and name-keyed table of this gate reads — the wire name of the
|
|
1115
|
+
* tool the call dispatches to: the Runner's station resolves a call made under an alias through the roster
|
|
1116
|
+
* (`rosterToolName`) before this gate runs; a host driving the gate resolves it the same way. */
|
|
1114
1117
|
event: {
|
|
1115
1118
|
toolCallId: string;
|
|
1116
1119
|
toolName: string;
|
|
@@ -10,7 +10,8 @@ export declare function messageRole(m: AgentMessage): string | undefined;
|
|
|
10
10
|
* same terms rather than on four. The READ twin of `noteResponseModel` (`brain/stream-shared.ts`), which
|
|
11
11
|
* states the same law on the write side; this side re-tests rather than trusting, because a HOST's own
|
|
12
12
|
* Brain mints these messages too. `undefined` ⇒ the provider named the stamped id, named nothing, or
|
|
13
|
-
* named something unreadable — ONE reading, never three.
|
|
13
|
+
* named something unreadable — ONE reading, never three. Readable = {@link isProviderEchoShape}, the write
|
|
14
|
+
* twin's own predicate.
|
|
14
15
|
*/
|
|
15
16
|
export declare function providerReportedModel(m: {
|
|
16
17
|
readonly model?: unknown;
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
+
import { isProviderEchoShape } from "../brain/errors.js";
|
|
1
2
|
export function messageRole(m) {
|
|
2
3
|
return m.role;
|
|
3
4
|
}
|
|
4
5
|
export function providerReportedModel(m) {
|
|
5
6
|
const reported = m.responseModel;
|
|
6
|
-
if (typeof reported !== "string" || reported
|
|
7
|
+
if (typeof reported !== "string" || !isProviderEchoShape(reported))
|
|
7
8
|
return undefined;
|
|
8
9
|
return reported === m.model ? undefined : reported;
|
|
9
10
|
}
|
|
@@ -172,6 +172,9 @@ export declare class PermissionModeController {
|
|
|
172
172
|
get current(): PermissionMode;
|
|
173
173
|
/** The three-member answer as it stands right now. */
|
|
174
174
|
receipt(): PermissionModeReceipt;
|
|
175
|
+
/** The ceiling's source moved to another leg (the parent session's next leg); `before` is this leg's receipt under the
|
|
176
|
+
* previous one. A ceiling fold the move produced — or changed — is announced, as the construction-time fold is. */
|
|
177
|
+
ceilingRebound(before: PermissionModeReceipt): void;
|
|
175
178
|
/** Bind the deployment's arming facts (once, after the caps resolved); announces a fold-back if one results. */
|
|
176
179
|
bindArming(arming: PermissionModeArming): PermissionModeReceipt;
|
|
177
180
|
/** A steer: the requested word changes; the arming fold is re-judged; a leg that became `auto` arms its classifier.
|
package/dist/core/mode-policy.js
CHANGED
|
@@ -136,6 +136,11 @@ export class PermissionModeController {
|
|
|
136
136
|
return { ...base, effective: declared, foldedBy: "inherited_ceiling" };
|
|
137
137
|
return { ...base, effective: declared };
|
|
138
138
|
}
|
|
139
|
+
ceilingRebound(before) {
|
|
140
|
+
const after = this.receipt();
|
|
141
|
+
if (after.foldedBy === "inherited_ceiling" && (before.foldedBy !== "inherited_ceiling" || before.effective !== after.effective))
|
|
142
|
+
this.onFold?.(after);
|
|
143
|
+
}
|
|
139
144
|
bindArming(arming) {
|
|
140
145
|
const before = this.receipt();
|
|
141
146
|
this.arming = arming;
|
|
@@ -141,6 +141,10 @@ export declare const READ_RULE_TOOL = "Read";
|
|
|
141
141
|
* catalog's declarations, exactly as the protective fences' covered set does.
|
|
142
142
|
*/
|
|
143
143
|
export declare function ruleToolGrammarOf(toolName: string): "command" | "path" | undefined;
|
|
144
|
+
/** The names the persisted grammar judges a call under: every one of the call's rule names (`ruleNamesOf` — the wire
|
|
145
|
+
* name, then the aliases that resolve to it) the grammar speaks for, so a stored `Bash(…)` or `Edit(…)` row reaches a
|
|
146
|
+
* tool that answers to that name under another wire name, as a policy's rule does. Empty ⇒ the grammar speaks for none. */
|
|
147
|
+
export declare function laneToolNamesOf(req: Pick<import("./tool-policy.js").ToolCallRequest, "toolName" | "face">): readonly string[];
|
|
144
148
|
/**
|
|
145
149
|
* Match forms. Four members, two of them one predicate:
|
|
146
150
|
* · `exact` — admits the one string the rule spells.
|
|
@@ -4,6 +4,7 @@ import { compileReadDeny } from "../tools/fs/read-deny.js";
|
|
|
4
4
|
import { carriesShellRedirection, parseLeadingCommandName, splitShellCompoundSegments } from "../tools/fs/bash-readonly-classifier.js";
|
|
5
5
|
import { inlineUntrusted } from "./untrusted-text.js";
|
|
6
6
|
import { pathTargetOf } from "./tool-registry.js";
|
|
7
|
+
import { ruleNamesOf } from "./tool-roster.js";
|
|
7
8
|
import { carriesDotSegment, isAbsoluteForFamily, isIdentityDir, isPathIdentity, isShellRootedSpellingUnmapped, isUncIdentity, pathFamilyOf, pathIdentityOf } from "../tools/fs/safety.js";
|
|
8
9
|
import { readShellCommand } from "./shell-lexer.js";
|
|
9
10
|
import { readOnlyLineObjection, readOnlyShellVerdict } from "./read-only-shell.js";
|
|
@@ -30,6 +31,9 @@ export function ruleToolGrammarOf(toolName) {
|
|
|
30
31
|
return "command";
|
|
31
32
|
return pathTargetOf(toolName) !== undefined ? "path" : undefined;
|
|
32
33
|
}
|
|
34
|
+
export function laneToolNamesOf(req) {
|
|
35
|
+
return ruleNamesOf(req).filter((n) => ruleToolGrammarOf(n) !== undefined);
|
|
36
|
+
}
|
|
33
37
|
export const MAX_RULE_TEXT_CHARS = 512;
|
|
34
38
|
export const BARE_INTERPRETER_NAMES = new Set([
|
|
35
39
|
"node", "deno", "bun", "python", "python2", "python3", "perl", "ruby", "php", "osascript",
|
|
@@ -6,6 +6,7 @@ import { pathFamilyOf, pathIdentityOf, spellingOfIdentity } from "../tools/fs/sa
|
|
|
6
6
|
import { effectivePathTargetOf } from "./effective-path-target.js";
|
|
7
7
|
import { protectivePathTargetOf } from "./tool-registry.js";
|
|
8
8
|
import { ruleDenialSentence } from "./rule-denial-sentence.js";
|
|
9
|
+
import { ruleNamesOf } from "./tool-roster.js";
|
|
9
10
|
export { parsePermissionRule };
|
|
10
11
|
export const BASH_GENERIC_PARAMS = new Set(catalogRuleFaceOf("Bash")?.params ?? []);
|
|
11
12
|
const DEFAULT_CAPS = {
|
|
@@ -379,11 +380,30 @@ export function createPermissionRulePolicy(rules, opts) {
|
|
|
379
380
|
ruleUnreadable: lane,
|
|
380
381
|
requiresRealApproval: true,
|
|
381
382
|
});
|
|
383
|
+
const entryFor = (req) => {
|
|
384
|
+
const entries = ruleNamesOf(req).flatMap((n) => {
|
|
385
|
+
const e = byTool.get(n);
|
|
386
|
+
return e !== undefined ? [e] : [];
|
|
387
|
+
});
|
|
388
|
+
if (entries.length <= 1)
|
|
389
|
+
return entries[0];
|
|
390
|
+
const bare = {};
|
|
391
|
+
for (const b of ["deny", "ask", "allow"]) {
|
|
392
|
+
const hit = entries.find((e) => e.bare[b] !== undefined)?.bare[b];
|
|
393
|
+
if (hit !== undefined)
|
|
394
|
+
bare[b] = hit;
|
|
395
|
+
}
|
|
396
|
+
return {
|
|
397
|
+
bare,
|
|
398
|
+
param: { deny: entries.flatMap((e) => e.param.deny), ask: entries.flatMap((e) => e.param.ask) },
|
|
399
|
+
content: { deny: entries.flatMap((e) => e.content.deny), ask: entries.flatMap((e) => e.content.ask) },
|
|
400
|
+
};
|
|
401
|
+
};
|
|
382
402
|
return {
|
|
383
403
|
nameSets: [nameSets],
|
|
384
404
|
...(pathDenyRules.length > 0 ? { pathDenyRules } : {}),
|
|
385
405
|
check(req) {
|
|
386
|
-
const entry =
|
|
406
|
+
const entry = entryFor(req);
|
|
387
407
|
const covering = hasCovering && protocolOf(req.toolName) !== undefined;
|
|
388
408
|
if (entry?.bare.deny) {
|
|
389
409
|
return { action: "deny", message: ruleDenial(req, entry.bare.deny.ruleText, entry.bare.deny.source) };
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
* (mandates, the org latch, the marked-call seat) rather than a function of the answer alone.
|
|
13
13
|
*/
|
|
14
14
|
import type { PermissionResult } from "./tool-policy.js";
|
|
15
|
-
import type { PersistedRuleHit, PersistedRuleHitRule } from "./hooks.js";
|
|
15
|
+
import type { PersistedRuleAnswer, PersistedRuleHit, PersistedRuleHitRule } from "./hooks.js";
|
|
16
16
|
/** The lane's one read, normalized: at most one of `hit` / `unreadable` / `coverage` (the gate's reader
|
|
17
17
|
* produces it; the arms below read it). */
|
|
18
18
|
export interface PersistedRuleRead {
|
|
@@ -23,6 +23,9 @@ export interface PersistedRuleRead {
|
|
|
23
23
|
/** The lexer's reason when the CALL could not be read against the person's deny/ask rows. */
|
|
24
24
|
readonly reason?: string;
|
|
25
25
|
}
|
|
26
|
+
/** The answers of one store read judged under each of a call's names, folded as one table folds its rows (deny >
|
|
27
|
+
* unreadable > ask > allow > coverage > nothing); among equals the first (the wire name's) stands. */
|
|
28
|
+
export declare function strongestAnswer(answers: readonly PersistedRuleAnswer[]): PersistedRuleAnswer;
|
|
26
29
|
/** Display form of a rule set (design/375 §5.1): every rule text is a DISPLAY value on a human trust
|
|
27
30
|
* boundary, and the set must stay readable however large a lane makes it — each member rides
|
|
28
31
|
* `inlineUntrusted` (200 cap, the same cap the durable leg's `RiskDescriptor.shadowedRule` carries),
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import { decisionAskClassesOf } from "./ask-origin.js";
|
|
2
2
|
import { inlineUntrusted } from "./untrusted-text.js";
|
|
3
|
+
export function strongestAnswer(answers) {
|
|
4
|
+
const rank = (a) => (a === undefined ? 0 : "unreadable" in a ? 4 : "behavior" in a ? { deny: 5, ask: 3, allow: 2 }[a.behavior] : 1);
|
|
5
|
+
return answers.reduce((best, a) => (rank(a) > rank(best) ? a : best), undefined);
|
|
6
|
+
}
|
|
3
7
|
export function disclosedRuleSet(rules) {
|
|
4
8
|
const shown = rules.slice(0, 5).map((r) => inlineUntrusted(r.rule, 200));
|
|
5
9
|
const rest = rules.length - shown.length;
|
|
@@ -15,5 +15,5 @@ export function assertNoRetiredKeys(bag, table, owner) {
|
|
|
15
15
|
}
|
|
16
16
|
export const RETIRED_TASK_SPEC_KEYS = Object.freeze({
|
|
17
17
|
autoModeRequested: 'the permission mode is one key now — write `permissionMode: "auto"`',
|
|
18
|
-
approverPosture: '
|
|
18
|
+
approverPosture: 'a posture never authorized anything; drop the key (the engine renders the posture from the effective mode). Releasing asks is a separate decision: `permissionMode: "bypassPermissions"`',
|
|
19
19
|
});
|
|
@@ -3,6 +3,7 @@ import { pathTargetValue } from "../tool-registry.js";
|
|
|
3
3
|
import { pathTargetBaseOf } from "../effective-path-target.js";
|
|
4
4
|
import { PATH_WRITE_TOOLS, isWithin } from "./session-rule-policy.js";
|
|
5
5
|
import { parseSkillToolEntry, skillSpecifierRejection } from "../skill-tool-specifier.js";
|
|
6
|
+
import { ruleNamesOf } from "../tool-roster.js";
|
|
6
7
|
export class ActiveSkillScope {
|
|
7
8
|
frames = [];
|
|
8
9
|
push(frame) {
|
|
@@ -22,7 +23,7 @@ export function createActiveSkillScopePolicy(opts) {
|
|
|
22
23
|
const { scope, env, rootPath, toolEffects } = opts;
|
|
23
24
|
return {
|
|
24
25
|
async check(req, signal) {
|
|
25
|
-
const
|
|
26
|
+
const names = ruleNamesOf(req);
|
|
26
27
|
const frames = scope.active();
|
|
27
28
|
if (frames.length === 0)
|
|
28
29
|
return { action: "allow" };
|
|
@@ -47,7 +48,7 @@ export function createActiveSkillScopePolicy(opts) {
|
|
|
47
48
|
}
|
|
48
49
|
return { manifest: m, byName };
|
|
49
50
|
});
|
|
50
|
-
if (perFrame.length === 0 || perFrame.some((f) => !f.byName.has(
|
|
51
|
+
if (perFrame.length === 0 || perFrame.some((f) => !names.some((n) => f.byName.has(n)))) {
|
|
51
52
|
const ids = manifests.map((m) => m.lineageId).join(", ");
|
|
52
53
|
return {
|
|
53
54
|
action: "deny",
|
|
@@ -56,7 +57,7 @@ export function createActiveSkillScopePolicy(opts) {
|
|
|
56
57
|
};
|
|
57
58
|
}
|
|
58
59
|
for (const frame of perFrame) {
|
|
59
|
-
const alternatives = frame.byName.get(
|
|
60
|
+
const alternatives = names.flatMap((n) => frame.byName.get(n) ?? []);
|
|
60
61
|
const rejections = [];
|
|
61
62
|
let admitted = false;
|
|
62
63
|
for (const entry of alternatives) {
|
|
@@ -79,7 +80,7 @@ export function createActiveSkillScopePolicy(opts) {
|
|
|
79
80
|
const declaredTarget = req.face?.pathTarget;
|
|
80
81
|
const skillScopeEligible = declaredTarget !== undefined && declaredTarget.skillScopeEligible === true;
|
|
81
82
|
if (pathConstrainingActive && !skillScopeEligible) {
|
|
82
|
-
const eff = toolEffects?.get(toolName) ?? "write";
|
|
83
|
+
const eff = toolEffects?.get(req.toolName) ?? "write";
|
|
83
84
|
if (eff !== "read") {
|
|
84
85
|
const ids = manifests.filter((m) => m.allowPaths?.length).map((m) => m.lineageId).join(", ");
|
|
85
86
|
return {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { isDegenerateCutMessage } from "../../brain/terminal-cause.js";
|
|
2
|
-
import { extractErrorCode, stripErrorCodePrefix } from "../../brain/errors.js";
|
|
2
|
+
import { extractErrorCode, isProviderEchoShape, stripErrorCodePrefix } from "../../brain/errors.js";
|
|
3
3
|
import { terminalProjection } from "./terminal-projection.js";
|
|
4
4
|
import { haltConsumed } from "./halt-attribution.js";
|
|
5
5
|
import { providerReportedModel } from "../message-utils.js";
|
|
@@ -127,7 +127,7 @@ export function terminalCauseOf(final, stats, flags) {
|
|
|
127
127
|
if (final.isApiErrorMessage === true) {
|
|
128
128
|
apiFailure = {
|
|
129
129
|
...(final.apiErrorStatus !== undefined ? { status: final.apiErrorStatus } : {}),
|
|
130
|
-
...(final.requestId
|
|
130
|
+
...(typeof final.requestId === "string" && isProviderEchoShape(final.requestId) ? { requestId: final.requestId } : {}),
|
|
131
131
|
};
|
|
132
132
|
}
|
|
133
133
|
const lifted = extractErrorCode(message);
|
|
@@ -59,6 +59,7 @@ import type { ActiveSkillFrame } from "./active-skill-scope.js";
|
|
|
59
59
|
import type { BudgetAxis } from "./assemble-result.js";
|
|
60
60
|
import type { GitStatusLaneRef } from "./git-status-frame.js";
|
|
61
61
|
import type { SessionReadFileStates } from "./session-read-file-states.js";
|
|
62
|
+
import type { LegModeCeiling, SessionModeSeats } from "./session-mode-seats.js";
|
|
62
63
|
import type { BlockedRef, OutputRef, SkillListingEntry } from "./synthetic-tools.js";
|
|
63
64
|
import type { createAttachmentState, WriteFamily } from "./turn-attachments.js";
|
|
64
65
|
import type { cacheFamilyOf } from "./usage-accounting.js";
|
|
@@ -443,6 +444,8 @@ export interface Prepared {
|
|
|
443
444
|
};
|
|
444
445
|
/** Removes the `spec.signal` abort listener on task end (else a long-lived signal leaks listeners). */
|
|
445
446
|
releaseSignal: () => void;
|
|
447
|
+
/** The leg has stopped: its children's mode ceiling moves to the session's current leg (called once its loop settled). */
|
|
448
|
+
endModeCeiling: () => void;
|
|
446
449
|
/**
|
|
447
450
|
* End-of-leg sweep for questions this leg's gate routed to a person. Called once by the
|
|
448
451
|
* run loop at task end, on every exit path. A binding that is still present means a person ANSWERED and
|
|
@@ -1362,13 +1365,17 @@ export interface InheritedGate {
|
|
|
1362
1365
|
*/
|
|
1363
1366
|
permissionMode?: PermissionMode;
|
|
1364
1367
|
/**
|
|
1365
|
-
* The parent's
|
|
1366
|
-
*
|
|
1367
|
-
*
|
|
1368
|
+
* The parent's live mode ceiling (opaque half; never serialized): the parent leg's mode while that leg runs, its
|
|
1369
|
+
* SESSION's current leg after it — the child's effective mode is DERIVED from it on every read, so a parent's steer
|
|
1370
|
+
* reaches a running child on the child's next arbitration without a forwarding frame, and the session's next leg
|
|
1371
|
+
* reaches a child that outlived the leg that spawned it (a session-scoped background child). `sessionId` is the
|
|
1372
|
+
* parent's, the address of the user notice a folded child declaration is announced on. `hold` (the engine's own
|
|
1373
|
+
* ceiling): the child is re-judged at each move of the ceiling, and a fold that produces is announced as at prepare.
|
|
1368
1374
|
*/
|
|
1369
1375
|
permissionModeCeiling?: {
|
|
1370
1376
|
readonly current: PermissionMode;
|
|
1371
1377
|
readonly sessionId: string;
|
|
1378
|
+
readonly hold?: LegModeCeiling["hold"];
|
|
1372
1379
|
};
|
|
1373
1380
|
/**
|
|
1374
1381
|
* Org-memory admission freeze (ruled 2026-08-05): the spawning chain's FROZEN admitted org-scope
|
|
@@ -1459,6 +1466,14 @@ export interface InheritedGate {
|
|
|
1459
1466
|
* the one every gate consults, here and on every descendant.
|
|
1460
1467
|
*/
|
|
1461
1468
|
arming?: AutoModeArmingRecipe;
|
|
1469
|
+
/**
|
|
1470
|
+
* The ancestor's live mode ceiling (opaque, never serialized; the chain's `permissionModeCeiling`): the classifier
|
|
1471
|
+
* answers the ancestor's asks only while this reads `auto` — else they go to the frozen approver. Absent (a
|
|
1472
|
+
* re-supplied entry) ⇒ the classifier answers as recorded.
|
|
1473
|
+
*/
|
|
1474
|
+
liveMode?: {
|
|
1475
|
+
readonly current: PermissionMode;
|
|
1476
|
+
};
|
|
1462
1477
|
};
|
|
1463
1478
|
/**
|
|
1464
1479
|
* Set ONLY on an entry whose `policy` is the folded form of an ancestor's PreToolUse screening face
|
|
@@ -1993,6 +2008,13 @@ export interface RunInternals {
|
|
|
1993
2008
|
* channel, same posture as every other field here.
|
|
1994
2009
|
*/
|
|
1995
2010
|
sessionReadStates?: SessionReadFileStates;
|
|
2011
|
+
/**
|
|
2012
|
+
* The Runner's per-session permission-mode seats ({@link SessionModeSeats}): the chain a leg hands its children
|
|
2013
|
+
* carries its SESSION's seat as the children's ceiling, and each leg binds itself to that seat once its prepare
|
|
2014
|
+
* commits — so a session-scoped background child follows the session's current leg. Always set by the Runner's own
|
|
2015
|
+
* prepare call (overriding any caller value); absent on a standalone prepareTask, which mints a seat of its own.
|
|
2016
|
+
*/
|
|
2017
|
+
sessionModeSeats?: SessionModeSeats;
|
|
1996
2018
|
/** Trusted system-injection sink (`Runner.runLocked` wires it to the live TaskStream queue plus the
|
|
1997
2019
|
* current harness lanes; never a public TaskSpec field), ADDRESSED TO THE SESSION rather than to this
|
|
1998
2020
|
* run: a producer wired here (a background shell, a watch, a delegation settle) keeps the closure long
|
|
@@ -27,6 +27,10 @@ export interface InheritedAutoMode {
|
|
|
27
27
|
decider: AutoModeDecider;
|
|
28
28
|
denialTracking?: AutoModeDenialTracker;
|
|
29
29
|
arming?: AutoModeArmingRecipe;
|
|
30
|
+
/** The ancestor's live mode seat: the decider answers only while it reads `auto` (absent ⇒ as recorded). */
|
|
31
|
+
liveMode?: {
|
|
32
|
+
readonly current: import("../ask-origin.js").PermissionMode;
|
|
33
|
+
};
|
|
30
34
|
}
|
|
31
35
|
/**
|
|
32
36
|
* A re-supplied chain entry (a cross-process redemption rebuilt its ancestor's classifier from the
|
|
@@ -179,7 +183,8 @@ export type InheritedClassifierJudgment<A extends InheritedAsk> =
|
|
|
179
183
|
export declare function judgeInheritedClassifier<A extends InheritedAsk>(opts: {
|
|
180
184
|
autoMode: InheritedAutoMode | undefined;
|
|
181
185
|
/** The leg's live permission mode (the gate's seat): under `dontAsk` / `bypassPermissions` / `plan` the decider is not
|
|
182
|
-
* dispatched (the mode's arbitration decides first). Absent ⇒ a decider on the chain is dispatched
|
|
186
|
+
* dispatched (the mode's arbitration decides first). Absent ⇒ a decider on the chain is dispatched (subject to the
|
|
187
|
+
* entry's own `autoMode.liveMode`). */
|
|
183
188
|
liveMode?: () => import("../ask-origin.js").PermissionMode;
|
|
184
189
|
ask: A;
|
|
185
190
|
req: ToolCallRequest;
|
|
@@ -80,17 +80,17 @@ export function inheritedAskCarry(judged, liveApprover, tracker) {
|
|
|
80
80
|
export async function judgeInheritedClassifier(opts) {
|
|
81
81
|
const { autoMode, ask, req } = opts;
|
|
82
82
|
const origin = askOriginOf(ask, INHERITED_STATION_FACTS);
|
|
83
|
-
const
|
|
83
|
+
const classifierStandsDown = () => {
|
|
84
84
|
const live = opts.liveMode?.();
|
|
85
|
-
return live === "dontAsk" || live === "bypassPermissions" || live === "plan";
|
|
85
|
+
return live === "dontAsk" || live === "bypassPermissions" || live === "plan" || (autoMode?.liveMode !== undefined && autoMode.liveMode.current !== "auto");
|
|
86
86
|
};
|
|
87
|
-
if (autoMode === undefined ||
|
|
87
|
+
if (autoMode === undefined || classifierStandsDown() || !classifyAsk(ask, INHERITED_STATION_FACTS).row.classifierEligible) {
|
|
88
88
|
return { kind: "resolve", ask, fallback: ask.denialLimitFallback, mintedHere: false, origin };
|
|
89
89
|
}
|
|
90
90
|
const verdict = await autoMode.decider
|
|
91
91
|
.decide({ req, ...(ask.message !== undefined ? { askMessage: ask.message } : {}) }, opts.signal)
|
|
92
92
|
.catch(() => ({ kind: "unavailable", cause: "error" }));
|
|
93
|
-
if (verdict.kind === "allow" &&
|
|
93
|
+
if (verdict.kind === "allow" && classifierStandsDown())
|
|
94
94
|
return { kind: "resolve", ask, fallback: ask.denialLimitFallback, mintedHere: false, origin };
|
|
95
95
|
const attributed = (d) => (verdict.classifier !== undefined ? attestClassifierRound(d, verdict.classifier, req) : d);
|
|
96
96
|
if (verdict.kind === "allow") {
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/** The file-history REQUEST door and its bad-value doors — the pre-acquire half of the file-history phase (`prepare-file-history.ts`). */
|
|
2
|
+
import type { FileHistoryStore } from "../file-history-store.js";
|
|
3
|
+
import type { RunnerDeps, TaskSpec } from "../types.js";
|
|
4
|
+
/**
|
|
5
|
+
* The file-history REQUEST door: every refusal the request (`TaskSpec`) and the deployment's own seats decide
|
|
6
|
+
* without the session, the env or the store's contents — the three bad-value doors (`RunnerDeps.onTrackFailure`,
|
|
7
|
+
* `RunnerDeps.fileHistoryStore` / `fileHistoryCapture`, `TaskSpec.fileHistory`), the retired `rewindFiles` restore
|
|
8
|
+
* sense, the restoreFiles / resumeAt / rewindFilesTo contradictions, and a restore asked of a run that holds no
|
|
9
|
+
* history (`rewind.capture_disabled` / `rewind.store_unconfigured`). SYNCHRONOUS and side-effect free; the driver
|
|
10
|
+
* calls it BEFORE the session is acquired, so a request refused here leaves the session — its leaf included —
|
|
11
|
+
* exactly as it was. What it resolves rides into the file-history phase (`prepareFileHistory`) unchanged.
|
|
12
|
+
*/
|
|
13
|
+
export declare function resolveFileHistoryRequest(spec: Pick<TaskSpec, "resumeAt" | "resumeAtMode" | "restoreFiles" | "rewindFilesTo" | "fileHistory">, deps: Pick<RunnerDeps, "onTrackFailure" | "fileHistoryStore" | "fileHistoryCapture">): FileHistoryRequest;
|
|
14
|
+
/** What {@link resolveFileHistoryRequest} resolved: the validated seats and words, the run's store (after every off
|
|
15
|
+
* switch) and its capture store, and the restore target (`undefined` = no restore asked). */
|
|
16
|
+
export interface FileHistoryRequest {
|
|
17
|
+
onTrackFailure: "refuse" | "proceed-unprotected";
|
|
18
|
+
wiredSeat: FileHistoryStore | "disabled" | undefined;
|
|
19
|
+
runDeclaredOff: boolean;
|
|
20
|
+
captureOff: boolean;
|
|
21
|
+
declaredOff: boolean;
|
|
22
|
+
fileHistoryStore: FileHistoryStore | undefined;
|
|
23
|
+
captureStore: FileHistoryStore | undefined;
|
|
24
|
+
rewindTarget: string | undefined;
|
|
25
|
+
rewindBefore: boolean;
|
|
26
|
+
}
|