@sema-agent/core 7.5.2 → 7.6.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 +41 -0
- package/dist/agents/cascade.d.ts +2 -2
- package/dist/agents/cascade.js +12 -10
- package/dist/agents/repair-loop.d.ts +5 -3
- package/dist/agents/repair-loop.js +13 -15
- package/dist/agents/subagent.d.ts +24 -42
- package/dist/agents/subagent.js +119 -105
- package/dist/agents/suspend-guard.d.ts +31 -19
- package/dist/agents/suspend-guard.js +14 -8
- package/dist/agents/teacher.js +9 -9
- package/dist/agents/team.d.ts +4 -3
- package/dist/agents/team.js +10 -8
- package/dist/agents/verify.d.ts +3 -3
- package/dist/agents/verify.js +17 -17
- package/dist/core/a2a.js +2 -1
- package/dist/core/ask-origin.d.ts +60 -7
- package/dist/core/ask-origin.js +26 -1
- package/dist/core/checkpoint-store.d.ts +78 -76
- package/dist/core/checkpoint-store.js +17 -1
- package/dist/core/gate-outcome.d.ts +189 -0
- package/dist/core/gate-outcome.js +70 -0
- package/dist/core/hooks.d.ts +18 -92
- package/dist/core/hooks.js +88 -85
- package/dist/core/mcp-failure.d.ts +104 -0
- package/dist/core/mcp-failure.js +128 -0
- package/dist/core/mcp.d.ts +21 -77
- package/dist/core/mcp.js +76 -150
- package/dist/core/pause-registry.d.ts +131 -0
- package/dist/core/pause-registry.js +27 -0
- package/dist/core/runner/assemble-result.d.ts +32 -41
- package/dist/core/runner/assemble-result.js +55 -74
- package/dist/core/runner/contracts.d.ts +46 -64
- package/dist/core/runner/denial-limit-arms.d.ts +1 -1
- package/dist/core/runner/denial-limit-arms.js +3 -3
- package/dist/core/runner/gate-exit.d.ts +74 -0
- package/dist/core/runner/gate-exit.js +55 -0
- package/dist/core/runner/park-commit.d.ts +17 -23
- package/dist/core/runner/park-commit.js +14 -15
- package/dist/core/runner/prepare-ask-lane.d.ts +0 -3
- package/dist/core/runner/prepare-ask-lane.js +3 -5
- package/dist/core/runner/prepare-boundary-parks.d.ts +3 -6
- package/dist/core/runner/prepare-boundary-parks.js +3 -3
- package/dist/core/runner/prepare-caps-and-workflow.js +1 -1
- package/dist/core/runner/prepare-gate-stations.d.ts +4 -7
- package/dist/core/runner/prepare-gate-stations.js +29 -54
- package/dist/core/runner/prepare-inherited-gate.js +1 -1
- package/dist/core/runner/prepare-memory.d.ts +44 -26
- package/dist/core/runner/prepare-park-ask.d.ts +2 -4
- package/dist/core/runner/prepare-park-ask.js +5 -5
- package/dist/core/runner/prepare-task.js +8 -9
- package/dist/core/runner/prepare-wiring-manifest.d.ts +7 -15
- package/dist/core/runner/prepare-wiring-manifest.js +9 -10
- package/dist/core/runner/runtask.d.ts +16 -31
- package/dist/core/runner/runtask.js +109 -120
- package/dist/core/runner/terminal-projection.d.ts +22 -0
- package/dist/core/runner/terminal-projection.js +28 -0
- package/dist/core/store-contracts/checkpoint-store-contract.d.ts +4 -1
- package/dist/core/store-contracts/checkpoint-store-contract.js +8 -2
- package/dist/core/terminal-cause.d.ts +137 -0
- package/dist/core/terminal-cause.js +9 -0
- package/dist/core/tool-policy.d.ts +43 -139
- package/dist/core/tool-policy.js +79 -112
- package/dist/core/types.d.ts +67 -164
- package/dist/core/wiring-manifest.d.ts +6 -3
- package/dist/core/workflow-journal-store.js +3 -4
- package/dist/engine/harness/agent-harness.d.ts +1 -1
- package/dist/index.d.ts +10 -7
- package/dist/index.js +8 -5
- package/dist/orchestration/builtin-workflows.d.ts +2 -2
- package/dist/orchestration/builtin-workflows.js +1 -1
- package/dist/orchestration/goal.js +8 -7
- package/dist/orchestration/run-spec.js +5 -3
- package/dist/orchestration/run-workflow-tool.d.ts +1 -1
- package/dist/orchestration/run-workflow-tool.js +4 -4
- package/dist/orchestration/workflow-governance.d.ts +4 -4
- package/dist/orchestration/workflow-governance.js +4 -2
- package/dist/orchestration/workflow-primitives.d.ts +1 -1
- package/dist/orchestration/workflow-primitives.js +1 -1
- package/dist/orchestration/workflow.d.ts +11 -0
- package/dist/orchestration/workflow.js +64 -39
- package/dist/prompts/supervisor.d.ts +1 -1
- package/dist/prompts/supervisor.js +3 -3
- package/dist/scenarios/scenario-registry.js +1 -1
- package/package.json +3 -1
- package/test/export-surface.snapshot.json +74 -22
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { isDegenerateCutMessage } from "../../brain/terminal-cause.js";
|
|
2
2
|
import { extractErrorCode, stripErrorCodePrefix } from "../../brain/errors.js";
|
|
3
|
+
import { terminalProjection } from "./terminal-projection.js";
|
|
3
4
|
const SALVAGE_ELIGIBLE_TERMINALS = new Set([
|
|
4
5
|
"output.degenerate",
|
|
5
6
|
"limits.max_tokens_exceeded",
|
|
@@ -40,30 +41,9 @@ function assistantText(msg) {
|
|
|
40
41
|
.join("");
|
|
41
42
|
return stripThinkTags(text);
|
|
42
43
|
}
|
|
43
|
-
export function
|
|
44
|
-
|
|
45
|
-
const text = final ? assistantText(final) : "";
|
|
46
|
-
if (!flags.unpricedSpend) {
|
|
47
|
-
const compactionMicroUsd = stats.compactionMicroUsd ?? 0;
|
|
48
|
-
const nestedSubagentMicroUsd = stats.nested?.costMicroUsd ?? 0;
|
|
49
|
-
stats.costBreakdown = {
|
|
50
|
-
llmRootMicroUsd: Math.max(0, stats.costMicroUsd - compactionMicroUsd),
|
|
51
|
-
nestedSubagentMicroUsd,
|
|
52
|
-
memoryConsolidationMicroUsd: 0,
|
|
53
|
-
compactionMicroUsd,
|
|
54
|
-
};
|
|
55
|
-
}
|
|
56
|
-
let status;
|
|
57
|
-
const result = text;
|
|
58
|
-
let errorMessage;
|
|
59
|
-
let errorCode;
|
|
44
|
+
export function terminalCauseOf(final, stats, flags) {
|
|
45
|
+
let terminal;
|
|
60
46
|
let apiFailure;
|
|
61
|
-
let blockedReason;
|
|
62
|
-
let salvagedOutput;
|
|
63
|
-
let checkpointToken;
|
|
64
|
-
let checkpointId;
|
|
65
|
-
let checkpointGate;
|
|
66
|
-
let workspaceRestoreMode;
|
|
67
47
|
const haltOwnsAbortedFinal = flags.userHalted === true &&
|
|
68
48
|
final?.stopReason === "aborted" &&
|
|
69
49
|
!flags.abortedLive &&
|
|
@@ -71,107 +51,108 @@ export function assembleResult(spec, sessionId, final, stats, flags) {
|
|
|
71
51
|
flags.abortedForTurns !== true;
|
|
72
52
|
const isDegenerate = final?.stopReason === "error" && isDegenerateCutMessage(final);
|
|
73
53
|
if (flags.outputInvalid) {
|
|
74
|
-
|
|
75
|
-
errorCode = "output.invalid";
|
|
76
|
-
errorMessage = "model did not produce a valid structured output within the retry limit";
|
|
54
|
+
terminal = { kind: "failed", code: "output.invalid", message: "model did not produce a valid structured output within the retry limit" };
|
|
77
55
|
}
|
|
78
56
|
else if (isDegenerate) {
|
|
79
|
-
|
|
80
|
-
errorCode = "output.degenerate";
|
|
81
|
-
errorMessage = final?.errorMessage;
|
|
57
|
+
terminal = { kind: "failed", code: "output.degenerate", ...(final?.errorMessage !== undefined ? { message: final.errorMessage } : {}) };
|
|
82
58
|
}
|
|
83
59
|
else if (flags.budgetHit) {
|
|
84
|
-
status = "failed";
|
|
85
|
-
errorCode = flags.budgetAxis === "tokens" ? "limits.max_tokens_exceeded" : "limits.max_cost_exceeded";
|
|
86
60
|
const axisName = flags.budgetAxis === "tokens" ? "limits.maxTokens" : "limits.maxCostUsd";
|
|
87
|
-
|
|
88
|
-
|
|
61
|
+
terminal = {
|
|
62
|
+
kind: "failed",
|
|
63
|
+
code: flags.budgetAxis === "tokens" ? "limits.max_tokens_exceeded" : "limits.max_cost_exceeded",
|
|
64
|
+
message: flags.budgetHit === "precall"
|
|
89
65
|
? `the estimated cost of the first call already exceeds ${axisName}; the task was not started`
|
|
90
|
-
: `cumulative usage exceeded ${axisName}
|
|
66
|
+
: `cumulative usage exceeded ${axisName}`,
|
|
67
|
+
};
|
|
91
68
|
}
|
|
92
69
|
else if (flags.suspendLoop) {
|
|
93
|
-
|
|
94
|
-
errorCode = "suspend.loop";
|
|
95
|
-
errorMessage = "task suspended too many times (resume/restart loop) — exceeded the suspend limit";
|
|
70
|
+
let message = "task suspended too many times (resume/restart loop) — exceeded the suspend limit";
|
|
96
71
|
const gates = stats.humanReview?.gates ?? [];
|
|
97
72
|
if (gates.length > 0 && gates.every((g) => g.decision === "allow")) {
|
|
98
|
-
|
|
73
|
+
message +=
|
|
99
74
|
`. Diagnosis: all ${gates.length} recorded gate decision(s) on this run were ALLOW — a gate that keeps ` +
|
|
100
75
|
`asking combined with an approver that keeps approving consumes the suspend allowance on legitimate ` +
|
|
101
76
|
`work. Remedies: answer asks at a LIVE onAsk (a synchronous allow parks nothing and consumes no ` +
|
|
102
77
|
`suspend), keep provably-benign commands inside the read boundary so the classifier auto-allows them, ` +
|
|
103
78
|
`or raise maxSuspends for genuinely approval-heavy tasks.`;
|
|
104
79
|
}
|
|
80
|
+
terminal = { kind: "failed", code: "suspend.loop", message };
|
|
105
81
|
}
|
|
106
82
|
else if (flags.threw) {
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
errorCode = flags.abortedForTimeout
|
|
83
|
+
const message = flags.threw instanceof Error ? flags.threw.message : String(flags.threw);
|
|
84
|
+
const code = flags.abortedForTimeout
|
|
110
85
|
? "limits.max_walltime_exceeded"
|
|
111
86
|
: flags.abortedForTurns
|
|
112
87
|
? "limits.max_turns_exceeded"
|
|
113
88
|
: flags.conflict
|
|
114
89
|
? "conflict"
|
|
115
90
|
: errorCodeOf(flags.threw);
|
|
91
|
+
terminal = { kind: "failed", ...(code !== undefined ? { code } : {}), message };
|
|
116
92
|
}
|
|
117
93
|
else if (flags.blockedReason) {
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
status = "suspended";
|
|
123
|
-
checkpointToken = flags.suspendRef.token;
|
|
124
|
-
checkpointId = flags.suspendRef.checkpointId;
|
|
125
|
-
checkpointGate = flags.suspendRef.gate;
|
|
126
|
-
workspaceRestoreMode = flags.suspendRef.restoreMode;
|
|
127
|
-
}
|
|
128
|
-
else if (flags.reviewRef) {
|
|
129
|
-
status = "needs_review";
|
|
130
|
-
errorCode = "review.pending";
|
|
131
|
-
checkpointToken = flags.reviewRef.token;
|
|
132
|
-
checkpointId = flags.reviewRef.checkpointId;
|
|
133
|
-
checkpointGate = flags.reviewRef.gate;
|
|
134
|
-
workspaceRestoreMode = flags.reviewRef.restoreMode;
|
|
94
|
+
terminal = { kind: "blocked", reason: flags.blockedReason };
|
|
95
|
+
}
|
|
96
|
+
else if (flags.paused) {
|
|
97
|
+
terminal = flags.paused;
|
|
135
98
|
}
|
|
136
99
|
else if ((flags.abortedLive || final?.stopReason === "aborted") && !haltOwnsAbortedFinal) {
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
errorMessage = final?.errorMessage ?? (flags.abortedForTurns ? "max turns exceeded" : "run aborted");
|
|
100
|
+
const code = flags.abortedForTimeout ? "limits.max_walltime_exceeded" : flags.abortedForTurns ? "limits.max_turns_exceeded" : undefined;
|
|
101
|
+
terminal = { kind: "failed", ...(code !== undefined ? { code } : {}), message: final?.errorMessage ?? (flags.abortedForTurns ? "max turns exceeded" : "run aborted") };
|
|
140
102
|
}
|
|
141
103
|
else if (flags.userHalted === true && (!final || haltOwnsAbortedFinal)) {
|
|
142
|
-
|
|
104
|
+
terminal = { kind: "completed" };
|
|
143
105
|
}
|
|
144
106
|
else if (!final) {
|
|
145
|
-
|
|
146
|
-
errorMessage = "no assistant message produced";
|
|
107
|
+
terminal = { kind: "failed", message: "no assistant message produced" };
|
|
147
108
|
}
|
|
148
109
|
else if (final.stopReason === "error") {
|
|
149
|
-
|
|
150
|
-
|
|
110
|
+
let message = final.errorMessage ?? "model error";
|
|
111
|
+
let code;
|
|
151
112
|
if (final.isApiErrorMessage === true) {
|
|
152
113
|
apiFailure = {
|
|
153
114
|
...(final.apiErrorStatus !== undefined ? { status: final.apiErrorStatus } : {}),
|
|
154
115
|
...(final.requestId !== undefined ? { requestId: final.requestId } : {}),
|
|
155
116
|
};
|
|
156
117
|
}
|
|
157
|
-
const lifted = extractErrorCode(
|
|
118
|
+
const lifted = extractErrorCode(message);
|
|
158
119
|
if (lifted) {
|
|
159
|
-
|
|
160
|
-
|
|
120
|
+
code = lifted;
|
|
121
|
+
message = stripErrorCodePrefix(message);
|
|
161
122
|
}
|
|
162
123
|
else if (flags.gitCoreOverBudget) {
|
|
163
|
-
|
|
124
|
+
code = "irreducible_core_over_budget";
|
|
164
125
|
}
|
|
165
126
|
else if (flags.conflict) {
|
|
166
|
-
|
|
127
|
+
code = "conflict";
|
|
167
128
|
}
|
|
168
129
|
else {
|
|
169
|
-
|
|
130
|
+
code = "provider.error";
|
|
170
131
|
}
|
|
132
|
+
terminal = { kind: "failed", code, message };
|
|
171
133
|
}
|
|
172
134
|
else {
|
|
173
|
-
|
|
135
|
+
terminal = { kind: "completed" };
|
|
174
136
|
}
|
|
137
|
+
return { terminal, apiFailure };
|
|
138
|
+
}
|
|
139
|
+
export function assembleResult(spec, sessionId, final, stats, flags) {
|
|
140
|
+
const taskId = spec.taskId ?? sessionId;
|
|
141
|
+
const text = final ? assistantText(final) : "";
|
|
142
|
+
if (!flags.unpricedSpend) {
|
|
143
|
+
const compactionMicroUsd = stats.compactionMicroUsd ?? 0;
|
|
144
|
+
const nestedSubagentMicroUsd = stats.nested?.costMicroUsd ?? 0;
|
|
145
|
+
stats.costBreakdown = {
|
|
146
|
+
llmRootMicroUsd: Math.max(0, stats.costMicroUsd - compactionMicroUsd),
|
|
147
|
+
nestedSubagentMicroUsd,
|
|
148
|
+
memoryConsolidationMicroUsd: 0,
|
|
149
|
+
compactionMicroUsd,
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
const result = text;
|
|
153
|
+
let salvagedOutput;
|
|
154
|
+
const { terminal, apiFailure } = terminalCauseOf(final, stats, flags);
|
|
155
|
+
const { status, errorCode } = terminalProjection(terminal);
|
|
175
156
|
if (errorCode !== undefined && SALVAGE_ELIGIBLE_TERMINALS.has(errorCode)) {
|
|
176
157
|
salvagedOutput = text.trim() || undefined;
|
|
177
158
|
}
|
|
@@ -181,5 +162,5 @@ export function assembleResult(spec, sessionId, final, stats, flags) {
|
|
|
181
162
|
if (flags.unpricedSpend)
|
|
182
163
|
delete publicStats.costMicroUsd;
|
|
183
164
|
const stampHaltedByUser = flags.userHalted === true && status !== "suspended" && status !== "needs_review";
|
|
184
|
-
return { taskId, ...(flags.runId !== undefined ? { runId: flags.runId } : {}), sessionId,
|
|
165
|
+
return { taskId, ...(flags.runId !== undefined ? { runId: flags.runId } : {}), sessionId, terminal, ...(flags.model !== undefined ? { model: flags.model } : {}), result: result.trim(), salvagedOutput, ...(apiFailure !== undefined ? { apiFailure } : {}), ...(retryAfterMs !== undefined ? { retryAfterMs } : {}), ...(flags.rewindNotes !== undefined && flags.rewindNotes.length > 0 ? { rewindNotes: flags.rewindNotes } : {}), ...(flags.editedFiles !== undefined && flags.editedFiles.length > 0 ? { editedFiles: flags.editedFiles } : {}), ...(flags.haltedOnUserRejection === true ? { haltedOnUserRejection: true } : {}), ...(stampHaltedByUser ? { haltedByUser: true } : {}), ...(flags.remoteEnvFailures !== undefined && flags.remoteEnvFailures.length > 0 ? { remoteEnvFailures: [...flags.remoteEnvFailures] } : {}), ...(flags.strandedHumanAnswers !== undefined && flags.strandedHumanAnswers.length > 0 ? { strandedHumanAnswers: flags.strandedHumanAnswers } : {}), ...(flags.effectiveReadFace !== undefined ? { effectiveReadFace: flags.effectiveReadFace } : {}), ...(flags.effectiveReadDenyPatterns !== undefined && flags.effectiveReadDenyPatterns.length > 0 ? { effectiveReadDenyPatterns: flags.effectiveReadDenyPatterns } : {}), ...(flags.effectiveMemoryScopes !== undefined ? { effectiveMemoryScopes: flags.effectiveMemoryScopes } : {}), ...(flags.effectiveReasoning !== undefined ? { effectiveReasoning: flags.effectiveReasoning } : {}), stats: publicStats };
|
|
185
166
|
}
|
|
@@ -26,7 +26,7 @@ import type { CompactionForkContext } from "../auto-compaction.js";
|
|
|
26
26
|
import type { AutoModeArmingRecipe } from "../auto-mode-arming.js";
|
|
27
27
|
import type { AutoModeDecider, AutoModeDenialTracker } from "../auto-mode.js";
|
|
28
28
|
import type { CacheBreakDetector, ToolFingerprintInput } from "../cache-break-detector.js";
|
|
29
|
-
import type { Checkpoint,
|
|
29
|
+
import type { Checkpoint, CheckpointState, CheckpointToken, PlatformLimitReason, ResourceLedger, ResourceLimitReason, SerializedCheckpointState } from "../checkpoint-store.js";
|
|
30
30
|
import type { ClearedProjectionLedger, ContextEditMachine, OccurrenceIndex } from "../context-edit.js";
|
|
31
31
|
import type { HookInvocationIdentity, Hooks, OrgGateVerdict, PermissionDeniedPayload, ToolGateInput } from "../hooks.js";
|
|
32
32
|
import type { MaterializedMcp } from "../mcp.js";
|
|
@@ -144,6 +144,18 @@ export interface FileHistoryBoundarySeat {
|
|
|
144
144
|
begin(entryId: string): void;
|
|
145
145
|
settle(): Promise<void>;
|
|
146
146
|
}
|
|
147
|
+
/**
|
|
148
|
+
* The commit-side facts of the run's ONE durable pause: the terminal cause the result assembles
|
|
149
|
+
* ({@link import("../types.js").PausedCause} — gate, token, checkpoint id, restore mode), the COMMITTED scope
|
|
150
|
+
* (read verbatim by the reap/cancel path — never re-derived from the spec, which the cross-scope re-scope
|
|
151
|
+
* may no longer match), and the id of the tool call the park is holding (only a `tool_approval` pending
|
|
152
|
+
* action parks ON a call; absent, never guessed, for the kinds that hold none).
|
|
153
|
+
*/
|
|
154
|
+
export interface CommittedPause {
|
|
155
|
+
cause: import("../types.js").PausedCause;
|
|
156
|
+
scope: string;
|
|
157
|
+
gatedCallId?: string;
|
|
158
|
+
}
|
|
147
159
|
export interface Prepared {
|
|
148
160
|
harness: AgentHarness;
|
|
149
161
|
/** The CONCRETE built-in session (engine-internal: prepare constructs/acquires `StoredSession` itself,
|
|
@@ -221,17 +233,12 @@ export interface Prepared {
|
|
|
221
233
|
*
|
|
222
234
|
* It is a sideband and not a field on the tool RESULT because a result is not a trustworthy carrier
|
|
223
235
|
* for this: `details` is arbitrary tool-authored data that post-tool hooks may also replace, so a
|
|
224
|
-
* failing tool could stamp
|
|
225
|
-
*
|
|
236
|
+
* failing tool could stamp a settlement on itself and tell a consumer's audit view that a person
|
|
237
|
+
* approved something nobody was ever shown. Provenance has to travel on a channel only the
|
|
226
238
|
* adjudicating layer can write. Same reason the entries are keyed by CALL id: the gate adjudicated
|
|
227
239
|
* that exact call, and the frame that reads it is that call's own.
|
|
228
240
|
*/
|
|
229
|
-
|
|
230
|
-
settledBy?: import("../tool-policy.js").ApprovalSettledBy;
|
|
231
|
-
approver?: string;
|
|
232
|
-
resolution?: import("../tool-policy.js").AskDenyResolution;
|
|
233
|
-
autoDenied?: true;
|
|
234
|
-
}>;
|
|
241
|
+
gateOutcomes: Map<string, import("../gate-outcome.js").GateOutcome>;
|
|
235
242
|
/**
|
|
236
243
|
* The parent-thread human-rejection halt fact (see `maybeHumanRejectionHalt`): present from the
|
|
237
244
|
* moment a bare human rejection halts the turn's batch until the run ends or the NEXT provider
|
|
@@ -554,24 +561,15 @@ export interface Prepared {
|
|
|
554
561
|
* and the Runner must tear down on task end. Undefined when the env came from a (caller-owned) static
|
|
555
562
|
* `deps.executionEnv` or the stub — those outlive the task and must NOT be destroyed here. */
|
|
556
563
|
ownedEnv?: ExecutionEnv;
|
|
557
|
-
/**
|
|
558
|
-
* (
|
|
559
|
-
*
|
|
560
|
-
*
|
|
561
|
-
* `
|
|
562
|
-
*
|
|
563
|
-
*
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
* Written unconditionally by the commit-side publisher precisely so a later park cannot inherit an
|
|
567
|
-
* earlier one's id. */
|
|
568
|
-
suspendRef: {
|
|
569
|
-
token?: CheckpointToken;
|
|
570
|
-
checkpointId?: string;
|
|
571
|
-
gate?: CheckpointGate;
|
|
572
|
-
scope?: string;
|
|
573
|
-
restoreMode?: "snapshot" | "park_only";
|
|
574
|
-
gatedCallId?: string;
|
|
564
|
+
/** The run's ONE committed durable pause ({@link CommittedPause}): a mutable holder the commit-side
|
|
565
|
+
* publisher (`publishCommittedSuspend`) writes exactly once per run when a park commits — an approval
|
|
566
|
+
* or content ask, a resource slice, a platform pause, a plan or dry-run review — and the run loop reads
|
|
567
|
+
* to assemble the result's `terminal: { kind: "paused", … }`. WHICH pause it is is the cause's own
|
|
568
|
+
* `gate.kind` (a registry row), not the holder it landed in: the two same-shaped holders that used to
|
|
569
|
+
* split the approval family from the review family, and the commit-side routing between them, are
|
|
570
|
+
* gone. `current` is absent until a park commits. */
|
|
571
|
+
pausedRef: {
|
|
572
|
+
current?: CommittedPause;
|
|
575
573
|
};
|
|
576
574
|
/** Ruled 2026-08-05 (matrix ruling arm A): set true by the resume engine when this leg EXECUTES the
|
|
577
575
|
* approved pending call — the restart-loop cap then counts from a fresh base (consecutive
|
|
@@ -579,22 +577,6 @@ export interface Prepared {
|
|
|
579
577
|
suspendProgressRef: {
|
|
580
578
|
executedApproved: boolean;
|
|
581
579
|
};
|
|
582
|
-
/** design/76 §2.5 (dry-run / shadow) + design/80 D-B (plan-gate): the DUAL of {@link suspendRef} for the
|
|
583
|
-
* REVIEW-PAUSE family — a `{kind:"needs_review"}` pause (a profile's dry-run interception committed a
|
|
584
|
-
* checkpoint whose predicted state-diff a human/judge must REVIEW) OR a `{kind:"plan_review"}` pause (a
|
|
585
|
-
* profile's plan-gate committed a checkpoint whose proposed PLAN a human must approve/edit/reject). The
|
|
586
|
-
* commit-side discriminant (`publishCommittedSuspend`) writes HERE for a `needs_review` OR `plan_review`
|
|
587
|
-
* gate and into {@link suspendRef} for every other gate kind — **never both** (else assemble-result's slot
|
|
588
|
-
* 8.6 `needs_review` branch is dead code, v4 MAJOR-A). The run loop reads it to assemble
|
|
589
|
-
* `status:"needs_review"`. Empty unless a review pause fired this run. */
|
|
590
|
-
reviewRef: {
|
|
591
|
-
token?: CheckpointToken;
|
|
592
|
-
checkpointId?: string;
|
|
593
|
-
gate?: CheckpointGate;
|
|
594
|
-
scope?: string;
|
|
595
|
-
restoreMode?: "snapshot" | "park_only";
|
|
596
|
-
gatedCallId?: string;
|
|
597
|
-
};
|
|
598
580
|
/** RB-439-a: the remote-workspace lifecycle failures this run hit, appended in call order and echoed
|
|
599
581
|
* verbatim on `TaskResult.remoteEnvFailures`. A shared array (not a per-call return) because a suspend
|
|
600
582
|
* refusal is reported through the deployment's `onError` side channel and the run then continues or
|
|
@@ -614,7 +596,7 @@ export interface Prepared {
|
|
|
614
596
|
* results, and a remote — or static caller-owned, never per-task-stub — env). The run loop calls it at a
|
|
615
597
|
* CLEAN turn boundary when a resource limit (turns/budget/walltime) was hit: it mints a `resource_limit`
|
|
616
598
|
* checkpoint + pauses the workspace + stops the loop cleanly (NOT abort). Returns true iff it committed a
|
|
617
|
-
* resumable checkpoint (
|
|
599
|
+
* resumable checkpoint (publishes the committed pause); false ⇒ caller falls through to normal limit handling.
|
|
618
600
|
* `sliceSpend` (Slice 4) is THIS slice's cost/tokens/turns, debited onto the cross-slice ledger. */
|
|
619
601
|
suspendForResource?: (reason: ResourceLimitReason, sliceSpend: {
|
|
620
602
|
costMicroUsd: number;
|
|
@@ -685,7 +667,7 @@ export interface Prepared {
|
|
|
685
667
|
};
|
|
686
668
|
/** design/80 D-B: present (≠ undefined) ONLY when a `checkpointStore` is wired (the deployment can pause). The
|
|
687
669
|
* run loop calls it at a CLEAN turn boundary when `reviewRequestRef.pending` is set: it mints a `plan_review`
|
|
688
|
-
* checkpoint (`status:"needs_review"
|
|
670
|
+
* checkpoint (`status:"needs_review"` by its registry row) + pauses the workspace + aborts the loop, reusing
|
|
689
671
|
* the SAME commit saga as the human/resource suspends. Returns true iff it committed a resumable checkpoint;
|
|
690
672
|
* false ⇒ the request could not be honored (caller drops it and continues). */
|
|
691
673
|
suspendForReview?: (reason?: string) => Promise<boolean>;
|
|
@@ -1298,6 +1280,25 @@ export interface InheritedGate {
|
|
|
1298
1280
|
hookEnv?: unknown;
|
|
1299
1281
|
}>;
|
|
1300
1282
|
}
|
|
1283
|
+
/** The runtime feature flags the stable prompt's harness-context block gates its sections on
|
|
1284
|
+
* (design/64 §6.3: claim only what the task has). Spread into the assembly's StablePromptContext
|
|
1285
|
+
* by the prompt-assembly phase, which re-derives the two orchestration flags against the post-exclusion roster. */
|
|
1286
|
+
export interface PromptFeatureFlags {
|
|
1287
|
+
policyEnabled: boolean;
|
|
1288
|
+
hooksEnabled: boolean;
|
|
1289
|
+
isolationEnabled: boolean;
|
|
1290
|
+
reminderMark: string;
|
|
1291
|
+
readFaceOpen: boolean;
|
|
1292
|
+
orchestrationEnabled: boolean;
|
|
1293
|
+
orchestrationDeferred: boolean;
|
|
1294
|
+
promptProfile: "simple" | "classic";
|
|
1295
|
+
fableMitigations: boolean;
|
|
1296
|
+
goalEnabled: boolean;
|
|
1297
|
+
awarenessEnabled: boolean;
|
|
1298
|
+
worktreeIsolated: boolean;
|
|
1299
|
+
withinTaskCompactionEnabled: boolean;
|
|
1300
|
+
isSubagent: boolean;
|
|
1301
|
+
}
|
|
1301
1302
|
/**
|
|
1302
1303
|
* design/78 Slice-1 (MAJOR-3 wiring): a TRUSTED, run-scoped internal channel into {@link prepareTask}, set
|
|
1303
1304
|
* ONLY by a trusted CORE caller (`runRepairLoop` via the Runner's internal `runTaskStream` arg) — NEVER from
|
|
@@ -1322,25 +1323,6 @@ export interface InheritedGate {
|
|
|
1322
1323
|
* them; the engine does not defend against it. The untrusted surface is {@link TaskSpec}, whose pre-first-
|
|
1323
1324
|
* await latch is a separate, pinned guarantee (prepare-task-phase-pins T1).
|
|
1324
1325
|
*/
|
|
1325
|
-
/** The runtime feature flags the stable prompt's harness-context block gates its sections on
|
|
1326
|
-
* (design/64 §6.3: claim only what the task has). Spread into the assembly's StablePromptContext
|
|
1327
|
-
* by the prompt-assembly phase, which re-derives the two orchestration flags against the post-exclusion roster. */
|
|
1328
|
-
export interface PromptFeatureFlags {
|
|
1329
|
-
policyEnabled: boolean;
|
|
1330
|
-
hooksEnabled: boolean;
|
|
1331
|
-
isolationEnabled: boolean;
|
|
1332
|
-
reminderMark: string;
|
|
1333
|
-
readFaceOpen: boolean;
|
|
1334
|
-
orchestrationEnabled: boolean;
|
|
1335
|
-
orchestrationDeferred: boolean;
|
|
1336
|
-
promptProfile: "simple" | "classic";
|
|
1337
|
-
fableMitigations: boolean;
|
|
1338
|
-
goalEnabled: boolean;
|
|
1339
|
-
awarenessEnabled: boolean;
|
|
1340
|
-
worktreeIsolated: boolean;
|
|
1341
|
-
withinTaskCompactionEnabled: boolean;
|
|
1342
|
-
isSubagent: boolean;
|
|
1343
|
-
}
|
|
1344
1326
|
export interface RunInternals {
|
|
1345
1327
|
/** The live repair bundle from a `runRepairLoop` attempt in flight (attemptCount>0). Serialized onto a
|
|
1346
1328
|
* checkpoint minted MID-attempt so a resume re-seeds it; undefined for any non-repair run. */
|
|
@@ -148,7 +148,7 @@ export declare function judgeInheritedClassifier<A extends InheritedAsk>(opts: {
|
|
|
148
148
|
subject: "this call" | "the approved edit";
|
|
149
149
|
}): Promise<InheritedClassifierJudgment<A>>;
|
|
150
150
|
/** The resolver's answer as the settlement reads it. */
|
|
151
|
-
export type FallbackResolution = Pick<ResolvedAsk, "action" | "
|
|
151
|
+
export type FallbackResolution = Pick<ResolvedAsk, "action" | "settlement" | "approverUnavailable">;
|
|
152
152
|
/** A deny nobody made, at a wrapper arm: no approver wired, or a blanket `onAsk:"allow"` refused as
|
|
153
153
|
* no approver. `approver_unavailable` is NOT headless here — it floats to the main gate, which owns
|
|
154
154
|
* that arm (a park, or the fail-closed refusal). */
|
|
@@ -53,7 +53,7 @@ export function createDenialLimitStop(opts) {
|
|
|
53
53
|
};
|
|
54
54
|
return { gateStopRef, stopForDenialLimit };
|
|
55
55
|
}
|
|
56
|
-
const INHERITED_STATION_FACTS = { contentQuestion: false, markedUnresolvable: false, org: undefined };
|
|
56
|
+
const INHERITED_STATION_FACTS = { contentQuestion: false, markedUnresolvable: false, org: undefined, tightened: undefined };
|
|
57
57
|
function fallbackCarry(fallback, liveApprover, tracker) {
|
|
58
58
|
if (fallback === undefined)
|
|
59
59
|
return {};
|
|
@@ -97,10 +97,10 @@ export async function judgeInheritedClassifier(opts) {
|
|
|
97
97
|
return { kind: "resolve", ask: minted, fallback, mintedHere: true, origin: askOriginOf(minted, INHERITED_STATION_FACTS) };
|
|
98
98
|
}
|
|
99
99
|
export function headlessDenyAtFold(r) {
|
|
100
|
-
return r.
|
|
100
|
+
return r.settlement?.kind === "no_approver" || r.settlement?.kind === "blanket_allow_refused";
|
|
101
101
|
}
|
|
102
102
|
export function headlessDenyAtRecheck(r) {
|
|
103
|
-
return headlessDenyAtFold(r) || r.
|
|
103
|
+
return headlessDenyAtFold(r) || r.settlement?.kind === "approver_unavailable" || r.approverUnavailable === true;
|
|
104
104
|
}
|
|
105
105
|
export function settleDenialLimitFallback(opts) {
|
|
106
106
|
const { fallback, resolved } = opts;
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The ONE mint of a {@link GateOutcome} — the record of what a tool-gate pass decided, what ask it settled
|
|
3
|
+
* and who asked. The gate accumulates two facts on its way to an exit (WHO refused, written once at the
|
|
4
|
+
* refusing site; WHAT was settled, written at the resolution site) and hands them here; this module
|
|
5
|
+
* assembles the record, runs the invariant screen, and refuses a record the engine composed wrong. Every
|
|
6
|
+
* projection of the outcome (the deny observer's payload, the call's `tool_end` frame, the engine's
|
|
7
|
+
* per-call sideband) is the object minted here, never a re-derivation.
|
|
8
|
+
*
|
|
9
|
+
* A machinery module on the prepare path's first stratum: it reaches down for the vocabulary only.
|
|
10
|
+
*/
|
|
11
|
+
import { type DeniedBy, type GateOutcome, type Settlement } from "../gate-outcome.js";
|
|
12
|
+
import type { AskOrigin } from "../ask-origin.js";
|
|
13
|
+
import { type PermissionResult, type ResolvedAsk } from "../tool-policy.js";
|
|
14
|
+
/** The ask a gate pass settled, as the gate captured it at its resolution site: the settlement the
|
|
15
|
+
* resolver composed and the origin word the ask carried. Both or neither (invariant I1). */
|
|
16
|
+
export interface SettledAsk {
|
|
17
|
+
settlement: Settlement;
|
|
18
|
+
origin: AskOrigin;
|
|
19
|
+
}
|
|
20
|
+
/** The facts a gate pass hands the mint. `deniedBy` is present exactly when the pass refused; `settled`
|
|
21
|
+
* exactly when the pass consumed an ask's settlement (a person's yes that was later vetoed included). */
|
|
22
|
+
export interface GateExitFacts {
|
|
23
|
+
deniedBy?: DeniedBy;
|
|
24
|
+
settled?: SettledAsk;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Mint the outcome. A defect here is an ENGINE defect (a refusing site that did not say who refused, a
|
|
28
|
+
* settlement without its origin, a person's yes beside a non-veto layer's deny) and throws: the gate's
|
|
29
|
+
* own composition is not a caller input, so there is no fail-closed "deny" to fall back to that would be
|
|
30
|
+
* more honest than the fault.
|
|
31
|
+
*/
|
|
32
|
+
export declare function mintGateOutcome(facts: GateExitFacts): GateOutcome;
|
|
33
|
+
/** A direct refusal by one layer, with no ask settled — the five short-circuit exits (a hook that threw or
|
|
34
|
+
* never answered, the hook's own deny, the compliance lock, the plan-mode block). */
|
|
35
|
+
export declare function directDeny(deniedBy: Exclude<DeniedBy, "ask_resolution">): GateOutcome;
|
|
36
|
+
/**
|
|
37
|
+
* The gate's settlement LEDGER for one pass — the one place "which ask did this pass consume" is written,
|
|
38
|
+
* with ONE rule for every attestation read (the main adjudication, the approval-edit re-check, the gate's
|
|
39
|
+
* own ask resolutions), so the three sites cannot disagree about what an attested decision means:
|
|
40
|
+
* - an engine attestation on a decision object (the approval factory's wait ended inside a policy
|
|
41
|
+
* `check()`; an inherited lane resolved an ancestor's ask) is a settled ask of THIS call — read off the
|
|
42
|
+
* identity-keyed seat, never off a caller-composable member;
|
|
43
|
+
* - a deny carrying a REFUSAL attestation is the ask's own resolution (`ask_resolution`); a deny carrying a
|
|
44
|
+
* person's YES is a later layer's VETO (the yes stays on the settlement, the layer is the disposition —
|
|
45
|
+
* invariant I3);
|
|
46
|
+
* - the LATEST resolution wins (a re-check refusal replaces the first approval; a re-approval renews it);
|
|
47
|
+
* - a SURVIVING ask supersedes any settlement read off an earlier decision (a tighten minted a new
|
|
48
|
+
* question; whoever resolves it settles it afresh).
|
|
49
|
+
* For a resolver's object the attestation outranks the exposed member; the member is the fallback for a
|
|
50
|
+
* resolver seat that composes without attesting (a hand-built resolver; the ask lane's own intercept
|
|
51
|
+
* deny). TRUST BOUNDARY, stated: `ToolGateInput.resolveAsk` is wired by prepare from the engine's own
|
|
52
|
+
* resolver — a wrapper that spreads a resolved object (dropping the identity-keyed attestation) and
|
|
53
|
+
* REPLACES the member is inside the engine's trust line, not a deployment's; making the member
|
|
54
|
+
* attestation-only is the follow-up that migrates the ask lane's composition and the test resolvers
|
|
55
|
+
* onto an engine-owned mint.
|
|
56
|
+
*/
|
|
57
|
+
export interface SettlementLedger {
|
|
58
|
+
/** The ask this pass consumed, or undefined when no ask was settled (a direct deny, a plain allow). */
|
|
59
|
+
readonly settled: SettledAsk | undefined;
|
|
60
|
+
/** An engine-composed settlement the gate itself minted (the unshown content ask's refusal). */
|
|
61
|
+
set(settled: SettledAsk): void;
|
|
62
|
+
/** Record an ask's resolution: the resolver's settlement beside the ask's own origin word. */
|
|
63
|
+
fromResolved(resolved: ResolvedAsk, asked: PermissionResult, toolName: string): void;
|
|
64
|
+
/** Read an adjudication's attestation. For a deny, returns the refusing layer word (`ask_resolution`
|
|
65
|
+
* for an attested refusal, `policy` otherwise — a veto when a yes rides the settlement); undefined
|
|
66
|
+
* for an allow or an ask. */
|
|
67
|
+
fromDecision(decision: PermissionResult): DeniedBy | undefined;
|
|
68
|
+
/** A surviving ask supersedes whatever an earlier decision settled. */
|
|
69
|
+
supersede(): void;
|
|
70
|
+
}
|
|
71
|
+
export declare function createSettlementLedger(call: {
|
|
72
|
+
toolCallId: string;
|
|
73
|
+
toolName: string;
|
|
74
|
+
}): SettlementLedger;
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { screenGateOutcome, SETTLEMENT_IS_REFUSAL } from "../gate-outcome.js";
|
|
2
|
+
import { engineSettlementOf } from "../tool-policy.js";
|
|
3
|
+
export function mintGateOutcome(facts) {
|
|
4
|
+
const settlement = facts.settled !== undefined ? Object.freeze({ ...facts.settled.settlement, who: Object.freeze({ ...facts.settled.settlement.who }) }) : undefined;
|
|
5
|
+
const outcome = Object.freeze({
|
|
6
|
+
disposition: Object.freeze(facts.deniedBy !== undefined ? { kind: "denied", deniedBy: facts.deniedBy } : { kind: "allowed" }),
|
|
7
|
+
...(facts.settled !== undefined ? { settlement: settlement, origin: facts.settled.origin } : {}),
|
|
8
|
+
});
|
|
9
|
+
const defects = screenGateOutcome(outcome);
|
|
10
|
+
if (defects.length > 0)
|
|
11
|
+
throw new Error(`the tool gate composed an outcome that violates its own record: ${defects.join("; ")}`);
|
|
12
|
+
return outcome;
|
|
13
|
+
}
|
|
14
|
+
export function directDeny(deniedBy) {
|
|
15
|
+
return mintGateOutcome({ deniedBy });
|
|
16
|
+
}
|
|
17
|
+
export function createSettlementLedger(call) {
|
|
18
|
+
let settled;
|
|
19
|
+
return {
|
|
20
|
+
get settled() {
|
|
21
|
+
return settled;
|
|
22
|
+
},
|
|
23
|
+
set(next) {
|
|
24
|
+
settled = next;
|
|
25
|
+
},
|
|
26
|
+
fromResolved(resolved, asked, toolName) {
|
|
27
|
+
if (asked.action !== "ask" || asked.origin === undefined) {
|
|
28
|
+
if (resolved.settlement === undefined)
|
|
29
|
+
return;
|
|
30
|
+
throw new Error(`an ask for "${toolName}" was resolved without an origin word — the origin stamp precedes every resolution`);
|
|
31
|
+
}
|
|
32
|
+
const attested = engineSettlementOf(resolved, call);
|
|
33
|
+
if (attested !== undefined) {
|
|
34
|
+
settled = { settlement: attested.settlement, origin: asked.origin };
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
if (resolved.settlement === undefined)
|
|
38
|
+
return;
|
|
39
|
+
settled = { settlement: resolved.settlement, origin: asked.origin };
|
|
40
|
+
},
|
|
41
|
+
fromDecision(decision) {
|
|
42
|
+
if (decision.action === "ask")
|
|
43
|
+
return undefined;
|
|
44
|
+
const attested = engineSettlementOf(decision, call);
|
|
45
|
+
if (attested !== undefined)
|
|
46
|
+
settled = { settlement: attested.settlement, origin: attested.origin };
|
|
47
|
+
if (decision.action !== "deny")
|
|
48
|
+
return undefined;
|
|
49
|
+
return attested !== undefined && SETTLEMENT_IS_REFUSAL[attested.settlement.kind] ? "ask_resolution" : "policy";
|
|
50
|
+
},
|
|
51
|
+
supersede() {
|
|
52
|
+
settled = undefined;
|
|
53
|
+
},
|
|
54
|
+
};
|
|
55
|
+
}
|
|
@@ -55,23 +55,23 @@ export declare function sanitizedTtlMs(ttlMs: number | undefined): number | unde
|
|
|
55
55
|
* SINGLE derivation on purpose. Two consumers need this answer: the abort-classification seam, which
|
|
56
56
|
* stamps it into the `details` of the results the loop mints for the contaminated siblings, and the
|
|
57
57
|
* `tool_end` projection, which puts it on the wire. Reading it from one function keeps the two from
|
|
58
|
-
* disagreeing about
|
|
58
|
+
* disagreeing about what a pause with no call means. They are not otherwise
|
|
59
59
|
* interchangeable: the frame face applies strictly narrower conditions on top of this answer (see
|
|
60
60
|
* `tool_end.gatedCallId`), so a frame may omit an id this function returns — never the reverse.
|
|
61
|
+
* "Which holder wins" is no longer a question: there is one holder, and a pause that holds no call
|
|
62
|
+
* publishes absence.
|
|
61
63
|
*
|
|
62
64
|
* The frame side must not read the id back out of a tool RESULT even though the marker is there: a
|
|
63
65
|
* result's `details` is written by the tool (and replaceable by post-tool hooks), so lifting a
|
|
64
66
|
* cross-call attribution from it would let any failing tool name an arbitrary call and put a phantom
|
|
65
|
-
* approval wait on someone else's frame. Same rule, same reason, as `
|
|
67
|
+
* approval wait on someone else's frame. Same rule, same reason, as `tool_end.gate`.
|
|
66
68
|
*
|
|
67
|
-
* The two holders are never both set (the commit-side discriminant writes exactly one); they are read
|
|
68
|
-
* here in assemble-result's slot order so the winner is the same one the terminal status is built from.
|
|
69
69
|
*/
|
|
70
|
-
export declare function gatedCallIdOf(p: Pick<Prepared, "
|
|
70
|
+
export declare function gatedCallIdOf(p: Pick<Prepared, "pausedRef">): string | undefined;
|
|
71
71
|
/**
|
|
72
72
|
* The abort-classification marker for the loop's short-circuit "Operation aborted" family: when a gate
|
|
73
|
-
* committed a durable park this run (`
|
|
74
|
-
*
|
|
73
|
+
* committed a durable park this run (`pausedRef.current`, whatever the pause's kind), the contaminated
|
|
74
|
+
* sibling frames carry something a consumer can key
|
|
75
75
|
* on instead of parsing the (contract-stable) result text (ruled 2026-08-04). The commit is published
|
|
76
76
|
* BEFORE the abort fires, so this read is race-free at mint time.
|
|
77
77
|
*
|
|
@@ -83,26 +83,20 @@ export declare function gatedCallIdOf(p: Pick<Prepared, "suspendRef" | "reviewRe
|
|
|
83
83
|
* Module-level rather than a closure in `prepareTask` (design/238 D-7: extract, don't accrete) — the
|
|
84
84
|
* marker's shape and its rules belong beside {@link gatedCallIdOf}, the derivation it reads.
|
|
85
85
|
*/
|
|
86
|
-
export declare function parkContaminationMarker(refs: Pick<Prepared, "
|
|
86
|
+
export declare function parkContaminationMarker(refs: Pick<Prepared, "pausedRef">): Record<string, unknown> | undefined;
|
|
87
87
|
/**
|
|
88
|
-
*
|
|
89
|
-
*
|
|
90
|
-
* the
|
|
91
|
-
*
|
|
92
|
-
* approval
|
|
93
|
-
*
|
|
94
|
-
*
|
|
95
|
-
* dry-run `{kind:"needs_review"}` gate (design/76 §2.5) AND the design/80 D-B `{kind:"plan_review"}`
|
|
96
|
-
* gate — both are human-review pauses that share the `needs_review` terminal (a `plan_review` resumes
|
|
97
|
-
* with a `plan_review` outcome, a `needs_review` with `dry_run_review`; the distinct gate.kind keeps the
|
|
98
|
-
* resume discriminant unambiguous). (The review checkpoints themselves are minted by a PROFILE, out of
|
|
99
|
-
* core scope; core only owns this routing seam + the gate/status/resume discriminants.)
|
|
88
|
+
* THE COMMIT-SIDE PUBLICATION. Called AFTER `commitSuspendSaga` returns true (the checkpoint is durably
|
|
89
|
+
* committed) to publish the run's one committed pause for the run loop's terminal assembly. There is no
|
|
90
|
+
* routing: the cause carries the committed gate whole, and WHICH terminal status it reads
|
|
91
|
+
* (`suspended` / `needs_review`) is the gate kind's registry row — the two holders the review family and
|
|
92
|
+
* the approval family used to be split across, and the branch that chose between them, are gone.
|
|
93
|
+
* (Review checkpoints themselves are minted by a PROFILE, out of core scope; core owns this seam and the
|
|
94
|
+
* gate/status/resume discriminants.)
|
|
100
95
|
*
|
|
101
|
-
* Module-level, taking the
|
|
102
|
-
* closure form captured nothing else, so hoisting it costs nothing and puts the discriminant beside the
|
|
96
|
+
* Module-level, taking the holder as a parameter (design/238 D-7: extract, don't accrete) — beside the
|
|
103
97
|
* two readers of what it publishes ({@link gatedCallIdOf}, {@link parkContaminationMarker}).
|
|
104
98
|
*/
|
|
105
|
-
export declare function publishCommittedSuspend(refs: Pick<Prepared, "
|
|
99
|
+
export declare function publishCommittedSuspend(refs: Pick<Prepared, "pausedRef">, token: CheckpointToken, gate: CheckpointGate, scope: string, remoteHandle: WorkspaceHandle | undefined, checkpointId?: string,
|
|
106
100
|
/** The COMMITTED row's pendingAction — passed whole, not as a pre-extracted id, so no call site can
|
|
107
101
|
* hand this seam an id the committed row does not actually park on. */
|
|
108
102
|
pendingAction?: PendingAction): void;
|