@claudexor/orchestrator 4.1.0 → 4.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/account-pool.d.ts +11 -0
- package/dist/account-pool.d.ts.map +1 -1
- package/dist/account-pool.js +9 -0
- package/dist/account-pool.js.map +1 -1
- package/dist/account-resolution.d.ts +3 -0
- package/dist/account-resolution.d.ts.map +1 -1
- package/dist/account-resolution.js +1 -0
- package/dist/account-resolution.js.map +1 -1
- package/dist/attemptTelemetry.d.ts +4 -6
- package/dist/attemptTelemetry.d.ts.map +1 -1
- package/dist/attemptTelemetry.js +1 -1
- package/dist/attemptTelemetry.js.map +1 -1
- package/dist/budgetFailure.d.ts +4 -1
- package/dist/budgetFailure.d.ts.map +1 -1
- package/dist/budgetFailure.js +4 -2
- package/dist/budgetFailure.js.map +1 -1
- package/dist/candidateEvidence.d.ts.map +1 -1
- package/dist/candidateEvidence.js +7 -2
- package/dist/candidateEvidence.js.map +1 -1
- package/dist/candidateOutputs.d.ts.map +1 -1
- package/dist/candidateOutputs.js +6 -0
- package/dist/candidateOutputs.js.map +1 -1
- package/dist/candidateReview.d.ts +3 -1
- package/dist/candidateReview.d.ts.map +1 -1
- package/dist/candidateReview.js +6 -1
- package/dist/candidateReview.js.map +1 -1
- package/dist/candidateTerminals.d.ts +12 -1
- package/dist/candidateTerminals.d.ts.map +1 -1
- package/dist/candidateTerminals.js +51 -21
- package/dist/candidateTerminals.js.map +1 -1
- package/dist/captureRefusal.d.ts +28 -0
- package/dist/captureRefusal.d.ts.map +1 -0
- package/dist/captureRefusal.js +54 -0
- package/dist/captureRefusal.js.map +1 -0
- package/dist/continuity-terminal.d.ts +3 -1
- package/dist/continuity-terminal.d.ts.map +1 -1
- package/dist/continuity-terminal.js.map +1 -1
- package/dist/contract-gates.d.ts +10 -2
- package/dist/contract-gates.d.ts.map +1 -1
- package/dist/contract-gates.js +14 -1
- package/dist/contract-gates.js.map +1 -1
- package/dist/credential-profile-rotation.js +2 -3
- package/dist/credential-profile-rotation.js.map +1 -1
- package/dist/delegationFailure.d.ts +104 -7
- package/dist/delegationFailure.d.ts.map +1 -1
- package/dist/delegationFailure.js +47 -31
- package/dist/delegationFailure.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/inrun-continuity-types.d.ts +13 -1
- package/dist/inrun-continuity-types.d.ts.map +1 -1
- package/dist/inrun-continuity.d.ts +9 -2
- package/dist/inrun-continuity.d.ts.map +1 -1
- package/dist/inrun-continuity.js +43 -23
- package/dist/inrun-continuity.js.map +1 -1
- package/dist/inrun-continuity.test-support.d.ts +6 -2
- package/dist/inrun-continuity.test-support.d.ts.map +1 -1
- package/dist/inrun-continuity.test-support.js +4 -1
- package/dist/inrun-continuity.test-support.js.map +1 -1
- package/dist/orchestrator-credentials.d.ts +5 -4
- package/dist/orchestrator-credentials.d.ts.map +1 -1
- package/dist/orchestrator-credentials.js +7 -4
- package/dist/orchestrator-credentials.js.map +1 -1
- package/dist/orchestrator.d.ts +11 -3
- package/dist/orchestrator.d.ts.map +1 -1
- package/dist/orchestrator.js +341 -414
- package/dist/orchestrator.js.map +1 -1
- package/dist/persistedPatch.js +2 -2
- package/dist/persistedPatch.js.map +1 -1
- package/dist/pool-attempt-evidence.d.ts +62 -0
- package/dist/pool-attempt-evidence.d.ts.map +1 -0
- package/dist/pool-attempt-evidence.js +153 -0
- package/dist/pool-attempt-evidence.js.map +1 -0
- package/dist/pre-progress-refusal.d.ts +43 -6
- package/dist/pre-progress-refusal.d.ts.map +1 -1
- package/dist/pre-progress-refusal.js +53 -8
- package/dist/pre-progress-refusal.js.map +1 -1
- package/dist/readonlyTerminals.d.ts +55 -0
- package/dist/readonlyTerminals.d.ts.map +1 -0
- package/dist/readonlyTerminals.js +205 -0
- package/dist/readonlyTerminals.js.map +1 -0
- package/dist/reviewEvidence.d.ts +4 -0
- package/dist/reviewEvidence.d.ts.map +1 -0
- package/dist/reviewEvidence.js +46 -0
- package/dist/reviewEvidence.js.map +1 -0
- package/dist/rotation-predicate.d.ts +7 -23
- package/dist/rotation-predicate.d.ts.map +1 -1
- package/dist/rotation-predicate.js +45 -0
- package/dist/rotation-predicate.js.map +1 -1
- package/dist/runEventLog.d.ts +1 -0
- package/dist/runEventLog.d.ts.map +1 -1
- package/dist/runEventLog.js +3 -2
- package/dist/runEventLog.js.map +1 -1
- package/dist/runScope.d.ts +86 -0
- package/dist/runScope.d.ts.map +1 -0
- package/dist/runScope.js +181 -0
- package/dist/runScope.js.map +1 -0
- package/dist/runTerminalContext.d.ts +4 -0
- package/dist/runTerminalContext.d.ts.map +1 -1
- package/dist/runTerminalContext.js.map +1 -1
- package/dist/runTerminalHelpers.d.ts +50 -0
- package/dist/runTerminalHelpers.d.ts.map +1 -0
- package/dist/runTerminalHelpers.js +200 -0
- package/dist/runTerminalHelpers.js.map +1 -0
- package/dist/runTerminalResults.d.ts +25 -3
- package/dist/runTerminalResults.d.ts.map +1 -1
- package/dist/runTerminalResults.js +61 -29
- package/dist/runTerminalResults.js.map +1 -1
- package/dist/runTerminals.d.ts +1 -12
- package/dist/runTerminals.d.ts.map +1 -1
- package/dist/runTerminals.js +113 -140
- package/dist/runTerminals.js.map +1 -1
- package/dist/secretDiff.d.ts +14 -11
- package/dist/secretDiff.d.ts.map +1 -1
- package/dist/secretDiff.js +21 -28
- package/dist/secretDiff.js.map +1 -1
- package/dist/terminalOutcome.d.ts +24 -4
- package/dist/terminalOutcome.d.ts.map +1 -1
- package/dist/terminalOutcome.js +32 -5
- package/dist/terminalOutcome.js.map +1 -1
- package/package.json +17 -17
package/dist/orchestrator.js
CHANGED
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
import { applyWorkEnvelope, promptWithPointers as renderPromptWithPointers, threadContextPointer, } from "./prompt-framing.js";
|
|
2
2
|
import { requestRefusalFailure } from "./runTerminalResults.js";
|
|
3
|
+
import { openRunScopeCustody, scopedRouteContext, scopedSpecEnv, } from "./runScope.js";
|
|
3
4
|
import { delegationBeltFor } from "./delegationBelt.js";
|
|
4
5
|
import { bindProcessingAdmission, processingAttemptCostEvidence, processingAdmissionForLease, updateProcessingStreamHold, ProcessingBudgetAdmissionError, } from "./processing-dispatch.js";
|
|
5
6
|
import { cancelledCandidatesResult, emptyCandidateResult, failedCandidatesResult, failAfterCandidates, } from "./candidateTerminals.js";
|
|
6
|
-
import {
|
|
7
|
+
import { deepScanFailureResult, readOnlyChainFailureResult, } from "./readonlyTerminals.js";
|
|
7
8
|
import { effectiveAuthPreference, observeNativeSessionEvent, resumeSessionForProfile, } from "./credential-profiles.js";
|
|
8
9
|
import { AdmissionProfileProbes, OrchestratorCredentials, reviewerProfileResolver, } from "./orchestrator-credentials.js";
|
|
9
10
|
import { continuityWorkspace, resumableOf, resumableTerminal, } from "./continuity-terminal.js";
|
|
10
11
|
import { openContinuity } from "./continue-from.js";
|
|
11
12
|
import { accountPoolRows } from "./account-pool.js";
|
|
13
|
+
import { PoolAttemptEvidence, terminalPoolAttempts, withPoolAttempts, } from "./pool-attempt-evidence.js";
|
|
12
14
|
import { writeRunTelemetryArtifact } from "./runTelemetryWriter.js";
|
|
13
15
|
import { buildFileBackedSynthesisInput, materializeWinnerOutputs, stageFileBackedContext, writeCandidateAttemptArtifacts, } from "./candidateOutputs.js";
|
|
14
16
|
import { processAttemptUsage } from "./attemptUsage.js";
|
|
@@ -16,7 +18,7 @@ import { captureDirectoryCandidate, directoryHasOutput, directoryCandidateStable
|
|
|
16
18
|
import { appliedAttemptFacts, assertDelegatedEvidence, isMutatingAccess, outerBoundaryNotice, scopedHarnessHome, selectLaneHome, followSpecHome, } from "./delegatedHome.js";
|
|
17
19
|
import * as AC from "./attemptUsageCost.js";
|
|
18
20
|
import * as custody from "./continuation-custody.js";
|
|
19
|
-
import { candidateRoster, candidateStatuses, convergenceOutcomeFacts, isWorkingCandidate, partitionCandidates, toCandidateEvidence,
|
|
21
|
+
import { candidateRoster, candidateStatuses, convergenceOutcomeFacts, isWorkingCandidate, partitionCandidates, toCandidateEvidence, } from "./candidateEvidence.js";
|
|
20
22
|
import { capabilityIntents } from "@claudexor/gateway";
|
|
21
23
|
import { reviewCandidateRuns, resolveEngineReview, unreviewedCandidateEvidence, evaluateUnreviewedConvergence, } from "./candidateReview.js";
|
|
22
24
|
import { join } from "node:path";
|
|
@@ -39,7 +41,7 @@ import { resolveRunInputDefaults } from "./run-input-resolution.js";
|
|
|
39
41
|
import { beginAnnouncedRun } from "./runEventLog.js";
|
|
40
42
|
import { arbitrationBudgetOptions } from "./decisionBudget.js";
|
|
41
43
|
import { buildRevisePrompt } from "./revisePrompt.js";
|
|
42
|
-
import { cancelledResult, declaredFailure, failTerminally, guardAnnouncedRun, writeFailure,
|
|
44
|
+
import { cancelledResult, declaredFailure, failTerminally, guardAnnouncedRun, writeFailure, cancelReasonFromSignalToken, } from "./runTerminals.js";
|
|
43
45
|
import { budgetFailureRecord, classifyBudgetFailure } from "./budgetFailure.js";
|
|
44
46
|
import { finalizeStructuredOutput } from "./structuredOutput.js";
|
|
45
47
|
import { admitRun } from "./runAdmission.js";
|
|
@@ -56,18 +58,18 @@ import { createAttemptTelemetry, observeAttemptTelemetry, setAttemptOutcome, tel
|
|
|
56
58
|
import { newAttemptOutputMarkers } from "./attemptOutputMarkers.js";
|
|
57
59
|
import * as delegateFailure from "./delegationFailure.js";
|
|
58
60
|
import * as secretDiff from "./secretDiff.js";
|
|
59
|
-
import {
|
|
60
|
-
import { finalizeAttempt, readOnlyNoSuccessTerminal, unrecoveredToolErrorFailure, webEvidenceFailure, } from "./attemptFinalize.js";
|
|
61
|
+
import { finalizeAttempt, unrecoveredToolErrorFailure, webEvidenceFailure, } from "./attemptFinalize.js";
|
|
61
62
|
import { resolveWorkReportEnvelope, unwrapWorkReportEnvelope, } from "./workReportEnvelope.js";
|
|
62
63
|
import { buildContinuationPacket, decideContinuation, synthesizeContinuationRequest, } from "./continuation.js";
|
|
63
64
|
import { interactionChannelFor } from "./interaction.js";
|
|
64
65
|
import { liveAttempt } from "./live-input.js";
|
|
65
|
-
import { gateSpecsFromContract, renderTestsEvidence } from "./contract-gates.js";
|
|
66
|
+
import { finalVerifyRun, gateSpecsFromContract, renderTestsEvidence } from "./contract-gates.js";
|
|
66
67
|
import { buildTaskContract } from "./task-contract-builder.js";
|
|
68
|
+
import { prepareReviewEvidenceDir, recordReviewEvidenceCleanup } from "./reviewEvidence.js";
|
|
67
69
|
import { ArtifactStore } from "@claudexor/artifact-store";
|
|
68
|
-
import { buildContextPack, rawContextForEnvelope,
|
|
70
|
+
import { buildContextPack, rawContextForEnvelope, writeEvidencePacket } from "@claudexor/context";
|
|
69
71
|
import { WorkspaceManager, captureRawPatchEnvelope, createRevertAnchorFromPatchOrNull, createRevertAnchorOrNull, consumeRawPatchEnvelope, snapshotTree, } from "@claudexor/workspace";
|
|
70
|
-
import { blockedDecisionOverride, finalVerifyBlocks,
|
|
72
|
+
import { blockedDecisionOverride, finalVerifyBlocks, verifyAndDeliverFiles, verifyAndDeliver, } from "@claudexor/delivery";
|
|
71
73
|
import { HarnessGateway } from "@claudexor/gateway";
|
|
72
74
|
import { ReadinessLedger, evaluateConvergence, failureSignature, gatesPassed, reviewCandidate, runGates, } from "@claudexor/review";
|
|
73
75
|
import { arbitrate } from "@claudexor/arbitration";
|
|
@@ -162,39 +164,54 @@ export class Orchestrator {
|
|
|
162
164
|
this.routingRationaleByRun.delete(settledRunId);
|
|
163
165
|
this.deps.delegationBudgetAuthority?.releaseRun(settledRunId);
|
|
164
166
|
};
|
|
167
|
+
// Process custody (owner 1A, #449; `runScope.ts`): the marker is minted and
|
|
168
|
+
// recorded BEFORE the first launch, rides every spec env (`scopedSpecEnv`),
|
|
169
|
+
// and every announced run HOLDS its terminal until the barrier has ended
|
|
170
|
+
// what still carries it. The reap never changes the verdict.
|
|
171
|
+
const custody = await openRunScopeCustody(this.deps.runScopes, runId);
|
|
172
|
+
resolved = { ...resolved, runScopeMarker: custody.marker };
|
|
165
173
|
try {
|
|
166
174
|
return await guardAnnouncedRun(resolved.signal, (announce) => {
|
|
175
|
+
const announced = custody.announced(announce);
|
|
167
176
|
switch (mode) {
|
|
168
177
|
case "ask":
|
|
169
178
|
// `--deep-scan` widens the answer into the bounded multi-scout
|
|
170
179
|
// research sweep with synthesis (the old `audit --swarm`/`explore`).
|
|
171
180
|
return resolved.deepScan
|
|
172
|
-
? this.runDeepScan(resolved,
|
|
173
|
-
: this.runAsk(resolved,
|
|
181
|
+
? this.runDeepScan(resolved, announced)
|
|
182
|
+
: this.runAsk(resolved, announced);
|
|
174
183
|
case "agent":
|
|
175
184
|
// Engine strategies are FLAGS on agent (v0.9 collapse): `--until-clean`
|
|
176
185
|
// and `--attempts` select the convergence loop; `--n` selects the race
|
|
177
186
|
// width; `--create` switches the candidate intent to create_from_scratch.
|
|
178
187
|
if (resolved.untilClean)
|
|
179
|
-
return this.runConvergence(resolved, mode, null,
|
|
188
|
+
return this.runConvergence(resolved, mode, null, announced);
|
|
180
189
|
if (resolved.attempts !== undefined && resolved.attempts !== null) {
|
|
181
|
-
return this.runConvergence(resolved, mode, resolved.attempts,
|
|
190
|
+
return this.runConvergence(resolved, mode, resolved.attempts, announced);
|
|
182
191
|
}
|
|
183
|
-
return this.runRace({ ...resolved, n: resolved.n ?? 1 }, mode,
|
|
192
|
+
return this.runRace({ ...resolved, n: resolved.n ?? 1 }, mode, announced);
|
|
184
193
|
case "plan":
|
|
185
|
-
return this.runPlan(resolved,
|
|
194
|
+
return this.runPlan(resolved, announced);
|
|
186
195
|
}
|
|
187
|
-
},
|
|
196
|
+
},
|
|
197
|
+
// The whole-run barrier: drain delegated children, then end the run's
|
|
198
|
+
// process scope and record its outcome before ANY terminal is flushed
|
|
199
|
+
// (a child-drain rejection keeps its error). Bounded by the reap deadline.
|
|
200
|
+
({ runId }) => custody.barrier(async () => {
|
|
188
201
|
const authority = this.deps.delegationBudgetAuthority;
|
|
189
202
|
if (!authority?.hasParent(runId))
|
|
190
203
|
return;
|
|
191
204
|
authority.beginParentClose(runId);
|
|
192
205
|
await authority.waitForChildren(runId);
|
|
193
|
-
},
|
|
194
|
-
// Single per-run terminalization hook:
|
|
195
|
-
//
|
|
196
|
-
//
|
|
197
|
-
|
|
206
|
+
}),
|
|
207
|
+
// Single per-run terminalization hook: the custody fallback for a path
|
|
208
|
+
// that never reached the barrier, then release per-run engine state
|
|
209
|
+
// (the delegated-child drain authority is released only AFTER
|
|
210
|
+
// settlement) on EVERY terminal.
|
|
211
|
+
async (settledRunId) => {
|
|
212
|
+
await custody.record();
|
|
213
|
+
releaseRunState(settledRunId);
|
|
214
|
+
});
|
|
198
215
|
}
|
|
199
216
|
catch (error) {
|
|
200
217
|
// A durable startup sink may refuse `run.created` before the strategy can
|
|
@@ -206,6 +223,9 @@ export class Orchestrator {
|
|
|
206
223
|
}
|
|
207
224
|
throw error;
|
|
208
225
|
}
|
|
226
|
+
finally {
|
|
227
|
+
await custody.settleUnannounced();
|
|
228
|
+
}
|
|
209
229
|
}
|
|
210
230
|
async resolveReviewers(cwd, runAuthPreference, onIgnoredSetting) {
|
|
211
231
|
if (this.deps.reviewers)
|
|
@@ -1217,7 +1237,7 @@ export class Orchestrator {
|
|
|
1217
1237
|
adapter: this.deps.registry.get(harnessId),
|
|
1218
1238
|
credentialProfile: sessionFields.credential_profile,
|
|
1219
1239
|
authPreference: sessionFields.auth_preference ?? "auto",
|
|
1220
|
-
laneEnv: this.laneHomeEnvFor(runInput, harnessId, profileId) ?? {},
|
|
1240
|
+
laneEnv: scopedSpecEnv(runInput.runScopeMarker, this.laneHomeEnvFor(runInput, harnessId, profileId) ?? {}) ?? {},
|
|
1221
1241
|
envInheritance: envInheritance(this.config(runInput.repoRoot)),
|
|
1222
1242
|
signal: runInput.signal,
|
|
1223
1243
|
processing,
|
|
@@ -1293,7 +1313,10 @@ export class Orchestrator {
|
|
|
1293
1313
|
* must record what it ran under, not just why it stopped. REQUIRED (no
|
|
1294
1314
|
* default): a silent fallback here would spawn a delegated attempt on the
|
|
1295
1315
|
* operator's real home while the record still claimed scoped state. */
|
|
1296
|
-
harnessHome, observedPaths = new Set()
|
|
1316
|
+
harnessHome, observedPaths = new Set(),
|
|
1317
|
+
/** The caller's, like `harnessHome`: its catch keeps the tries' accounts.
|
|
1318
|
+
* REQUIRED, so no caller can drop them by omission again. */
|
|
1319
|
+
poolEvidence) {
|
|
1297
1320
|
const adapter = routed.adapter;
|
|
1298
1321
|
const knobs = this.routeSpecKnobs(routed, contract, modelHint, effortHint);
|
|
1299
1322
|
// Isolated scoped-home sessions are never retained after disposal.
|
|
@@ -1328,6 +1351,9 @@ export class Orchestrator {
|
|
|
1328
1351
|
if (routed.browserRequirement.effective && artifactRelativeDir === null) {
|
|
1329
1352
|
throw new Error("browser artifact ownership marker was not persisted");
|
|
1330
1353
|
}
|
|
1354
|
+
// The run's scope marker rides every spec env (plus the host facts when the
|
|
1355
|
+
// HOME is scoped), added AFTER the lane/profile home so that patch is kept.
|
|
1356
|
+
const scopedEnv = scopedSpecEnv(runInput?.runScopeMarker, harnessHome.env);
|
|
1331
1357
|
let spec = HarnessRunSpec.parse({
|
|
1332
1358
|
session_id: newId("ses"),
|
|
1333
1359
|
intent,
|
|
@@ -1359,7 +1385,7 @@ export class Orchestrator {
|
|
|
1359
1385
|
...(inPlaceEnvelope && sessionFields?.resume_session_id
|
|
1360
1386
|
? { resume_session_id: sessionFields.resume_session_id }
|
|
1361
1387
|
: {}),
|
|
1362
|
-
...(
|
|
1388
|
+
...(scopedEnv ? { env: scopedEnv } : {}),
|
|
1363
1389
|
raw_context_packet: rawContextPacket,
|
|
1364
1390
|
stream_deltas: streamDeltas,
|
|
1365
1391
|
});
|
|
@@ -1396,6 +1422,7 @@ export class Orchestrator {
|
|
|
1396
1422
|
let beltFailureDisclosed = false;
|
|
1397
1423
|
const errors = [];
|
|
1398
1424
|
let answer = new secretDiff.CountedAnswerAssembly();
|
|
1425
|
+
let currentCapture;
|
|
1399
1426
|
const retryPolicy = transientRetryPolicy(this.config(contract.repo.root));
|
|
1400
1427
|
// QA-024: the delegation belt is the ONLY engine-owned extra MCP server
|
|
1401
1428
|
// injected into an agent lane (the browser MCP rides its own field), so its
|
|
@@ -1422,9 +1449,11 @@ export class Orchestrator {
|
|
|
1422
1449
|
harnessHome,
|
|
1423
1450
|
workOrder: prompt,
|
|
1424
1451
|
firstPrompt: spec.prompt,
|
|
1452
|
+
poolEvidence,
|
|
1425
1453
|
}), { spec, continuation: runInput?.continuation, store, telemetry }));
|
|
1426
1454
|
try {
|
|
1427
1455
|
for (let nativeTry = 0; !signal?.aborted; nativeTry += 1) {
|
|
1456
|
+
currentCapture = undefined;
|
|
1428
1457
|
// A3 per-try isolation: neither output nor progress markers leak across tries.
|
|
1429
1458
|
if (nativeTry > 0) {
|
|
1430
1459
|
answer = new secretDiff.CountedAnswerAssembly();
|
|
@@ -1440,9 +1469,8 @@ export class Orchestrator {
|
|
|
1440
1469
|
? AbortSignal.any([signal, attemptAbort.signal])
|
|
1441
1470
|
: attemptAbort.signal;
|
|
1442
1471
|
activeSessionId = runSpec.session_id;
|
|
1443
|
-
continuity.beginTry(runSpec, nativeTry, attemptAbort);
|
|
1444
1472
|
// #363: bound before the spawn — a credential change mid-try voids it.
|
|
1445
|
-
const refusal =
|
|
1473
|
+
const refusal = continuity.beginTry(runSpec, nativeTry, attemptAbort);
|
|
1446
1474
|
const transientStart = telemetry.transientFailures.length;
|
|
1447
1475
|
const rateLimitStart = telemetry.rateLimits.length;
|
|
1448
1476
|
let rawPatch = null;
|
|
@@ -1566,30 +1594,42 @@ export class Orchestrator {
|
|
|
1566
1594
|
// #31: the centralized retry gate reads the classified `retryable` verdict.
|
|
1567
1595
|
const sawRetryable = newTransients.some((f) => f.retryable);
|
|
1568
1596
|
const sawTypedLimit = telemetry.rateLimits.length > rateLimitStart;
|
|
1597
|
+
const tryFacts = {
|
|
1598
|
+
harnessErrored,
|
|
1599
|
+
originalError: errors[0],
|
|
1600
|
+
budgetStopped: streamBudgetDenied,
|
|
1601
|
+
aborted: signal?.aborted === true,
|
|
1602
|
+
requestRefused: Boolean(telemetry.requestRefusal),
|
|
1603
|
+
newTransients,
|
|
1604
|
+
markers: telemetry.outputMarkers,
|
|
1605
|
+
};
|
|
1606
|
+
// Its account row first: an infrastructure failure below never erases it.
|
|
1607
|
+
continuity.settleTry(tryFacts);
|
|
1569
1608
|
if (directory)
|
|
1570
1609
|
await captureDirectory();
|
|
1571
|
-
|
|
1610
|
+
currentCapture = directory
|
|
1611
|
+
? undefined
|
|
1612
|
+
: await secretDiff.captureCandidateDiff(wsm, envelope);
|
|
1613
|
+
const currentDiff = secretDiff.capturedDiff(currentCapture);
|
|
1572
1614
|
const workspaceUnchanged = directory
|
|
1573
1615
|
? directoryCapture.files?.noChanges === true
|
|
1574
|
-
: currentDiff
|
|
1616
|
+
: currentDiff?.trim().length === 0;
|
|
1575
1617
|
const deliverableEmpty = workspaceUnchanged && answer.text().length === 0;
|
|
1576
1618
|
const delivered = !harnessErrored && !deliverableEmpty && !signal?.aborted;
|
|
1577
1619
|
this.credentials.noteTryServed(refusal, telemetry.outputMarkers, delivered);
|
|
1578
1620
|
const verdict = await continuity.afterTry({
|
|
1621
|
+
...tryFacts,
|
|
1622
|
+
// A cancel or deadline that landed during capture still stops the
|
|
1623
|
+
// attempt; the try's own facts above stay its evidence.
|
|
1624
|
+
stopped: signal?.aborted === true,
|
|
1579
1625
|
runSpec,
|
|
1580
1626
|
nativeTry,
|
|
1581
|
-
harnessErrored,
|
|
1582
|
-
budgetStopped: streamBudgetDenied,
|
|
1583
|
-
aborted: signal?.aborted === true,
|
|
1584
|
-
requestRefused: Boolean(telemetry.requestRefusal),
|
|
1585
|
-
newTransients,
|
|
1586
1627
|
sawTypedLimit,
|
|
1587
1628
|
sawRetryable,
|
|
1588
1629
|
answer,
|
|
1589
1630
|
rawDeliverableEmpty: deliverableEmpty,
|
|
1590
1631
|
workspaceDiffNonEmpty: !workspaceUnchanged,
|
|
1591
|
-
currentDiff,
|
|
1592
|
-
markers: telemetry.outputMarkers,
|
|
1632
|
+
...(currentDiff === undefined ? {} : { currentDiff }),
|
|
1593
1633
|
lastLimit: telemetry.rateLimits.at(-1) ?? null,
|
|
1594
1634
|
refusal,
|
|
1595
1635
|
telemetry,
|
|
@@ -1613,8 +1653,13 @@ export class Orchestrator {
|
|
|
1613
1653
|
signal?.removeEventListener("abort", onAbort);
|
|
1614
1654
|
}
|
|
1615
1655
|
// A pool-exhausted terminal is rotation's verdict, not the transient
|
|
1616
|
-
// machinery's
|
|
1617
|
-
|
|
1656
|
+
// machinery's, and a stopped run exhausted no retries: no
|
|
1657
|
+
// `route.transient.exhausted` rides along with either.
|
|
1658
|
+
if (harnessErrored &&
|
|
1659
|
+
!poolExhausted &&
|
|
1660
|
+
!processingRefusal &&
|
|
1661
|
+
!telemetry.requestRefusal &&
|
|
1662
|
+
!signal?.aborted) {
|
|
1618
1663
|
emitTransientExhausted((t, p) => log?.emit(t, p), adapter.id, attemptId, telemetry, retryPolicy.maxRetries);
|
|
1619
1664
|
}
|
|
1620
1665
|
const attemptStreamEndedMs = Date.now();
|
|
@@ -1634,9 +1679,10 @@ export class Orchestrator {
|
|
|
1634
1679
|
: await secretDiff.captureCandidateWorkspace({
|
|
1635
1680
|
...{ wsm, envelope, inPlace: inPlaceEnvelope, projectRoot: store.repoRoot },
|
|
1636
1681
|
...{ answerText, answerMatches: answer.secretLikeMatches() },
|
|
1682
|
+
holder: { runId: paths.runId, runDir: paths.root },
|
|
1683
|
+
incompleteCapture: currentCapture?.captureIncomplete ? currentCapture : undefined,
|
|
1637
1684
|
});
|
|
1638
1685
|
const { diff, captureRefusal } = captured;
|
|
1639
|
-
harnessErrored = secretDiff.recordCaptureRefusal(captureRefusal, errors, harnessErrored);
|
|
1640
1686
|
const deliverableEvidence = diff.trim().length > 0 || Boolean(answerText) || directoryHasOutput(directoryCapture.files);
|
|
1641
1687
|
// Cancelled attempts skip gates: running a 600s-per-gate suite delays the ack
|
|
1642
1688
|
// and burns compute on a result nobody will adopt. Diff/attempt.yaml
|
|
@@ -1650,7 +1696,7 @@ export class Orchestrator {
|
|
|
1650
1696
|
}
|
|
1651
1697
|
const gates = gateSignalAborted
|
|
1652
1698
|
? []
|
|
1653
|
-
: await runGates(gateSpecsFromContract(contract), {
|
|
1699
|
+
: await runGates(gateSpecsFromContract(contract, runInput?.runScopeMarker), {
|
|
1654
1700
|
cwd: envelope.worktree_path,
|
|
1655
1701
|
env: wsm.envFor(envelope),
|
|
1656
1702
|
signal,
|
|
@@ -1695,7 +1741,9 @@ export class Orchestrator {
|
|
|
1695
1741
|
if (finalized.outcomeClass === "contract_failure" && unwrapped.contractViolation)
|
|
1696
1742
|
errors.push(`work_report contract: ${unwrapped.contractViolation}`);
|
|
1697
1743
|
const deliverablePresent = finalized.deliverablePresent;
|
|
1698
|
-
|
|
1744
|
+
if (captureRefusal)
|
|
1745
|
+
errors.push(captureRefusal.detail);
|
|
1746
|
+
const errored = harnessErrored || webBlocked || captureRefusal !== undefined;
|
|
1699
1747
|
const resumable = await continuity.finish({
|
|
1700
1748
|
runSpec: spec,
|
|
1701
1749
|
errored,
|
|
@@ -1705,6 +1753,7 @@ export class Orchestrator {
|
|
|
1705
1753
|
workState: finalized.workState,
|
|
1706
1754
|
});
|
|
1707
1755
|
setAttemptOutcome(telemetry, {
|
|
1756
|
+
captureFailed: captureRefusal !== undefined,
|
|
1708
1757
|
deliverablePresent,
|
|
1709
1758
|
gatesPassed: gates.length > 0 ? gatesPassed(gates) : null,
|
|
1710
1759
|
harnessErrored,
|
|
@@ -1818,9 +1867,11 @@ export class Orchestrator {
|
|
|
1818
1867
|
probeReadyProfiles: (spec, tried) => runInput
|
|
1819
1868
|
? this.credentials.readyProfileIdsForRotation(runInput, adapter.id, spec.credential_profile ?? null, tried, spec.model_hint ?? null)
|
|
1820
1869
|
: Promise.resolve(new Set()),
|
|
1870
|
+
bindRefusal: (spec, producer) => this.credentials.bindPreProgressRefusal(adapter.id, spec, producer),
|
|
1821
1871
|
rotationObservations: (spec, transients, refusal) => this.credentials.rotationObservations(adapter, spec, transients, refusal),
|
|
1822
1872
|
emit: (type, payload) => log?.emit(type, payload),
|
|
1823
1873
|
route: routed,
|
|
1874
|
+
poolEvidence: lane.poolEvidence,
|
|
1824
1875
|
newSessionId: () => newId("ses"),
|
|
1825
1876
|
thread: runInput?.threadId
|
|
1826
1877
|
? {
|
|
@@ -1842,7 +1893,7 @@ export class Orchestrator {
|
|
|
1842
1893
|
const mutatingRun = isMutatingAccess(contract.access.effective_profile);
|
|
1843
1894
|
const planBrief = verifiedPlanBrief(input);
|
|
1844
1895
|
const quotaSnapshots = this.quotaSnapshotPreflight();
|
|
1845
|
-
const { store, paths, log, ledger } = beginAnnouncedRun({
|
|
1896
|
+
const { store, paths, log, ledger, runContext } = beginAnnouncedRun({
|
|
1846
1897
|
input,
|
|
1847
1898
|
contract,
|
|
1848
1899
|
quotaSnapshots,
|
|
@@ -1998,6 +2049,7 @@ export class Orchestrator {
|
|
|
1998
2049
|
});
|
|
1999
2050
|
}
|
|
2000
2051
|
const runsBySlot = new Array(slots.length);
|
|
2052
|
+
runContext.attempts = () => runsBySlot.filter((run) => run !== undefined);
|
|
2001
2053
|
// D-16d: one-shot continuation budget shared across the concurrent candidate
|
|
2002
2054
|
// slots (parity with the read-only chain's single counter). Claimed
|
|
2003
2055
|
// synchronously (check-then-increment with no await between), so two slots
|
|
@@ -2050,8 +2102,9 @@ export class Orchestrator {
|
|
|
2050
2102
|
const effectiveWeb = this.discloseWebUpgrade(log, slot.routed, knobs.webPolicy, slot.attemptId);
|
|
2051
2103
|
let envelope;
|
|
2052
2104
|
// Declared OUTSIDE the try so the catch below can state what the attempt
|
|
2053
|
-
// actually ran under, not merely that it stopped.
|
|
2105
|
+
// actually ran under, not merely that it stopped — and on which accounts.
|
|
2054
2106
|
let harnessHome;
|
|
2107
|
+
const tried = new PoolAttemptEvidence(adapter.id, slot.attemptId);
|
|
2055
2108
|
try {
|
|
2056
2109
|
log.emit("harness.started", {
|
|
2057
2110
|
harness_id: adapter.id,
|
|
@@ -2088,7 +2141,7 @@ export class Orchestrator {
|
|
|
2088
2141
|
budgetStopped = true;
|
|
2089
2142
|
return true;
|
|
2090
2143
|
}, input, requestedSingleCandidate, // W-C4 deltas: single-candidate chat lane only (racing = noise x N)
|
|
2091
|
-
undefined, undefined, harnessHome);
|
|
2144
|
+
undefined, undefined, harnessHome, undefined, tried);
|
|
2092
2145
|
ledger.settle(slot.lease.lease_id, attemptUsageCostSettlement(run.cost, run.costEstimated, run.attemptId, run.harnessId, run.telemetry.authMode, run.telemetry.usageCost));
|
|
2093
2146
|
log.emit("harness.completed", {
|
|
2094
2147
|
harness_id: adapter.id,
|
|
@@ -2153,6 +2206,7 @@ export class Orchestrator {
|
|
|
2153
2206
|
continuation_of: run.attemptId,
|
|
2154
2207
|
});
|
|
2155
2208
|
const contLeaseId = contLease.lease?.lease_id ?? "";
|
|
2209
|
+
const contTried = new PoolAttemptEvidence(adapter.id, contAttemptId);
|
|
2156
2210
|
try {
|
|
2157
2211
|
const contRun = await this.runCandidateInEnvelope(slot.routed, envelope, contAttemptId, slot.label, contract, input.prompt, store, paths, wsm, ledger, {
|
|
2158
2212
|
lease: contLease.lease,
|
|
@@ -2166,7 +2220,7 @@ export class Orchestrator {
|
|
|
2166
2220
|
return false;
|
|
2167
2221
|
budgetStopped = true;
|
|
2168
2222
|
return true;
|
|
2169
|
-
}, input, requestedSingleCandidate, undefined, packet.pointerLine ?? undefined, harnessHome);
|
|
2223
|
+
}, input, requestedSingleCandidate, undefined, packet.pointerLine ?? undefined, harnessHome, undefined, contTried);
|
|
2170
2224
|
ledger.settle(contLeaseId, attemptUsageCostSettlement(contRun.cost, contRun.costEstimated, contRun.attemptId, contRun.harnessId, contRun.telemetry.authMode, contRun.telemetry.usageCost));
|
|
2171
2225
|
log.emit("harness.completed", {
|
|
2172
2226
|
harness_id: adapter.id,
|
|
@@ -2193,6 +2247,8 @@ export class Orchestrator {
|
|
|
2193
2247
|
error: safeErrorMessage(err),
|
|
2194
2248
|
});
|
|
2195
2249
|
}
|
|
2250
|
+
// Whichever attempt speaks names both attempts' failed tries, in order.
|
|
2251
|
+
effectiveRun.poolAttempts = [...(run.poolAttempts ?? []), ...contTried.snapshot()];
|
|
2196
2252
|
}
|
|
2197
2253
|
else {
|
|
2198
2254
|
ledger.cancel(contLease.lease?.lease_id ?? "");
|
|
@@ -2238,6 +2294,7 @@ export class Orchestrator {
|
|
|
2238
2294
|
slot.routed.delegationRequirement,
|
|
2239
2295
|
], knobs.model),
|
|
2240
2296
|
infraPhase,
|
|
2297
|
+
poolAttempts: tried.snapshot(),
|
|
2241
2298
|
// Keep a TYPED pre-spawn refusal (spent quota window + its reset)
|
|
2242
2299
|
// alive past this catch; `message` alone would force the terminal to
|
|
2243
2300
|
// read prose back out.
|
|
@@ -2252,10 +2309,17 @@ export class Orchestrator {
|
|
|
2252
2309
|
};
|
|
2253
2310
|
await runParallelCandidates(slots, this.deps.runtimeConcurrencyCaps, runSlot);
|
|
2254
2311
|
const runs = runsBySlot.filter((r) => r !== undefined);
|
|
2312
|
+
// From here `runs` is the lane's one attempt collection (a synthesis that
|
|
2313
|
+
// returns joins it); a synthesis that threw first leaves only its tries.
|
|
2314
|
+
let thrownSynthesis = null;
|
|
2315
|
+
const history = () => thrownSynthesis ? [...runs, thrownSynthesis] : runs;
|
|
2316
|
+
runContext.attempts = history;
|
|
2255
2317
|
// Fail-closed terminal: a delegated mutating run whose attempts state
|
|
2256
2318
|
// neither historical proof nor deliberate absence refuses instead of passing.
|
|
2257
2319
|
assertDelegatedEvidence(input.delegated === true, candidateAccess, runs);
|
|
2258
|
-
|
|
2320
|
+
// One candidate-terminal context for every race terminal below; its
|
|
2321
|
+
// telemetry writer reads the live roster, so a later synth attempt counts.
|
|
2322
|
+
const raceTerminal = {
|
|
2259
2323
|
store,
|
|
2260
2324
|
paths,
|
|
2261
2325
|
log,
|
|
@@ -2264,6 +2328,11 @@ export class Orchestrator {
|
|
|
2264
2328
|
mode,
|
|
2265
2329
|
ledger,
|
|
2266
2330
|
runs,
|
|
2331
|
+
writeTelemetry: () => this.writeRunTelemetry(store, paths, contract, runId, taskId, mode, candidateRoster(runs), null),
|
|
2332
|
+
};
|
|
2333
|
+
const cancelledRaceResult = () => cancelledCandidatesResult({
|
|
2334
|
+
...raceTerminal,
|
|
2335
|
+
attempts: history(),
|
|
2267
2336
|
git: input.workspaceKind !== "directory"
|
|
2268
2337
|
? {
|
|
2269
2338
|
live: input.inPlace === true &&
|
|
@@ -2275,7 +2344,6 @@ export class Orchestrator {
|
|
|
2275
2344
|
}
|
|
2276
2345
|
: undefined,
|
|
2277
2346
|
signal: input.signal,
|
|
2278
|
-
writeTelemetry: () => this.writeRunTelemetry(store, paths, contract, runId, taskId, mode, candidateRoster(runs), null),
|
|
2279
2347
|
});
|
|
2280
2348
|
// Revert divergence fence for the single-candidate in-place path: the
|
|
2281
2349
|
// candidate mutated the LIVE tree during execution above, so the post-turn
|
|
@@ -2302,31 +2370,34 @@ export class Orchestrator {
|
|
|
2302
2370
|
}
|
|
2303
2371
|
const failedDelegation = delegateFailure.dominantRaceCandidateFailure(runs);
|
|
2304
2372
|
if (failedDelegation) {
|
|
2305
|
-
const failure = delegateFailure.candidateFailureTerminal(failedDelegation, "race");
|
|
2306
2373
|
await disposeReviewEnvelopes();
|
|
2307
|
-
|
|
2308
|
-
|
|
2309
|
-
|
|
2310
|
-
|
|
2311
|
-
|
|
2312
|
-
|
|
2313
|
-
|
|
2314
|
-
|
|
2315
|
-
|
|
2316
|
-
|
|
2317
|
-
|
|
2318
|
-
|
|
2319
|
-
|
|
2320
|
-
|
|
2321
|
-
|
|
2322
|
-
|
|
2323
|
-
|
|
2324
|
-
|
|
2325
|
-
|
|
2374
|
+
const failedOutput = {
|
|
2375
|
+
...{ store, log, paths, execRoot, preTurnSha, taskId, mode, run: failedDelegation },
|
|
2376
|
+
live: input.inPlace === true &&
|
|
2377
|
+
input.workspaceKind !== "directory" &&
|
|
2378
|
+
failedDelegation.reviewCwd === execRoot,
|
|
2379
|
+
postTurnSha: earlyPostTurnSha,
|
|
2380
|
+
kind: input.create === true ? "new_repo" : "patch",
|
|
2381
|
+
};
|
|
2382
|
+
// A live candidate whose capture failed (no Delegate failure) ends in the
|
|
2383
|
+
// shared no-working-candidate terminal, as in the isolated race and
|
|
2384
|
+
// convergence: a recorded budget denial, a budget terminal the ledger
|
|
2385
|
+
// settled (stream hard cap, exact overshoot, unverifiable cash) or a
|
|
2386
|
+
// unanimous typed refusal leads, its live receipt carries the same facts,
|
|
2387
|
+
// and the capture facts and inspection actions follow. Any other dominant
|
|
2388
|
+
// capture failure joins it only under a settled budget terminal.
|
|
2389
|
+
if (delegateFailure.candidateFailureKind(failedDelegation) === "capture" &&
|
|
2390
|
+
(failedOutput.live || ledger.terminal() !== null))
|
|
2391
|
+
return failedCandidatesResult({
|
|
2392
|
+
...raceTerminal,
|
|
2393
|
+
budgetDenial,
|
|
2394
|
+
...(mutatingRun ? { liveCapture: failedOutput } : {}),
|
|
2326
2395
|
});
|
|
2327
|
-
|
|
2328
|
-
|
|
2329
|
-
|
|
2396
|
+
const failure = delegateFailure.candidateFailureTerminal(failedDelegation, "race");
|
|
2397
|
+
if (mutatingRun)
|
|
2398
|
+
await delegateFailure.persistFailedCandidateOutput(failedOutput);
|
|
2399
|
+
raceTerminal.writeTelemetry();
|
|
2400
|
+
return failTerminally(log, store, paths, runId, taskId, mode, failure.phase, failure.error, ledger.spend(), { ...failure.metadata, attempts: runs });
|
|
2330
2401
|
}
|
|
2331
2402
|
if (runs.length === 0)
|
|
2332
2403
|
return emptyCandidateResult({
|
|
@@ -2348,18 +2419,7 @@ export class Orchestrator {
|
|
|
2348
2419
|
const workingRuns = partitionCandidates(runs).working;
|
|
2349
2420
|
if (workingRuns.length === 0) {
|
|
2350
2421
|
await disposeReviewEnvelopes();
|
|
2351
|
-
return failedCandidatesResult({
|
|
2352
|
-
store,
|
|
2353
|
-
paths,
|
|
2354
|
-
log,
|
|
2355
|
-
runId,
|
|
2356
|
-
taskId,
|
|
2357
|
-
mode,
|
|
2358
|
-
ledger,
|
|
2359
|
-
runs,
|
|
2360
|
-
budgetDenial,
|
|
2361
|
-
writeTelemetry: () => this.writeRunTelemetry(store, paths, contract, runId, taskId, mode, candidateRoster(runs), null),
|
|
2362
|
-
});
|
|
2422
|
+
return failedCandidatesResult({ ...raceTerminal, budgetDenial });
|
|
2363
2423
|
}
|
|
2364
2424
|
// QA-025: only announce that review STARTED when the panel will actually
|
|
2365
2425
|
// run. A candidate that changed no files is skipped inside reviewRuns; a
|
|
@@ -2396,12 +2456,12 @@ export class Orchestrator {
|
|
|
2396
2456
|
// candidates (a trivial greeting in agent mode no longer burns two reviewers on
|
|
2397
2457
|
// "(empty diff)"). Candidates still flow through arbitration/gates so the
|
|
2398
2458
|
// no_op/answer outcome and gate failures are unchanged.
|
|
2399
|
-
evidences = await this.reviewRuns(workingRuns, reviewers, reviewVerified, reviewDir, input.repoRoot, contract, store, paths, log, ledger, taskId, input.signal, this.reservationEstimateUsd(input));
|
|
2459
|
+
evidences = await this.reviewRuns(workingRuns, reviewers, reviewVerified, reviewDir, input.repoRoot, contract, store, paths, log, ledger, taskId, input.signal, this.reservationEstimateUsd(input), false, input.runScopeMarker);
|
|
2400
2460
|
}
|
|
2401
2461
|
catch (err) {
|
|
2402
2462
|
// Review preflight/evidence failures end TERMINALLY with artifacts —
|
|
2403
2463
|
// never as an escaped throw that orphans the run dir.
|
|
2404
|
-
return failAfterCandidates(
|
|
2464
|
+
return failAfterCandidates(raceTerminal, "review", err);
|
|
2405
2465
|
}
|
|
2406
2466
|
finally {
|
|
2407
2467
|
// Review preflight failures must not leak candidate worktrees.
|
|
@@ -2431,6 +2491,7 @@ export class Orchestrator {
|
|
|
2431
2491
|
});
|
|
2432
2492
|
if (lease.granted) {
|
|
2433
2493
|
let envelope;
|
|
2494
|
+
const synthTried = new PoolAttemptEvidence(synthRouted.adapter.id, "synth");
|
|
2434
2495
|
try {
|
|
2435
2496
|
const plan = buildSynthesisPlan(evidences);
|
|
2436
2497
|
const synthesisInput = buildFileBackedSynthesisInput({
|
|
@@ -2459,7 +2520,7 @@ export class Orchestrator {
|
|
|
2459
2520
|
budgetStopped = true;
|
|
2460
2521
|
budgetDenial ??= denial;
|
|
2461
2522
|
},
|
|
2462
|
-
}, candidateAccess, input.onHarnessEvent, input.signal, undefined, input.effort, "synthesize", log, effectiveWeb, interactionChannelFor(input, log, runId, taskId, "synth", synthAdapter.id, synthRouted.supportsInteractive), undefined, input, false, synthesisInput.content, undefined, synthHome);
|
|
2523
|
+
}, candidateAccess, input.onHarnessEvent, input.signal, undefined, input.effort, "synthesize", log, effectiveWeb, interactionChannelFor(input, log, runId, taskId, "synth", synthAdapter.id, synthRouted.supportsInteractive), undefined, input, false, synthesisInput.content, undefined, synthHome, undefined, synthTried);
|
|
2463
2524
|
ledger.settle(lease.lease?.lease_id ?? "", attemptUsageCostSettlement(run.cost, run.costEstimated, run.attemptId, run.harnessId, run.telemetry.authMode, run.telemetry.usageCost));
|
|
2464
2525
|
reviewEnvelopes.push(envelope);
|
|
2465
2526
|
envelope = undefined;
|
|
@@ -2468,12 +2529,12 @@ export class Orchestrator {
|
|
|
2468
2529
|
runs.push(run);
|
|
2469
2530
|
try {
|
|
2470
2531
|
if (isWorkingCandidate(run)) {
|
|
2471
|
-
const synthEvidence = await this.reviewRuns([run], reviewers, reviewVerified, reviewDir, input.repoRoot, contract, store, paths, log, ledger, taskId, input.signal, this.reservationEstimateUsd(input));
|
|
2532
|
+
const synthEvidence = await this.reviewRuns([run], reviewers, reviewVerified, reviewDir, input.repoRoot, contract, store, paths, log, ledger, taskId, input.signal, this.reservationEstimateUsd(input), false, input.runScopeMarker);
|
|
2472
2533
|
evidences.push(...synthEvidence);
|
|
2473
2534
|
workingRuns.push(run);
|
|
2474
2535
|
}
|
|
2475
2536
|
if (input.signal?.aborted) {
|
|
2476
|
-
return cancelledResult(log, runId, taskId, mode, paths.root, candidateStatuses(runs),
|
|
2537
|
+
return cancelledResult(log, runId, taskId, mode, paths.root, candidateStatuses(runs), raceTerminal.writeTelemetry, ledger.spend(), input.signal, store, undefined, undefined, history());
|
|
2477
2538
|
}
|
|
2478
2539
|
}
|
|
2479
2540
|
finally {
|
|
@@ -2481,6 +2542,9 @@ export class Orchestrator {
|
|
|
2481
2542
|
}
|
|
2482
2543
|
}
|
|
2483
2544
|
catch (err) {
|
|
2545
|
+
// Not yet in `runs`: the run goes on, so its tries' accounts stay in the history.
|
|
2546
|
+
if (!runs.some((run) => run.attemptId === "synth"))
|
|
2547
|
+
thrownSynthesis = { poolAttempts: synthTried.snapshot() };
|
|
2484
2548
|
ledger.settle(lease.lease?.lease_id ?? "", AC.attemptFailureCost(err, "synthesis-error").settlement);
|
|
2485
2549
|
log.emit("harness.completed", {
|
|
2486
2550
|
attempt_id: "synth",
|
|
@@ -2506,7 +2570,7 @@ export class Orchestrator {
|
|
|
2506
2570
|
}
|
|
2507
2571
|
catch (err) {
|
|
2508
2572
|
// Arbitration throws end terminally with artifacts, never as an orphan.
|
|
2509
|
-
return failAfterCandidates({
|
|
2573
|
+
return failAfterCandidates({ ...raceTerminal, attempts: history() }, "arbitration", err);
|
|
2510
2574
|
}
|
|
2511
2575
|
log.emit("arbitration.completed", {
|
|
2512
2576
|
winner: result.decision.winner,
|
|
@@ -2570,9 +2634,7 @@ export class Orchestrator {
|
|
|
2570
2634
|
facts.lifecycle === "succeeded" &&
|
|
2571
2635
|
facts.review !== "blocked" &&
|
|
2572
2636
|
!input.signal?.aborted) {
|
|
2573
|
-
finalVerify = winnerRun
|
|
2574
|
-
? await finalVerifyFiles(winnerRun.files, undefined, gateSpecsFromContract(contract), log)
|
|
2575
|
-
: await finalVerifyPatch(execRoot, winnerRun, gateSpecsFromContract(contract), log);
|
|
2637
|
+
finalVerify = await finalVerifyRun(execRoot, winnerRun, contract, log, input);
|
|
2576
2638
|
finalVerifyFailed = finalVerifyBlocks(finalVerify);
|
|
2577
2639
|
if (finalVerifyFailed)
|
|
2578
2640
|
facts = { ...facts, checks: "failed", reason: "checks_failed" };
|
|
@@ -2588,6 +2650,9 @@ export class Orchestrator {
|
|
|
2588
2650
|
});
|
|
2589
2651
|
store.writeYaml(join(paths.arbitrationDir, "pairwise.yaml"), result.pairwise);
|
|
2590
2652
|
const decisionPath = join(paths.arbitrationDir, "decision.yaml");
|
|
2653
|
+
// Every attempt's failed tries by the FINAL outcome (delivery below can
|
|
2654
|
+
// still fail the checks); a review or check block is not an account failure.
|
|
2655
|
+
const racePool = () => terminalPoolAttempts(facts, history());
|
|
2591
2656
|
if (winnerRun) {
|
|
2592
2657
|
for (const path of materializeWinnerOutputs({
|
|
2593
2658
|
attemptDir: join(paths.attemptsDir, winnerRun.attemptId),
|
|
@@ -2617,7 +2682,7 @@ export class Orchestrator {
|
|
|
2617
2682
|
facts.checks !== "failed" &&
|
|
2618
2683
|
!workStateVetoes(facts);
|
|
2619
2684
|
if (input.inPlace === true && !inPlaceWinner && directoryAdoptable) {
|
|
2620
|
-
const delivered = await verifyAndDeliverFiles(execRoot, winnerRun.files, {}, gateSpecsFromContract(contract), (freshVerify) => finalVerifyBlocks(freshVerify)
|
|
2685
|
+
const delivered = await verifyAndDeliverFiles(execRoot, winnerRun.files, {}, gateSpecsFromContract(contract, input.runScopeMarker), (freshVerify) => finalVerifyBlocks(freshVerify)
|
|
2621
2686
|
? (freshVerify.reason ?? "final verify failed before directory race adoption")
|
|
2622
2687
|
: null, log);
|
|
2623
2688
|
store.writeYaml(join(paths.finalDir, "delivery_receipt.yaml"), delivered);
|
|
@@ -2680,7 +2745,7 @@ export class Orchestrator {
|
|
|
2680
2745
|
}
|
|
2681
2746
|
else if (adoptable) {
|
|
2682
2747
|
// Protected apply preserves the live tree or reports tree_mutated.
|
|
2683
|
-
const applied = await verifyAndDeliver(execRoot, winnerRun.diff, { mode: "apply", protectedApply: true }, gateSpecsFromContract(contract), (freshVerify) => {
|
|
2748
|
+
const applied = await verifyAndDeliver(execRoot, winnerRun.diff, { mode: "apply", protectedApply: true }, gateSpecsFromContract(contract, input.runScopeMarker), (freshVerify) => {
|
|
2684
2749
|
finalVerify = freshVerify;
|
|
2685
2750
|
return finalVerifyBlocks(freshVerify)
|
|
2686
2751
|
? (freshVerify.reason ?? "final verify failed before race adoption")
|
|
@@ -2766,7 +2831,7 @@ export class Orchestrator {
|
|
|
2766
2831
|
},
|
|
2767
2832
|
});
|
|
2768
2833
|
}
|
|
2769
|
-
store.writeText(join(paths.finalDir, "summary.md"), renderSummary(runId, mode, { ...result.decision, facts }, evidences, synth.reason, actualReviewVerified, secretDiff.secretLikeSummaryLine(winnerRun.secretLike)));
|
|
2834
|
+
store.writeText(join(paths.finalDir, "summary.md"), withPoolAttempts(renderSummary(runId, mode, { ...result.decision, facts }, evidences, synth.reason, actualReviewVerified, secretDiff.secretLikeSummaryLine(winnerRun.secretLike)), racePool()));
|
|
2770
2835
|
// summary.md is a DIAGNOSTIC artifact only (V8/PLAN addendum 2): it no
|
|
2771
2836
|
// longer carries primary-output authority. A clean applyable success or a
|
|
2772
2837
|
// winner answer still marks it ready for legacy INV-116 ordering; any
|
|
@@ -2792,8 +2857,14 @@ export class Orchestrator {
|
|
|
2792
2857
|
// (applyable, no_changes, or not-verified) is an honest completion.
|
|
2793
2858
|
const needsDecisionTerminal = facts.review === "blocked" || facts.checks === "failed";
|
|
2794
2859
|
const isFailureTerminal = facts.lifecycle !== "succeeded" || needsDecisionTerminal;
|
|
2860
|
+
const pool = racePool();
|
|
2795
2861
|
if (deliveryFailureReason && !finalVerifyFailed) {
|
|
2796
|
-
|
|
2862
|
+
const refused = deliveryRefusalFailure(deliveryFailureReason, paths.root);
|
|
2863
|
+
writeFailure(store, paths, {
|
|
2864
|
+
...refused,
|
|
2865
|
+
safeMessage: withPoolAttempts(refused.safeMessage, pool),
|
|
2866
|
+
poolAttempts: pool,
|
|
2867
|
+
});
|
|
2797
2868
|
}
|
|
2798
2869
|
else if (finalVerifyFailed) {
|
|
2799
2870
|
writeFailure(store, paths, {
|
|
@@ -2801,9 +2872,10 @@ export class Orchestrator {
|
|
|
2801
2872
|
// RunFailure.category is a closed enum; "validation" is the honest
|
|
2802
2873
|
// bucket (the winner failed re-validation on a fresh base).
|
|
2803
2874
|
category: "validation",
|
|
2804
|
-
safeMessage: finalVerify?.applied_cleanly === null
|
|
2875
|
+
safeMessage: withPoolAttempts(finalVerify?.applied_cleanly === null
|
|
2805
2876
|
? `final verify ERRORED before proving the patch against a clean base: ${finalVerify?.reason ?? "verify infrastructure error"}`
|
|
2806
|
-
: `final verify failed: ${finalVerify?.reason ?? (finalVerify?.gates_passed === false ? "deterministic gates failed on the fresh verify tree" : "unknown")}`,
|
|
2877
|
+
: `final verify failed: ${finalVerify?.reason ?? (finalVerify?.gates_passed === false ? "deterministic gates failed on the fresh verify tree" : "unknown")}`, pool),
|
|
2878
|
+
poolAttempts: pool,
|
|
2807
2879
|
runDir: paths.root,
|
|
2808
2880
|
nextActions: finalVerify?.applied_cleanly === null
|
|
2809
2881
|
? [
|
|
@@ -2835,11 +2907,12 @@ export class Orchestrator {
|
|
|
2835
2907
|
: "internal",
|
|
2836
2908
|
harnessId: winnerRun?.errored ? winnerRun.harnessId : undefined,
|
|
2837
2909
|
attemptId: winnerRun?.errored ? winnerRun.attemptId : undefined,
|
|
2838
|
-
safeMessage: needsHuman
|
|
2910
|
+
safeMessage: withPoolAttempts(needsHuman
|
|
2839
2911
|
? `review escalated to a human decision: ${result.decision.why_winner}`
|
|
2840
2912
|
: needsDecisionTerminal
|
|
2841
2913
|
? `review blocked before apply: ${result.decision.why_winner}`
|
|
2842
|
-
: result.decision.why_winner,
|
|
2914
|
+
: result.decision.why_winner, pool),
|
|
2915
|
+
poolAttempts: pool,
|
|
2843
2916
|
rawDetailRef: winnerRun?.errored
|
|
2844
2917
|
? `attempts/${winnerRun.attemptId}/attempt.yaml`
|
|
2845
2918
|
: undefined,
|
|
@@ -2859,7 +2932,7 @@ export class Orchestrator {
|
|
|
2859
2932
|
],
|
|
2860
2933
|
});
|
|
2861
2934
|
if (!winnerRun) {
|
|
2862
|
-
store.writeText(join(paths.finalDir, "summary.md"), `# Run ${runId} (${mode})\n\n- Lifecycle: ${facts.lifecycle}${facts.reason ? ` (${facts.reason})` : ""}\n- Phase: arbitration\n\n${result.decision.why_winner}\n`);
|
|
2935
|
+
store.writeText(join(paths.finalDir, "summary.md"), `# Run ${runId} (${mode})\n\n- Lifecycle: ${facts.lifecycle}${facts.reason ? ` (${facts.reason})` : ""}\n- Phase: arbitration\n\n${withPoolAttempts(result.decision.why_winner, pool)}\n`);
|
|
2863
2936
|
log.emit("output.ready", {
|
|
2864
2937
|
kind: "summary",
|
|
2865
2938
|
path: "final/summary.md",
|
|
@@ -2875,6 +2948,8 @@ export class Orchestrator {
|
|
|
2875
2948
|
lifecycle: facts.lifecycle,
|
|
2876
2949
|
facts,
|
|
2877
2950
|
reason: facts.reason,
|
|
2951
|
+
// A work-state veto is still a block: it keeps the earlier refusals.
|
|
2952
|
+
...(pool.length > 0 ? { pool_attempts: pool } : {}),
|
|
2878
2953
|
...resumableTerminal(store, paths, winnerRun ? [winnerRun] : runs, isFailureTerminal),
|
|
2879
2954
|
});
|
|
2880
2955
|
}
|
|
@@ -2884,6 +2959,7 @@ export class Orchestrator {
|
|
|
2884
2959
|
log.emit("run.blocked", {
|
|
2885
2960
|
lifecycle: facts.lifecycle,
|
|
2886
2961
|
facts,
|
|
2962
|
+
pool_attempts: pool,
|
|
2887
2963
|
...resumableTerminal(store, paths, runs, isFailureTerminal),
|
|
2888
2964
|
phase: deliveryFailureReason && !finalVerifyFailed
|
|
2889
2965
|
? "delivery"
|
|
@@ -2899,6 +2975,7 @@ export class Orchestrator {
|
|
|
2899
2975
|
facts,
|
|
2900
2976
|
reason: facts.reason,
|
|
2901
2977
|
phase: "arbitration",
|
|
2978
|
+
pool_attempts: pool,
|
|
2902
2979
|
failure_ref: "final/failure.yaml",
|
|
2903
2980
|
...resumableTerminal(store, paths, runs, isFailureTerminal),
|
|
2904
2981
|
});
|
|
@@ -2911,7 +2988,7 @@ export class Orchestrator {
|
|
|
2911
2988
|
facts,
|
|
2912
2989
|
winner: result.decision.winner,
|
|
2913
2990
|
runDir: paths.root,
|
|
2914
|
-
summary: result.decision.why_winner,
|
|
2991
|
+
summary: withPoolAttempts(result.decision.why_winner, pool),
|
|
2915
2992
|
candidates: candidateStatuses(runs),
|
|
2916
2993
|
decisionPath,
|
|
2917
2994
|
reviewVerified: actualReviewVerified,
|
|
@@ -2954,7 +3031,7 @@ export class Orchestrator {
|
|
|
2954
3031
|
transientRetryPolicy: input.transientRetryPolicy ?? transientRetryPolicy(this.config(input.cwd)),
|
|
2955
3032
|
});
|
|
2956
3033
|
}
|
|
2957
|
-
async reviewRuns(runs, reviewers, reviewVerified, reviewDir, cwd, contract, store, paths, log, ledger, taskId, signal, reservationEstimateUsd, reviewUnchanged = false) {
|
|
3034
|
+
async reviewRuns(runs, reviewers, reviewVerified, reviewDir, cwd, contract, store, paths, log, ledger, taskId, signal, reservationEstimateUsd, reviewUnchanged = false, runScopeMarker) {
|
|
2958
3035
|
return reviewCandidateRuns({
|
|
2959
3036
|
runs,
|
|
2960
3037
|
reviewers,
|
|
@@ -2970,50 +3047,17 @@ export class Orchestrator {
|
|
|
2970
3047
|
signal,
|
|
2971
3048
|
reservationEstimateUsd,
|
|
2972
3049
|
reviewUnchanged,
|
|
3050
|
+
runScopeMarker,
|
|
2973
3051
|
}, {
|
|
2974
3052
|
prepareReviewEvidenceDir: this.prepareReviewEvidenceDir.bind(this),
|
|
2975
|
-
recordReviewEvidenceCleanup
|
|
3053
|
+
recordReviewEvidenceCleanup,
|
|
2976
3054
|
reviewScoped: this.reviewScoped.bind(this),
|
|
2977
3055
|
config: this.config.bind(this),
|
|
2978
3056
|
});
|
|
2979
3057
|
}
|
|
2980
|
-
|
|
2981
|
-
|
|
2982
|
-
|
|
2983
|
-
// the candidate tree would contaminate the Git diff and, worse, overwrite a
|
|
2984
|
-
// user-owned path with the same name.
|
|
2985
|
-
if (!existsSync(sourceDir)) {
|
|
2986
|
-
throw new Error(`review evidence preflight failed for ${sourceDir}: source packet missing`);
|
|
2987
|
-
}
|
|
2988
|
-
return this.requireReviewEvidence(sourceDir);
|
|
2989
|
-
}
|
|
2990
|
-
requireReviewEvidence(dir) {
|
|
2991
|
-
const result = preflightEvidence(dir);
|
|
2992
|
-
if (result.ok)
|
|
2993
|
-
return dir;
|
|
2994
|
-
const missing = result.missing.length ? `missing=${result.missing.join(",")}` : "";
|
|
2995
|
-
const empty = result.empty.length ? `empty=${result.empty.join(",")}` : "";
|
|
2996
|
-
throw new Error(`review evidence preflight failed for ${dir}: ${[missing, empty].filter(Boolean).join(" ")}`);
|
|
2997
|
-
}
|
|
2998
|
-
cleanupReviewEvidenceDir(_candidateEvidenceDir, _candidateCwd) {
|
|
2999
|
-
// No candidate-tree packet exists in v2; external runtime retention is
|
|
3000
|
-
// governed by the artifact/journal lifecycle rather than best-effort rm.
|
|
3001
|
-
return null;
|
|
3002
|
-
}
|
|
3003
|
-
recordReviewEvidenceCleanup(store, metadataPath, attemptId, candidateEvidenceDir, candidateCwd) {
|
|
3004
|
-
const cleanupMetadata = this.cleanupReviewEvidenceDir(candidateEvidenceDir, candidateCwd);
|
|
3005
|
-
if (!cleanupMetadata)
|
|
3006
|
-
return;
|
|
3007
|
-
try {
|
|
3008
|
-
store.writeYaml(metadataPath, {
|
|
3009
|
-
...cleanupMetadata,
|
|
3010
|
-
attempt_id: attemptId,
|
|
3011
|
-
});
|
|
3012
|
-
}
|
|
3013
|
-
catch {
|
|
3014
|
-
// Cleanup telemetry must not mask the review/revalidation failure that
|
|
3015
|
-
// triggered best-effort cleanup.
|
|
3016
|
-
}
|
|
3058
|
+
/** The instance method is the seam tests stub to make evidence setup fail. */
|
|
3059
|
+
prepareReviewEvidenceDir(sourceDir, candidateCwd) {
|
|
3060
|
+
return prepareReviewEvidenceDir(sourceDir, candidateCwd);
|
|
3017
3061
|
}
|
|
3018
3062
|
async runConvergence(input, mode, maxAttempts, announce) {
|
|
3019
3063
|
const taskId = input.taskId ?? newId("task");
|
|
@@ -3022,7 +3066,7 @@ export class Orchestrator {
|
|
|
3022
3066
|
const contract = this.buildContract(input, taskId, mode);
|
|
3023
3067
|
const planBrief = verifiedPlanBrief(input);
|
|
3024
3068
|
const quotaSnapshots = this.quotaSnapshotPreflight();
|
|
3025
|
-
const { store, paths, log, ledger } = beginAnnouncedRun({
|
|
3069
|
+
const { store, paths, log, ledger, runContext } = beginAnnouncedRun({
|
|
3026
3070
|
input,
|
|
3027
3071
|
contract,
|
|
3028
3072
|
quotaSnapshots,
|
|
@@ -3159,6 +3203,8 @@ export class Orchestrator {
|
|
|
3159
3203
|
let interrupted = false; // D-16 r8: terminalizes the run interrupted
|
|
3160
3204
|
let lastFindings = [];
|
|
3161
3205
|
let lastRun = null;
|
|
3206
|
+
const convergenceRuns = [];
|
|
3207
|
+
runContext.attempts = () => convergenceRuns;
|
|
3162
3208
|
let actualReviewVerified = false;
|
|
3163
3209
|
let lastFinalReviewClean = false;
|
|
3164
3210
|
// Honest apply-state for in-place convergence: the attempts mutate the LIVE
|
|
@@ -3248,6 +3294,7 @@ export class Orchestrator {
|
|
|
3248
3294
|
const effectiveWeb = this.discloseWebUpgrade(log, routed, knobs.webPolicy, attemptId);
|
|
3249
3295
|
let run;
|
|
3250
3296
|
let harnessHome;
|
|
3297
|
+
const tried = new PoolAttemptEvidence(adapter.id, attemptId);
|
|
3251
3298
|
try {
|
|
3252
3299
|
harnessHome = this.harnessHomeFor(wsm, envelope, routed, input);
|
|
3253
3300
|
log.emit("harness.started", {
|
|
@@ -3266,7 +3313,7 @@ export class Orchestrator {
|
|
|
3266
3313
|
ledger.updateHold(lease.lease?.lease_id ?? "", streamedUsd);
|
|
3267
3314
|
return ledger.tier() === "hard";
|
|
3268
3315
|
}, input, true, // convergence runs one candidate: live deltas on (W-C4)
|
|
3269
|
-
undefined, undefined, harnessHome, observedPaths);
|
|
3316
|
+
undefined, undefined, harnessHome, observedPaths, tried);
|
|
3270
3317
|
ledger.settle(lease.lease?.lease_id ?? "", attemptUsageCostSettlement(run.cost, run.costEstimated, run.attemptId, run.harnessId, run.telemetry.authMode, run.telemetry.usageCost));
|
|
3271
3318
|
log.emit("harness.completed", {
|
|
3272
3319
|
harness_id: adapter.id,
|
|
@@ -3303,6 +3350,7 @@ export class Orchestrator {
|
|
|
3303
3350
|
errored: true,
|
|
3304
3351
|
costEstimated: failureCost.estimated,
|
|
3305
3352
|
errors: [message],
|
|
3353
|
+
poolAttempts: tried.snapshot(),
|
|
3306
3354
|
...(declared.code ? { declaredFailure: declared } : {}),
|
|
3307
3355
|
telemetry: createAttemptTelemetry(knobs.webPolicy, contract.external_context.web_required, effectiveWeb, [routed.browserRequirement, routed.denyRequirement, routed.delegationRequirement], knobs.model),
|
|
3308
3356
|
};
|
|
@@ -3318,6 +3366,7 @@ export class Orchestrator {
|
|
|
3318
3366
|
}
|
|
3319
3367
|
}
|
|
3320
3368
|
lastRun = run;
|
|
3369
|
+
convergenceRuns.push(run);
|
|
3321
3370
|
// Fail-closed twin of the candidate lane's gate: this loop terminalizes
|
|
3322
3371
|
// per attempt, so the proof is spent per attempt.
|
|
3323
3372
|
assertDelegatedEvidence(input.delegated === true, convergenceAccess, [run]);
|
|
@@ -3337,34 +3386,28 @@ export class Orchestrator {
|
|
|
3337
3386
|
// Cancellation/deadline keeps priority over a belt failure finalized concurrently.
|
|
3338
3387
|
if (input.signal?.aborted || processingBudgetDenial)
|
|
3339
3388
|
break;
|
|
3389
|
+
// When this attempt's capture failed (and no Delegate failure leads), a
|
|
3390
|
+
// budget terminal the ledger settled on it (stream hard cap, exact overshoot,
|
|
3391
|
+
// unverifiable cash) outranks that capture failure and its typed refusal
|
|
3392
|
+
// (race parity): the budget-aware finalizer below names the budget cause and
|
|
3393
|
+
// remedy first, with the capture facts, source and inspection actions after.
|
|
3394
|
+
if (delegateFailure.candidateFailureKind(run) === "capture" && ledger.terminal()) {
|
|
3395
|
+
exhausted = true;
|
|
3396
|
+
break;
|
|
3397
|
+
}
|
|
3340
3398
|
if (run.telemetry.requestRefusal ||
|
|
3341
3399
|
run.telemetry.effortResolution?.resolution === "rejected")
|
|
3342
3400
|
break;
|
|
3343
3401
|
if (delegateFailure.candidateFailureKind(run)) {
|
|
3344
3402
|
const failure = delegateFailure.candidateFailureTerminal(run, "convergence");
|
|
3345
|
-
|
|
3346
|
-
|
|
3347
|
-
|
|
3348
|
-
|
|
3349
|
-
|
|
3350
|
-
|
|
3351
|
-
attemptId,
|
|
3352
|
-
harnessId: run.harnessId,
|
|
3353
|
-
facts: makeOutcomeFacts("failed", { noChanges: run.files.noChanges }),
|
|
3354
|
-
log,
|
|
3355
|
-
});
|
|
3356
|
-
}
|
|
3357
|
-
else if (input.workspaceKind !== "directory") {
|
|
3358
|
-
await delegateFailure.persistFailedInPlaceWorkProduct({
|
|
3359
|
-
...{ store, log, paths, execRoot, preTurnSha, taskId, mode },
|
|
3360
|
-
live: input.inPlace === true,
|
|
3361
|
-
run,
|
|
3362
|
-
kind: input.create === true ? "new_repo" : "patch",
|
|
3363
|
-
attempts: attempt,
|
|
3364
|
-
});
|
|
3365
|
-
}
|
|
3403
|
+
await delegateFailure.persistFailedCandidateOutput({
|
|
3404
|
+
...{ store, log, paths, execRoot, preTurnSha, taskId, mode, run },
|
|
3405
|
+
live: input.inPlace === true && input.workspaceKind !== "directory",
|
|
3406
|
+
kind: input.create === true ? "new_repo" : "patch",
|
|
3407
|
+
attempts: attempt,
|
|
3408
|
+
});
|
|
3366
3409
|
this.writeRunTelemetry(store, paths, contract, runId, taskId, mode, attemptTelemetries, null);
|
|
3367
|
-
return failTerminally(log, store, paths, runId, taskId, mode, failure.phase, failure.error, ledger.spend(), failure.metadata);
|
|
3410
|
+
return failTerminally(log, store, paths, runId, taskId, mode, failure.phase, failure.error, ledger.spend(), { ...failure.metadata, attempts: convergenceRuns });
|
|
3368
3411
|
}
|
|
3369
3412
|
if (input.workspaceKind === "directory" && run.captureRefusal) {
|
|
3370
3413
|
return failedCandidatesResult({
|
|
@@ -3376,6 +3419,7 @@ export class Orchestrator {
|
|
|
3376
3419
|
runId,
|
|
3377
3420
|
taskId,
|
|
3378
3421
|
runs: [run],
|
|
3422
|
+
attempts: convergenceRuns,
|
|
3379
3423
|
writeTelemetry: () => this.writeRunTelemetry(store, paths, contract, runId, taskId, mode, attemptTelemetries, null),
|
|
3380
3424
|
});
|
|
3381
3425
|
}
|
|
@@ -3398,7 +3442,7 @@ export class Orchestrator {
|
|
|
3398
3442
|
log.emit("review.skipped", { reason: "not_requested", attempt_id: attemptId });
|
|
3399
3443
|
return evaluateUnreviewedConvergence(evidence, contract);
|
|
3400
3444
|
}
|
|
3401
|
-
const [evidence] = await this.reviewRuns([run], reviewers, reviewVerified, reviewDir, input.repoRoot, contract, store, paths, log, ledger, taskId, input.signal, this.reservationEstimateUsd(input), true);
|
|
3445
|
+
const [evidence] = await this.reviewRuns([run], reviewers, reviewVerified, reviewDir, input.repoRoot, contract, store, paths, log, ledger, taskId, input.signal, this.reservationEstimateUsd(input), true, input.runScopeMarker);
|
|
3402
3446
|
if (!evidence)
|
|
3403
3447
|
throw new Error("Convergence review produced no evidence");
|
|
3404
3448
|
lastFindings = evidence.findings;
|
|
@@ -3435,7 +3479,17 @@ export class Orchestrator {
|
|
|
3435
3479
|
facts: makeOutcomeFacts("failed", { noChanges: run.files.noChanges }),
|
|
3436
3480
|
log,
|
|
3437
3481
|
});
|
|
3438
|
-
return failAfterCandidates({
|
|
3482
|
+
return failAfterCandidates({
|
|
3483
|
+
log,
|
|
3484
|
+
store,
|
|
3485
|
+
paths,
|
|
3486
|
+
runId,
|
|
3487
|
+
taskId,
|
|
3488
|
+
mode,
|
|
3489
|
+
ledger,
|
|
3490
|
+
runs: [run],
|
|
3491
|
+
attempts: convergenceRuns,
|
|
3492
|
+
}, "review", err);
|
|
3439
3493
|
}
|
|
3440
3494
|
if (conv.converged) {
|
|
3441
3495
|
converged = true;
|
|
@@ -3554,11 +3608,12 @@ export class Orchestrator {
|
|
|
3554
3608
|
facts = decision.facts;
|
|
3555
3609
|
}
|
|
3556
3610
|
}
|
|
3557
|
-
|
|
3611
|
+
// Captured files decide noChanges; an incomplete capture leaves it unknown.
|
|
3612
|
+
if (lastRun?.files || lastRun?.captureRefusal)
|
|
3558
3613
|
facts = {
|
|
3559
3614
|
...facts,
|
|
3560
|
-
noChanges: lastRun.files
|
|
3561
|
-
reason: facts.reason === "no_changes" && lastRun.files
|
|
3615
|
+
noChanges: lastRun.files?.noChanges ?? null,
|
|
3616
|
+
reason: facts.reason === "no_changes" && lastRun.files?.noChanges !== true ? null : facts.reason,
|
|
3562
3617
|
};
|
|
3563
3618
|
// A budget terminal turns a succeeded lifecycle into a failed one (D8).
|
|
3564
3619
|
const convBudgetTerminal = ledger.terminal();
|
|
@@ -3569,6 +3624,19 @@ export class Orchestrator {
|
|
|
3569
3624
|
review_requested: contract.review_requested,
|
|
3570
3625
|
});
|
|
3571
3626
|
}
|
|
3627
|
+
// The shared classifier owns a budget terminal's cause and remedy: a
|
|
3628
|
+
// processing re-admission denial, or the ledger's settled terminal once it
|
|
3629
|
+
// stopped the loop over an incomplete capture (the capture facts and
|
|
3630
|
+
// inspection actions follow it in the failure record).
|
|
3631
|
+
const convBudgetMapping = processingBudgetDenial ||
|
|
3632
|
+
(convBudgetTerminal && lastRun?.captureRefusal && isBudgetTerminal(facts.reason))
|
|
3633
|
+
? classifyBudgetFailure({ denial: processingBudgetDenial, terminal: convBudgetTerminal })
|
|
3634
|
+
: null;
|
|
3635
|
+
if (convBudgetMapping &&
|
|
3636
|
+
facts.lifecycle === "failed" &&
|
|
3637
|
+
isBudgetTerminal(facts.reason) &&
|
|
3638
|
+
facts.reason !== convBudgetMapping.reason)
|
|
3639
|
+
facts = { ...facts, reason: convBudgetMapping.reason };
|
|
3572
3640
|
// A reviewer escalation to a human forces the REVIEW axis to blocked.
|
|
3573
3641
|
const needsHuman = lastFindings.some((f) => f.severity === "NEEDS_HUMAN" && isBlocking(f));
|
|
3574
3642
|
if (needsHuman && facts.lifecycle === "succeeded" && facts.review !== "blocked") {
|
|
@@ -3587,13 +3655,13 @@ export class Orchestrator {
|
|
|
3587
3655
|
facts.lifecycle === "succeeded" &&
|
|
3588
3656
|
facts.review !== "blocked" &&
|
|
3589
3657
|
!input.signal?.aborted) {
|
|
3590
|
-
convFinalVerify = lastRun
|
|
3591
|
-
? await finalVerifyFiles(lastRun.files, undefined, gateSpecsFromContract(contract), log)
|
|
3592
|
-
: await finalVerifyPatch(execRoot, lastRun, gateSpecsFromContract(contract), log);
|
|
3658
|
+
convFinalVerify = await finalVerifyRun(execRoot, lastRun, contract, log, input);
|
|
3593
3659
|
if (finalVerifyBlocks(convFinalVerify))
|
|
3594
3660
|
facts = { ...facts, checks: "failed", reason: "checks_failed" };
|
|
3595
3661
|
}
|
|
3596
3662
|
const convNeedsDecision = facts.review === "blocked" || facts.checks === "failed";
|
|
3663
|
+
// Every attempt's failed tries by the actual outcome; a block is not an account failure.
|
|
3664
|
+
const convPool = terminalPoolAttempts(facts, convergenceRuns);
|
|
3597
3665
|
if (decision) {
|
|
3598
3666
|
// Shared honesty owner (same as the race path): a needs-decision terminal
|
|
3599
3667
|
// overrides the persisted decision; final_verify is recorded either way.
|
|
@@ -3615,16 +3683,13 @@ export class Orchestrator {
|
|
|
3615
3683
|
reviewAllowsApply(facts) &&
|
|
3616
3684
|
facts.checks !== "failed" &&
|
|
3617
3685
|
!workStateVetoes(facts);
|
|
3618
|
-
if (lastRun.files) {
|
|
3619
|
-
|
|
3620
|
-
|
|
3621
|
-
store,
|
|
3622
|
-
|
|
3623
|
-
|
|
3624
|
-
|
|
3625
|
-
harnessId: lastRun.harnessId,
|
|
3626
|
-
facts,
|
|
3627
|
-
log,
|
|
3686
|
+
if (lastRun.files || lastRun.captureRefusal) {
|
|
3687
|
+
// An incomplete capture has no exact patch: never publish an empty one.
|
|
3688
|
+
await delegateFailure.persistFailedCandidateOutput({
|
|
3689
|
+
...{ store, log, paths, execRoot, preTurnSha, taskId, mode, facts, run: lastRun },
|
|
3690
|
+
live: input.inPlace === true && input.workspaceKind !== "directory",
|
|
3691
|
+
kind: input.create === true ? "new_repo" : "patch",
|
|
3692
|
+
attempts: attempt,
|
|
3628
3693
|
});
|
|
3629
3694
|
}
|
|
3630
3695
|
else {
|
|
@@ -3671,7 +3736,7 @@ export class Orchestrator {
|
|
|
3671
3736
|
store.writeText(join(paths.finalDir, "answer.md"), lastRun.answerText);
|
|
3672
3737
|
log.emit("output.ready", { kind: "answer", path: "final/answer.md" });
|
|
3673
3738
|
}
|
|
3674
|
-
store.writeText(join(paths.finalDir, "summary.md"), `# Run ${runId} (${mode})\n\n- Lifecycle: ${facts.lifecycle}${facts.reason ? ` (${facts.reason})` : ""}\n- Attempts: ${attempt}\n- Winner: ${lastRun.attemptId}\n- Review verified (cross-family): ${actualReviewVerified}\n- Apply recommendation: ${decision?.apply_recommendation ?? "inspect"}${stuckNoProgressReason ? `\n- No-progress reason: ${stuckNoProgressReason}` : ""}${secretDiff.summaryDisclosure(lastRun.secretLike)}\n`);
|
|
3739
|
+
store.writeText(join(paths.finalDir, "summary.md"), `${withPoolAttempts(`# Run ${runId} (${mode})\n\n- Lifecycle: ${facts.lifecycle}${facts.reason ? ` (${facts.reason})` : ""}\n- Attempts: ${attempt}\n- Winner: ${lastRun.attemptId}\n- Review verified (cross-family): ${actualReviewVerified}\n- Apply recommendation: ${decision?.apply_recommendation ?? "inspect"}${stuckNoProgressReason ? `\n- No-progress reason: ${stuckNoProgressReason}` : ""}${secretDiff.summaryDisclosure(lastRun.secretLike)}`, convPool)}\n`);
|
|
3675
3740
|
// Lifecycle invariant (all modes): output.ready precedes the terminal
|
|
3676
3741
|
// event so a client that applied the terminal event has the output.
|
|
3677
3742
|
log.emit("output.ready", {
|
|
@@ -3690,9 +3755,7 @@ export class Orchestrator {
|
|
|
3690
3755
|
const convDeclared = !convNeedsDecision && facts.lifecycle === "failed" && !isBudgetTerminal(facts.reason)
|
|
3691
3756
|
? lastRun?.declaredFailure
|
|
3692
3757
|
: undefined;
|
|
3693
|
-
const
|
|
3694
|
-
? classifyBudgetFailure({ denial: processingBudgetDenial, terminal: ledger.terminal() })
|
|
3695
|
-
: null;
|
|
3758
|
+
const inspect = secretDiff.captureInspectionActions(lastRun?.captureRefusal);
|
|
3696
3759
|
writeFailure(store, paths, {
|
|
3697
3760
|
phase: convNeedsDecision ? "review" : "convergence",
|
|
3698
3761
|
category: isBudgetTerminal(facts.reason)
|
|
@@ -3702,50 +3765,58 @@ export class Orchestrator {
|
|
|
3702
3765
|
: convNeedsDecision
|
|
3703
3766
|
? "policy"
|
|
3704
3767
|
: (convDeclared?.category ?? "internal"),
|
|
3705
|
-
code:
|
|
3768
|
+
code: convBudgetMapping?.code ?? convDeclared?.code ?? null,
|
|
3706
3769
|
resetsAt: convDeclared?.resetsAt ?? null,
|
|
3707
3770
|
requestRefusal: convDeclared?.requestRefusal,
|
|
3708
|
-
safeMessage: convNeedsDecision
|
|
3709
|
-
? `review escalated to a human decision after ${attempt} attempt(s)`
|
|
3710
|
-
: facts.reason === "stuck_no_progress"
|
|
3711
|
-
? (stuckNoProgressReason ?? `stuck_no_progress after ${attempt} attempt(s)`)
|
|
3712
|
-
: `${facts.lifecycle}${facts.reason ? ` (${facts.reason})` : ""} after ${attempt} attempt(s)${lastDiffStable ? "" : " (diff changed after review; review is stale)"}`,
|
|
3713
3771
|
harnessId: lastRun?.harnessId,
|
|
3714
3772
|
attemptId: lastRun?.attemptId,
|
|
3715
3773
|
runDir: paths.root,
|
|
3716
|
-
|
|
3717
|
-
|
|
3718
|
-
|
|
3719
|
-
|
|
3720
|
-
|
|
3721
|
-
|
|
3722
|
-
|
|
3723
|
-
|
|
3724
|
-
|
|
3725
|
-
|
|
3726
|
-
"Accept the risk to apply this exact patch, or discard the change",
|
|
3727
|
-
]
|
|
3728
|
-
: facts.reason === "stuck_no_progress"
|
|
3774
|
+
captureFailure: lastRun?.captureRefusal?.failure,
|
|
3775
|
+
nextActions: [
|
|
3776
|
+
...(facts.lifecycle === "cancelled"
|
|
3777
|
+
? facts.reason === "wall_clock_exceeded"
|
|
3778
|
+
? [
|
|
3779
|
+
"Inspect the partial work kept from before the deadline",
|
|
3780
|
+
"Increase --max-seconds or narrow the scope, then re-run",
|
|
3781
|
+
]
|
|
3782
|
+
: ["Retry if cancellation was accidental"]
|
|
3783
|
+
: convNeedsDecision
|
|
3729
3784
|
? [
|
|
3730
|
-
"
|
|
3731
|
-
"
|
|
3732
|
-
"Fix the gate or provide a different repair instruction",
|
|
3785
|
+
"Review the blocking findings on the run's turn",
|
|
3786
|
+
"Accept the risk to apply this exact patch, or discard the change",
|
|
3733
3787
|
]
|
|
3734
|
-
:
|
|
3735
|
-
|
|
3736
|
-
|
|
3737
|
-
|
|
3738
|
-
|
|
3739
|
-
|
|
3740
|
-
|
|
3788
|
+
: facts.reason === "stuck_no_progress"
|
|
3789
|
+
? [
|
|
3790
|
+
"Inspect the stable patch",
|
|
3791
|
+
"Inspect the failing gate output",
|
|
3792
|
+
"Fix the gate or provide a different repair instruction",
|
|
3793
|
+
]
|
|
3794
|
+
: lastRun?.captureRefusal // no latest patch exists to inspect
|
|
3795
|
+
? secretDiff.captureRefusalNextActions(lastRun.captureRefusal)
|
|
3796
|
+
: [
|
|
3797
|
+
"Open diagnostics",
|
|
3798
|
+
"Inspect latest patch and review findings",
|
|
3799
|
+
"Retry with more attempts or a narrower prompt",
|
|
3800
|
+
]),
|
|
3801
|
+
...inspect,
|
|
3802
|
+
],
|
|
3803
|
+
...(convBudgetMapping
|
|
3804
|
+
? budgetFailureRecord(convBudgetMapping, { runDir: paths.root, followUps: inspect })
|
|
3741
3805
|
: {}),
|
|
3806
|
+
safeMessage: withPoolAttempts(convBudgetMapping?.safeMessage ??
|
|
3807
|
+
(convNeedsDecision
|
|
3808
|
+
? `review escalated to a human decision after ${attempt} attempt(s)`
|
|
3809
|
+
: facts.reason === "stuck_no_progress"
|
|
3810
|
+
? (stuckNoProgressReason ?? `stuck_no_progress after ${attempt} attempt(s)`)
|
|
3811
|
+
: `${facts.lifecycle}${facts.reason ? ` (${facts.reason})` : ""} after ${attempt} attempt(s)${lastDiffStable ? "" : " (diff changed after review; review is stale)"}`), convPool),
|
|
3812
|
+
poolAttempts: convPool,
|
|
3742
3813
|
});
|
|
3743
3814
|
// D-16 r8/r9: an INTERRUPTED envelope run still gets its diagnostic
|
|
3744
3815
|
// summary + output.ready (only patch/work_product are withheld) — the
|
|
3745
3816
|
// ARCHITECTURE event contract guarantees output.ready precedes the
|
|
3746
3817
|
// terminal in every mode.
|
|
3747
3818
|
if (!lastRun || (interrupted && input.inPlace !== true && !lastRun.files)) {
|
|
3748
|
-
store.writeText(join(paths.finalDir, "summary.md"), `# Run ${runId} (${mode})\n\n- Lifecycle: ${facts.lifecycle}${facts.reason ? ` (${facts.reason})` : ""}\n- Attempts: ${attempt}\n`);
|
|
3819
|
+
store.writeText(join(paths.finalDir, "summary.md"), `${withPoolAttempts(`# Run ${runId} (${mode})\n\n- Lifecycle: ${facts.lifecycle}${facts.reason ? ` (${facts.reason})` : ""}\n- Attempts: ${attempt}`, convPool)}\n`);
|
|
3749
3820
|
log.emit("output.ready", {
|
|
3750
3821
|
kind: "summary",
|
|
3751
3822
|
path: "final/summary.md",
|
|
@@ -3753,8 +3824,9 @@ export class Orchestrator {
|
|
|
3753
3824
|
});
|
|
3754
3825
|
}
|
|
3755
3826
|
}
|
|
3756
|
-
// work_product.emitted only when a product was actually written (r9)
|
|
3757
|
-
|
|
3827
|
+
// work_product.emitted only when a product was actually written (r9); a
|
|
3828
|
+
// capture refusal's live record announces itself.
|
|
3829
|
+
if (lastRun && !lastRun.captureRefusal && (lastRun.files || !interrupted || input.inPlace)) {
|
|
3758
3830
|
log.emit("work_product.emitted", { winner: lastRun.attemptId });
|
|
3759
3831
|
}
|
|
3760
3832
|
if (!convIsFailureTerminal) {
|
|
@@ -3764,6 +3836,8 @@ export class Orchestrator {
|
|
|
3764
3836
|
facts,
|
|
3765
3837
|
reason: facts.reason,
|
|
3766
3838
|
attempts: attempt,
|
|
3839
|
+
// A work-state veto is still a block: it keeps the earlier refusals.
|
|
3840
|
+
...(convPool.length > 0 ? { pool_attempts: convPool } : {}),
|
|
3767
3841
|
});
|
|
3768
3842
|
}
|
|
3769
3843
|
else if (facts.lifecycle === "succeeded") {
|
|
@@ -3773,6 +3847,7 @@ export class Orchestrator {
|
|
|
3773
3847
|
facts,
|
|
3774
3848
|
attempts: attempt,
|
|
3775
3849
|
phase: "review",
|
|
3850
|
+
pool_attempts: convPool,
|
|
3776
3851
|
failure_ref: "final/failure.yaml",
|
|
3777
3852
|
});
|
|
3778
3853
|
}
|
|
@@ -3784,6 +3859,7 @@ export class Orchestrator {
|
|
|
3784
3859
|
reason: facts.reason,
|
|
3785
3860
|
attempts: attempt,
|
|
3786
3861
|
phase: "convergence",
|
|
3862
|
+
pool_attempts: convPool,
|
|
3787
3863
|
failure_ref: "final/failure.yaml",
|
|
3788
3864
|
// QA-041: surface the typed deadline reason on the terminal event so the
|
|
3789
3865
|
// daemon job result / Control API can distinguish it from a user cancel.
|
|
@@ -3806,9 +3882,9 @@ export class Orchestrator {
|
|
|
3806
3882
|
...(facts.lifecycle === "cancelled" && convAbortReason
|
|
3807
3883
|
? { cancelReason: convAbortReason }
|
|
3808
3884
|
: {}),
|
|
3809
|
-
summary: converged
|
|
3885
|
+
summary: withPoolAttempts(converged
|
|
3810
3886
|
? `converged in ${attempt} attempt(s)`
|
|
3811
|
-
: `${facts.lifecycle} after ${attempt} attempt(s)`,
|
|
3887
|
+
: `${facts.lifecycle} after ${attempt} attempt(s)`, convPool),
|
|
3812
3888
|
candidates: lastRun
|
|
3813
3889
|
? [
|
|
3814
3890
|
{
|
|
@@ -3848,9 +3924,9 @@ export class Orchestrator {
|
|
|
3848
3924
|
...sessionFields,
|
|
3849
3925
|
...this.harnessSpecKnobs(contract, knobs, args.intent),
|
|
3850
3926
|
env_inheritance: envInheritance(this.config(input.repoRoot)),
|
|
3851
|
-
env: (args.laneRun
|
|
3927
|
+
env: scopedSpecEnv(input.runScopeMarker, (args.laneRun
|
|
3852
3928
|
? this.laneHomeEnvFor(input, adapter.id, sessionFields.credential_profile?.profile_id ?? input.credentialProfileId ?? null)
|
|
3853
|
-
: null) ?? args.fallbackHome,
|
|
3929
|
+
: null) ?? args.fallbackHome),
|
|
3854
3930
|
});
|
|
3855
3931
|
this.credentials.stampProfileSelection(spec, input, adapter.id);
|
|
3856
3932
|
const plannerAbort = new AbortController();
|
|
@@ -3903,9 +3979,9 @@ export class Orchestrator {
|
|
|
3903
3979
|
// harnesses run sequentially. Doctor, model inventory, and spawn share each
|
|
3904
3980
|
// lane's durable HOME; the disposable context remains the non-thread fallback.
|
|
3905
3981
|
const laneRun = Boolean(input.threadId);
|
|
3906
|
-
const roHome = resolveReadOnlyRouteContext(this.execRootOf(input), laneRun
|
|
3982
|
+
const roHome = scopedRouteContext(resolveReadOnlyRouteContext(this.execRootOf(input), laneRun
|
|
3907
3983
|
? (id) => this.laneHomeEnvFor(input, id, input.credentialProfileId ?? null)
|
|
3908
|
-
: undefined);
|
|
3984
|
+
: undefined), input.runScopeMarker);
|
|
3909
3985
|
let adapters;
|
|
3910
3986
|
try {
|
|
3911
3987
|
adapters = await this.resolveCandidateAdapters({ ...input, n: undefined }, "plan", ledger, log, roHome, runId);
|
|
@@ -4190,7 +4266,7 @@ export class Orchestrator {
|
|
|
4190
4266
|
*/
|
|
4191
4267
|
deepScanReducerDeps(input, contract, log) {
|
|
4192
4268
|
return {
|
|
4193
|
-
newReadOnlyHome: () => resolveReadOnlyRouteContext(this.execRootOf(input)),
|
|
4269
|
+
newReadOnlyHome: () => scopedRouteContext(resolveReadOnlyRouteContext(this.execRootOf(input)), input.runScopeMarker),
|
|
4194
4270
|
costEvidence: (harnessId, attemptId, routed) =>
|
|
4195
4271
|
// The reducer admits under a finite estimate floor (mirror of the n>1
|
|
4196
4272
|
// scout reserve) so a subscription route is not refused for lacking a
|
|
@@ -4213,7 +4289,7 @@ export class Orchestrator {
|
|
|
4213
4289
|
resume_session_id: null,
|
|
4214
4290
|
...this.harnessSpecKnobs(contract, knobs, "synthesize"),
|
|
4215
4291
|
env_inheritance: envInheritance(this.config(input.repoRoot)),
|
|
4216
|
-
env: homeEnv,
|
|
4292
|
+
env: scopedSpecEnv(input.runScopeMarker, homeEnv),
|
|
4217
4293
|
});
|
|
4218
4294
|
this.credentials.stampProfileSelection(spec, input, routed.adapter.id);
|
|
4219
4295
|
// D-16: compile the WorkReport transport onto the reducer spec (the
|
|
@@ -4242,7 +4318,7 @@ export class Orchestrator {
|
|
|
4242
4318
|
// recorded user intent is the CALLER's goal.
|
|
4243
4319
|
const contract = this.buildContract({ ...input, prompt: opts.contractIntent ?? prompt }, taskId, opts.mode);
|
|
4244
4320
|
const quotaSnapshots = this.quotaSnapshotPreflight();
|
|
4245
|
-
const { store, paths, log, ledger } = beginAnnouncedRun({
|
|
4321
|
+
const { store, paths, log, ledger, runContext } = beginAnnouncedRun({
|
|
4246
4322
|
input,
|
|
4247
4323
|
contract,
|
|
4248
4324
|
quotaSnapshots,
|
|
@@ -4265,9 +4341,9 @@ export class Orchestrator {
|
|
|
4265
4341
|
// and the next lane turn resumes it (INV-034). Its doctor/model/spawn path
|
|
4266
4342
|
// shares that lane HOME; deep-scan and one-shot asks keep the disposable one.
|
|
4267
4343
|
const laneRun = Boolean(input.threadId) && opts.mode === "ask" && !opts.deepScan;
|
|
4268
|
-
const roHome = resolveReadOnlyRouteContext(this.execRootOf(input), laneRun
|
|
4344
|
+
const roHome = scopedRouteContext(resolveReadOnlyRouteContext(this.execRootOf(input), laneRun
|
|
4269
4345
|
? (id) => this.laneHomeEnvFor(input, id, input.credentialProfileId ?? null)
|
|
4270
|
-
: undefined);
|
|
4346
|
+
: undefined), input.runScopeMarker);
|
|
4271
4347
|
let adapters, width;
|
|
4272
4348
|
try {
|
|
4273
4349
|
({ adapters, width } = await resolveReadOnlyCandidates({
|
|
@@ -4302,6 +4378,14 @@ export class Orchestrator {
|
|
|
4302
4378
|
};
|
|
4303
4379
|
}
|
|
4304
4380
|
const attempts = [];
|
|
4381
|
+
// A launched attempt's tries' accounts, owned here until its record joins
|
|
4382
|
+
// `attempts` (as an Agent slot owns them): a throw after its tries settled
|
|
4383
|
+
// still names them on the run's terminal, and never twice.
|
|
4384
|
+
const launched = new Set();
|
|
4385
|
+
runContext.attempts = () => [
|
|
4386
|
+
...attempts,
|
|
4387
|
+
...[...launched].map((tried) => ({ poolAttempts: tried.snapshot() })),
|
|
4388
|
+
];
|
|
4305
4389
|
const attemptTelemetries = [];
|
|
4306
4390
|
let fallbackOpen = false;
|
|
4307
4391
|
let budgetStopped = false;
|
|
@@ -4445,9 +4529,9 @@ export class Orchestrator {
|
|
|
4445
4529
|
// A thread lane turn spawns in its DURABLE per-lane home so the native
|
|
4446
4530
|
// session it records is reachable for resume next turn; everything else
|
|
4447
4531
|
// uses the disposable route-context home.
|
|
4448
|
-
env: (laneRun
|
|
4532
|
+
env: scopedSpecEnv(input.runScopeMarker, (laneRun
|
|
4449
4533
|
? this.laneHomeEnvFor(input, adapter.id, sessionFields.credential_profile?.profile_id ?? input.credentialProfileId ?? null)
|
|
4450
|
-
: null) ?? roHome.env,
|
|
4534
|
+
: null) ?? roHome.env),
|
|
4451
4535
|
});
|
|
4452
4536
|
this.credentials.stampProfileSelection(spec, input, adapter.id);
|
|
4453
4537
|
const reportInteraction = interactionChannelFor(input, log, runId, taskId, attemptId, adapter.id, routed.supportsInteractive);
|
|
@@ -4513,12 +4597,15 @@ export class Orchestrator {
|
|
|
4513
4597
|
const retryPolicy = transientRetryPolicy(this.config(input.repoRoot));
|
|
4514
4598
|
let activeSessionId = spec.session_id;
|
|
4515
4599
|
const live = liveAttempt(input, routed, paths, contract, attemptId, () => activeSessionId);
|
|
4600
|
+
const tried = new PoolAttemptEvidence(adapter.id, attemptId);
|
|
4601
|
+
launched.add(tried);
|
|
4516
4602
|
let continuity;
|
|
4517
4603
|
({ continuity, spec } = await openContinuity(this.continuityDeps(adapter, routed, contract, attemptId, paths, input, log, {
|
|
4518
4604
|
envelope: null,
|
|
4519
4605
|
harnessHome: null,
|
|
4520
4606
|
workOrder: input.prompt,
|
|
4521
4607
|
firstPrompt: spec.prompt,
|
|
4608
|
+
poolEvidence: tried,
|
|
4522
4609
|
}), { spec, continuation: input.continuation, store, telemetry }));
|
|
4523
4610
|
const onAbort = () => void adapter.cancel?.(activeSessionId)?.catch(() => { });
|
|
4524
4611
|
if (input.signal) {
|
|
@@ -4553,9 +4640,8 @@ export class Orchestrator {
|
|
|
4553
4640
|
runSpec.extra["abortSignal"] = input.signal
|
|
4554
4641
|
? AbortSignal.any([input.signal, tryAbort.signal])
|
|
4555
4642
|
: tryAbort.signal;
|
|
4556
|
-
continuity.beginTry(runSpec, nativeTry, tryAbort);
|
|
4557
4643
|
// #363: bound before the spawn — a credential change mid-try voids it.
|
|
4558
|
-
const refusal =
|
|
4644
|
+
const refusal = continuity.beginTry(runSpec, nativeTry, tryAbort);
|
|
4559
4645
|
const transientStart = telemetry.transientFailures.length;
|
|
4560
4646
|
const rateLimitStart = telemetry.rateLimits.length;
|
|
4561
4647
|
log.emit("harness.started", {
|
|
@@ -4630,8 +4716,6 @@ export class Orchestrator {
|
|
|
4630
4716
|
if (!recordAdapterThrow(telemetry, err))
|
|
4631
4717
|
break;
|
|
4632
4718
|
}
|
|
4633
|
-
if (streamBudgetDenied)
|
|
4634
|
-
break;
|
|
4635
4719
|
if (telemetry.requestRefusal)
|
|
4636
4720
|
break;
|
|
4637
4721
|
const newTransients = telemetry.transientFailures.slice(transientStart);
|
|
@@ -4640,10 +4724,14 @@ export class Orchestrator {
|
|
|
4640
4724
|
const reportSoFar = answer.text();
|
|
4641
4725
|
const delivered = !harnessError && reportSoFar.length > 0 && !input.signal?.aborted;
|
|
4642
4726
|
this.credentials.noteTryServed(refusal, telemetry.outputMarkers, delivered);
|
|
4727
|
+
// A budget stop ends the attempt; typed progress before it still served the account.
|
|
4728
|
+
if (streamBudgetDenied)
|
|
4729
|
+
break;
|
|
4643
4730
|
const verdict = await continuity.afterTry({
|
|
4644
4731
|
runSpec,
|
|
4645
4732
|
nativeTry,
|
|
4646
4733
|
harnessErrored: harnessError !== null,
|
|
4734
|
+
originalError: harnessError,
|
|
4647
4735
|
aborted: input.signal?.aborted === true,
|
|
4648
4736
|
requestRefused: Boolean(telemetry.requestRefusal),
|
|
4649
4737
|
newTransients,
|
|
@@ -4761,6 +4849,7 @@ export class Orchestrator {
|
|
|
4761
4849
|
: {}),
|
|
4762
4850
|
...continuity.terminalFacts(roResumable),
|
|
4763
4851
|
});
|
|
4852
|
+
launched.delete(tried);
|
|
4764
4853
|
if (opts.deepScan) {
|
|
4765
4854
|
store.writeText(join(paths.findingsDir, `${attemptId}-error.md`), `# Explorer ${attemptId} failed\n\n${harnessError}\n`);
|
|
4766
4855
|
}
|
|
@@ -4786,6 +4875,7 @@ export class Orchestrator {
|
|
|
4786
4875
|
...(scoutInterrupted ? { interrupted: true } : {}),
|
|
4787
4876
|
...continuity.terminalFacts(roResumable),
|
|
4788
4877
|
});
|
|
4878
|
+
launched.delete(tried);
|
|
4789
4879
|
if (opts.deepScan) {
|
|
4790
4880
|
const warningNote = toolWarnings(telemetry).length
|
|
4791
4881
|
? `\n\n> Tool warnings: ${toolWarnings(telemetry)
|
|
@@ -4946,188 +5036,20 @@ export class Orchestrator {
|
|
|
4946
5036
|
harnessId: a.harnessId,
|
|
4947
5037
|
status: a.status,
|
|
4948
5038
|
}));
|
|
4949
|
-
const cancelledTerminal = () => cancelledResult(log, runId, taskId, opts.mode, paths.root, candidateSummaries, () => this.writeRunTelemetry(store, paths, contract, runId, taskId, opts.mode, attemptTelemetries, null), ledger.spend(), input.signal, store, undefined, resumableOf(attempts, "cancelled"));
|
|
5039
|
+
const cancelledTerminal = () => cancelledResult(log, runId, taskId, opts.mode, paths.root, candidateSummaries, () => this.writeRunTelemetry(store, paths, contract, runId, taskId, opts.mode, attemptTelemetries, null), ledger.spend(), input.signal, store, undefined, resumableOf(attempts, "cancelled"), attempts);
|
|
4950
5040
|
if (input.signal?.aborted)
|
|
4951
5041
|
return cancelledTerminal();
|
|
4952
5042
|
const succeededReadonly = attempts.filter((a) => a.status === "success");
|
|
4953
|
-
|
|
4954
|
-
|
|
4955
|
-
|
|
4956
|
-
|
|
4957
|
-
|
|
4958
|
-
|
|
4959
|
-
|
|
4960
|
-
? classifyBudgetFailure({ denial: budgetDenial, terminal: ledger.terminal() })
|
|
4961
|
-
: null;
|
|
4962
|
-
const singleError = budgetMapping?.safeMessage ??
|
|
4963
|
-
last?.error ??
|
|
4964
|
-
(budgetStopped ? "budget exhausted before any attempt" : "harness failed");
|
|
4965
|
-
if (fallbackOpen || webBlocked) {
|
|
4966
|
-
log.emit("route.fallback.exhausted", {
|
|
4967
|
-
harness_id: last?.harnessId ?? null,
|
|
4968
|
-
attempt_id: last?.attemptId ?? null,
|
|
4969
|
-
reason: "web_evidence_unsatisfied",
|
|
4970
|
-
error: singleError,
|
|
4971
|
-
});
|
|
4972
|
-
fallbackOpen = false;
|
|
4973
|
-
}
|
|
4974
|
-
const partialReport = [...attempts].reverse().find((a) => a.report)?.report ?? "";
|
|
4975
|
-
this.writeRunTelemetry(store, paths, contract, runId, taskId, opts.mode, attemptTelemetries, null);
|
|
4976
|
-
store.writeText(join(paths.contextDir, "context_error.md"), `# ${budgetMapping ? "Budget Denied" : "Harness Error"}\n\n${singleError}\n`);
|
|
4977
|
-
const roEventRefs = attempts.map((a) => `attempts/${a.attemptId}/events.jsonl`);
|
|
4978
|
-
if (budgetMapping) {
|
|
4979
|
-
writeFailure(store, paths, budgetFailureRecord(budgetMapping, { eventRefs: roEventRefs, runDir: paths.root }));
|
|
4980
|
-
}
|
|
4981
|
-
else {
|
|
4982
|
-
// #31: classify the harness cause across the read-only attempts so auth
|
|
4983
|
-
// guidance appears only on a real auth failure.
|
|
4984
|
-
const roCategory = dominantHarnessFailureCategory(attemptTelemetries.flatMap((a) => a.telemetry.transientFailures));
|
|
4985
|
-
// A5: the terminal already speaks with the LAST attempt's message; its
|
|
4986
|
-
// typed refusal rides along machine-readably instead of reducing to it.
|
|
4987
|
-
const roDeclared = webBlocked ? undefined : last?.declaredFailure;
|
|
4988
|
-
writeFailure(store, paths, {
|
|
4989
|
-
phase: "harness",
|
|
4990
|
-
category: webBlocked ? "policy" : (roDeclared?.category ?? "harness_error"),
|
|
4991
|
-
code: roDeclared?.code ?? null,
|
|
4992
|
-
harnessId: last?.harnessId,
|
|
4993
|
-
attemptId: last?.attemptId,
|
|
4994
|
-
safeMessage: singleError,
|
|
4995
|
-
eventRefs: roEventRefs,
|
|
4996
|
-
runDir: paths.root,
|
|
4997
|
-
resetsAt: roDeclared?.resetsAt ?? null,
|
|
4998
|
-
requestRefusal: roDeclared?.requestRefusal,
|
|
4999
|
-
vendorFailure: attemptVendorFailure(attemptTelemetries, last?.attemptId),
|
|
5000
|
-
nextActions: harnessFailureNextActions(roCategory),
|
|
5001
|
-
});
|
|
5002
|
-
}
|
|
5003
|
-
// QA-036: re-check the DELIVERABLE through the shared finalizer helper —
|
|
5004
|
-
// a blocked Ask that produced NO answer can no longer read as a succeeded
|
|
5005
|
-
// "Needs review" run (exit 0); it is an honest failure (exit 1).
|
|
5006
|
-
const roTerminal = readOnlyNoSuccessTerminal({
|
|
5007
|
-
webBlocked,
|
|
5008
|
-
hasDeliverable: partialReport.trim().length > 0,
|
|
5009
|
-
budgetStopped,
|
|
5010
|
-
attemptsCount: attempts.length,
|
|
5011
|
-
});
|
|
5012
|
-
const terminalFacts = makeOutcomeFacts(roTerminal.lifecycle, {
|
|
5013
|
-
...(roTerminal.review ? { review: roTerminal.review } : {}),
|
|
5014
|
-
reason: budgetMapping?.reason ?? roTerminal.reason,
|
|
5015
|
-
});
|
|
5016
|
-
const terminalHarnessId = budgetMapping?.harnessId ?? last?.harnessId;
|
|
5017
|
-
store.writeText(join(paths.finalDir, "summary.md"), `# Run ${runId} (${opts.mode})\n\n- Harness: ${terminalHarnessId ?? "none"}\n- Lifecycle: ${terminalFacts.lifecycle}${terminalFacts.reason ? ` (${terminalFacts.reason})` : ""}\n\n${singleError}\n`);
|
|
5018
|
-
log.emit("output.ready", { kind: "summary", path: "final/summary.md", state: "diagnostic" });
|
|
5019
|
-
if (terminalFacts.lifecycle === "succeeded") {
|
|
5020
|
-
log.emit("run.blocked", {
|
|
5021
|
-
lifecycle: terminalFacts.lifecycle,
|
|
5022
|
-
facts: terminalFacts,
|
|
5023
|
-
harness_id: terminalHarnessId,
|
|
5024
|
-
error: singleError,
|
|
5025
|
-
failure_ref: "final/failure.yaml",
|
|
5026
|
-
...resumableTerminal(store, paths, attempts, true),
|
|
5027
|
-
});
|
|
5028
|
-
}
|
|
5029
|
-
else {
|
|
5030
|
-
log.emit("run.failed", {
|
|
5031
|
-
lifecycle: terminalFacts.lifecycle,
|
|
5032
|
-
facts: terminalFacts,
|
|
5033
|
-
reason: terminalFacts.reason,
|
|
5034
|
-
phase: budgetMapping?.phase,
|
|
5035
|
-
harness_id: terminalHarnessId,
|
|
5036
|
-
error: singleError,
|
|
5037
|
-
failure_ref: "final/failure.yaml",
|
|
5038
|
-
...resumableTerminal(store, paths, attempts, true),
|
|
5039
|
-
});
|
|
5040
|
-
}
|
|
5041
|
-
return {
|
|
5042
|
-
spendUsd: ledger.spend(),
|
|
5043
|
-
runId,
|
|
5044
|
-
taskId,
|
|
5045
|
-
mode: opts.mode,
|
|
5046
|
-
lifecycle: terminalFacts.lifecycle,
|
|
5047
|
-
facts: terminalFacts,
|
|
5048
|
-
winner: null,
|
|
5049
|
-
runDir: paths.root,
|
|
5050
|
-
summary: singleError,
|
|
5051
|
-
candidates: attempts.map((a) => ({
|
|
5052
|
-
attemptId: a.attemptId,
|
|
5053
|
-
harnessId: a.harnessId,
|
|
5054
|
-
status: a.status,
|
|
5055
|
-
})),
|
|
5056
|
-
};
|
|
5057
|
-
}
|
|
5043
|
+
const readonlyTerminal = {
|
|
5044
|
+
...{ store, paths, log, runId, taskId, mode: opts.mode, ledger, attempts },
|
|
5045
|
+
...{ attemptTelemetries, budgetStopped, budgetDenial },
|
|
5046
|
+
writeTelemetry: () => this.writeRunTelemetry(store, paths, contract, runId, taskId, opts.mode, attemptTelemetries, null),
|
|
5047
|
+
};
|
|
5048
|
+
if (!opts.deepScan && succeededReadonly.length === 0)
|
|
5049
|
+
return readOnlyChainFailureResult({ ...readonlyTerminal, fallbackOpen });
|
|
5058
5050
|
const succeeded = succeededReadonly;
|
|
5059
|
-
if (opts.deepScan && succeeded.length === 0)
|
|
5060
|
-
|
|
5061
|
-
// QA-050/QA-019: only a pure-denial scan may claim a budget failure.
|
|
5062
|
-
const scanBudgetMapping = budgetStopped && !blocked && attempts.every((a) => a.budgetDenied === true)
|
|
5063
|
-
? classifyBudgetFailure({ denial: budgetDenial, terminal: ledger.terminal() })
|
|
5064
|
-
: null;
|
|
5065
|
-
// D-16 r8: ALL scouts out of context → the aggregate is interrupted.
|
|
5066
|
-
const allInterrupted = attempts.length > 0 && attempts.every((a) => a.interrupted === true);
|
|
5067
|
-
const scanFailFacts = allInterrupted
|
|
5068
|
-
? makeOutcomeFacts("interrupted", { reason: "context_capacity_exhausted" })
|
|
5069
|
-
: makeOutcomeFacts("failed", {
|
|
5070
|
-
reason: scanBudgetMapping ? scanBudgetMapping.reason : "harness_failed",
|
|
5071
|
-
});
|
|
5072
|
-
const message = scanBudgetMapping
|
|
5073
|
-
? scanBudgetMapping.safeMessage
|
|
5074
|
-
: attempts.map((a) => `${a.attemptId}/${a.harnessId}: ${a.error ?? "failed"}`).join("\n");
|
|
5075
|
-
this.writeRunTelemetry(store, paths, contract, runId, taskId, opts.mode, attemptTelemetries, null);
|
|
5076
|
-
if (scanBudgetMapping) {
|
|
5077
|
-
writeFailure(store, paths, budgetFailureRecord(scanBudgetMapping, {
|
|
5078
|
-
eventRefs: attempts.map((a) => `attempts/${a.attemptId}/events.jsonl`),
|
|
5079
|
-
runDir: paths.root,
|
|
5080
|
-
}));
|
|
5081
|
-
}
|
|
5082
|
-
else {
|
|
5083
|
-
// Mixed causes stay generic; only unanimous evidence may name the scan's cause.
|
|
5084
|
-
const declared = blocked ? null : unanimousDeclaredFailure(attempts);
|
|
5085
|
-
const scanCategory = dominantHarnessFailureCategory(attemptTelemetries.flatMap((a) => a.telemetry.transientFailures));
|
|
5086
|
-
writeFailure(store, paths, {
|
|
5087
|
-
phase: "harness",
|
|
5088
|
-
category: blocked ? "policy" : (declared?.category ?? "harness_error"),
|
|
5089
|
-
code: declared?.code,
|
|
5090
|
-
resetsAt: declared?.resetsAt,
|
|
5091
|
-
requestRefusal: declared?.requestRefusal,
|
|
5092
|
-
safeMessage: message || "all explorers failed",
|
|
5093
|
-
eventRefs: attempts.map((a) => `attempts/${a.attemptId}/events.jsonl`),
|
|
5094
|
-
runDir: paths.root,
|
|
5095
|
-
nextActions: [
|
|
5096
|
-
...harnessFailureNextActions(scanCategory).filter((a) => !a.startsWith("Retry")),
|
|
5097
|
-
"Reduce explore width",
|
|
5098
|
-
"Retry after setup",
|
|
5099
|
-
],
|
|
5100
|
-
});
|
|
5101
|
-
}
|
|
5102
|
-
// QA-036: no successful explorer means no synthesizable deliverable.
|
|
5103
|
-
store.writeText(join(paths.finalDir, "summary.md"), `# Run ${runId} (${opts.mode})\n\n- Lifecycle: ${scanFailFacts.lifecycle}${scanFailFacts.reason ? ` (${scanFailFacts.reason})` : ""}\n\n${message}\n`);
|
|
5104
|
-
log.emit("output.ready", { kind: "summary", path: "final/summary.md", state: "diagnostic" });
|
|
5105
|
-
log.emit("run.failed", {
|
|
5106
|
-
lifecycle: scanFailFacts.lifecycle,
|
|
5107
|
-
facts: scanFailFacts,
|
|
5108
|
-
reason: scanFailFacts.reason,
|
|
5109
|
-
phase: scanBudgetMapping ? scanBudgetMapping.phase : "harness",
|
|
5110
|
-
error: message,
|
|
5111
|
-
failure_ref: "final/failure.yaml",
|
|
5112
|
-
...resumableTerminal(store, paths, attempts, true),
|
|
5113
|
-
});
|
|
5114
|
-
return {
|
|
5115
|
-
spendUsd: ledger.spend(),
|
|
5116
|
-
runId,
|
|
5117
|
-
taskId,
|
|
5118
|
-
mode: opts.mode,
|
|
5119
|
-
lifecycle: scanFailFacts.lifecycle,
|
|
5120
|
-
facts: scanFailFacts,
|
|
5121
|
-
winner: null,
|
|
5122
|
-
runDir: paths.root,
|
|
5123
|
-
summary: message,
|
|
5124
|
-
candidates: attempts.map((a) => ({
|
|
5125
|
-
attemptId: a.attemptId,
|
|
5126
|
-
harnessId: a.harnessId,
|
|
5127
|
-
status: a.status,
|
|
5128
|
-
})),
|
|
5129
|
-
};
|
|
5130
|
-
}
|
|
5051
|
+
if (opts.deepScan && succeeded.length === 0)
|
|
5052
|
+
return deepScanFailureResult(readonlyTerminal);
|
|
5131
5053
|
const unsuccessful = attempts.filter((a) => a.status !== "success");
|
|
5132
5054
|
// #27 / D-6: a multi-scout deep scan runs ONE bounded synthesis reducer over
|
|
5133
5055
|
// the raw scout reports so the final artifact is a real merge, not a
|
|
@@ -5231,7 +5153,9 @@ export class Orchestrator {
|
|
|
5231
5153
|
.map((a) => `${a.attemptId}:${a.harnessId}:${a.status}`)
|
|
5232
5154
|
.join(", ");
|
|
5233
5155
|
const secretLike = secretDiff.answerSecretLikeFinding(answerMatches);
|
|
5234
|
-
|
|
5156
|
+
// A delivered report that still ends unsuccessfully or blocked keeps it and names every failed try.
|
|
5157
|
+
const reportPool = terminalPoolAttempts(terminalFacts, attempts);
|
|
5158
|
+
store.writeText(join(paths.finalDir, "summary.md"), `# Run ${runId} (${opts.mode})\n\n- Harnesses: ${harnessLabel}\n- Lifecycle: ${terminalFacts.lifecycle}${terminalFacts.reason ? ` (${terminalFacts.reason})` : ""}${secretDiff.summaryDisclosure(secretLike)}\n\n${withPoolAttempts(report, reportPool)}\n`);
|
|
5235
5159
|
const reportProducerAttemptId = opts.deepScan &&
|
|
5236
5160
|
deepScanSynthesis?.status === "succeeded" &&
|
|
5237
5161
|
deepScanSynthesis.reducer_attempt_id
|
|
@@ -5258,7 +5182,8 @@ export class Orchestrator {
|
|
|
5258
5182
|
writeFailure(store, paths, {
|
|
5259
5183
|
phase: "executor",
|
|
5260
5184
|
category: terminalFacts.reason === "context_capacity_exhausted" ? "harness_error" : "budget",
|
|
5261
|
-
safeMessage: `read-only report ended ${terminalFacts.lifecycle}${terminalFacts.reason ? ` (${terminalFacts.reason.replaceAll("_", " ")})` : ""}`,
|
|
5185
|
+
safeMessage: withPoolAttempts(`read-only report ended ${terminalFacts.lifecycle}${terminalFacts.reason ? ` (${terminalFacts.reason.replaceAll("_", " ")})` : ""}`, reportPool),
|
|
5186
|
+
poolAttempts: reportPool,
|
|
5262
5187
|
runDir: paths.root,
|
|
5263
5188
|
nextActions: terminalFacts.reason === "context_capacity_exhausted"
|
|
5264
5189
|
? ["Inspect the partial report", "Re-run with a narrower scope"]
|
|
@@ -5269,6 +5194,7 @@ export class Orchestrator {
|
|
|
5269
5194
|
facts: terminalFacts,
|
|
5270
5195
|
reason: terminalFacts.reason,
|
|
5271
5196
|
phase: "executor",
|
|
5197
|
+
pool_attempts: reportPool,
|
|
5272
5198
|
failure_ref: "final/failure.yaml",
|
|
5273
5199
|
...resumableTerminal(store, paths, attempts, true),
|
|
5274
5200
|
});
|
|
@@ -5278,6 +5204,7 @@ export class Orchestrator {
|
|
|
5278
5204
|
lifecycle: terminalFacts.lifecycle,
|
|
5279
5205
|
facts: terminalFacts,
|
|
5280
5206
|
reason: terminalFacts.reason,
|
|
5207
|
+
pool_attempts: reportPool,
|
|
5281
5208
|
...resumableTerminal(store, paths, attempts, true),
|
|
5282
5209
|
});
|
|
5283
5210
|
}
|
|
@@ -5298,7 +5225,7 @@ export class Orchestrator {
|
|
|
5298
5225
|
facts: terminalFacts,
|
|
5299
5226
|
winner: null,
|
|
5300
5227
|
runDir: paths.root,
|
|
5301
|
-
summary: redactSecrets(report).slice(0, 400),
|
|
5228
|
+
summary: withPoolAttempts(redactSecrets(report).slice(0, 400), reportPool),
|
|
5302
5229
|
candidates: candidateSummaries,
|
|
5303
5230
|
};
|
|
5304
5231
|
}
|