@claudexor/orchestrator 3.2.0 → 3.3.7-rc.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.
Files changed (45) hide show
  1. package/dist/attemptUsageCost.d.ts +7 -1
  2. package/dist/attemptUsageCost.d.ts.map +1 -1
  3. package/dist/attemptUsageCost.js +7 -1
  4. package/dist/attemptUsageCost.js.map +1 -1
  5. package/dist/budgetFailure.d.ts +1 -1
  6. package/dist/budgetFailure.d.ts.map +1 -1
  7. package/dist/budgetFailure.js.map +1 -1
  8. package/dist/candidateEvidence.d.ts +24 -0
  9. package/dist/candidateEvidence.d.ts.map +1 -1
  10. package/dist/candidateEvidence.js +26 -0
  11. package/dist/candidateEvidence.js.map +1 -1
  12. package/dist/credential-profile-rotation.d.ts.map +1 -1
  13. package/dist/credential-profile-rotation.js +23 -3
  14. package/dist/credential-profile-rotation.js.map +1 -1
  15. package/dist/credential-profiles.d.ts +60 -1
  16. package/dist/credential-profiles.d.ts.map +1 -1
  17. package/dist/credential-profiles.js +89 -1
  18. package/dist/credential-profiles.js.map +1 -1
  19. package/dist/delegatedHome.d.ts +156 -0
  20. package/dist/delegatedHome.d.ts.map +1 -0
  21. package/dist/delegatedHome.js +179 -0
  22. package/dist/delegatedHome.js.map +1 -0
  23. package/dist/index.d.ts +1 -1
  24. package/dist/index.d.ts.map +1 -1
  25. package/dist/index.js +1 -1
  26. package/dist/index.js.map +1 -1
  27. package/dist/orchestrator.d.ts +27 -2
  28. package/dist/orchestrator.d.ts.map +1 -1
  29. package/dist/orchestrator.js +102 -19
  30. package/dist/orchestrator.js.map +1 -1
  31. package/dist/planQuestions.js +4 -1
  32. package/dist/planQuestions.js.map +1 -1
  33. package/dist/requestRequirements.d.ts +15 -2
  34. package/dist/requestRequirements.d.ts.map +1 -1
  35. package/dist/requestRequirements.js +17 -3
  36. package/dist/requestRequirements.js.map +1 -1
  37. package/dist/runTerminalResults.d.ts +26 -1
  38. package/dist/runTerminalResults.d.ts.map +1 -1
  39. package/dist/runTerminalResults.js +34 -4
  40. package/dist/runTerminalResults.js.map +1 -1
  41. package/dist/runTerminals.d.ts +2 -2
  42. package/dist/runTerminals.d.ts.map +1 -1
  43. package/dist/runTerminals.js +2 -2
  44. package/dist/runTerminals.js.map +1 -1
  45. package/package.json +17 -17
@@ -1,10 +1,11 @@
1
1
  import { existsSync } from "node:fs";
2
- import { effectiveAuthPreference, observeNativeSessionEvent, preflightCredentialProfile, preflightDefaultSubject, probeCredentialProfileStatus, profileHeadroomBreach, profileStatusAdmits, resolveCredentialProfile, resumeSessionForProfile, rotateSpecOnTypedLimit, selectedProfileAvailability, staticRotationCandidates, } from "./credential-profiles.js";
2
+ import { effectiveAuthPreference, observeNativeSessionEvent, preflightCredentialProfile, preflightDefaultSubject, probeCredentialProfileStatus, profileHeadroomBreach, profileStatusAdmits, resolveCredentialProfile, resumeSessionForProfile, rotateSpecOnTypedLimit, selectedProfileAvailability, staticRotationCandidates, vendorVerifiedProfileStatus, } from "./credential-profiles.js";
3
3
  import { writeRunTelemetryArtifact } from "./runTelemetryWriter.js";
4
4
  import { buildFileBackedSynthesisInput, materializeWinnerOutputs, stageFileBackedContext, writeCandidateAttemptArtifacts, } from "./candidateOutputs.js";
5
5
  import { processAttemptUsage } from "./attemptUsage.js";
6
+ import { appliedAttemptFacts, assertDelegatedEvidence, confinementNotice, externallyConfinedLane, scopedHarnessHome, } from "./delegatedHome.js";
6
7
  import * as AC from "./attemptUsageCost.js";
7
- import { candidateRoster, convergenceOutcomeFacts, isWorkingCandidate, partitionCandidates, toCandidateEvidence, } from "./candidateEvidence.js";
8
+ import { candidateRoster, convergenceOutcomeFacts, isWorkingCandidate, partitionCandidates, toCandidateEvidence, unanimousDeclaredFailure, } from "./candidateEvidence.js";
8
9
  import { capabilityIntents } from "@claudexor/gateway";
9
10
  import { policyFindings } from "./policyFindings.js";
10
11
  import { join } from "node:path";
@@ -26,7 +27,7 @@ import { resolveRunInputDefaults } from "./run-input-resolution.js";
26
27
  import { beginAnnouncedRun } from "./runEventLog.js";
27
28
  import { arbitrationBudgetOptions, decisionBudgetSummary } from "./decisionBudget.js";
28
29
  import { buildRevisePrompt } from "./revisePrompt.js";
29
- import { cancelledResult, failTerminally, guardAnnouncedRun, writeFailure, } from "./runTerminals.js";
30
+ import { cancelledResult, declaredFailure, failTerminally, guardAnnouncedRun, writeFailure, } from "./runTerminals.js";
30
31
  import { budgetFailureRecord, classifyBudgetFailure } from "./budgetFailure.js";
31
32
  import { assertOutputSchemaCompiles, finalizeStructuredOutput } from "./structuredOutput.js";
32
33
  import { transientRetryDelayMs, promptWithEngineConstraints, sleep, redactHarnessEvent, harnessEventPayload, safeErrorMessage, renderSummary, observeBudgetSignals, rotateOnStall, recordCleanAttemptMetrics, envInheritance, transientRetryPolicy, reviewerTimeoutMs, harnessInactivityTimeoutMs, observeAuthSwitch, emitPrimaryDivergence, emitPoolDegraded, deliveryRefusalFailure, winnerNeedsHuman, writeRaceDeliveryDecision, } from "./runSupport.js";
@@ -407,8 +408,19 @@ export class Orchestrator {
407
408
  const policy = this.config(repoRoot)?.global.harnesses?.[harnessId]?.profile_policy;
408
409
  return policy ?? { limit_action: "fail", rotation_eligible: [], headroom_threshold: 0.9 };
409
410
  }
411
+ /** The quota poller's authenticated vendor evidence for THIS decision epoch,
412
+ * read once so every profile in the epoch is judged against one observation
413
+ * set. This is what makes profile readiness at admission mean the same thing
414
+ * it means on the Accounts card (INV-135 honesty). */
415
+ vendorQuotaObservations() {
416
+ return {
417
+ snapshots: this.deps.quotaSnapshots?.() ?? [],
418
+ absences: this.deps.quotaAbsences?.() ?? [],
419
+ };
420
+ }
410
421
  /** Fresh profile readiness for one rotation decision epoch. Accounts uses
411
- * the same probe wrapper + admission predicate when projecting next_up. */
422
+ * the same probe wrapper + vendor overlay + admission predicate when
423
+ * projecting next_up. */
412
424
  async readyProfileIdsForRotation(input, harnessId, current, excluded = new Set()) {
413
425
  const profiles = staticRotationCandidates({
414
426
  registry: this.config(input.repoRoot)?.global.credential_profiles ?? [],
@@ -418,9 +430,12 @@ export class Orchestrator {
418
430
  excluded,
419
431
  });
420
432
  const adapter = this.deps.registry.get(harnessId);
433
+ const quota = this.vendorQuotaObservations();
421
434
  const entries = await Promise.all(profiles.map(async (profile) => ({
422
435
  profile,
423
- status: await probeCredentialProfileStatus(profile, adapter?.probeCredentialProfile?.bind(adapter)),
436
+ // Rotating INTO a profile the vendor has already rejected would spend a
437
+ // whole attempt to rediscover the 401 the poller reported a minute ago.
438
+ status: vendorVerifiedProfileStatus(await probeCredentialProfileStatus(profile, adapter?.probeCredentialProfile?.bind(adapter)), quota),
424
439
  })));
425
440
  return new Set(entries
426
441
  .filter(({ profile, status }) => profileStatusAdmits(profile, status))
@@ -576,6 +591,9 @@ export class Orchestrator {
576
591
  dropped.push(detail);
577
592
  droppedLanes.push({ harnessId, stage, detail });
578
593
  };
594
+ // One observation set for the whole admission pass, so two lanes cannot be
595
+ // judged against different vendor epochs.
596
+ const vendorQuota = this.vendorQuotaObservations();
579
597
  for (const id of ids) {
580
598
  const adapter = this.deps.registry.get(id);
581
599
  if (!adapter) {
@@ -639,6 +657,11 @@ export class Orchestrator {
639
657
  profileId: this.effectiveProfileId(input, id),
640
658
  harnessId: id,
641
659
  probe: profileAdapter?.probeCredentialProfile?.bind(profileAdapter),
660
+ // `verification: passed` from the local store only means a login file
661
+ // is present. The poller's authenticated vendor call is the only
662
+ // liveness evidence we have, and admission is the surface that must
663
+ // act on it — otherwise the run dispatches into a revoked token.
664
+ quota: vendorQuota,
642
665
  });
643
666
  if (profileVerdict !== null) {
644
667
  if (profileVerdict === "available") {
@@ -671,7 +694,7 @@ export class Orchestrator {
671
694
  const readOnlyIntent = intent === "plan" || intent === "spec" || intent === "explain" || intent === "audit";
672
695
  const requiredAccess = this.requestRequirements.adapterAccess(intent, manifest.capabilities.implementation_transport, readOnlyIntent
673
696
  ? "readonly"
674
- : (input.access ?? this.config(input.repoRoot).trust.access_default));
697
+ : (input.access ?? this.config(input.repoRoot).trust.access_default), externallyConfinedLane(input.delegated === true));
675
698
  const accessSupported = !requiredAccess || manifest.access_profiles_supported.includes(requiredAccess);
676
699
  const webSupport = manifest.capabilities.web_policy;
677
700
  // Match routeSpecKnobs: a per-harness web default upgrades run-level auto,
@@ -1332,11 +1355,28 @@ export class Orchestrator {
1332
1355
  return { pointerLine: null };
1333
1356
  }
1334
1357
  }
1335
- async runCandidateInEnvelope(routed, envelope, attemptId, label, contract, prompt, store, paths, wsm, ledger, access = "workspace_write", onHarnessEvent, signal, modelHint, effortHint, intent = "implement", log, effectiveWebMode, interaction, budgetGuard, runInput, streamDeltas = false, fileBackedContext,
1358
+ /**
1359
+ * The ONE place an attempt's HOME and OS boundary are decided.
1360
+ *
1361
+ * Called by every caller of `runCandidateInEnvelope` INSIDE that caller's
1362
+ * try, so a delegated refusal is caught by the same catch that writes the
1363
+ * attempt record, and so the decided home is in scope there.
1364
+ */
1365
+ harnessHomeFor(wsm, envelope, routed, runInput) {
1366
+ // Refuses (never silently degrades) when a delegated run cannot be confined.
1367
+ return scopedHarnessHome(wsm, envelope, envelope.worktree_path === envelope.repo_root, runInput?.delegated === true, routed.adapterAccess);
1368
+ }
1369
+ async runCandidateInEnvelope(routed, envelope, attemptId, label, contract, prompt, store, paths, wsm, ledger, access, onHarnessEvent, signal, modelHint, effortHint, intent, log, effectiveWebMode, interaction, budgetGuard, runInput, streamDeltas, fileBackedContext,
1336
1370
  /** D-16d: when set, the mechanical continuation checkpoint pointer for a
1337
1371
  * one-shot fresh-session continuation — appended to the prompt so the model
1338
1372
  * (and the offline fake) re-grounds in the exhausted attempt's partial work. */
1339
- continuationPointer) {
1373
+ continuationPointer,
1374
+ /** Decided by `harnessHomeFor` in the CALLER, so the per-attempt applied
1375
+ * facts exist in the caller's catch too: an attempt that ran and then threw
1376
+ * must record what it ran under, not just why it stopped. REQUIRED (no
1377
+ * default): a silent fallback here would spawn a delegated child on the
1378
+ * operator's real home while the record still claimed confinement. */
1379
+ harnessHome) {
1340
1380
  const adapter = routed.adapter;
1341
1381
  const knobs = this.routeSpecKnobs(routed, contract, modelHint, effortHint);
1342
1382
  // Isolated scoped-home sessions are never retained after disposal.
@@ -1357,7 +1397,9 @@ export class Orchestrator {
1357
1397
  intent,
1358
1398
  // Engine-derived read-only prompt constraints: protected/auto-protected
1359
1399
  // paths PLUS the exact typed gate argv the run will execute (QA-022 FIX B).
1360
- prompt: promptWithEngineConstraints([prompt, laneContinuity?.pointerLine, continuationPointer]
1400
+ prompt: promptWithEngineConstraints(
1401
+ // A child running without an OS boundary is TOLD so (disclosure 2 of 3).
1402
+ [prompt, confinementNotice(harnessHome), laneContinuity?.pointerLine, continuationPointer]
1361
1403
  .filter((s) => typeof s === "string" && s.length > 0)
1362
1404
  .join("\n\n"), contract.constraints.protected_paths, contract.constraints.auto_protected_paths, contract.constraints.protected_path_approvals, contract.tests.commands),
1363
1405
  attachments: runInput?.attachments ?? [],
@@ -1379,9 +1421,14 @@ export class Orchestrator {
1379
1421
  ...(inPlaceEnvelope && sessionFields?.resume_session_id
1380
1422
  ? { resume_session_id: sessionFields.resume_session_id }
1381
1423
  : {}),
1382
- // Scoped harness home only for isolated envelopes; in-place runs use the
1383
- // native environment so the resumed vendor session is actually reachable.
1384
- ...(inPlaceEnvelope ? {} : { env: wsm.envFor(envelope) }),
1424
+ // Scoped harness home for isolated envelopes AND for every delegated run;
1425
+ // an ordinary in-place run keeps the native environment so the resumed
1426
+ // vendor session is reachable. See scopedHarnessHome for the cost a
1427
+ // delegated in-place attempt pays for that confinement.
1428
+ ...(harnessHome.env ? { env: harnessHome.env } : {}),
1429
+ // The OS boundary itself. The shared CLI run loop wraps the argv with it;
1430
+ // no adapter opts in, so no adapter can forget.
1431
+ confinement: harnessHome.confinement,
1385
1432
  raw_context_packet: rawContextPacket,
1386
1433
  stream_deltas: streamDeltas,
1387
1434
  });
@@ -1392,6 +1439,9 @@ export class Orchestrator {
1392
1439
  const workEnvelope = this.workReportEnvelopeFor(routed, contract, Boolean(interaction));
1393
1440
  const workReportMode = this.applyWorkEnvelope(spec, workEnvelope);
1394
1441
  const inactivityMs = harnessInactivityTimeoutMs(this.config(contract.repo.root));
1442
+ // Named once: the attempt record, the CandidateRun and the terminal gate all
1443
+ // read the SAME applied facts rather than three re-derivations.
1444
+ const applied = appliedAttemptFacts(harnessHome, spec.access, sessionFields?.credential_profile?.profile_id ?? runInput?.credentialProfileId ?? null);
1395
1445
  const attemptStartedMs = Date.now();
1396
1446
  const budgetSignalState = { quotaPressureDisclosed: false };
1397
1447
  const triedProfiles = new Set(); // W5.4 failover: each profile at most once
@@ -1763,6 +1813,10 @@ export class Orchestrator {
1763
1813
  ...(secretDiffRefusal ? { secret_diff_refusal: secretDiffRefusal } : {}),
1764
1814
  gates: gates.map((g) => ({ id: g.id, status: g.status })),
1765
1815
  branch: envelope.branch_name,
1816
+ // Applied facts, not promises: the caller of a delegated run
1817
+ // verifies the confinement it asked for instead of trusting it.
1818
+ // Built by the SAME shape the failure path writes.
1819
+ ...applied,
1766
1820
  },
1767
1821
  }), {
1768
1822
  totalUsd: cost,
@@ -1786,6 +1840,7 @@ export class Orchestrator {
1786
1840
  telemetry,
1787
1841
  ...(secretDiffRefusal ? { secretDiffRefusal } : {}),
1788
1842
  outcomeClass: finalized.outcomeClass,
1843
+ applied,
1789
1844
  };
1790
1845
  }
1791
1846
  interactionChannelFor(input, log, runId, taskId, attemptId, harnessId,
@@ -2055,6 +2110,9 @@ export class Orchestrator {
2055
2110
  const knobs = this.routeSpecKnobs(slot.routed, contract, downgradeModel ?? undefined, input.effort);
2056
2111
  const effectiveWeb = this.discloseWebUpgrade(log, slot.routed, knobs.webPolicy, slot.attemptId);
2057
2112
  let envelope;
2113
+ // Declared OUTSIDE the try so the catch below can state what the attempt
2114
+ // actually ran under, not merely that it stopped.
2115
+ let harnessHome;
2058
2116
  try {
2059
2117
  log.emit("harness.started", {
2060
2118
  harness_id: adapter.id,
@@ -2074,6 +2132,7 @@ export class Orchestrator {
2074
2132
  requestedSingleCandidate &&
2075
2133
  slot.routed.implementationTransport !== "git_patch_envelope",
2076
2134
  });
2135
+ harnessHome = this.harnessHomeFor(wsm, envelope, slot.routed, input);
2077
2136
  const run = await this.runCandidateInEnvelope(slot.routed, envelope, slot.attemptId, slot.label, contract, input.prompt, store, paths, wsm, ledger, candidateAccess, (ev) => {
2078
2137
  const safeEv = redactHarnessEvent(ev);
2079
2138
  safeInvoke(input.onHarnessEvent, safeEv);
@@ -2084,7 +2143,8 @@ export class Orchestrator {
2084
2143
  return false;
2085
2144
  budgetStopped = true;
2086
2145
  return true;
2087
- }, input, requestedSingleCandidate);
2146
+ }, input, requestedSingleCandidate, // W-C4 deltas: single-candidate chat lane only (racing = noise x N)
2147
+ undefined, undefined, harnessHome);
2088
2148
  ledger.settle(slot.leaseId, attemptUsageCostSettlement(run.cost, run.costEstimated, run.attemptId, run.harnessId, run.telemetry.authMode, run.telemetry.usageCost));
2089
2149
  log.emit("harness.completed", {
2090
2150
  harness_id: adapter.id,
@@ -2160,7 +2220,7 @@ export class Orchestrator {
2160
2220
  return false;
2161
2221
  budgetStopped = true;
2162
2222
  return true;
2163
- }, input, requestedSingleCandidate, undefined, packet.pointerLine ?? undefined);
2223
+ }, input, requestedSingleCandidate, undefined, packet.pointerLine ?? undefined, harnessHome);
2164
2224
  ledger.settle(contLeaseId, attemptUsageCostSettlement(contRun.cost, contRun.costEstimated, contRun.attemptId, contRun.harnessId, contRun.telemetry.authMode, contRun.telemetry.usageCost));
2165
2225
  log.emit("harness.completed", {
2166
2226
  harness_id: adapter.id,
@@ -2206,6 +2266,7 @@ export class Orchestrator {
2206
2266
  const failureCost = AC.attemptFailureCost(err, "post-stream-error", 0);
2207
2267
  ledger.settle(slot.leaseId, failureCost.settlement);
2208
2268
  const message = safeErrorMessage(err);
2269
+ const declared = declaredFailure(err);
2209
2270
  const infraPhase = envelope === undefined ? "workspace" : "harness";
2210
2271
  log.emit("harness.completed", {
2211
2272
  harness_id: adapter.id,
@@ -2214,7 +2275,7 @@ export class Orchestrator {
2214
2275
  error: message,
2215
2276
  phase: infraPhase,
2216
2277
  });
2217
- store.writeYaml(join(paths.attemptsDir, slot.attemptId, "attempt.yaml"), AC.attemptFailureRecord(slot.attemptId, adapter.id, failureCost, infraPhase, message));
2278
+ store.writeYaml(join(paths.attemptsDir, slot.attemptId, "attempt.yaml"), AC.attemptFailureRecord(slot.attemptId, adapter.id, failureCost, infraPhase, message, appliedAttemptFacts(harnessHome, slot.routed.adapterAccess, this.effectiveProfileId(input, adapter.id))));
2218
2279
  runsBySlot[slotIdx] = {
2219
2280
  attemptId: slot.attemptId,
2220
2281
  harnessId: adapter.id,
@@ -2231,6 +2292,11 @@ export class Orchestrator {
2231
2292
  slot.routed.delegationRequirement,
2232
2293
  ], knobs.model),
2233
2294
  infraPhase,
2295
+ // Keep a TYPED pre-spawn refusal (spent quota window + its reset)
2296
+ // alive past this catch; `message` alone would force the terminal to
2297
+ // read prose back out.
2298
+ ...(declared.code ? { declaredFailure: declared } : {}),
2299
+ applied: appliedAttemptFacts(harnessHome, slot.routed.adapterAccess, this.effectiveProfileId(input, adapter.id)),
2234
2300
  };
2235
2301
  }
2236
2302
  finally {
@@ -2240,6 +2306,9 @@ export class Orchestrator {
2240
2306
  };
2241
2307
  await runBounded(slots, Math.min(slots.length, MAX_PARALLEL_CANDIDATES), runSlot);
2242
2308
  const runs = runsBySlot.filter((r) => r !== undefined);
2309
+ // Fail-closed terminal: a delegated mutating run whose attempts cannot state
2310
+ // their applied confinement refuses instead of passing.
2311
+ assertDelegatedEvidence(input.delegated === true, candidateAccess, runs);
2243
2312
  const cancelledCandidates = () => runs.map((r) => ({
2244
2313
  attemptId: r.attemptId,
2245
2314
  harnessId: r.harnessId,
@@ -2363,15 +2432,21 @@ export class Orchestrator {
2363
2432
  // harness cause (timeout, rate limit, crash, config) gets remediation that
2364
2433
  // fits it, instead of a doomed "Check harness authentication".
2365
2434
  const harnessCategory = dominantHarnessFailureCategory(first.telemetry.transientFailures);
2435
+ // A run speaks with a candidate's TYPED refusal only when EVERY candidate
2436
+ // died of the same one (candidateEvidence owns that rule); mixed causes
2437
+ // keep the honest harness terminal.
2438
+ const unanimous = unanimousDeclaredFailure(runs);
2366
2439
  writeFailure(store, paths, {
2367
2440
  phase,
2368
- category: phase === "workspace" ? "project" : "harness_error",
2441
+ category: unanimous?.category ?? (phase === "workspace" ? "project" : "harness_error"),
2442
+ code: unanimous?.code ?? null,
2369
2443
  harnessId: first.harnessId,
2370
2444
  attemptId: first.attemptId,
2371
2445
  safeMessage: rootCause,
2372
2446
  rawDetailRef: `attempts/${first.attemptId}/attempt.yaml`,
2373
2447
  eventRefs: existingEventRefs,
2374
2448
  runDir: paths.root,
2449
+ resetsAt: unanimous?.resetsAt ?? null,
2375
2450
  nextActions: first.secretDiffRefusal
2376
2451
  ? secretDiff.secretDiffNextActions(first.secretDiffRefusal)
2377
2452
  : phase === "workspace"
@@ -2483,11 +2558,12 @@ export class Orchestrator {
2483
2558
  dirtyPolicy: "snapshot",
2484
2559
  accessProfile: candidateAccess,
2485
2560
  });
2561
+ const synthHome = this.harnessHomeFor(wsm, envelope, synthRouted, input);
2486
2562
  const run = await this.runCandidateInEnvelope(synthRouted, envelope, "synth", "Synthesis", contract, synthesisInput.prompt, store, paths, wsm, ledger, candidateAccess, (ev) => {
2487
2563
  const safeEv = redactHarnessEvent(ev);
2488
2564
  safeInvoke(input.onHarnessEvent, safeEv);
2489
2565
  log.emit("harness.event", harnessEventPayload(synthAdapter.id, "synth", safeEv));
2490
- }, input.signal, undefined, input.effort, "synthesize", log, effectiveWeb, this.interactionChannelFor(input, log, runId, taskId, "synth", synthAdapter.id, synthRouted.supportsInteractive), undefined, input, false, synthesisInput.content);
2566
+ }, input.signal, undefined, input.effort, "synthesize", log, effectiveWeb, this.interactionChannelFor(input, log, runId, taskId, "synth", synthAdapter.id, synthRouted.supportsInteractive), undefined, input, false, synthesisInput.content, undefined, synthHome);
2491
2567
  ledger.settle(lease.lease?.lease_id ?? "", attemptUsageCostSettlement(run.cost, run.costEstimated, run.attemptId, run.harnessId, run.telemetry.authMode, run.telemetry.usageCost));
2492
2568
  reviewEnvelopes.push(envelope);
2493
2569
  envelope = undefined;
@@ -3329,7 +3405,9 @@ export class Orchestrator {
3329
3405
  const knobs = this.routeSpecKnobs(routed, contract, undefined, input.effort);
3330
3406
  const effectiveWeb = this.discloseWebUpgrade(log, routed, knobs.webPolicy, attemptId);
3331
3407
  let run;
3408
+ let harnessHome;
3332
3409
  try {
3410
+ harnessHome = this.harnessHomeFor(wsm, envelope, routed, input);
3333
3411
  log.emit("harness.started", {
3334
3412
  harness_id: adapter.id,
3335
3413
  attempt_id: attemptId,
@@ -3343,7 +3421,8 @@ export class Orchestrator {
3343
3421
  }, input.signal, undefined, input.effort, "repair", log, effectiveWeb, this.interactionChannelFor(input, log, runId, taskId, attemptId, adapter.id, routed.supportsInteractive), (streamedUsd) => {
3344
3422
  ledger.updateHold(lease.lease?.lease_id ?? "", streamedUsd);
3345
3423
  return ledger.tier() === "hard";
3346
- }, input, true);
3424
+ }, input, true, // convergence runs one candidate: live deltas on (W-C4)
3425
+ undefined, undefined, harnessHome);
3347
3426
  ledger.settle(lease.lease?.lease_id ?? "", attemptUsageCostSettlement(run.cost, run.costEstimated, run.attemptId, run.harnessId, run.telemetry.authMode, run.telemetry.usageCost));
3348
3427
  log.emit("harness.completed", {
3349
3428
  harness_id: adapter.id,
@@ -3366,8 +3445,9 @@ export class Orchestrator {
3366
3445
  status: "failed",
3367
3446
  error: message,
3368
3447
  });
3369
- store.writeYaml(join(paths.attemptsDir, attemptId, "attempt.yaml"), AC.attemptFailureRecord(attemptId, adapter.id, failureCost, "harness", message));
3448
+ store.writeYaml(join(paths.attemptsDir, attemptId, "attempt.yaml"), AC.attemptFailureRecord(attemptId, adapter.id, failureCost, "harness", message, appliedAttemptFacts(harnessHome, routed.adapterAccess, this.effectiveProfileId(input, adapter.id))));
3370
3449
  run = {
3450
+ applied: appliedAttemptFacts(harnessHome, routed.adapterAccess, this.effectiveProfileId(input, adapter.id)),
3371
3451
  attemptId,
3372
3452
  harnessId: adapter.id,
3373
3453
  label: `Attempt ${attempt}`,
@@ -3381,6 +3461,9 @@ export class Orchestrator {
3381
3461
  };
3382
3462
  }
3383
3463
  lastRun = run;
3464
+ // Fail-closed twin of the candidate lane's gate: this loop terminalizes
3465
+ // per attempt, so the proof is spent per attempt.
3466
+ assertDelegatedEvidence(input.delegated === true, convergenceAccess, [run]);
3384
3467
  attemptTelemetries.push({ attemptId, harnessId: adapter.id, telemetry: run.telemetry });
3385
3468
  // Cancellation/deadline keeps priority over a belt failure finalized concurrently.
3386
3469
  if (input.signal?.aborted)