@claudexor/orchestrator 3.1.2 → 3.2.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/dist/attemptFinalize.d.ts +14 -11
- package/dist/attemptFinalize.d.ts.map +1 -1
- package/dist/attemptFinalize.js +41 -14
- package/dist/attemptFinalize.js.map +1 -1
- package/dist/attemptTelemetry.d.ts +7 -0
- package/dist/attemptTelemetry.d.ts.map +1 -1
- package/dist/attemptTelemetry.js +2 -1
- package/dist/attemptTelemetry.js.map +1 -1
- package/dist/attemptUsageCost.d.ts +17 -1
- package/dist/attemptUsageCost.d.ts.map +1 -1
- package/dist/attemptUsageCost.js +11 -1
- package/dist/attemptUsageCost.js.map +1 -1
- package/dist/budgetFailure.js.map +1 -1
- package/dist/credential-profile-rotation.d.ts +145 -0
- package/dist/credential-profile-rotation.d.ts.map +1 -0
- package/dist/credential-profile-rotation.js +315 -0
- package/dist/credential-profile-rotation.js.map +1 -0
- package/dist/credential-profiles.d.ts +35 -134
- package/dist/credential-profiles.d.ts.map +1 -1
- package/dist/credential-profiles.js +73 -295
- package/dist/credential-profiles.js.map +1 -1
- package/dist/decisionBudget.d.ts +2 -2
- package/dist/decisionBudget.d.ts.map +1 -1
- package/dist/deepScanReducer.d.ts +14 -44
- package/dist/deepScanReducer.d.ts.map +1 -1
- package/dist/deepScanReducer.js +240 -128
- package/dist/deepScanReducer.js.map +1 -1
- package/dist/delegationBudgetAuthority.d.ts.map +1 -1
- package/dist/delegationBudgetAuthority.js.map +1 -1
- package/dist/delegationFailure.d.ts.map +1 -1
- package/dist/git-precondition.d.ts +11 -0
- package/dist/git-precondition.d.ts.map +1 -0
- package/dist/git-precondition.js +69 -0
- package/dist/git-precondition.js.map +1 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/interaction.d.ts +14 -6
- package/dist/interaction.d.ts.map +1 -1
- package/dist/interaction.js +106 -19
- package/dist/interaction.js.map +1 -1
- package/dist/orchestrator.d.ts +39 -83
- package/dist/orchestrator.d.ts.map +1 -1
- package/dist/orchestrator.js +480 -676
- package/dist/orchestrator.js.map +1 -1
- package/dist/planBrief.d.ts +27 -0
- package/dist/planBrief.d.ts.map +1 -0
- package/dist/planBrief.js +42 -0
- package/dist/planBrief.js.map +1 -0
- package/dist/planRun.d.ts +4 -3
- package/dist/planRun.d.ts.map +1 -1
- package/dist/planRun.js +33 -33
- package/dist/planRun.js.map +1 -1
- package/dist/plannerAttempt.d.ts +64 -0
- package/dist/plannerAttempt.d.ts.map +1 -0
- package/dist/plannerAttempt.js +235 -0
- package/dist/plannerAttempt.js.map +1 -0
- package/dist/policyFindings.js.map +1 -1
- package/dist/requestRequirements.d.ts +28 -1
- package/dist/requestRequirements.d.ts.map +1 -1
- package/dist/requestRequirements.js +89 -10
- package/dist/requestRequirements.js.map +1 -1
- package/dist/reviewerPanel.js.map +1 -1
- package/dist/root-ledger.d.ts.map +1 -1
- package/dist/root-ledger.js +2 -0
- package/dist/root-ledger.js.map +1 -1
- package/dist/run-bounded.d.ts.map +1 -1
- package/dist/run-bounded.js +7 -1
- package/dist/run-bounded.js.map +1 -1
- package/dist/run-input-resolution.d.ts.map +1 -1
- package/dist/run-input-resolution.js +26 -8
- package/dist/run-input-resolution.js.map +1 -1
- package/dist/runEventLog.d.ts +48 -0
- package/dist/runEventLog.d.ts.map +1 -0
- package/dist/runEventLog.js +63 -0
- package/dist/runEventLog.js.map +1 -0
- package/dist/runFacts.d.ts +23 -0
- package/dist/runFacts.d.ts.map +1 -0
- package/dist/runFacts.js +179 -0
- package/dist/runFacts.js.map +1 -0
- package/dist/runFactsBuilder.d.ts +5 -0
- package/dist/runFactsBuilder.d.ts.map +1 -0
- package/dist/runFactsBuilder.js +412 -0
- package/dist/runFactsBuilder.js.map +1 -0
- package/dist/runFactsDeliverable.d.ts +13 -0
- package/dist/runFactsDeliverable.d.ts.map +1 -0
- package/dist/runFactsDeliverable.js +94 -0
- package/dist/runFactsDeliverable.js.map +1 -0
- package/dist/runFactsPresentation.d.ts +7 -0
- package/dist/runFactsPresentation.d.ts.map +1 -0
- package/dist/runFactsPresentation.js +113 -0
- package/dist/runFactsPresentation.js.map +1 -0
- package/dist/runFactsReview.d.ts +7 -0
- package/dist/runFactsReview.d.ts.map +1 -0
- package/dist/runFactsReview.js +63 -0
- package/dist/runFactsReview.js.map +1 -0
- package/dist/runSupport.d.ts +17 -15
- package/dist/runSupport.d.ts.map +1 -1
- package/dist/runSupport.js +16 -0
- package/dist/runSupport.js.map +1 -1
- package/dist/runTerminalContext.d.ts +31 -0
- package/dist/runTerminalContext.d.ts.map +1 -0
- package/dist/runTerminalContext.js +12 -0
- package/dist/runTerminalContext.js.map +1 -0
- package/dist/runTerminalResults.d.ts +55 -0
- package/dist/runTerminalResults.d.ts.map +1 -0
- package/dist/runTerminalResults.js +134 -0
- package/dist/runTerminalResults.js.map +1 -0
- package/dist/runTerminals.d.ts +4 -80
- package/dist/runTerminals.d.ts.map +1 -1
- package/dist/runTerminals.js +357 -260
- package/dist/runTerminals.js.map +1 -1
- package/dist/secretDiff.d.ts +3 -0
- package/dist/secretDiff.d.ts.map +1 -1
- package/dist/secretDiff.js +7 -0
- package/dist/secretDiff.js.map +1 -1
- package/dist/structuredOutput.d.ts.map +1 -1
- package/dist/structuredOutput.js.map +1 -1
- package/dist/task-contract-builder.d.ts +35 -0
- package/dist/task-contract-builder.d.ts.map +1 -0
- package/dist/task-contract-builder.js +114 -0
- package/dist/task-contract-builder.js.map +1 -0
- package/dist/transientClassify.js.map +1 -1
- package/package.json +17 -17
package/dist/runTerminals.js
CHANGED
|
@@ -8,23 +8,15 @@
|
|
|
8
8
|
* forever.
|
|
9
9
|
*/
|
|
10
10
|
import { join } from "node:path";
|
|
11
|
-
import {
|
|
12
|
-
import { redactSecrets } from "@claudexor/util";
|
|
11
|
+
import { makeOutcomeFacts, needsOperatorAttention, RunOutcomeFacts, } from "@claudexor/schema";
|
|
12
|
+
import { readTextSafe, redactSecrets } from "@claudexor/util";
|
|
13
13
|
import { budgetFailureRecord, classifyBudgetFailure } from "./budgetFailure.js";
|
|
14
14
|
import { reconcileDecisionBudget, settledBudgetSnapshot, } from "./decisionBudget.js";
|
|
15
15
|
import { reconcileDecisionTerminal } from "./decisionTerminalReconciliation.js";
|
|
16
|
+
import { prepareRunFactsFailureReceipt, prepareRunFactsReceipt } from "./runFacts.js";
|
|
17
|
+
import { cancelledResult, failTerminally, writeFailure } from "./runTerminalResults.js";
|
|
16
18
|
import { clearFailureArtifact, reconcileWorkProductTerminal, terminalOutcomeFacts, } from "./terminalOutcome.js";
|
|
17
|
-
export
|
|
18
|
-
return {
|
|
19
|
-
...identity,
|
|
20
|
-
spend: () => ledger.spend(),
|
|
21
|
-
valuation: () => ledger.valuation(),
|
|
22
|
-
spendEstimated: () => ledger.estimated(),
|
|
23
|
-
valuationKnowledge: () => ledger.valuationKnowledge(),
|
|
24
|
-
budgetTerminal: () => ledger.terminal(),
|
|
25
|
-
recheckBudgetAfterBarrier: hasDelegateAuthority,
|
|
26
|
-
};
|
|
27
|
-
}
|
|
19
|
+
export { cancelledResult, failTerminally, writeFailure };
|
|
28
20
|
function ownsDelegateDrain(context) {
|
|
29
21
|
try {
|
|
30
22
|
return context.recheckBudgetAfterBarrier?.() === true;
|
|
@@ -64,133 +56,77 @@ function postDrainBudgetFailure(context, terminal, budget, prior) {
|
|
|
64
56
|
spendUsd: budget.spendUsd,
|
|
65
57
|
};
|
|
66
58
|
}
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
logRefs: failure.logRefs ?? [],
|
|
77
|
-
eventRefs: failure.eventRefs ?? [],
|
|
78
|
-
runDir: failure.runDir ?? paths.root,
|
|
79
|
-
nextActions: failure.nextActions ?? [],
|
|
80
|
-
});
|
|
59
|
+
function terminalEventTypeFor(_originalType, outcome) {
|
|
60
|
+
if (outcome.lifecycle !== "succeeded")
|
|
61
|
+
return "run.failed";
|
|
62
|
+
if (needsOperatorAttention(outcome, false))
|
|
63
|
+
return "run.blocked";
|
|
64
|
+
// The canonical axes own terminal type. Preserving a producer-authored
|
|
65
|
+
// run.blocked with clean axes creates an unexplainable block (no reason or
|
|
66
|
+
// required action), so it is normalized to completed.
|
|
67
|
+
return "run.completed";
|
|
81
68
|
}
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
}
|
|
103
|
-
catch {
|
|
104
|
-
/* partial telemetry is best-effort on the cancel path */
|
|
69
|
+
function cancellationFacts(signal, prior) {
|
|
70
|
+
return terminalOutcomeFacts(prior, "cancelled", typeof signal.reason === "string" && signal.reason === "wall_clock_exceeded"
|
|
71
|
+
? "wall_clock_exceeded"
|
|
72
|
+
: "user_cancelled");
|
|
73
|
+
}
|
|
74
|
+
function terminalOutcomeFromPayload(payload) {
|
|
75
|
+
const parsed = RunOutcomeFacts.safeParse(payload["facts"]);
|
|
76
|
+
if (parsed.success)
|
|
77
|
+
return parsed.data;
|
|
78
|
+
// Older in-process terminal producers (including the Delegate drain
|
|
79
|
+
// contract) supplied only lifecycle. Keep that compatible while treating an
|
|
80
|
+
// explicitly malformed facts object as a contradiction that must fail
|
|
81
|
+
// closed through the terminal-facts path below.
|
|
82
|
+
if (!Object.prototype.hasOwnProperty.call(payload, "facts")) {
|
|
83
|
+
const lifecycle = payload["lifecycle"];
|
|
84
|
+
if (lifecycle === "succeeded" ||
|
|
85
|
+
lifecycle === "failed" ||
|
|
86
|
+
lifecycle === "cancelled" ||
|
|
87
|
+
lifecycle === "interrupted") {
|
|
88
|
+
return makeOutcomeFacts(lifecycle);
|
|
105
89
|
}
|
|
106
90
|
}
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
: "run cancelled";
|
|
113
|
-
// Materialize the diagnostic summary BEFORE announcing it — output.ready must
|
|
114
|
-
// point at a file that exists (partial-output honesty), and it must precede
|
|
115
|
-
// the terminal in every mode (INV-116).
|
|
116
|
-
let summaryWritten = false;
|
|
117
|
-
if (store) {
|
|
118
|
-
try {
|
|
119
|
-
store.writeText(join(runDir, "final", "summary.md"), `# Run ${runId} (${mode})\n\n- Lifecycle: cancelled\n${cancelReason ? `- Reason: ${cancelReason}\n` : ""}\n${summaryText}\n`);
|
|
120
|
-
summaryWritten = true;
|
|
121
|
-
}
|
|
122
|
-
catch {
|
|
123
|
-
/* best-effort: a write failure must not mask the cancel terminal */
|
|
124
|
-
}
|
|
91
|
+
return RunOutcomeFacts.parse(payload["facts"]);
|
|
92
|
+
}
|
|
93
|
+
function settledSpend(a) {
|
|
94
|
+
try {
|
|
95
|
+
return a.spend ? a.spend() : null;
|
|
125
96
|
}
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
// terminal below, just without a pointer to a nonexistent artifact.
|
|
129
|
-
if (summaryWritten) {
|
|
130
|
-
log.emit("output.ready", { kind: "summary", path: "final/summary.md", state: "diagnostic" });
|
|
97
|
+
catch {
|
|
98
|
+
return null;
|
|
131
99
|
}
|
|
132
|
-
const cancelFacts = terminalOutcomeFacts(priorFacts, "cancelled", cancelReason === "wall_clock_exceeded" ? "wall_clock_exceeded" : "user_cancelled");
|
|
133
|
-
log.emit("run.failed", {
|
|
134
|
-
lifecycle: "cancelled",
|
|
135
|
-
facts: cancelFacts,
|
|
136
|
-
reason: cancelFacts.reason,
|
|
137
|
-
...(cancelReason ? { cancel_reason: cancelReason } : {}),
|
|
138
|
-
});
|
|
139
|
-
return {
|
|
140
|
-
runId,
|
|
141
|
-
taskId,
|
|
142
|
-
mode,
|
|
143
|
-
lifecycle: "cancelled",
|
|
144
|
-
facts: cancelFacts,
|
|
145
|
-
winner: null,
|
|
146
|
-
runDir,
|
|
147
|
-
summary: summaryText,
|
|
148
|
-
candidates,
|
|
149
|
-
...(spendUsd !== undefined ? { spendUsd } : {}),
|
|
150
|
-
...(cancelReason ? { cancelReason } : {}),
|
|
151
|
-
};
|
|
152
100
|
}
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
const
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
runDir: paths.root,
|
|
172
|
-
nextActions: failureMeta.nextActions ?? ["Open diagnostics", "Retry the run"],
|
|
173
|
-
});
|
|
174
|
-
log.emit("output.ready", { kind: "summary", path: "final/summary.md", state: "diagnostic" });
|
|
175
|
-
log.emit("run.failed", {
|
|
176
|
-
lifecycle: "failed",
|
|
177
|
-
facts: failFacts,
|
|
178
|
-
reason: failFacts.reason,
|
|
179
|
-
phase,
|
|
180
|
-
error: message,
|
|
181
|
-
failure_ref: "final/failure.yaml",
|
|
182
|
-
});
|
|
101
|
+
function isTerminalRecoveryRequired(error) {
|
|
102
|
+
return (!!error &&
|
|
103
|
+
typeof error === "object" &&
|
|
104
|
+
"code" in error &&
|
|
105
|
+
error.code === "terminal_recovery_required");
|
|
106
|
+
}
|
|
107
|
+
function committedOutcome(a) {
|
|
108
|
+
const terminal = [...a.log.readAll().events]
|
|
109
|
+
.reverse()
|
|
110
|
+
.find((event) => event.type === "run.completed" ||
|
|
111
|
+
event.type === "run.blocked" ||
|
|
112
|
+
event.type === "run.failed");
|
|
113
|
+
const parsed = RunOutcomeFacts.safeParse(terminal?.payload["facts"]);
|
|
114
|
+
return parsed.success ? parsed.data : null;
|
|
115
|
+
}
|
|
116
|
+
function committedResult(a, outcome, spendUsd) {
|
|
117
|
+
const summary = readTextSafe(join(a.paths.finalDir, "summary.md"))?.trim().slice(0, 400) ??
|
|
118
|
+
`run terminal committed with lifecycle ${outcome.lifecycle}`;
|
|
183
119
|
return {
|
|
184
|
-
runId,
|
|
185
|
-
taskId,
|
|
186
|
-
mode,
|
|
187
|
-
lifecycle:
|
|
188
|
-
facts:
|
|
120
|
+
runId: a.runId,
|
|
121
|
+
taskId: a.taskId,
|
|
122
|
+
mode: a.mode,
|
|
123
|
+
lifecycle: outcome.lifecycle,
|
|
124
|
+
facts: outcome,
|
|
189
125
|
winner: null,
|
|
190
|
-
runDir: paths.root,
|
|
191
|
-
summary
|
|
126
|
+
runDir: a.paths.root,
|
|
127
|
+
summary,
|
|
192
128
|
candidates: [],
|
|
193
|
-
|
|
129
|
+
spendUsd,
|
|
194
130
|
};
|
|
195
131
|
}
|
|
196
132
|
/**
|
|
@@ -213,6 +149,8 @@ beforeTerminal,
|
|
|
213
149
|
onSettled) {
|
|
214
150
|
let announced = null;
|
|
215
151
|
let preparedResult = null;
|
|
152
|
+
let canonicalOutcome = null;
|
|
153
|
+
let terminalPreparationError = null;
|
|
216
154
|
let barrierAttempted = false;
|
|
217
155
|
let barrierFailed = false;
|
|
218
156
|
const runBarrier = async (context) => {
|
|
@@ -228,145 +166,304 @@ onSettled) {
|
|
|
228
166
|
}
|
|
229
167
|
};
|
|
230
168
|
try {
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
169
|
+
let result;
|
|
170
|
+
try {
|
|
171
|
+
result = await body((a) => {
|
|
172
|
+
announced = a;
|
|
173
|
+
a.log.setBeforeTerminal((originalType, payload) => {
|
|
174
|
+
try {
|
|
175
|
+
// Cancellation wins only until the synchronous terminal commit
|
|
176
|
+
// starts. A later abort cannot rewrite an already-durable success.
|
|
177
|
+
const payloadOutcome = terminalOutcomeFromPayload(payload);
|
|
178
|
+
const terminalOutcome = signal?.aborted
|
|
179
|
+
? cancellationFacts(signal, payloadOutcome)
|
|
180
|
+
: payloadOutcome;
|
|
181
|
+
const prepared = prepareRunFactsReceipt(a, terminalOutcome);
|
|
182
|
+
canonicalOutcome = prepared.facts.outcome;
|
|
183
|
+
return {
|
|
184
|
+
type: terminalEventTypeFor(originalType, prepared.facts.outcome),
|
|
185
|
+
payload: {
|
|
186
|
+
...payload,
|
|
187
|
+
lifecycle: prepared.facts.outcome.lifecycle,
|
|
188
|
+
facts: prepared.facts.outcome,
|
|
189
|
+
reason: prepared.facts.outcome.reason,
|
|
190
|
+
run_facts: prepared.facts,
|
|
191
|
+
},
|
|
192
|
+
commit: prepared.commit,
|
|
193
|
+
rollback: () => {
|
|
194
|
+
prepared.rollback();
|
|
195
|
+
canonicalOutcome = null;
|
|
196
|
+
},
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
catch (error) {
|
|
200
|
+
// A contradictory terminal projection must fail the run, not throw
|
|
201
|
+
// out of the hook and retry the same contradiction forever.
|
|
202
|
+
const detail = redactSecrets(error instanceof Error ? error.message : String(error));
|
|
203
|
+
terminalPreparationError = `terminal facts preparation failed: ${detail}`;
|
|
204
|
+
const failureOutcome = makeOutcomeFacts("failed", {
|
|
205
|
+
reason: "harness_failed",
|
|
206
|
+
});
|
|
207
|
+
let failureRefWritten = false;
|
|
208
|
+
try {
|
|
209
|
+
a.store.writeText(join(a.paths.finalDir, "summary.md"), `# Run ${a.runId} (${a.mode})\n\n- Lifecycle: failed\n- Phase: terminal_facts\n\n${terminalPreparationError}\n`);
|
|
210
|
+
writeFailure(a.store, a.paths, {
|
|
211
|
+
phase: "terminal_facts",
|
|
212
|
+
category: "internal",
|
|
213
|
+
safeMessage: terminalPreparationError,
|
|
214
|
+
runDir: a.paths.root,
|
|
215
|
+
nextActions: ["Open diagnostics", "Inspect final/run_facts.yaml", "Retry the run"],
|
|
216
|
+
});
|
|
217
|
+
failureRefWritten = true;
|
|
218
|
+
}
|
|
219
|
+
catch {
|
|
220
|
+
/* terminal event remains the fail-closed authority */
|
|
221
|
+
}
|
|
222
|
+
try {
|
|
223
|
+
const prepared = prepareRunFactsFailureReceipt(a, failureOutcome);
|
|
224
|
+
canonicalOutcome = prepared.facts.outcome;
|
|
225
|
+
return {
|
|
226
|
+
type: "run.failed",
|
|
227
|
+
payload: {
|
|
228
|
+
lifecycle: prepared.facts.outcome.lifecycle,
|
|
229
|
+
facts: prepared.facts.outcome,
|
|
230
|
+
reason: prepared.facts.outcome.reason,
|
|
231
|
+
run_facts: prepared.facts,
|
|
232
|
+
phase: "terminal_facts",
|
|
233
|
+
error: terminalPreparationError,
|
|
234
|
+
...(failureRefWritten ? { failure_ref: "final/failure.yaml" } : {}),
|
|
235
|
+
},
|
|
236
|
+
commit: prepared.commit,
|
|
237
|
+
rollback: () => {
|
|
238
|
+
prepared.rollback();
|
|
239
|
+
canonicalOutcome = null;
|
|
240
|
+
},
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
catch {
|
|
244
|
+
// If even the minimal receipt cannot be written, still commit an
|
|
245
|
+
// honest failed terminal. The daemon job state then fails closed.
|
|
246
|
+
canonicalOutcome = failureOutcome;
|
|
247
|
+
return {
|
|
248
|
+
type: "run.failed",
|
|
249
|
+
payload: {
|
|
250
|
+
lifecycle: failureOutcome.lifecycle,
|
|
251
|
+
facts: failureOutcome,
|
|
252
|
+
reason: failureOutcome.reason,
|
|
253
|
+
phase: "terminal_facts",
|
|
254
|
+
error: terminalPreparationError,
|
|
255
|
+
...(failureRefWritten ? { failure_ref: "final/failure.yaml" } : {}),
|
|
256
|
+
},
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
});
|
|
261
|
+
});
|
|
262
|
+
preparedResult = result;
|
|
263
|
+
const context = announced;
|
|
264
|
+
if (context) {
|
|
265
|
+
await runBarrier(context);
|
|
266
|
+
const budget = settledBudgetSnapshot(context);
|
|
267
|
+
const delegateBarrierArmed = ownsDelegateDrain(context);
|
|
268
|
+
if (delegateBarrierArmed && signal?.aborted) {
|
|
269
|
+
context.log.clearDeferredTerminal();
|
|
270
|
+
const cancelFacts = terminalOutcomeFacts(result.facts, "cancelled", signal.reason === "wall_clock_exceeded" ? "wall_clock_exceeded" : "user_cancelled");
|
|
271
|
+
const cancelSummary = signal.reason === "wall_clock_exceeded"
|
|
272
|
+
? "run cancelled: wall-clock deadline (maxSeconds) exceeded"
|
|
273
|
+
: "run cancelled";
|
|
274
|
+
let reconciledCancelFacts = cancelFacts;
|
|
275
|
+
try {
|
|
276
|
+
reconcileDecisionBudget(context, budget);
|
|
277
|
+
reconciledCancelFacts = reconcileDecisionTerminal(context, {
|
|
278
|
+
facts: cancelFacts,
|
|
279
|
+
why: cancelSummary,
|
|
280
|
+
preparedFacts: result.facts,
|
|
281
|
+
});
|
|
282
|
+
reconcileWorkProductTerminal(context, reconciledCancelFacts);
|
|
283
|
+
clearFailureArtifact(context);
|
|
284
|
+
}
|
|
285
|
+
catch {
|
|
286
|
+
/* the cancellation terminal below remains authoritative */
|
|
287
|
+
}
|
|
288
|
+
const cancelled = cancelledResult(context.log, context.runId, context.taskId, context.mode, context.paths.root, result.candidates, undefined, budget.spendUsd, signal, context.store, reconciledCancelFacts);
|
|
289
|
+
context.log.flushDeferredTerminal();
|
|
290
|
+
result = {
|
|
291
|
+
...result,
|
|
292
|
+
lifecycle: "cancelled",
|
|
293
|
+
facts: cancelled.facts,
|
|
294
|
+
summary: cancelled.summary,
|
|
295
|
+
spendUsd: cancelled.spendUsd,
|
|
296
|
+
...(cancelled.cancelReason ? { cancelReason: cancelled.cancelReason } : {}),
|
|
297
|
+
};
|
|
256
298
|
}
|
|
257
|
-
|
|
258
|
-
|
|
299
|
+
else {
|
|
300
|
+
const budgetTerminal = delegateBarrierArmed ? (context.budgetTerminal?.() ?? null) : null;
|
|
301
|
+
if (budgetTerminal && result.lifecycle === "succeeded") {
|
|
302
|
+
context.log.clearDeferredTerminal();
|
|
303
|
+
result = postDrainBudgetFailure(context, budgetTerminal, budget, result);
|
|
304
|
+
context.log.flushDeferredTerminal();
|
|
305
|
+
}
|
|
306
|
+
else {
|
|
307
|
+
if (delegateBarrierArmed)
|
|
308
|
+
reconcileDecisionBudget(context, budget);
|
|
309
|
+
context.log.flushDeferredTerminal();
|
|
310
|
+
if (budget.spendUsd !== null)
|
|
311
|
+
result = { ...result, spendUsd: budget.spendUsd };
|
|
312
|
+
}
|
|
259
313
|
}
|
|
260
|
-
const cancelled = cancelledResult(context.log, context.runId, context.taskId, context.mode, context.paths.root, result.candidates, undefined, budget.spendUsd, signal, context.store, reconciledCancelFacts);
|
|
261
|
-
context.log.flushDeferredTerminal();
|
|
262
|
-
return {
|
|
263
|
-
...result,
|
|
264
|
-
lifecycle: "cancelled",
|
|
265
|
-
facts: cancelled.facts,
|
|
266
|
-
summary: cancelled.summary,
|
|
267
|
-
spendUsd: cancelled.spendUsd,
|
|
268
|
-
...(cancelled.cancelReason ? { cancelReason: cancelled.cancelReason } : {}),
|
|
269
|
-
};
|
|
270
|
-
}
|
|
271
|
-
const budgetTerminal = delegateBarrierArmed ? (context.budgetTerminal?.() ?? null) : null;
|
|
272
|
-
if (budgetTerminal && result.lifecycle === "succeeded") {
|
|
273
|
-
context.log.clearDeferredTerminal();
|
|
274
|
-
const failed = postDrainBudgetFailure(context, budgetTerminal, budget, result);
|
|
275
|
-
context.log.flushDeferredTerminal();
|
|
276
|
-
return failed;
|
|
277
314
|
}
|
|
278
|
-
if (delegateBarrierArmed)
|
|
279
|
-
reconcileDecisionBudget(context, budget);
|
|
280
|
-
context.log.flushDeferredTerminal();
|
|
281
|
-
if (budget.spendUsd !== null)
|
|
282
|
-
return { ...result, spendUsd: budget.spendUsd };
|
|
283
|
-
}
|
|
284
|
-
return result;
|
|
285
|
-
}
|
|
286
|
-
catch (err) {
|
|
287
|
-
// TS cannot see the closure assignment; the cast is safe (set-once).
|
|
288
|
-
const a = announced;
|
|
289
|
-
if (!a)
|
|
290
|
-
throw err;
|
|
291
|
-
a.log.clearDeferredTerminal();
|
|
292
|
-
let terminalError = err;
|
|
293
|
-
try {
|
|
294
|
-
await runBarrier(a);
|
|
295
315
|
}
|
|
296
|
-
catch (
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
});
|
|
320
|
-
reconcileWorkProductTerminal(a, reconciledCancelFacts);
|
|
321
|
-
clearFailureArtifact(a);
|
|
322
|
-
}
|
|
323
|
-
catch {
|
|
324
|
-
/* the cancellation terminal below remains authoritative */
|
|
316
|
+
catch (err) {
|
|
317
|
+
// TS cannot see the closure assignment; the cast is safe (set-once).
|
|
318
|
+
const a = announced;
|
|
319
|
+
if (!a)
|
|
320
|
+
throw err;
|
|
321
|
+
// The partition journal already owns this terminal. A safety-net emit
|
|
322
|
+
// would hit the poisoned writer, erase the typed recovery signal, and
|
|
323
|
+
// let the daemon persist a false generic failure over durable truth.
|
|
324
|
+
if (isTerminalRecoveryRequired(err))
|
|
325
|
+
throw err;
|
|
326
|
+
if (a.log.terminalCommitted()) {
|
|
327
|
+
const outcome = canonicalOutcome ?? committedOutcome(a);
|
|
328
|
+
if (!outcome)
|
|
329
|
+
throw err;
|
|
330
|
+
const spendUsd = settledSpend(a);
|
|
331
|
+
result = preparedResult
|
|
332
|
+
? {
|
|
333
|
+
...preparedResult,
|
|
334
|
+
lifecycle: outcome.lifecycle,
|
|
335
|
+
facts: outcome,
|
|
336
|
+
...(spendUsd !== null ? { spendUsd } : {}),
|
|
337
|
+
}
|
|
338
|
+
: committedResult(a, outcome, spendUsd);
|
|
325
339
|
}
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
340
|
+
else {
|
|
341
|
+
a.log.clearDeferredTerminal();
|
|
342
|
+
let terminalError = err;
|
|
343
|
+
try {
|
|
344
|
+
await runBarrier(a);
|
|
345
|
+
}
|
|
346
|
+
catch (barrierError) {
|
|
347
|
+
terminalError = barrierError;
|
|
348
|
+
}
|
|
349
|
+
// Settled-spend accounting is part of the terminal contract on EVERY
|
|
350
|
+
// path (the orchestrate executor aggregates it); a broken spend snapshot
|
|
351
|
+
// must not mask the original failure, so it degrades to null loudly-typed.
|
|
352
|
+
const budget = settledBudgetSnapshot(a);
|
|
353
|
+
const spendUsd = budget.spendUsd;
|
|
354
|
+
const delegateBarrierArmed = ownsDelegateDrain(a);
|
|
355
|
+
if (signal?.aborted && terminalError === err) {
|
|
356
|
+
const priorFacts = preparedResult?.facts;
|
|
357
|
+
const cancelFacts = terminalOutcomeFacts(priorFacts, "cancelled", signal.reason === "wall_clock_exceeded" ? "wall_clock_exceeded" : "user_cancelled");
|
|
358
|
+
const cancelSummary = signal.reason === "wall_clock_exceeded"
|
|
359
|
+
? "run cancelled: wall-clock deadline (maxSeconds) exceeded"
|
|
360
|
+
: "run cancelled";
|
|
361
|
+
let reconciledCancelFacts = cancelFacts;
|
|
362
|
+
try {
|
|
363
|
+
if (delegateBarrierArmed)
|
|
364
|
+
reconcileDecisionBudget(a, budget);
|
|
365
|
+
reconciledCancelFacts = reconcileDecisionTerminal(a, {
|
|
366
|
+
facts: cancelFacts,
|
|
367
|
+
why: cancelSummary,
|
|
368
|
+
...(preparedResult ? { preparedFacts: preparedResult.facts } : {}),
|
|
369
|
+
});
|
|
370
|
+
reconcileWorkProductTerminal(a, reconciledCancelFacts);
|
|
371
|
+
clearFailureArtifact(a);
|
|
372
|
+
}
|
|
373
|
+
catch {
|
|
374
|
+
/* the cancellation terminal below remains authoritative */
|
|
375
|
+
}
|
|
376
|
+
const cancelled = cancelledResult(a.log, a.runId, a.taskId, a.mode, a.paths.root, preparedResult?.candidates ?? [], undefined, spendUsd,
|
|
377
|
+
// A wall-clock abort surfaced as a throw must keep its reason and
|
|
378
|
+
// materialize the diagnostic summary, exactly like the checkpoint paths.
|
|
379
|
+
signal, a.store, reconciledCancelFacts);
|
|
380
|
+
a.log.flushDeferredTerminal();
|
|
381
|
+
result = preparedResult
|
|
382
|
+
? {
|
|
383
|
+
...preparedResult,
|
|
384
|
+
lifecycle: "cancelled",
|
|
385
|
+
facts: cancelled.facts,
|
|
386
|
+
summary: cancelled.summary,
|
|
387
|
+
spendUsd: cancelled.spendUsd,
|
|
388
|
+
...(cancelled.cancelReason ? { cancelReason: cancelled.cancelReason } : {}),
|
|
389
|
+
}
|
|
390
|
+
: cancelled;
|
|
391
|
+
}
|
|
392
|
+
else {
|
|
393
|
+
const priorFacts = preparedResult?.facts;
|
|
394
|
+
const pendingFacts = terminalOutcomeFacts(priorFacts, "failed", "harness_failed");
|
|
395
|
+
const pendingSummary = redactSecrets(terminalError instanceof Error ? terminalError.message : String(terminalError));
|
|
396
|
+
let reconciledFailureFacts = pendingFacts;
|
|
397
|
+
try {
|
|
398
|
+
if (delegateBarrierArmed)
|
|
399
|
+
reconcileDecisionBudget(a, budget);
|
|
400
|
+
reconciledFailureFacts = reconcileDecisionTerminal(a, {
|
|
401
|
+
facts: pendingFacts,
|
|
402
|
+
why: pendingSummary,
|
|
403
|
+
...(preparedResult ? { preparedFacts: preparedResult.facts } : {}),
|
|
404
|
+
});
|
|
405
|
+
reconcileWorkProductTerminal(a, reconciledFailureFacts);
|
|
406
|
+
}
|
|
407
|
+
catch {
|
|
408
|
+
/* the failure terminal below remains authoritative */
|
|
409
|
+
}
|
|
410
|
+
const failed = failTerminally(a.log, a.store, a.paths, a.runId, a.taskId, a.mode, barrierFailed ? "delegation_drain" : a.phase, terminalError, spendUsd, { priorFacts: reconciledFailureFacts });
|
|
411
|
+
a.log.flushDeferredTerminal();
|
|
412
|
+
result = preparedResult
|
|
413
|
+
? {
|
|
414
|
+
...preparedResult,
|
|
415
|
+
lifecycle: "failed",
|
|
416
|
+
facts: failed.facts,
|
|
417
|
+
summary: failed.summary,
|
|
418
|
+
spendUsd: failed.spendUsd,
|
|
419
|
+
}
|
|
420
|
+
: failed;
|
|
339
421
|
}
|
|
340
|
-
|
|
422
|
+
}
|
|
341
423
|
}
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
424
|
+
// Defensive fallback for a strategy that returned without a terminal emit:
|
|
425
|
+
// materialize/announce a summary when needed, then pass through the same
|
|
426
|
+
// pre-terminal receipt transaction as every normal strategy.
|
|
427
|
+
const a = announced;
|
|
428
|
+
if (a && !a.log.terminalCommitted()) {
|
|
429
|
+
const events = a.log.readAll().events;
|
|
430
|
+
if (!events.some((event) => event.type === "output.ready")) {
|
|
431
|
+
const summaryPath = join(a.paths.finalDir, "summary.md");
|
|
432
|
+
let summaryPresent = readTextSafe(summaryPath) !== null;
|
|
433
|
+
if (!summaryPresent) {
|
|
434
|
+
try {
|
|
435
|
+
a.store.writeText(summaryPath, `# Run ${a.runId} (${a.mode})\n\n${redactSecrets(result.summary)}\n`);
|
|
436
|
+
summaryPresent = true;
|
|
437
|
+
}
|
|
438
|
+
catch {
|
|
439
|
+
/* never point output.ready at a file that did not materialize */
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
if (summaryPresent) {
|
|
443
|
+
a.log.emit("output.ready", {
|
|
444
|
+
kind: "summary",
|
|
445
|
+
path: "final/summary.md",
|
|
446
|
+
state: "diagnostic",
|
|
447
|
+
});
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
a.log.emit(terminalEventTypeFor("run.completed", result.facts), {
|
|
451
|
+
lifecycle: result.facts.lifecycle,
|
|
452
|
+
facts: result.facts,
|
|
453
|
+
reason: result.facts.reason,
|
|
353
454
|
});
|
|
354
|
-
reconcileWorkProductTerminal(a, reconciledFailureFacts);
|
|
355
455
|
}
|
|
356
|
-
|
|
357
|
-
|
|
456
|
+
// TS cannot see assignments performed inside EventLog's hook closure.
|
|
457
|
+
const settledOutcome = canonicalOutcome;
|
|
458
|
+
if (settledOutcome) {
|
|
459
|
+
result = {
|
|
460
|
+
...result,
|
|
461
|
+
lifecycle: settledOutcome.lifecycle,
|
|
462
|
+
facts: settledOutcome,
|
|
463
|
+
...(terminalPreparationError ? { winner: null, summary: terminalPreparationError } : {}),
|
|
464
|
+
};
|
|
358
465
|
}
|
|
359
|
-
|
|
360
|
-
a.log.flushDeferredTerminal();
|
|
361
|
-
return preparedResult
|
|
362
|
-
? {
|
|
363
|
-
...preparedResult,
|
|
364
|
-
lifecycle: "failed",
|
|
365
|
-
facts: failed.facts,
|
|
366
|
-
summary: failed.summary,
|
|
367
|
-
spendUsd: failed.spendUsd,
|
|
368
|
-
}
|
|
369
|
-
: failed;
|
|
466
|
+
return result;
|
|
370
467
|
}
|
|
371
468
|
finally {
|
|
372
469
|
// Release per-run engine state on EVERY terminal path (normal return, failure
|