@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
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
export function resolveFileHistoryRequest(spec, deps) {
|
|
2
|
+
const onTrackFailure = deps.onTrackFailure === undefined ? "refuse" : deps.onTrackFailure;
|
|
3
|
+
if (onTrackFailure !== "refuse" && onTrackFailure !== "proceed-unprotected") {
|
|
4
|
+
const e = new Error(`RunnerDeps.onTrackFailure must be "refuse" or "proceed-unprotected"; got ${JSON.stringify(deps.onTrackFailure)} — refusing prepare rather than silently defaulting a first-touch-failure policy.`);
|
|
5
|
+
e.code = "config.invalid_on_track_failure";
|
|
6
|
+
throw e;
|
|
7
|
+
}
|
|
8
|
+
const wiredSeat = resolveWiredFileHistoryStore(deps.fileHistoryStore);
|
|
9
|
+
const runDeclaredOff = resolveRunFileHistoryWord(spec.fileHistory);
|
|
10
|
+
const captureOff = resolveFileHistoryCaptureWord(deps.fileHistoryCapture, wiredSeat);
|
|
11
|
+
const declaredOff = wiredSeat === "disabled" || runDeclaredOff;
|
|
12
|
+
const fileHistoryStore = declaredOff ? undefined : wiredSeat;
|
|
13
|
+
const captureStore = captureOff ? undefined : fileHistoryStore;
|
|
14
|
+
const legacyRewindFiles = spec.rewindFiles;
|
|
15
|
+
if (legacyRewindFiles === true) {
|
|
16
|
+
if (spec.resumeAt !== undefined) {
|
|
17
|
+
const e = new Error(`rewind-files: TaskSpec.rewindFiles is retired (design/381 — rewind now converges the per-edited-file history, not a whole-tree snapshot). This request pairs it with resumeAt, i.e. the RESTORE sense: set restoreFiles: true instead. (The capture sense needs no request anymore — first-touch tracking is on whenever a RunnerDeps.fileHistoryStore is wired and neither RunnerDeps.fileHistoryCapture nor TaskSpec.fileHistory turns it off.)`);
|
|
18
|
+
e.code = "rewind.rewind_files_retired";
|
|
19
|
+
throw e;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
if (spec.restoreFiles === true && spec.resumeAt === undefined) {
|
|
23
|
+
const e = new Error(`rewind-files: restoreFiles: true needs a resumeAt anchor — restoring files means converging them to a specific prior entry's boundary, and this spec names none. Use resumeAt + restoreFiles to move conversation and files together, or rewindFilesTo alone for a code-only restore.`);
|
|
24
|
+
e.code = "rewind.invalid_spec";
|
|
25
|
+
throw e;
|
|
26
|
+
}
|
|
27
|
+
if (spec.resumeAt !== undefined && spec.rewindFilesTo !== undefined) {
|
|
28
|
+
const e = new Error(`rewind-files: resumeAt ("${spec.resumeAt}") and rewindFilesTo ("${spec.rewindFilesTo}") were both set — resumeAt already anchors the file restore when restoreFiles is true, so a separate rewindFilesTo target is a conflicting request. Drop one of the two.`);
|
|
29
|
+
e.code = "rewind.conflicting_targets";
|
|
30
|
+
throw e;
|
|
31
|
+
}
|
|
32
|
+
const rewindTarget = spec.resumeAt !== undefined ? (spec.restoreFiles === true ? spec.resumeAt : undefined) : spec.rewindFilesTo;
|
|
33
|
+
const rewindBefore = rewindTarget !== undefined && spec.resumeAt !== undefined && spec.resumeAtMode === "before";
|
|
34
|
+
if (rewindTarget !== undefined && fileHistoryStore === undefined) {
|
|
35
|
+
if (runDeclaredOff)
|
|
36
|
+
throw captureDisabledRefusal(rewindTarget);
|
|
37
|
+
const e = new Error(wiredSeat === "disabled"
|
|
38
|
+
? `rewind-files: restoring files to entry "${rewindTarget}" requires a history backend, but this deployment declared file history disabled (RunnerDeps.fileHistoryStore: "disabled"), so there is no history to restore — the files were NOT rewound`
|
|
39
|
+
: `rewind-files: restoring files to entry "${rewindTarget}" requires a history backend, but this deployment wired no RunnerDeps.fileHistoryStore — no file history was ever recorded, so the files were NOT rewound`);
|
|
40
|
+
e.code = "rewind.store_unconfigured";
|
|
41
|
+
throw e;
|
|
42
|
+
}
|
|
43
|
+
return { onTrackFailure, wiredSeat, runDeclaredOff, captureOff, declaredOff, fileHistoryStore, captureStore, rewindTarget, rewindBefore };
|
|
44
|
+
}
|
|
45
|
+
function resolveWiredFileHistoryStore(candidate) {
|
|
46
|
+
if (candidate === undefined || candidate === "disabled")
|
|
47
|
+
return candidate;
|
|
48
|
+
const verbs = ["trackEdit", "annulTrack", "snapshot", "restore", "canRestore", "reap", "adoptScope"];
|
|
49
|
+
const verbOf = (v) => {
|
|
50
|
+
try {
|
|
51
|
+
return candidate[v];
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
return undefined;
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
const probeable = (typeof candidate === "object" && candidate !== null) || typeof candidate === "function";
|
|
58
|
+
const missing = probeable ? verbs.filter((v) => typeof verbOf(v) !== "function") : [...verbs];
|
|
59
|
+
if (missing.length > 0) {
|
|
60
|
+
const shape = candidate === null ? "null" : probeable ? "a value that is not a FileHistoryStore" : `a ${typeof candidate}`;
|
|
61
|
+
const e = new Error(`RunnerDeps.fileHistoryStore was wired with ${shape} — missing contract verbs: ${missing.join(", ")}. Refusing prepare rather than half-wiring the rewind seat (a present-but-unusable store makes every Edit/Write/NotebookEdit fail its first-touch backup while restore requests report "no store wired"). Write "disabled" (or omit the key) to run without file history.`);
|
|
62
|
+
e.code = "config.invalid_file_history_store";
|
|
63
|
+
throw e;
|
|
64
|
+
}
|
|
65
|
+
return candidate;
|
|
66
|
+
}
|
|
67
|
+
function resolveRunFileHistoryWord(word) {
|
|
68
|
+
if (word === undefined || word === "disabled")
|
|
69
|
+
return word === "disabled";
|
|
70
|
+
const e = new Error(`rewind-files: TaskSpec.fileHistory holds ${shownWord(word)} — the only value is "disabled"; omit the key to leave the deployment's setting in force`);
|
|
71
|
+
e.code = "rewind.invalid_spec";
|
|
72
|
+
throw e;
|
|
73
|
+
}
|
|
74
|
+
function resolveFileHistoryCaptureWord(word, wiredSeat) {
|
|
75
|
+
if (word === undefined || (word === "off" && wiredSeat !== undefined && wiredSeat !== "disabled"))
|
|
76
|
+
return word === "off";
|
|
77
|
+
const e = new Error(word === "off"
|
|
78
|
+
? `RunnerDeps.fileHistoryCapture: "off" requires a wired RunnerDeps.fileHistoryStore — nothing to switch off; to drop history entirely write fileHistoryStore: "disabled"`
|
|
79
|
+
: `RunnerDeps.fileHistoryCapture holds ${shownWord(word)} — the only value is "off"; omit the key to record whenever a fileHistoryStore is wired`);
|
|
80
|
+
e.code = "config.invalid_file_history_capture";
|
|
81
|
+
throw e;
|
|
82
|
+
}
|
|
83
|
+
function shownWord(word) {
|
|
84
|
+
return typeof word === "string" ? JSON.stringify(word.slice(0, 40)) : word === null || typeof word === "boolean" || typeof word === "number" ? String(word) : `a ${typeof word}`;
|
|
85
|
+
}
|
|
86
|
+
function captureDisabledRefusal(target) {
|
|
87
|
+
const e = new Error(`rewind-files: this run set TaskSpec.fileHistory: "disabled", so it neither records nor restores file history — restoring files to entry "${target}" was refused and the files were NOT rewound`);
|
|
88
|
+
e.code = "rewind.capture_disabled";
|
|
89
|
+
return e;
|
|
90
|
+
}
|
|
@@ -29,6 +29,7 @@ import type { AskRuleEvidence, ToolCallRequest } from "../tool-policy.js";
|
|
|
29
29
|
import { persistedRuleMandateOf, type RuleClearanceFacts } from "../ask-origin.js";
|
|
30
30
|
import { type OrgGateVerdict, type PersistedRuleAnswer, type PersistedRuleHit, type PersistedRuleUnreadable } from "../hooks.js";
|
|
31
31
|
import type { PersistedRule, PersistedRuleVerdict, RuleOffer, SegmentCoverage } from "../permission-rule-model.js";
|
|
32
|
+
import { type ToolCallLike } from "../tool-registry.js";
|
|
32
33
|
import { type OrgRuleResolution } from "../permission-rule-org.js";
|
|
33
34
|
import { type EffectivePermissionRules, type PermissionRuleStoreProvider } from "../permission-rule-provider.js";
|
|
34
35
|
/** The one SHELL tool the persisted-rule lane's COMMAND grammar speaks for (the rule-offer face mints for
|
|
@@ -246,7 +247,7 @@ export declare function createRuleOffersOf(cfg: {
|
|
|
246
247
|
current: string;
|
|
247
248
|
} | undefined;
|
|
248
249
|
deniesDirectoryRead: ((directory: string) => boolean) | undefined;
|
|
249
|
-
}): (
|
|
250
|
+
}): (call: Pick<ToolCallLike, "toolName" | "face">, args: unknown, ask: RuleOffersAskFacts | undefined) => {
|
|
250
251
|
ruleOffers?: readonly RuleOffer[];
|
|
251
252
|
ruleOffersAbsence?: RuleOffersAbsence;
|
|
252
253
|
execCwd?: string;
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import { persistedRuleMandateOf, ruleClearanceOf } from "../ask-origin.js";
|
|
2
2
|
import {} from "../hooks.js";
|
|
3
|
-
import { adjudicatePersistedPathRules, adjudicatePersistedRules, ruleToolGrammarOf, segmentCoverageOf, suggestRulesForCommand } from "../permission-rule-model.js";
|
|
3
|
+
import { adjudicatePersistedPathRules, adjudicatePersistedRules, laneToolNamesOf, ruleToolGrammarOf, segmentCoverageOf, suggestRulesForCommand } from "../permission-rule-model.js";
|
|
4
4
|
import { isAbsolutePathForm } from "../../tools/fs/safety.js";
|
|
5
5
|
import { effectivePathTargetOf } from "../effective-path-target.js";
|
|
6
|
-
import { declaredPathTargetOf, protectivePathTargetOf } from "../tool-registry.js";
|
|
6
|
+
import { anyNamed, declaredPathTargetOf, firstNamed, protectivePathTargetOf, ruleNamesOf } from "../tool-registry.js";
|
|
7
7
|
import { orgRuleVerdictFor } from "../permission-rule-org.js";
|
|
8
8
|
import { ownerStoreOf } from "../permission-rule-provider.js";
|
|
9
9
|
import { thrownText } from "../thrown-value.js";
|
|
10
|
+
import { strongestAnswer } from "../persisted-rule-arms.js";
|
|
10
11
|
import { gateWaitFailureOf, withGateTimeout } from "../gate-bounded-await.js";
|
|
11
12
|
import { ORG_ADJUDICATION_TIMEOUT_MS } from "../permission-rule-org.js";
|
|
12
13
|
export const PERSISTED_RULE_TOOL = "Bash";
|
|
@@ -105,9 +106,9 @@ export function createPermissionRuleLanes(cfg) {
|
|
|
105
106
|
}
|
|
106
107
|
const revisionCell = orgRevisionEvidenceOf(resolution, cfg.onRevisionDefect);
|
|
107
108
|
const command = req.args?.command;
|
|
108
|
-
if (req.
|
|
109
|
+
if (!laneToolNamesOf(req).includes(PERSISTED_RULE_TOOL) || typeof command !== "string")
|
|
109
110
|
return { status: "available", ...revisionCell };
|
|
110
|
-
const verdict = orgRuleVerdictFor(resolution.rules, { tool:
|
|
111
|
+
const verdict = orgRuleVerdictFor(resolution.rules, { tool: PERSISTED_RULE_TOOL, command });
|
|
111
112
|
return verdict === undefined ? { status: "available", ...revisionCell } : { status: "available", verdict, ...revisionCell };
|
|
112
113
|
},
|
|
113
114
|
}
|
|
@@ -115,8 +116,8 @@ export function createPermissionRuleLanes(cfg) {
|
|
|
115
116
|
const personalAdjudicate = async (req) => {
|
|
116
117
|
if (anonymous && !cfg.localOwnerDeclared)
|
|
117
118
|
return undefined;
|
|
118
|
-
const
|
|
119
|
-
if (
|
|
119
|
+
const laneNames = laneToolNamesOf(req);
|
|
120
|
+
if (laneNames.length === 0)
|
|
120
121
|
return undefined;
|
|
121
122
|
let view;
|
|
122
123
|
try {
|
|
@@ -128,7 +129,7 @@ export function createPermissionRuleLanes(cfg) {
|
|
|
128
129
|
if (view.unreadable !== undefined)
|
|
129
130
|
skipped(view.unreadable, view.unreadableSources);
|
|
130
131
|
try {
|
|
131
|
-
const answer = judgeAgainst(view.rules, req,
|
|
132
|
+
const answer = strongestAnswer(laneNames.map((n) => judgeAgainst(view.rules, { ...req, toolName: n }, ruleToolGrammarOf(n))));
|
|
132
133
|
return view.unreadable !== undefined && (answer === undefined || "segmentCoverage" in answer) ? { unreadable: true } : answer;
|
|
133
134
|
}
|
|
134
135
|
catch (err) {
|
|
@@ -156,15 +157,17 @@ export function createPermissionRuleLanes(cfg) {
|
|
|
156
157
|
return { personal, org };
|
|
157
158
|
}
|
|
158
159
|
export function createRuleOffersOf(cfg) {
|
|
159
|
-
return (
|
|
160
|
+
return (call, args, ask) => {
|
|
161
|
+
const toolName = call.toolName;
|
|
160
162
|
if (!cfg.laneArmed || toolName !== PERSISTED_RULE_TOOL)
|
|
161
163
|
return {};
|
|
162
164
|
if ((cfg.principal === undefined || cfg.principal === "") && !cfg.localOwnerDeclared)
|
|
163
165
|
return {};
|
|
166
|
+
const tierSeat = firstNamed(cfg.irreversibilityTier, ruleNamesOf(call));
|
|
164
167
|
const closedDoor = ruleClearanceOf(ask, {
|
|
165
|
-
egress: cfg.egressTools
|
|
166
|
-
irreversibility:
|
|
167
|
-
shellGated: cfg.shellGatedTools.has(
|
|
168
|
+
egress: anyNamed(cfg.egressTools, ruleNamesOf(call)),
|
|
169
|
+
irreversibility: tierSeat?.value,
|
|
170
|
+
shellGated: tierSeat !== undefined && cfg.shellGatedTools.has(tierSeat.name),
|
|
168
171
|
}).word;
|
|
169
172
|
if (closedDoor !== undefined)
|
|
170
173
|
return { ruleOffersAbsence: closedDoor };
|
|
@@ -77,7 +77,7 @@ export interface PrepareAskLaneInput {
|
|
|
77
77
|
isDelegatedChild?: true;
|
|
78
78
|
};
|
|
79
79
|
/** borrowed-readonly — the policy-chain phase's per-tool risk-axes projection (`riskAxes` on the ask). */
|
|
80
|
-
riskAxesOf: (
|
|
80
|
+
riskAxesOf: (call: Pick<ToolCallRequest, "toolName" | "face">) => {
|
|
81
81
|
riskAxes?: {
|
|
82
82
|
irreversible?: boolean;
|
|
83
83
|
egress?: boolean;
|
|
@@ -96,10 +96,10 @@ export function prepareAskLane(input) {
|
|
|
96
96
|
const p = approvalPreviewOf(req.toolName, req.args);
|
|
97
97
|
return { ...(p.preview !== undefined ? { preview: p.preview } : {}), ...(p.withheld !== undefined ? { previewWithheld: p.withheld } : {}) };
|
|
98
98
|
})(),
|
|
99
|
-
...ruleOffersOf(req
|
|
99
|
+
...ruleOffersOf(req, req.args, decision.action === "ask" ? decision : undefined),
|
|
100
100
|
message: decision.message ?? `approval required for "${req.toolName}"`,
|
|
101
101
|
...askSourceIdentity(),
|
|
102
|
-
...riskAxesOf(req
|
|
102
|
+
...riskAxesOf(req),
|
|
103
103
|
...(decision.action === "ask" && decision.requiresRealApproval === true ? { requiresRealApproval: true } : {}),
|
|
104
104
|
...(decision.action === "ask" ? askMandateMember(decision) : {}),
|
|
105
105
|
...(decision.action === "ask" && decision.persistedRuleShadowed !== undefined ? { persistedRuleShadowed: decision.persistedRuleShadowed } : {}),
|
|
@@ -12,7 +12,9 @@
|
|
|
12
12
|
* before the move (a minted ownedEnv is destroyed there, the session view forgotten). The phase acquires
|
|
13
13
|
* no resource of its own — the two seats it returns are closures over the store the deployment owns.
|
|
14
14
|
*
|
|
15
|
-
* The
|
|
15
|
+
* The request door (the bad-value doors and every refusal the request decides alone) lives in
|
|
16
|
+
* `./file-history-request.ts` and runs BEFORE the session is acquired; this phase receives its resolution.
|
|
17
|
+
* The module-level helpers below the phase (`restoredAbsPathsOf`,
|
|
16
18
|
* the refusal's epoch-tail wording helper, `adoptForkedFileHistory` with its per-store carry memo,
|
|
17
19
|
* `createFileHistoryBoundarySeat`, `resolveFileHistoryCoordinates` / `resolveFileHistoryScope` /
|
|
18
20
|
* `fileHistoryFilesystemIdentity`, `childScopeRewindRefusal`) moved with it unchanged; the two the test
|
|
@@ -22,13 +24,17 @@ import type { ExecutionEnv } from "../../internal/harness.js";
|
|
|
22
24
|
import type { StoredSession } from "../../engine/session/session.js";
|
|
23
25
|
import type { RunnerDeps, TaskResult, TaskSpec, TrackFileEditHook } from "../types.js";
|
|
24
26
|
import type { FileHistoryBoundarySeat, RunInternals } from "./contracts.js";
|
|
27
|
+
import type { FileHistoryRequest } from "./file-history-request.js";
|
|
25
28
|
export interface PrepareFileHistoryInput {
|
|
26
29
|
/** borrowed-readonly — the deployment seats this phase reads: the first-touch failure policy, the history
|
|
27
30
|
* store and its capture switch (all validated HERE, once), the incident sink, and the retired-epoch diagnostic probe. */
|
|
28
|
-
deps: Pick<RunnerDeps, "
|
|
31
|
+
deps: Pick<RunnerDeps, "onError">;
|
|
29
32
|
/** borrowed-readonly — the REBOUND spec's rewind request fields and off-switch word (the retired `rewindFiles`
|
|
30
33
|
* spelling is read off the same object as an unknown key) and the caller's cancel signal for the restore. */
|
|
31
|
-
spec: Pick<TaskSpec, "resumeAt" | "
|
|
34
|
+
spec: Pick<TaskSpec, "resumeAt" | "restoreFiles" | "acceptPartialRestore" | "signal">;
|
|
35
|
+
/** borrowed-readonly — the request door's resolution ({@link resolveFileHistoryRequest}), judged before the session was
|
|
36
|
+
* acquired: every refusal the request and the deployment's seats decide on their own has already thrown. */
|
|
37
|
+
request: FileHistoryRequest;
|
|
32
38
|
/** borrowed-readonly — the trusted lineage a delegating parent handed this run (absent for a root run
|
|
33
39
|
* and for a host's own fork), read for the recording-scope decision only. */
|
|
34
40
|
internals: Pick<RunInternals, "fileHistoryLineage"> | undefined;
|
|
@@ -2,45 +2,16 @@ import { NodeExecutionEnv } from "../../engine/execution-env/node-execution-env.
|
|
|
2
2
|
import { isRemoteExecutionEnv } from "../remote-env.js";
|
|
3
3
|
import { resolveTrackKey } from "../file-history-store.js";
|
|
4
4
|
export async function prepareFileHistory(input) {
|
|
5
|
-
const { deps, spec, internals, executionEnv, taskRootFinal, handsEnabled, sessionId, session, abortController } = input;
|
|
5
|
+
const { deps, spec, request, internals, executionEnv, taskRootFinal, handsEnabled, sessionId, session, abortController } = input;
|
|
6
6
|
const rewindNotes = [];
|
|
7
|
-
const onTrackFailure
|
|
8
|
-
if (
|
|
9
|
-
const e = new Error(`RunnerDeps.onTrackFailure must be "refuse" or "proceed-unprotected"; got ${JSON.stringify(deps.onTrackFailure)} — refusing prepare rather than silently defaulting a first-touch-failure policy.`);
|
|
10
|
-
e.code = "config.invalid_on_track_failure";
|
|
11
|
-
throw e;
|
|
12
|
-
}
|
|
13
|
-
const wiredSeat = resolveWiredFileHistoryStore(deps.fileHistoryStore);
|
|
14
|
-
const runDeclaredOff = resolveRunFileHistoryWord(spec.fileHistory);
|
|
15
|
-
const captureOff = resolveFileHistoryCaptureWord(deps.fileHistoryCapture, wiredSeat);
|
|
16
|
-
const declaredOff = wiredSeat === "disabled" || runDeclaredOff;
|
|
17
|
-
const fileHistoryStore = declaredOff ? undefined : wiredSeat;
|
|
18
|
-
const captureStore = captureOff ? undefined : fileHistoryStore;
|
|
19
|
-
const legacyRewindFiles = spec.rewindFiles;
|
|
20
|
-
if (legacyRewindFiles === true) {
|
|
21
|
-
if (spec.resumeAt !== undefined) {
|
|
22
|
-
const e = new Error(`rewind-files: TaskSpec.rewindFiles is retired (design/381 — rewind now converges the per-edited-file history, not a whole-tree snapshot). This request pairs it with resumeAt, i.e. the RESTORE sense: set restoreFiles: true instead. (The capture sense needs no request anymore — first-touch tracking is on whenever a RunnerDeps.fileHistoryStore is wired and neither RunnerDeps.fileHistoryCapture nor TaskSpec.fileHistory turns it off.)`);
|
|
23
|
-
e.code = "rewind.rewind_files_retired";
|
|
24
|
-
throw e;
|
|
25
|
-
}
|
|
7
|
+
const { onTrackFailure, captureOff, declaredOff, fileHistoryStore, captureStore, rewindTarget, rewindBefore } = request;
|
|
8
|
+
if (spec.rewindFiles === true) {
|
|
26
9
|
try {
|
|
27
10
|
deps.onError?.(new Error(`TaskSpec.rewindFiles (capture sense) is retired and was ignored: per-edited-file first-touch tracking is on whenever RunnerDeps.fileHistoryStore is wired and neither RunnerDeps.fileHistoryCapture nor TaskSpec.fileHistory turns it off, so a per-turn capture request has nothing left to ask for. Drop the field; the restore sense moved to TaskSpec.restoreFiles.`), { phase: "config", sessionId });
|
|
28
11
|
}
|
|
29
12
|
catch {
|
|
30
13
|
}
|
|
31
14
|
}
|
|
32
|
-
if (spec.restoreFiles === true && spec.resumeAt === undefined) {
|
|
33
|
-
const e = new Error(`rewind-files: restoreFiles: true needs a resumeAt anchor — restoring files means converging them to a specific prior entry's boundary, and this spec names none. Use resumeAt + restoreFiles to move conversation and files together, or rewindFilesTo alone for a code-only restore.`);
|
|
34
|
-
e.code = "rewind.invalid_spec";
|
|
35
|
-
throw e;
|
|
36
|
-
}
|
|
37
|
-
if (spec.resumeAt !== undefined && spec.rewindFilesTo !== undefined) {
|
|
38
|
-
const e = new Error(`rewind-files: resumeAt ("${spec.resumeAt}") and rewindFilesTo ("${spec.rewindFilesTo}") were both set — resumeAt already anchors the file restore when restoreFiles is true, so a separate rewindFilesTo target is a conflicting request. Drop one of the two.`);
|
|
39
|
-
e.code = "rewind.conflicting_targets";
|
|
40
|
-
throw e;
|
|
41
|
-
}
|
|
42
|
-
const rewindTarget = spec.resumeAt !== undefined ? (spec.restoreFiles === true ? spec.resumeAt : undefined) : spec.rewindFilesTo;
|
|
43
|
-
const rewindBefore = rewindTarget !== undefined && spec.resumeAt !== undefined && spec.resumeAtMode === "before";
|
|
44
15
|
if (spec.resumeAt !== undefined && spec.restoreFiles !== true) {
|
|
45
16
|
rewindNotes.push({
|
|
46
17
|
code: "conversation_only",
|
|
@@ -64,18 +35,7 @@ export async function prepareFileHistory(input) {
|
|
|
64
35
|
await adoptForkedFileHistory(captureStore, session, sessionId, deps.onError);
|
|
65
36
|
if (rewindTarget !== undefined && historyScope !== sessionId)
|
|
66
37
|
throw childScopeRewindRefusal(historyScope, rewindTarget);
|
|
67
|
-
if (fileHistoryStore
|
|
68
|
-
if (rewindTarget !== undefined && runDeclaredOff)
|
|
69
|
-
throw captureDisabledRefusal(rewindTarget);
|
|
70
|
-
if (rewindTarget !== undefined) {
|
|
71
|
-
const e = new Error(wiredSeat === "disabled"
|
|
72
|
-
? `rewind-files: restoring files to entry "${rewindTarget}" requires a history backend, but this deployment declared file history disabled (RunnerDeps.fileHistoryStore: "disabled"), so there is no history to restore — the files were NOT rewound`
|
|
73
|
-
: `rewind-files: restoring files to entry "${rewindTarget}" requires a history backend, but this deployment wired no RunnerDeps.fileHistoryStore — no file history was ever recorded, so the files were NOT rewound`);
|
|
74
|
-
e.code = "rewind.store_unconfigured";
|
|
75
|
-
throw e;
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
else if (!handsEnabled) {
|
|
38
|
+
if (fileHistoryStore !== undefined && !handsEnabled) {
|
|
79
39
|
if (rewindTarget !== undefined) {
|
|
80
40
|
rewindNotes.push({
|
|
81
41
|
code: "files_env_unsupported",
|
|
@@ -168,52 +128,6 @@ function restoredAbsPathsOf(restored, root) {
|
|
|
168
128
|
}
|
|
169
129
|
return out;
|
|
170
130
|
}
|
|
171
|
-
function resolveWiredFileHistoryStore(candidate) {
|
|
172
|
-
if (candidate === undefined || candidate === "disabled")
|
|
173
|
-
return candidate;
|
|
174
|
-
const verbs = ["trackEdit", "annulTrack", "snapshot", "restore", "canRestore", "reap", "adoptScope"];
|
|
175
|
-
const verbOf = (v) => {
|
|
176
|
-
try {
|
|
177
|
-
return candidate[v];
|
|
178
|
-
}
|
|
179
|
-
catch {
|
|
180
|
-
return undefined;
|
|
181
|
-
}
|
|
182
|
-
};
|
|
183
|
-
const probeable = (typeof candidate === "object" && candidate !== null) || typeof candidate === "function";
|
|
184
|
-
const missing = probeable ? verbs.filter((v) => typeof verbOf(v) !== "function") : [...verbs];
|
|
185
|
-
if (missing.length > 0) {
|
|
186
|
-
const shape = candidate === null ? "null" : probeable ? "a value that is not a FileHistoryStore" : `a ${typeof candidate}`;
|
|
187
|
-
const e = new Error(`RunnerDeps.fileHistoryStore was wired with ${shape} — missing contract verbs: ${missing.join(", ")}. Refusing prepare rather than half-wiring the rewind seat (a present-but-unusable store makes every Edit/Write/NotebookEdit fail its first-touch backup while restore requests report "no store wired"). Write "disabled" (or omit the key) to run without file history.`);
|
|
188
|
-
e.code = "config.invalid_file_history_store";
|
|
189
|
-
throw e;
|
|
190
|
-
}
|
|
191
|
-
return candidate;
|
|
192
|
-
}
|
|
193
|
-
function resolveRunFileHistoryWord(word) {
|
|
194
|
-
if (word === undefined || word === "disabled")
|
|
195
|
-
return word === "disabled";
|
|
196
|
-
const e = new Error(`rewind-files: TaskSpec.fileHistory holds ${shownWord(word)} — the only value is "disabled"; omit the key to leave the deployment's setting in force`);
|
|
197
|
-
e.code = "rewind.invalid_spec";
|
|
198
|
-
throw e;
|
|
199
|
-
}
|
|
200
|
-
function resolveFileHistoryCaptureWord(word, wiredSeat) {
|
|
201
|
-
if (word === undefined || (word === "off" && wiredSeat !== undefined && wiredSeat !== "disabled"))
|
|
202
|
-
return word === "off";
|
|
203
|
-
const e = new Error(word === "off"
|
|
204
|
-
? `RunnerDeps.fileHistoryCapture: "off" requires a wired RunnerDeps.fileHistoryStore — nothing to switch off; to drop history entirely write fileHistoryStore: "disabled"`
|
|
205
|
-
: `RunnerDeps.fileHistoryCapture holds ${shownWord(word)} — the only value is "off"; omit the key to record whenever a fileHistoryStore is wired`);
|
|
206
|
-
e.code = "config.invalid_file_history_capture";
|
|
207
|
-
throw e;
|
|
208
|
-
}
|
|
209
|
-
function shownWord(word) {
|
|
210
|
-
return typeof word === "string" ? JSON.stringify(word.slice(0, 40)) : word === null || typeof word === "boolean" || typeof word === "number" ? String(word) : `a ${typeof word}`;
|
|
211
|
-
}
|
|
212
|
-
function captureDisabledRefusal(target) {
|
|
213
|
-
const e = new Error(`rewind-files: this run set TaskSpec.fileHistory: "disabled", so it neither records nor restores file history — restoring files to entry "${target}" was refused and the files were NOT rewound`);
|
|
214
|
-
e.code = "rewind.capture_disabled";
|
|
215
|
-
return e;
|
|
216
|
-
}
|
|
217
131
|
function rewindRefusalTail() {
|
|
218
132
|
return `Boundaries are keyed by a turn's INITIAL entry and exist only for turns that recorded history (a RunnerDeps.fileHistoryStore wired, capture on, and the run's file history not disabled); a boundary from the retired whole-tree snapshot era is not restorable by this seat, and reaped boundaries have none.`;
|
|
219
133
|
}
|
|
@@ -39,6 +39,7 @@ import type { CwdRef } from "../../tools/fs/fs-shared.js";
|
|
|
39
39
|
import type { AskLane, IrreversibilityTier, ParkAsk, Prepared, ReversibilityProbes, StopForDenialLimit } from "./contracts.js";
|
|
40
40
|
import type { PermissionRuleLanes } from "./permission-rule-lanes.js";
|
|
41
41
|
import { type ToolRosterDeltaSeat } from "../tool-roster.js";
|
|
42
|
+
import type { ToolCallLike } from "../tool-registry.js";
|
|
42
43
|
import { type FrameSettlement } from "../frame-settlement.js";
|
|
43
44
|
/**
|
|
44
45
|
* The parent-thread human-rejection boundary — judged ONCE here, from the pass's own record (CC parity:
|
|
@@ -174,7 +175,7 @@ export interface PrepareGateStationsInput {
|
|
|
174
175
|
readonly signal: AbortSignal;
|
|
175
176
|
};
|
|
176
177
|
/** borrowed-readonly — the shell-seat provenance set: the shell-running tools whose tier is the engine's shell seat's, not a
|
|
177
|
-
* deployment mark (the gate's `shellGated` attribution is the
|
|
178
|
+
* deployment mark (the gate's `shellGated` attribution is the membership of the name the call's tier was read under). */
|
|
178
179
|
shellGatedTools: ReadonlySet<string>;
|
|
179
180
|
/** borrowed-readonly — the armed classifier's live seat (read per call: a leg steered into `auto` arms after prepare). */
|
|
180
181
|
autoModeDecider: {
|
|
@@ -211,8 +212,8 @@ export interface PrepareGateStationsInput {
|
|
|
211
212
|
questionToolMounted: boolean;
|
|
212
213
|
/** borrowed-readonly — whether sandbox admission is armed (isolated env + park prerequisites). */
|
|
213
214
|
sandboxAdmissionArmed: boolean;
|
|
214
|
-
/** borrowed-readonly — the per-
|
|
215
|
-
sandboxBoundaryCapable: (
|
|
215
|
+
/** borrowed-readonly — the per-call boundary-capability fact the sandbox admission reads (the call's wire name and names). */
|
|
216
|
+
sandboxBoundaryCapable: (call: Pick<ToolCallLike, "toolName" | "face">) => boolean;
|
|
216
217
|
/** borrowed-readonly — the sandbox-admission trace sink (the deferred ancestor disclosure and the gate's own). */
|
|
217
218
|
emitSandboxAdmitted: (info: {
|
|
218
219
|
toolCallId: string;
|
|
@@ -9,7 +9,7 @@ import { askUnresolvableNotice } from "../ask-unresolvable-notice.js";
|
|
|
9
9
|
import { askReachesPerson } from "../wiring-manifest.js";
|
|
10
10
|
import { classifierUnavailableSeat } from "./classifier-ladder.js";
|
|
11
11
|
import { PATH_CONFINABLE_WRITE_TOOLS } from "./session-rule-policy.js";
|
|
12
|
-
import { toolCallFaceOf } from "../tool-roster.js";
|
|
12
|
+
import { anyNamed, callAnswersTo, callFaceSeat, firstNamed, rosterToolName, ruleNamesOf, toolCallFaceOf } from "../tool-roster.js";
|
|
13
13
|
import { noteHostRewrite } from "../frame-settlement.js";
|
|
14
14
|
function screenGateResult(result) {
|
|
15
15
|
if (result.suspend !== undefined)
|
|
@@ -85,11 +85,15 @@ export function prepareGateStations(input) {
|
|
|
85
85
|
writeProtectionArmed ||
|
|
86
86
|
shellReadCheck !== undefined;
|
|
87
87
|
if (toolCallGateArmedRef.armed) {
|
|
88
|
-
harness.on("tool_call", async (
|
|
88
|
+
harness.on("tool_call", async (called) => {
|
|
89
|
+
const e = { ...called, toolName: rosterToolName(toolRosterDeltas.current, called.toolName) };
|
|
90
|
+
const call = { toolName: e.toolName, ...callFaceSeat(toolCallFaceOf(toolRosterDeltas.current, called.toolName)) };
|
|
91
|
+
const names = ruleNamesOf(call);
|
|
92
|
+
const tierSeat = firstNamed(irreversibilityTier, names);
|
|
89
93
|
blockedToolCalls.delete(e.toolCallId);
|
|
90
94
|
inheritedAskGrants.delete(e.toolCallId);
|
|
91
95
|
{
|
|
92
|
-
const complianceDeny = complianceCallDenial(complianceDenies,
|
|
96
|
+
const complianceDeny = names.map((n) => complianceCallDenial(complianceDenies, n)).find((d) => d !== undefined);
|
|
93
97
|
if (complianceDeny !== undefined) {
|
|
94
98
|
if (blockedTracked)
|
|
95
99
|
blockedToolCalls.add(e.toolCallId);
|
|
@@ -113,24 +117,24 @@ export function prepareGateStations(input) {
|
|
|
113
117
|
hookTimeoutMs,
|
|
114
118
|
...(handsCwdRef !== undefined ? { trackedCwd: () => handsCwdRef.current } : {}),
|
|
115
119
|
...(hookEnvFace !== undefined ? { hookEnv: hookEnvFace } : {}),
|
|
116
|
-
toolFaceOf: (name) => toolCallFaceOf(toolRosterDeltas.current, name),
|
|
120
|
+
toolFaceOf: (name) => toolCallFaceOf(toolRosterDeltas.current, name === e.toolName ? called.toolName : name),
|
|
117
121
|
adjudicate,
|
|
118
122
|
resolveAsk: resolveAskBound,
|
|
119
123
|
suspendAsk,
|
|
120
124
|
resolveContentAsk,
|
|
121
|
-
egress: egressTools
|
|
122
|
-
peerMessage:
|
|
125
|
+
egress: anyNamed(egressTools, names),
|
|
126
|
+
peerMessage: callAnswersTo(call, SEND_MESSAGE_TOOL_NAME),
|
|
123
127
|
...(writeProtectionCheck !== undefined ? { writeProtectionCheck } : {}),
|
|
124
128
|
...(shellReadCheck !== undefined ? { shellReadCheck } : {}),
|
|
125
|
-
irreversibility:
|
|
126
|
-
reversibilityProbe: reversibilityProbes.get(
|
|
129
|
+
irreversibility: tierSeat?.value,
|
|
130
|
+
reversibilityProbe: tierSeat !== undefined ? reversibilityProbes.get(tierSeat.name) : undefined,
|
|
127
131
|
abortSignal: abortController.signal,
|
|
128
132
|
...(notifyPermissionDenied ? { permissionDenied: notifyPermissionDenied } : {}),
|
|
129
133
|
onHookError: notifyHookError,
|
|
130
|
-
shellGated: shellGatedTools.has(
|
|
134
|
+
shellGated: tierSeat !== undefined && shellGatedTools.has(tierSeat.name),
|
|
131
135
|
...(autoModeDecider.current !== undefined ? { autoMode: { decider: autoModeDecider.current, ...(autoModeDenialTracking.current !== undefined ? { denialTracking: autoModeDenialTracking.current } : {}), unavailable: classifierUnavailableSeat({ station: "leg", ...(autoModeDecider.current.timeoutMs !== undefined ? { budgetMs: autoModeDecider.current.timeoutMs } : {}), approvalSeat: askReachesPerson(askRoute), onNotice: deps.onNotice, sessionId, runId }) } } : {}),
|
|
132
136
|
permissionMode: () => permissionMode.current,
|
|
133
|
-
...(() => { const s = toolMarkSources
|
|
137
|
+
...(() => { const s = firstNamed(toolMarkSources, names)?.value; return s !== undefined ? { toolMarkSources: s } : {}; })(),
|
|
134
138
|
...(a2aIsolatePeers === true ? { a2aIsolatePeers: true } : {}),
|
|
135
139
|
onModeReleased,
|
|
136
140
|
ancestorArbitrationOf: (toolCallId) => ancestorArbitrations.get(toolCallId),
|
|
@@ -183,7 +187,7 @@ export function prepareGateStations(input) {
|
|
|
183
187
|
? {
|
|
184
188
|
sandboxAdmission: {
|
|
185
189
|
askClassesOf: (toolCallId) => foldAskClasses.get(toolCallId),
|
|
186
|
-
boundaryCapable: sandboxBoundaryCapable(
|
|
190
|
+
boundaryCapable: sandboxBoundaryCapable(call),
|
|
187
191
|
onAdmitted: emitSandboxAdmitted,
|
|
188
192
|
},
|
|
189
193
|
}
|
|
@@ -66,7 +66,7 @@ export interface PrepareInheritedGateInput {
|
|
|
66
66
|
/** borrowed-readonly — the trusted spawn-side channel: `inheritedGate` (the LIVE half of the chain fold), `ownOrgAdmissionRef`
|
|
67
67
|
* (the same-session continuation's freeze), `delegationProvenance` (the F-012 stamp), and the identity / peer / notification
|
|
68
68
|
* seats the ctx enrichment forwards. Never mutated. */
|
|
69
|
-
internals: Pick<RunInternals, "inheritedGate" | "ownOrgAdmissionRef" | "delegationProvenance" | "memoryCaptureAncestors" | "parentTaskId" | "parentSessionId" | "rootSessionId" | "placementRoot" | "explicitAgentName" | "peerSelfRef" | "peerInboundChainRef" | "onTaskNotification" | "onSubagentSpawn" | "insideFork" | "questionFaceStripped"> | undefined;
|
|
69
|
+
internals: Pick<RunInternals, "inheritedGate" | "sessionModeSeats" | "ownOrgAdmissionRef" | "delegationProvenance" | "memoryCaptureAncestors" | "parentTaskId" | "parentSessionId" | "rootSessionId" | "placementRoot" | "explicitAgentName" | "peerSelfRef" | "peerInboundChainRef" | "onTaskNotification" | "onSubagentSpawn" | "insideFork" | "questionFaceStripped"> | undefined;
|
|
70
70
|
/** borrowed-readonly — the resume leg, or undefined on a fresh one: `seed.inheritedGate` is the CHECKPOINT half of the chain
|
|
71
71
|
* fold (the trust floor the live half may only tighten), `seed.readFace` the hands-less re-park's carried section. */
|
|
72
72
|
resume: Pick<PrepareResume, "seed" | "outcome"> | undefined;
|
|
@@ -197,6 +197,13 @@ export interface PrepareInheritedGateResult {
|
|
|
197
197
|
* seat, or the checkpoint's recorded word), folded against the arming facts the caps phase binds later. Every mode
|
|
198
198
|
* consumer reads `current` at its own call; the steer verb writes it. */
|
|
199
199
|
permissionMode: PermissionModeController;
|
|
200
|
+
/** owned — the leg's commit of its mode seats, called by the driver once prepare succeeded (never on a failed one):
|
|
201
|
+
* makes this leg its SESSION's current leg and holds the leg's own engine ceiling until `legSignal` aborts, so each
|
|
202
|
+
* move of that ceiling re-judges this leg. */
|
|
203
|
+
commitModeSeats: (legSignal: AbortSignal) => void;
|
|
204
|
+
/** owned — the leg has STOPPED (the run leg's teardown, after its loop settled): from here its children read the
|
|
205
|
+
* session's current leg. Idempotent; a leg that never reaches it keeps answering its children itself. */
|
|
206
|
+
endModeCeiling: () => void;
|
|
200
207
|
/** owned — the seed ∩ live admitted org scopes (one side absent ⇒ the other; both absent ⇒ undefined). */
|
|
201
208
|
inheritedAdmittedOrgScopes: readonly string[] | undefined;
|
|
202
209
|
/** owned — the prior own org verdict: checkpoint ∩ same-session ref (either alone stands; neither ⇒ undefined). */
|
|
@@ -7,6 +7,7 @@ import { StubExecutionEnv } from "../stub-env.js";
|
|
|
7
7
|
import { askApproverIdentity, durableMandateSourceOf } from "../tool-policy.js";
|
|
8
8
|
import { deliverEngineNotice } from "../types.js";
|
|
9
9
|
import { PermissionModeController, modeRequestAfterResume } from "../mode-policy.js";
|
|
10
|
+
import { SessionModeSeats } from "./session-mode-seats.js";
|
|
10
11
|
import { migrateLegacyModeSeed } from "../checkpoint-store.js";
|
|
11
12
|
import { attachRebuiltDenialTrackers, createDenialLimitStop } from "./denial-limit-arms.js";
|
|
12
13
|
import { asThrownError } from "../thrown-value.js";
|
|
@@ -56,6 +57,11 @@ export function prepareInheritedGate(input) {
|
|
|
56
57
|
};
|
|
57
58
|
const permissionMode = new PermissionModeController(modeRequestAfterResume(permissionModeSeat, modeSeed, resume?.outcome), ceiling, { onFold: announceModeFold, ...(modeSeed.prePlanMode !== undefined ? { prePlanMode: modeSeed.prePlanMode } : {}) });
|
|
58
59
|
announceModeFold(permissionMode.receipt());
|
|
60
|
+
const { ceiling: legModeCeiling, commit: commitLegModeCeiling, end: endModeCeiling } = (internals?.sessionModeSeats ?? new SessionModeSeats()).legCeiling(sessionId, permissionMode);
|
|
61
|
+
const commitModeSeats = (legSignal) => {
|
|
62
|
+
commitLegModeCeiling();
|
|
63
|
+
parentSeat?.hold?.(permissionMode, legSignal);
|
|
64
|
+
};
|
|
59
65
|
const liveAdmittedOrg = liveInheritedGate?.admittedOrgScopes;
|
|
60
66
|
const seedAdmittedOrg = seedInheritedGate?.admittedOrgScopes;
|
|
61
67
|
const inheritedAdmittedOrgScopes = liveAdmittedOrg === undefined ? seedAdmittedOrg : seedAdmittedOrg === undefined ? liveAdmittedOrg : liveAdmittedOrg.filter((s) => seedAdmittedOrg.includes(s));
|
|
@@ -156,7 +162,7 @@ export function prepareInheritedGate(input) {
|
|
|
156
162
|
...(durableMandate ? { durableMandate: true } : {}),
|
|
157
163
|
...(contentMandate ? { contentMandate: true } : {}),
|
|
158
164
|
...(autoModeDecider !== undefined
|
|
159
|
-
? { autoMode: { decider: autoModeDecider, ...(autoModeDenialTracking !== undefined ? { denialTracking: autoModeDenialTracking } : {}), ...(autoModeArming !== undefined ? { arming: autoModeArming } : {}) } }
|
|
165
|
+
? { autoMode: { decider: autoModeDecider, ...(autoModeDenialTracking !== undefined ? { denialTracking: autoModeDenialTracking } : {}), ...(autoModeArming !== undefined ? { arming: autoModeArming } : {}), liveMode: legModeCeiling } }
|
|
160
166
|
: {}),
|
|
161
167
|
},
|
|
162
168
|
]
|
|
@@ -172,7 +178,7 @@ export function prepareInheritedGate(input) {
|
|
|
172
178
|
...(durableMandate ? { durableMandate: true } : {}),
|
|
173
179
|
...(contentMandate ? { contentMandate: true } : {}),
|
|
174
180
|
...(autoModeDecider !== undefined
|
|
175
|
-
? { autoMode: { decider: autoModeDecider, ...(autoModeDenialTracking !== undefined ? { denialTracking: autoModeDenialTracking } : {}), ...(autoModeArming !== undefined ? { arming: autoModeArming } : {}) } }
|
|
181
|
+
? { autoMode: { decider: autoModeDecider, ...(autoModeDenialTracking !== undefined ? { denialTracking: autoModeDenialTracking } : {}), ...(autoModeArming !== undefined ? { arming: autoModeArming } : {}), liveMode: legModeCeiling } }
|
|
176
182
|
: {}),
|
|
177
183
|
},
|
|
178
184
|
]
|
|
@@ -182,7 +188,7 @@ export function prepareInheritedGate(input) {
|
|
|
182
188
|
...(ancestorRules.length > 0 ? { ancestorRules } : {}),
|
|
183
189
|
...(effectiveShellGate !== undefined ? { shellGate: effectiveShellGate } : {}),
|
|
184
190
|
permissionMode: permissionMode.current,
|
|
185
|
-
permissionModeCeiling:
|
|
191
|
+
permissionModeCeiling: legModeCeiling,
|
|
186
192
|
...(parentConstraints.length > 0 ? { parentConstraints } : {}),
|
|
187
193
|
admittedOrgScopes: memoryAdmittedOrgScopesRef.current,
|
|
188
194
|
...(orgGovernedProvenance ? { orgAdmissionGoverned: true } : {}),
|
|
@@ -295,5 +301,5 @@ export function prepareInheritedGate(input) {
|
|
|
295
301
|
...(spec.enablePlanMode === true ? { enterPlanMode } : {}),
|
|
296
302
|
};
|
|
297
303
|
};
|
|
298
|
-
return { liveInheritedGate, seedInheritedGate, inheritedAncestorRules, inheritedShellGate, inheritedParentConstraints, permissionMode, inheritedAdmittedOrgScopes, priorOwnOrgVerdict, orgGovernedProvenance, effectiveShellGate, ownSessionRulesRef, ancestorSessionRulesRef, ancestorRulesNow, memoryAdmittedOrgScopesRef, ownOrgVerdictRef, orgAdmissionCheckpointState, faceCheckpointSection, faceCheckpointState, writeFaceCheckpointSection, f012CheckpointState, frozenOnAsk, hookEnvSource, notifyOwnHookCrash, frozenOnQuestion, parentSpecSeat, provenanceForChildrenRef, delegationSettlementRef, inheritedGateForChildren, carrierReadFace, carrierWriteFace, fullShellReachable, gateStopRef, stopForDenialLimit, enrichSpecToolCtx };
|
|
304
|
+
return { liveInheritedGate, seedInheritedGate, inheritedAncestorRules, inheritedShellGate, inheritedParentConstraints, permissionMode, commitModeSeats, endModeCeiling, inheritedAdmittedOrgScopes, priorOwnOrgVerdict, orgGovernedProvenance, effectiveShellGate, ownSessionRulesRef, ancestorSessionRulesRef, ancestorRulesNow, memoryAdmittedOrgScopesRef, ownOrgVerdictRef, orgAdmissionCheckpointState, faceCheckpointSection, faceCheckpointState, writeFaceCheckpointSection, f012CheckpointState, frozenOnAsk, hookEnvSource, notifyOwnHookCrash, frozenOnQuestion, parentSpecSeat, provenanceForChildrenRef, delegationSettlementRef, inheritedGateForChildren, carrierReadFace, carrierWriteFace, fullShellReachable, gateStopRef, stopForDenialLimit, enrichSpecToolCtx };
|
|
299
305
|
}
|
|
@@ -29,7 +29,7 @@ import type { RunnerDeps, RuntimeCaps, ShellGateDoctrine, TaskSpec } from "../ty
|
|
|
29
29
|
import type { CwdRef } from "../../tools/fs/fs-shared.js";
|
|
30
30
|
import type { AdvertisedWritableDirs } from "./advertised-writable-dirs.js";
|
|
31
31
|
import { type ContentAskBinding } from "./content-ask-bindings.js";
|
|
32
|
-
import type { AskLane, ParkAsk, Prepared, SuspendSaga } from "./contracts.js";
|
|
32
|
+
import type { AskLane, IrreversibilityTier, ParkAsk, Prepared, SuspendSaga } from "./contracts.js";
|
|
33
33
|
import type { createRuleOffersOf } from "./permission-rule-lanes.js";
|
|
34
34
|
import { type ToolRosterDeltaSeat } from "../tool-roster.js";
|
|
35
35
|
export interface PrepareParkAskInput {
|
|
@@ -112,8 +112,10 @@ export interface PrepareParkAskInput {
|
|
|
112
112
|
/** borrowed-mutable — the run-scoped remote-lifecycle failure log. Writer here: a refused `suspendVM` (`push`). */
|
|
113
113
|
remoteEnvFailures: Prepared["remoteEnvFailures"];
|
|
114
114
|
/** borrowed-readonly — the shell-seat provenance set: the shell-running tools whose tier is the engine's shell seat's, not a
|
|
115
|
-
* deployment mark (the risk descriptor's `shellGated` is the
|
|
115
|
+
* deployment mark (the risk descriptor's `shellGated` is the membership of the name the call's tier is read under). */
|
|
116
116
|
shellGatedTools: ReadonlySet<string>;
|
|
117
|
+
/** borrowed-readonly — the per-tool tier, read only for the name the call's tier seat is read under (as at the gate). */
|
|
118
|
+
irreversibilityTier: IrreversibilityTier;
|
|
117
119
|
/** borrowed-readonly — the operator's shell floor (`"always"`), persisted on a shell-gated descriptor; undefined = no floor. */
|
|
118
120
|
effectiveShellGate: ShellGateDoctrine | undefined;
|
|
119
121
|
/** borrowed-readonly — the EFFECTIVE durable-approval opt-in (the caller's, or the synthesized mandate scope): picks the
|
|
@@ -15,7 +15,7 @@ import { checkpointScopeOf } from "./checkpoint-scope.js";
|
|
|
15
15
|
import { CONTENT_ASK_BINDING_CAP } from "./content-ask-bindings.js";
|
|
16
16
|
import { DEFAULT_UNATTENDED_APPROVAL_TTL_MS, publishCommittedSuspend, sanitizedTtlMs } from "./park-commit.js";
|
|
17
17
|
import { remoteEnvFailureNote } from "./remote-env-retry.js";
|
|
18
|
-
import { callFaceSeat, parkedToolIdentity, parkedToolFace } from "../tool-roster.js";
|
|
18
|
+
import { callFaceSeat, firstNamed, parkedToolIdentity, parkedToolFace, ruleNamesOf } from "../tool-roster.js";
|
|
19
19
|
class ParkRefusal extends Error {
|
|
20
20
|
constructor(message, options) {
|
|
21
21
|
super(message, options);
|
|
@@ -26,7 +26,7 @@ const wordlessDemand = (toolName) => {
|
|
|
26
26
|
throw new Error(`a real-approval demand for "${toolName}" reached the park with no origin word — the origin stamp precedes every park`);
|
|
27
27
|
};
|
|
28
28
|
export function prepareParkAsk(input) {
|
|
29
|
-
const { askLane, saga, spec, deps, sessionId, checkpointStore, toolRosterDeltas, parkLaneArmed, contentAskRoutable, liveQuestionFace, mountedQuestionTool, contentAskBindings, lateStrandedAnswers, discloseStrandedAnswers, onAsk, runtimeCaps, inheritedUnavailableAsks, basePolicyForResumeEdit, budgetSnapshot, handsCwdRef, advertisedWritableDirs, offloadStore, ownedEnv, incompleteSuspendAdapter, session, sessions, suspendChainBase, maxSuspends, remoteEnvFailures, shellGatedTools, effectiveShellGate, durableApproval, priorLedger, liveSpendRef, resourceTotal, faceCheckpointState, f012CheckpointState, orgAdmissionCheckpointState, ruleOffersOf, now, humanReviewRef, abortController, harness, pausedRef } = input;
|
|
29
|
+
const { askLane, saga, spec, deps, sessionId, checkpointStore, toolRosterDeltas, parkLaneArmed, contentAskRoutable, liveQuestionFace, mountedQuestionTool, contentAskBindings, lateStrandedAnswers, discloseStrandedAnswers, onAsk, runtimeCaps, inheritedUnavailableAsks, basePolicyForResumeEdit, budgetSnapshot, handsCwdRef, advertisedWritableDirs, offloadStore, ownedEnv, incompleteSuspendAdapter, session, sessions, suspendChainBase, maxSuspends, remoteEnvFailures, shellGatedTools, irreversibilityTier, effectiveShellGate, durableApproval, priorLedger, liveSpendRef, resourceTotal, faceCheckpointState, f012CheckpointState, orgAdmissionCheckpointState, ruleOffersOf, now, humanReviewRef, abortController, harness, pausedRef } = input;
|
|
30
30
|
if (askLane === undefined || saga === undefined)
|
|
31
31
|
return { parkAsk: undefined };
|
|
32
32
|
const { composedCallSignal, approvalPreviewOf } = askLane;
|
|
@@ -234,6 +234,7 @@ export function prepareParkAsk(input) {
|
|
|
234
234
|
remoteHandle = parkOnlyHandle(parkOnlyRemoteEnv);
|
|
235
235
|
}
|
|
236
236
|
token = mintCheckpointToken();
|
|
237
|
+
const tierSeat = firstNamed(irreversibilityTier, ruleNamesOf(req));
|
|
237
238
|
const riskDescriptor = buildRiskDescriptor({
|
|
238
239
|
toolName: req.toolName,
|
|
239
240
|
args: parkedArgs,
|
|
@@ -241,7 +242,7 @@ export function prepareParkAsk(input) {
|
|
|
241
242
|
...(shadowedRule !== undefined ? { shadowedRule } : {}),
|
|
242
243
|
...(probeReason !== undefined ? { probeReason } : {}),
|
|
243
244
|
...(probeCause !== undefined ? { probeCause } : {}),
|
|
244
|
-
shellGated: shellGatedTools.has(
|
|
245
|
+
shellGated: tierSeat !== undefined && shellGatedTools.has(tierSeat.name),
|
|
245
246
|
...(effectiveShellGate !== undefined ? { shellGateDoctrine: effectiveShellGate } : {}),
|
|
246
247
|
});
|
|
247
248
|
gate =
|
|
@@ -316,7 +317,7 @@ export function prepareParkAsk(input) {
|
|
|
316
317
|
...(rowRealApproval !== undefined ? { requiresRealApproval: true } : {}),
|
|
317
318
|
...askMandateMember(clearanceFacts),
|
|
318
319
|
...askCarryRowMembers(carry),
|
|
319
|
-
...ruleOffersOf(req
|
|
320
|
+
...ruleOffersOf(req, parkedArgs, clearanceFacts),
|
|
320
321
|
boundInputHash: boundInputHashOf(parkedArgs),
|
|
321
322
|
batchToolCallIds,
|
|
322
323
|
completedCallIds,
|