audit-tools 0.55.5 → 0.55.6

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 (166) hide show
  1. package/dist/audit/cli/auditStep.d.ts +15 -1
  2. package/dist/audit/cli/auditStep.d.ts.map +1 -1
  3. package/dist/audit/cli/auditStep.js +24 -11
  4. package/dist/audit/cli/auditStep.js.map +1 -1
  5. package/dist/audit/cli/cleanup.d.ts.map +1 -1
  6. package/dist/audit/cli/cleanup.js +26 -2
  7. package/dist/audit/cli/cleanup.js.map +1 -1
  8. package/dist/audit/cli/conceptualDispatch.d.ts +2 -0
  9. package/dist/audit/cli/conceptualDispatch.d.ts.map +1 -1
  10. package/dist/audit/cli/conceptualDispatch.js +29 -8
  11. package/dist/audit/cli/conceptualDispatch.js.map +1 -1
  12. package/dist/audit/cli/dispatch/hostHandoff.d.ts +37 -3
  13. package/dist/audit/cli/dispatch/hostHandoff.d.ts.map +1 -1
  14. package/dist/audit/cli/dispatch/hostHandoff.js +185 -16
  15. package/dist/audit/cli/dispatch/hostHandoff.js.map +1 -1
  16. package/dist/audit/cli/fanoutLanes.d.ts +30 -0
  17. package/dist/audit/cli/fanoutLanes.d.ts.map +1 -1
  18. package/dist/audit/cli/fanoutLanes.js +76 -8
  19. package/dist/audit/cli/fanoutLanes.js.map +1 -1
  20. package/dist/audit/cli/nextStepCommand.d.ts +3 -0
  21. package/dist/audit/cli/nextStepCommand.d.ts.map +1 -1
  22. package/dist/audit/cli/nextStepCommand.js +104 -66
  23. package/dist/audit/cli/nextStepCommand.js.map +1 -1
  24. package/dist/audit/cli/nextStepHelpers.d.ts +49 -14
  25. package/dist/audit/cli/nextStepHelpers.d.ts.map +1 -1
  26. package/dist/audit/cli/nextStepHelpers.js +164 -68
  27. package/dist/audit/cli/nextStepHelpers.js.map +1 -1
  28. package/dist/audit/cli/reviewRun.d.ts +4 -0
  29. package/dist/audit/cli/reviewRun.d.ts.map +1 -1
  30. package/dist/audit/cli/reviewRun.js +1 -1
  31. package/dist/audit/cli/reviewRun.js.map +1 -1
  32. package/dist/audit/cli/semanticReviewStep.d.ts +2 -0
  33. package/dist/audit/cli/semanticReviewStep.d.ts.map +1 -1
  34. package/dist/audit/cli/semanticReviewStep.js +5 -50
  35. package/dist/audit/cli/semanticReviewStep.js.map +1 -1
  36. package/dist/audit/cli/validateResultsCommand.d.ts.map +1 -1
  37. package/dist/audit/cli/validateResultsCommand.js +3 -1
  38. package/dist/audit/cli/validateResultsCommand.js.map +1 -1
  39. package/dist/audit/extractors/fsIntake.d.ts +13 -0
  40. package/dist/audit/extractors/fsIntake.d.ts.map +1 -1
  41. package/dist/audit/extractors/fsIntake.js +11 -1
  42. package/dist/audit/extractors/fsIntake.js.map +1 -1
  43. package/dist/audit/io/artifacts.d.ts +35 -9
  44. package/dist/audit/io/artifacts.d.ts.map +1 -1
  45. package/dist/audit/io/artifacts.js +84 -67
  46. package/dist/audit/io/artifacts.js.map +1 -1
  47. package/dist/audit/io/rollover.d.ts +18 -0
  48. package/dist/audit/io/rollover.d.ts.map +1 -0
  49. package/dist/audit/io/rollover.js +152 -0
  50. package/dist/audit/io/rollover.js.map +1 -0
  51. package/dist/audit/io/runSnapshot.d.ts +149 -0
  52. package/dist/audit/io/runSnapshot.d.ts.map +1 -0
  53. package/dist/audit/io/runSnapshot.js +710 -0
  54. package/dist/audit/io/runSnapshot.js.map +1 -0
  55. package/dist/audit/orchestrator/advanceTypes.d.ts +11 -0
  56. package/dist/audit/orchestrator/advanceTypes.d.ts.map +1 -1
  57. package/dist/audit/orchestrator/autoFixExecutor.d.ts +9 -0
  58. package/dist/audit/orchestrator/autoFixExecutor.d.ts.map +1 -1
  59. package/dist/audit/orchestrator/autoFixExecutor.js +22 -2
  60. package/dist/audit/orchestrator/autoFixExecutor.js.map +1 -1
  61. package/dist/audit/orchestrator/charterExtractionExecutor.d.ts.map +1 -1
  62. package/dist/audit/orchestrator/charterExtractionExecutor.js +18 -3
  63. package/dist/audit/orchestrator/charterExtractionExecutor.js.map +1 -1
  64. package/dist/audit/orchestrator/charterPackets.d.ts +11 -1
  65. package/dist/audit/orchestrator/charterPackets.d.ts.map +1 -1
  66. package/dist/audit/orchestrator/charterPackets.js +13 -0
  67. package/dist/audit/orchestrator/charterPackets.js.map +1 -1
  68. package/dist/audit/orchestrator/designReviewPrompt.d.ts +9 -0
  69. package/dist/audit/orchestrator/designReviewPrompt.d.ts.map +1 -1
  70. package/dist/audit/orchestrator/designReviewPrompt.js +69 -31
  71. package/dist/audit/orchestrator/designReviewPrompt.js.map +1 -1
  72. package/dist/audit/orchestrator/designReviewSnapshot.d.ts +1 -0
  73. package/dist/audit/orchestrator/designReviewSnapshot.d.ts.map +1 -1
  74. package/dist/audit/orchestrator/designReviewSnapshot.js +2 -1
  75. package/dist/audit/orchestrator/designReviewSnapshot.js.map +1 -1
  76. package/dist/audit/orchestrator/executorRunners.d.ts.map +1 -1
  77. package/dist/audit/orchestrator/executorRunners.js +13 -4
  78. package/dist/audit/orchestrator/executorRunners.js.map +1 -1
  79. package/dist/audit/orchestrator/intakeExecutors.d.ts +8 -1
  80. package/dist/audit/orchestrator/intakeExecutors.d.ts.map +1 -1
  81. package/dist/audit/orchestrator/intakeExecutors.js +13 -4
  82. package/dist/audit/orchestrator/intakeExecutors.js.map +1 -1
  83. package/dist/audit/orchestrator/obligationPolicy.d.ts.map +1 -1
  84. package/dist/audit/orchestrator/obligationPolicy.js +4 -1
  85. package/dist/audit/orchestrator/obligationPolicy.js.map +1 -1
  86. package/dist/audit/orchestrator/planningExecutors.d.ts +1 -1
  87. package/dist/audit/orchestrator/planningExecutors.d.ts.map +1 -1
  88. package/dist/audit/orchestrator/planningExecutors.js +40 -6
  89. package/dist/audit/orchestrator/planningExecutors.js.map +1 -1
  90. package/dist/audit/orchestrator/systemicChallengeExecutor.d.ts.map +1 -1
  91. package/dist/audit/orchestrator/systemicChallengeExecutor.js +4 -1
  92. package/dist/audit/orchestrator/systemicChallengeExecutor.js.map +1 -1
  93. package/dist/audit/orchestrator/taskBuilder.d.ts +20 -0
  94. package/dist/audit/orchestrator/taskBuilder.d.ts.map +1 -1
  95. package/dist/audit/orchestrator/taskBuilder.js +80 -18
  96. package/dist/audit/orchestrator/taskBuilder.js.map +1 -1
  97. package/dist/audit/systemic/reviewFileMap.d.ts +72 -21
  98. package/dist/audit/systemic/reviewFileMap.d.ts.map +1 -1
  99. package/dist/audit/systemic/reviewFileMap.js +96 -45
  100. package/dist/audit/systemic/reviewFileMap.js.map +1 -1
  101. package/dist/audit/systemic/secondOrderAdversaryPrompt.d.ts +7 -0
  102. package/dist/audit/systemic/secondOrderAdversaryPrompt.d.ts.map +1 -1
  103. package/dist/audit/systemic/secondOrderAdversaryPrompt.js +39 -8
  104. package/dist/audit/systemic/secondOrderAdversaryPrompt.js.map +1 -1
  105. package/dist/remediate/phases/close.d.ts.map +1 -1
  106. package/dist/remediate/phases/close.js +19 -4
  107. package/dist/remediate/phases/close.js.map +1 -1
  108. package/dist/remediate/phases/triage.d.ts +2 -1
  109. package/dist/remediate/phases/triage.d.ts.map +1 -1
  110. package/dist/remediate/phases/triage.js +46 -25
  111. package/dist/remediate/phases/triage.js.map +1 -1
  112. package/dist/remediate/phases/triageConformance.d.ts +14 -0
  113. package/dist/remediate/phases/triageConformance.d.ts.map +1 -0
  114. package/dist/remediate/phases/triageConformance.js +189 -0
  115. package/dist/remediate/phases/triageConformance.js.map +1 -0
  116. package/dist/remediate/state/decisionConsumption.d.ts +110 -0
  117. package/dist/remediate/state/decisionConsumption.d.ts.map +1 -0
  118. package/dist/remediate/state/decisionConsumption.js +122 -0
  119. package/dist/remediate/state/decisionConsumption.js.map +1 -0
  120. package/dist/remediate/state/store.d.ts +4 -0
  121. package/dist/remediate/state/store.d.ts.map +1 -1
  122. package/dist/remediate/state/store.js +16 -0
  123. package/dist/remediate/state/store.js.map +1 -1
  124. package/dist/remediate/steps/dispatch/contractConformanceReview.d.ts +9 -0
  125. package/dist/remediate/steps/dispatch/contractConformanceReview.d.ts.map +1 -1
  126. package/dist/remediate/steps/dispatch/contractConformanceReview.js +32 -14
  127. package/dist/remediate/steps/dispatch/contractConformanceReview.js.map +1 -1
  128. package/dist/remediate/steps/dispatch/hostHandoff.d.ts.map +1 -1
  129. package/dist/remediate/steps/dispatch/hostHandoff.js +4 -0
  130. package/dist/remediate/steps/dispatch/hostHandoff.js.map +1 -1
  131. package/dist/remediate/steps/gateCommands.d.ts.map +1 -1
  132. package/dist/remediate/steps/gateCommands.js +6 -52
  133. package/dist/remediate/steps/gateCommands.js.map +1 -1
  134. package/dist/remediate/steps/nextStep.d.ts +7 -0
  135. package/dist/remediate/steps/nextStep.d.ts.map +1 -1
  136. package/dist/remediate/steps/nextStep.js +56 -19
  137. package/dist/remediate/steps/nextStep.js.map +1 -1
  138. package/dist/shared/contentKey.d.ts +3 -2
  139. package/dist/shared/contentKey.d.ts.map +1 -1
  140. package/dist/shared/contentKey.js +3 -2
  141. package/dist/shared/contentKey.js.map +1 -1
  142. package/dist/shared/decompose/charterExtraction.d.ts +189 -1
  143. package/dist/shared/decompose/charterExtraction.d.ts.map +1 -1
  144. package/dist/shared/decompose/charterExtraction.js +17 -2
  145. package/dist/shared/decompose/charterExtraction.js.map +1 -1
  146. package/dist/shared/git.d.ts +30 -1
  147. package/dist/shared/git.d.ts.map +1 -1
  148. package/dist/shared/git.js +65 -6
  149. package/dist/shared/git.js.map +1 -1
  150. package/dist/shared/loopCorePaths.d.ts.map +1 -1
  151. package/dist/shared/loopCorePaths.js +2 -0
  152. package/dist/shared/loopCorePaths.js.map +1 -1
  153. package/dist/shared/prompts.d.ts +2 -0
  154. package/dist/shared/prompts.d.ts.map +1 -1
  155. package/dist/shared/prompts.js +8 -2
  156. package/dist/shared/prompts.js.map +1 -1
  157. package/dist/shared/submission/ingestReport.d.ts.map +1 -1
  158. package/dist/shared/submission/ingestReport.js +19 -1
  159. package/dist/shared/submission/ingestReport.js.map +1 -1
  160. package/dist/shared/types/charter.d.ts +9 -0
  161. package/dist/shared/types/charter.d.ts.map +1 -1
  162. package/dist/shared/types/charter.js +7 -0
  163. package/dist/shared/types/charter.js.map +1 -1
  164. package/docs/audit-pkg/contracts.md +6 -2
  165. package/docs/audit-pkg/operator-guide.md +13 -4
  166. package/package.json +2 -1
@@ -1,6 +1,6 @@
1
1
  import { designReviewInputRevision } from "../orchestrator/designReviewProjection.js";
2
2
  import { decodeAuditReviewSubmission, readAuditReviewSubmission, currentAuditReviewInputRevision, auditLaneReviewRequirement } from "./reviewSubmission.js";
3
- // sites-pinned: tests/audit/next-step-helpers.test.ts, tests/audit/charter-emit-order.test.ts, tests/audit/executor-registry-sync.test.ts, tests/audit/pipeline-integration.test.ts
3
+ // sites-pinned: tests/audit/next-step-helpers.test.ts, tests/audit/charter-emit-order.test.ts, tests/audit/executor-registry-sync.test.ts, tests/audit/pipeline-integration.test.ts, tests/audit/next-step-integrity-reintake.test.ts
4
4
  /**
5
5
  * Extracted helpers for the next-step command.
6
6
  *
@@ -11,14 +11,16 @@ import { decodeAuditReviewSubmission, readAuditReviewSubmission, currentAuditRev
11
11
  import { AnalyzerRunConsentDecisionSchema, readRunConsentUnlocked, updateRunConsentUnlocked } from "../../shared/analyzerRunConsent.js";
12
12
  import { mkdir, readFile, unlink } from "node:fs/promises";
13
13
  import { join, relative } from "node:path";
14
- import { advance, ANALYZER_SETTINGS, describeStoppedFold, RunLogger, hashContent, isFileMissingError, isJsonParseError, isRecord, readJsonFile, readSubmissionIngestHistory, readTrailingSubmissionRefusals, persistAnalyzerSettings, writeJsonFile, } from "audit-tools/shared";
14
+ import { AUDIT_FINDINGS_FILENAME, auditFindingsPath, promotedAuditFindingsPath, advance, ANALYZER_SETTINGS, describeStoppedFold, RunLogger, hashContent, isFileMissingError, isJsonParseError, isRecord, readJsonFile, readSubmissionIngestHistory, readTrailingSubmissionRefusals, persistAnalyzerSettings, writeJsonFile, writeTextFile, } from "audit-tools/shared";
15
15
  import { loadArtifactBundle, promoteFinalAuditReport, } from "../io/artifacts.js";
16
+ import { ensureRunSourceRoot, removeRunSnapshot, resolveRunSince } from "../io/runSnapshot.js";
17
+ import { markRunEnded } from "../io/rollover.js";
16
18
  import { auditReportPath, groundDesignFindings, repoPathUniverse, systemicChallengeSchema, laneAssetsDir, promotedAuditReportPath, } from "audit-tools/shared";
17
19
  import { charterExtractionKindsForCeiling } from "./charterExtractionPrompt.js";
18
20
  import { archiveCharterPackets } from "../orchestrator/charterPacketArchive.js";
19
21
  import { LANE_SUBMISSION_SCHEMAS, charterLaneSchema, describeSubmissionShapeMismatch, unwrapSubmissionArray, } from "./laneValidators.js";
20
22
  import { ConceptualJudgeSubmissionSchema, buildConceptualReviewAdjudication, deriveConceptualVerificationStatus, loadConceptualPerspectiveFindings, MalformedConceptualPerspectivesError, readConceptualReviewRoundManifest, SubmittedDesignFindingSchema, suppliedToolVerdictIssue, } from "../types/conceptualAdjudication.js";
21
- import { advanceAudit, engineMaxTransitions, findExecutorFailure, runSingleAdvanceStep, startAdvanceHeartbeat, MAX_DRAIN_STEPS, } from "../orchestrator/advance.js";
23
+ import { engineMaxTransitions, findExecutorFailure, runSingleAdvanceStep, startAdvanceHeartbeat, MAX_DRAIN_STEPS, } from "../orchestrator/advance.js";
22
24
  import { buildDesignReviewSnapshot, isDesignReviewStale, } from "../orchestrator/designReviewSnapshot.js";
23
25
  import { computeStaleArtifacts, emitStalenessRecord, isMetadataMigrationStaleness, resetStalenessDedup, } from "../orchestrator/staleness.js";
24
26
  import { commitFold, createFoldTransaction, markSubmissionApplied, describeQuarantineLocation, quarantineLocationPhrase, quarantineSubmissionFile, quarantineSurvivalNote, recoverStagedSubmissions, stageLaneSubmission, } from "./foldTransaction.js";
@@ -39,7 +41,8 @@ import { buildPendingAuditTasks } from "./dispatch/packetFilter.js";
39
41
  import { buildLineIndex } from "./lineIndex.js";
40
42
  import { ingestAuditHostResults, } from "./dispatch/hostHandoff.js";
41
43
  import { auditIngestRemedy, } from "../validation/ingestIssueCodes.js";
42
- import { CHARTER_EXTRACTION_MERGED_FILENAME, AUDIT_GATE_SUBMISSION_SCOPE, GATE_LANES, charterExtractionCoverageFilename, charterExtractionLane, closeDispatchedLaneOutcomes, laneSubmissionPath, laneSubmissionId, recordLaneOutcome, systemicChallengeLane, } from "./laneSubmissions.js";
44
+ import { buildCharterPacketManifest, carriedCharterLane, materializeCharterPacket, } from "../orchestrator/charterPackets.js";
45
+ import { CHARTER_EXTRACTION_MERGED_FILENAME, AUDIT_GATE_SUBMISSION_SCOPE, GATE_LANES, charterExtractionCoverageFilename, charterExtractionLane, charterExtractionPacketFilename, closeDispatchedLaneOutcomes, laneSubmissionPath, laneSubmissionId, recordLaneOutcome, systemicChallengeLane, } from "./laneSubmissions.js";
43
46
  import { recordHostResultOutcomes, renderIngestReportLines } from "audit-tools/shared";
44
47
  /**
45
48
  * Read a lane's submission from the TOOL-COMPUTED path its emission bound —
@@ -90,6 +93,17 @@ export async function tryConsumeSubmission(artifactsDir, lane, tx, currentInputR
90
93
  throw error;
91
94
  }
92
95
  }
96
+ /**
97
+ * The fold's source root. Throws when read before the fold resolved the run's
98
+ * snapshot: a content read against the LIVE root would silently break the
99
+ * frozen-input property, so there is no fallback.
100
+ */
101
+ export function requireSourceRoot(params) {
102
+ if (params.sourceRoot === undefined) {
103
+ throw new Error("next-step: a content read ran before the run's snapshot was resolved");
104
+ }
105
+ return params.sourceRoot;
106
+ }
93
107
  /**
94
108
  * The return-kind set as RUNTIME data, so a drift guard can IMPORT the real set
95
109
  * instead of transcribing it into a literal that silently agrees with a second
@@ -153,14 +167,37 @@ async function promoteCompletedReport(artifactsDir) {
153
167
  readFile(inPlacePath, "utf8").catch(() => null),
154
168
  ]);
155
169
  // A missing in-place render alongside an existing promoted file is the
156
- // legitimate re-entry AFTER promotion (promotion deletes artifactsDir);
170
+ // legitimate re-entry AFTER promotion (the next run's rollover deletes it);
157
171
  // a PRESENT in-place render that differs is precisely the stale-promotion
158
172
  // case and must fall through to promote.
159
173
  const alreadyPromoted = promotedText !== null && (inPlaceText === null || promotedText === inPlaceText);
160
- if (alreadyPromoted)
161
- return promotedPath;
162
- const promoted = await promoteFinalAuditReport({ artifactsDir });
163
- return promoted.promoted ? promotedPath : inPlacePath;
174
+ // The machine contract must be one level up before the run may end: the
175
+ // rollover deletes the in-place copy and re-archives only the append-only
176
+ // diagnostics (INV 1). Identity, not existence, here too — a PREVIOUS
177
+ // audit's promoted contract always exists — so a contract whose earlier copy
178
+ // failed is promoted again rather than taken as archived.
179
+ const [inPlaceFindings, promotedFindings] = await Promise.all([
180
+ readFile(auditFindingsPath(artifactsDir)).catch(() => null),
181
+ readFile(promotedAuditFindingsPath(artifactsDir)).catch(() => null),
182
+ ]);
183
+ const findingsCurrent = inPlaceFindings === null || (promotedFindings !== null && inPlaceFindings.equals(promotedFindings));
184
+ let findingsArchived = true;
185
+ if (!alreadyPromoted || !findingsCurrent) {
186
+ const promoted = await promoteFinalAuditReport({ artifactsDir });
187
+ if (!promoted.promoted)
188
+ return inPlacePath;
189
+ findingsArchived = !(promoted.unarchived ?? []).some((member) => member.startsWith(AUDIT_FINDINGS_FILENAME));
190
+ }
191
+ // A presented, promoted report ENDS the run, whatever its status (a rendered
192
+ // report on a stopped fold included — re-reading the same frozen snapshot
193
+ // would only stop it again): its snapshot goes, and the marker lets the next
194
+ // next-step roll the dir over (`rollOverFinishedRun`).
195
+ for (const problem of await removeRunSnapshot(artifactsDir)) {
196
+ process.stderr.write(`[audit-code] run snapshot cleanup: ${problem}\n`);
197
+ }
198
+ if (findingsArchived)
199
+ await markRunEnded(artifactsDir);
200
+ return promotedPath;
164
201
  }
165
202
  /**
166
203
  * Build the terminal step for a deterministic fold that has stopped advancing
@@ -217,7 +254,7 @@ function applyRunConsent(options, consent) {
217
254
  }
218
255
  export async function handleAnalyzerConsentBranch(params, bundle, state, analyzersRef, tx) {
219
256
  const pending = pendingAnalyzerConsent({
220
- root: params.root,
257
+ root: requireSourceRoot(params),
221
258
  analyzers: analyzersRef.value,
222
259
  externalAcquisitionEnabled: params.externalAcquisition?.enabled,
223
260
  analyzerConsent: params.externalAcquisition?.analyzerConsent,
@@ -259,7 +296,7 @@ export async function handleGraphEnrichmentBranch(params, bundle, state, analyze
259
296
  // plan draw's classifier (`obligationPolicy.ts`) and this fold agree EXACTLY
260
297
  // on when the analyzer-install consent / edge-reasoning turns are owed.
261
298
  const pauseInputs = {
262
- root: params.root,
299
+ root: requireSourceRoot(params),
263
300
  analyzers: analyzersRef.value,
264
301
  graphLlmEdgeReasoning: params.graphLlmEdgeReasoning,
265
302
  };
@@ -341,6 +378,7 @@ export async function handleGraphEnrichmentBranch(params, bundle, state, analyze
341
378
  // and an unforced call would re-enter a drain.
342
379
  const applied = await runStep({
343
380
  root: params.root,
381
+ sourceRoot: requireSourceRoot(params),
344
382
  artifactsDir: params.artifactsDir,
345
383
  analyzers: analyzersRef.value,
346
384
  graphLlmEdgeReasoning: true,
@@ -1024,7 +1062,7 @@ export async function handleDesignReviewBranch(params, bundle, state, tx) {
1024
1062
  * above for the two gates that deviate and are not run through this engine.
1025
1063
  */
1026
1064
  async function runOmittableGate(descriptor, params, bundle, state, tx) {
1027
- const incoming = await tryConsumeSubmission(params.artifactsDir, descriptor.lane, tx, auditLaneReviewRequirement(descriptor.lane) === "ordinary" ? undefined : () => currentAuditReviewInputRevision(params.root, bundle, descriptor.lane));
1065
+ const incoming = await tryConsumeSubmission(params.artifactsDir, descriptor.lane, tx, auditLaneReviewRequirement(descriptor.lane) === "ordinary" ? undefined : () => currentAuditReviewInputRevision(requireSourceRoot(params), bundle, descriptor.lane));
1028
1066
  if (incoming.status === "malformed") {
1029
1067
  // Not-JSON submission: same quarantine-loudly lifecycle as a mis-shaped one.
1030
1068
  await quarantineMisshapedSubmission(params.artifactsDir, incoming.path, descriptor.lane, incoming.reason);
@@ -1067,6 +1105,7 @@ export async function handleSynthesisNarrativeBranch(params, bundle, state, tx)
1067
1105
  schema: LANE_SUBMISSION_SCHEMAS[GATE_LANES.synthesis_narrative],
1068
1106
  apply: (_value, path, p, foldBundle) => runAuditStepUnlocked({
1069
1107
  root: p.root,
1108
+ sourceRoot: requireSourceRoot(p),
1070
1109
  artifactsDir: p.artifactsDir,
1071
1110
  preferredExecutor: "synthesis_narrative_executor",
1072
1111
  narrativeResultsPath: path,
@@ -1100,6 +1139,7 @@ export async function handleIntentEquivalenceBranch(params, bundle, state, tx) {
1100
1139
  if (parsed.success) {
1101
1140
  const applied = await runAuditStepUnlocked({
1102
1141
  root: params.root,
1142
+ sourceRoot: requireSourceRoot(params),
1103
1143
  artifactsDir: params.artifactsDir,
1104
1144
  preferredExecutor: "intent_equivalence_executor",
1105
1145
  intentEquivalenceVerdictPath: incoming.path,
@@ -1136,6 +1176,7 @@ export async function handleCriticalFlowFallbackBranch(params, bundle, state, tx
1136
1176
  schema: LANE_SUBMISSION_SCHEMAS[GATE_LANES.critical_flow_fallback],
1137
1177
  apply: (_value, path, p, foldBundle) => runAuditStepUnlocked({
1138
1178
  root: p.root,
1179
+ sourceRoot: requireSourceRoot(p),
1139
1180
  artifactsDir: p.artifactsDir,
1140
1181
  preferredExecutor: "critical_flow_fallback_executor",
1141
1182
  criticalFlowFallbackResultsPath: path,
@@ -1157,6 +1198,21 @@ export async function handleCriticalFlowFallbackBranch(params, bundle, state, tx
1157
1198
  * - a `deep`/`deepest` ceiling with no submission yet → `return` the host step
1158
1199
  * that renders the charter-extraction prompt.
1159
1200
  */
1201
+ /**
1202
+ * Materialize one charter kind's evidence packet and its coverage manifest into
1203
+ * the lane assets and return the packet's digest. The emitter calls it for every
1204
+ * kind (each lane reads its packet), and so does the merge (the executor checks
1205
+ * every lane's citations against its kind's manifest — a CARRIED lane's
1206
+ * included — and folds every kind's coverage into the register). Idempotent:
1207
+ * the packet derives from the bundle and the frozen snapshot.
1208
+ */
1209
+ export async function writeCharterLaneAssets(params) {
1210
+ const packetPath = join(laneAssetsDir(params.artifactsDir), charterExtractionPacketFilename(params.kind));
1211
+ const packet = await materializeCharterPacket({ root: params.sourceRoot, bundle: params.bundle, kind: params.kind });
1212
+ await writeTextFile(packetPath, packet.markdown);
1213
+ await writeJsonFile(join(laneAssetsDir(params.artifactsDir), charterExtractionCoverageFilename(params.kind)), buildCharterPacketManifest(params.kind, packet.excerpts, packet.coverage));
1214
+ return { packetPath, packet, digest: hashContent(packet.markdown) };
1215
+ }
1160
1216
  export async function handleCharterExtractionBranch(params, bundle, state, tx) {
1161
1217
  const ceiling = resolveCharterCeiling(bundle.intent_checkpoint);
1162
1218
  // Shallow ceiling (default): omit deterministically, no host turn, no lanes.
@@ -1176,13 +1232,33 @@ export async function handleCharterExtractionBranch(params, bundle, state, tx) {
1176
1232
  const kinds = charterExtractionKindsForCeiling(ceiling);
1177
1233
  const universe = new Set((bundle.repo_manifest?.files ?? []).map((file) => file.path));
1178
1234
  const laneValues = new Map();
1235
+ // A kind whose packet did not change since the previous register's lane was
1236
+ // written is CARRIED (the emitter asked no lane for it); every other kind is
1237
+ // authored from a lane submission. The digest is the one each authored lane
1238
+ // records.
1239
+ const carried = new Map();
1240
+ const digests = new Map();
1241
+ for (const kind of kinds) {
1242
+ const { digest } = await writeCharterLaneAssets({
1243
+ sourceRoot: requireSourceRoot(params),
1244
+ artifactsDir: params.artifactsDir,
1245
+ bundle,
1246
+ kind,
1247
+ });
1248
+ digests.set(kind, digest);
1249
+ const reused = carriedCharterLane(bundle, kind, digest);
1250
+ if (reused !== undefined)
1251
+ carried.set(kind, reused);
1252
+ }
1179
1253
  let quarantinedAny = false;
1180
1254
  for (const kind of kinds) {
1255
+ if (carried.has(kind))
1256
+ continue;
1181
1257
  const lane = charterExtractionLane(kind);
1182
1258
  // Staged per lane; an INCOMPLETE set is restored to its bound paths at
1183
1259
  // commit (un-applied), which is exactly the K-of-N resume the design
1184
1260
  // wants — pending lanes survive on disk until every lane is present.
1185
- const incoming = await tryConsumeSubmission(params.artifactsDir, lane, tx, () => currentAuditReviewInputRevision(params.root, bundle, lane));
1261
+ const incoming = await tryConsumeSubmission(params.artifactsDir, lane, tx, () => currentAuditReviewInputRevision(requireSourceRoot(params), bundle, lane));
1186
1262
  if (incoming.status === "absent")
1187
1263
  continue;
1188
1264
  if (incoming.status === "malformed") {
@@ -1199,7 +1275,7 @@ export async function handleCharterExtractionBranch(params, bundle, state, tx) {
1199
1275
  await quarantineMisshapedSubmission(params.artifactsDir, incoming.path, lane, parsed.error);
1200
1276
  }
1201
1277
  }
1202
- if (!quarantinedAny && laneValues.size === kinds.length) {
1278
+ if (!quarantinedAny && laneValues.size + carried.size === kinds.length) {
1203
1279
  // Complete + valid: tool-side merge (stable by lane order = canonical kind
1204
1280
  // order), then one executor ingest; unlink lane files only after apply.
1205
1281
  // The post-apply unlink is the standard consumed-submission lifecycle
@@ -1213,10 +1289,11 @@ export async function handleCharterExtractionBranch(params, bundle, state, tx) {
1213
1289
  // lane check itself (owner review of prompt 8, 2026-09-17).
1214
1290
  const merged = {
1215
1291
  // `inputs` was checked at the gate; the merged lane carries the DAG only.
1216
- lanes: kinds.map((kind) => {
1292
+ lanes: kinds.filter((kind) => !carried.has(kind)).map((kind) => {
1217
1293
  const { inputs: _declared, ...dag } = laneValues.get(kind).value;
1218
- return { kind, ...dag };
1294
+ return { kind, packet_sha256: digests.get(kind), ...dag };
1219
1295
  }),
1296
+ carried: [...carried.values()],
1220
1297
  };
1221
1298
  // The merged submission is TOOL-written, so it lives with the other lane
1222
1299
  // assets rather than under `submissions/` (which holds only what a host
@@ -1225,6 +1302,7 @@ export async function handleCharterExtractionBranch(params, bundle, state, tx) {
1225
1302
  await writeJsonFile(mergedPath, merged);
1226
1303
  const applied = await runAuditStepUnlocked({
1227
1304
  root: params.root,
1305
+ sourceRoot: requireSourceRoot(params),
1228
1306
  artifactsDir: params.artifactsDir,
1229
1307
  preferredExecutor: "charter_extraction_executor",
1230
1308
  charterSubmissionPath: mergedPath,
@@ -1276,6 +1354,7 @@ export async function handleCharterComparisonBranch(params, bundle, state, tx) {
1276
1354
  schema: LANE_SUBMISSION_SCHEMAS[GATE_LANES.charter_comparison],
1277
1355
  apply: (_value, path, p, foldBundle) => runAuditStepUnlocked({
1278
1356
  root: p.root,
1357
+ sourceRoot: requireSourceRoot(p),
1279
1358
  artifactsDir: p.artifactsDir,
1280
1359
  preferredExecutor: "charter_comparison_executor",
1281
1360
  charterComparisonSubmissionPath: path,
@@ -1296,6 +1375,7 @@ export async function handleCharterFidelityBranch(params, bundle, state, tx) {
1296
1375
  schema: LANE_SUBMISSION_SCHEMAS[GATE_LANES.charter_fidelity],
1297
1376
  apply: (_value, path, p, foldBundle) => runAuditStepUnlocked({
1298
1377
  root: p.root,
1378
+ sourceRoot: requireSourceRoot(p),
1299
1379
  artifactsDir: p.artifactsDir,
1300
1380
  preferredExecutor: "charter_fidelity_executor",
1301
1381
  charterFidelitySubmissionPath: path,
@@ -1325,6 +1405,7 @@ export async function handleCharterClarificationBranch(params, bundle, state, tx
1325
1405
  schema: LANE_SUBMISSION_SCHEMAS[GATE_LANES.charter_clarification],
1326
1406
  apply: (_value, path, p, foldBundle) => runAuditStepUnlocked({
1327
1407
  root: p.root,
1408
+ sourceRoot: requireSourceRoot(p),
1328
1409
  artifactsDir: p.artifactsDir,
1329
1410
  preferredExecutor: "charter_clarification_executor",
1330
1411
  clarificationAnswersPath: path,
@@ -1362,6 +1443,7 @@ export async function handleSystemicChallengeBranch(params, bundle, state, tx) {
1362
1443
  schema: systemicChallengeSchema(repoPathUniverse(bundle.repo_manifest)),
1363
1444
  apply: (_value, path, p, foldBundle, staged) => runAuditStepUnlocked({
1364
1445
  root: p.root,
1446
+ sourceRoot: requireSourceRoot(p),
1365
1447
  artifactsDir: p.artifactsDir,
1366
1448
  preferredExecutor: "systemic_challenge_executor",
1367
1449
  systemicChallengePath: path,
@@ -1404,9 +1486,10 @@ export async function executeAndRecord(params, analyzers, decision, index, lastS
1404
1486
  status: "running",
1405
1487
  started_at: startedAt,
1406
1488
  });
1407
- const indexes = await manifestIndexes(params.root, bundle, ctx.manifestIndexCache);
1489
+ const indexes = await manifestIndexes(requireSourceRoot(params), bundle, ctx.manifestIndexCache);
1408
1490
  const result = await runSingleAdvanceStep(bundle, {
1409
- root: params.root,
1491
+ root: requireSourceRoot(params),
1492
+ repositoryRoot: params.root,
1410
1493
  artifactsDir: params.artifactsDir,
1411
1494
  analyzers,
1412
1495
  graphLlmEdgeReasoning: params.graphLlmEdgeReasoning,
@@ -1539,8 +1622,8 @@ export async function checkNoProgressBeforeDispatch(ctx) {
1539
1622
  timestamp: new Date().toISOString(),
1540
1623
  });
1541
1624
  // A terminal INTENT, not the built terminal: `buildTerminalStep` can
1542
- // PROMOTE (which deletes artifactsDir), so it must run after the fold's
1543
- // commit and outside its hold. The marker above is the in-fold record.
1625
+ // PROMOTE (which writes outside the dir and removes a complete run's
1626
+ // snapshot), so it must run after the fold's commit and outside its hold. The marker above is the in-fold record.
1544
1627
  return {
1545
1628
  kind: "terminal_intent",
1546
1629
  bundle: ctx.bundle,
@@ -1991,15 +2074,20 @@ async function ingestAvailableInspectionResults(bundle, ctx) {
1991
2074
  // so the accept decision sees the same disk truth `runAuditStep`'s batch gate
1992
2075
  // validates against; the audit-task manifest comes from the same bundle.
1993
2076
  const lineIndexForIngest = bundle.repo_manifest
1994
- ? await buildLineIndex(ctx.params.root, bundle.repo_manifest)
2077
+ ? await buildLineIndex(requireSourceRoot(ctx.params), bundle.repo_manifest)
1995
2078
  : undefined;
2079
+ // The set replayed below. The ingest validates those entries again against
2080
+ // this same tree, so the batch gate cannot refuse what it is handed.
2081
+ const pendingIds = new Set(buildPendingAuditTasks(bundle).map((task) => task.task_id));
1996
2082
  try {
1997
2083
  const ingested = await ingestAuditHostResults({
1998
2084
  root: ctx.params.root,
2085
+ sourceRoot: requireSourceRoot(ctx.params),
1999
2086
  artifactsDir: ctx.params.artifactsDir,
2000
2087
  runId: currentRun.run_id,
2001
2088
  auditTasks: bundle.audit_tasks ?? [],
2002
2089
  lineIndex: lineIndexForIngest,
2090
+ pendingTaskIds: pendingIds,
2003
2091
  });
2004
2092
  acceptedResults = ingested.accepted_results;
2005
2093
  ingestIssues = ingested.issues;
@@ -2036,13 +2124,13 @@ async function ingestAvailableInspectionResults(bundle, ctx) {
2036
2124
  // Even a refused-only arrival must be visible on the next host step.
2037
2125
  mergeFoldAdvisoriesInto(ctx.foldAdvisoriesRef.value, { issues: ingestIssues, validationWarnings });
2038
2126
  await writePendingAdvisories(ctx.params.artifactsDir, ctx.foldAdvisoriesRef.value);
2039
- const pendingIds = new Set(buildPendingAuditTasks(bundle).map((task) => task.task_id));
2040
2127
  const pendingAccepted = acceptedResults.filter((result) => pendingIds.has(result.task_id));
2041
2128
  if (pendingAccepted.length > 0) {
2042
2129
  // Forced, lock-free, against the CARRIED bundle: the fold owns the one
2043
2130
  // hold and the one persist.
2044
2131
  const ingested = await runAuditStepUnlocked({
2045
2132
  root: ctx.params.root,
2133
+ sourceRoot: requireSourceRoot(ctx.params),
2046
2134
  artifactsDir: ctx.params.artifactsDir,
2047
2135
  preferredExecutor: "result_ingestion_executor",
2048
2136
  auditResultsData: [...pendingAccepted],
@@ -2069,6 +2157,7 @@ async function runHostDelegationObligation(bundle, ctx) {
2069
2157
  // other half).
2070
2158
  const review = await ensureSemanticReviewRunUnlocked({
2071
2159
  root: ctx.params.root,
2160
+ sourceRoot: requireSourceRoot(ctx.params),
2072
2161
  artifactsDir: ctx.params.artifactsDir,
2073
2162
  bundle,
2074
2163
  state,
@@ -2119,7 +2208,12 @@ async function withFoldAdvisories(step, ctx) {
2119
2208
  if (step.kind !== "semantic_review") {
2120
2209
  return {
2121
2210
  ...step,
2122
- advisoryNotice: renderCarriedAdvisoryLines(carried),
2211
+ // An architecture step that carries a ready-inspection run names that
2212
+ // run's workload in its own task, above the carried report (the
2213
+ // `withReadyInspection` section); the pending items are in it.
2214
+ advisoryNotice: renderCarriedAdvisoryLines(carried, step.inspectionRun !== undefined && step.kind !== "blocked"
2215
+ ? "named_above"
2216
+ : "next_call"),
2123
2217
  };
2124
2218
  }
2125
2219
  return {
@@ -2142,14 +2236,15 @@ async function withFoldAdvisories(step, ctx) {
2142
2236
  * the bullet format and the headings, and the copy had already gone stale
2143
2237
  * against the section it claimed to match.
2144
2238
  *
2145
- * The workload is `next_call` here: this report rides a step of another kind,
2146
- * so no workload is published below it.
2239
+ * The report rides a step of another kind and follows its whole task, so the
2240
+ * workload is `named_above` when that task names a ready-inspection workload,
2241
+ * and `next_call` otherwise.
2147
2242
  */
2148
- function renderCarriedAdvisoryLines(carried) {
2243
+ function renderCarriedAdvisoryLines(carried, workload) {
2149
2244
  return renderIngestReportLines({
2150
2245
  issues: carried.ingestIssues,
2151
2246
  remedy: auditIngestRemedy,
2152
- workload: "next_call",
2247
+ workload,
2153
2248
  advisories: carried.validationWarnings,
2154
2249
  });
2155
2250
  }
@@ -2223,8 +2318,8 @@ function foldHeartbeatRecord(obligation, durationMs) {
2223
2318
  * Drive the deterministic fold for one `next-step` call.
2224
2319
  *
2225
2320
  * Structure mirrors remediate-code's `decideNextStepLoop` (the proven engine
2226
- * consumer): a PREAMBLE (the `index===0` file-integrity re-intake, the analog of
2227
- * remediate's `forceReplan`) then the shared `advance` running audit's `PRIORITY`
2321
+ * consumer): source-integrity refresh under the hold, then the shared
2322
+ * `advance` running audit's `PRIORITY`
2228
2323
  * obligations. Each deterministic executor `transition`s (folding the whole chain
2229
2324
  * into one host round-trip); host-delegation / dispatch / terminal obligations
2230
2325
  * `emit` the host-actionable step.
@@ -2245,7 +2340,10 @@ export async function runDeterministicForNextStep(params) {
2245
2340
  // engine directly rather than through `advanceAudit`).
2246
2341
  const heartbeat = startAdvanceHeartbeat();
2247
2342
  try {
2248
- return await runDeterministicFold(params, heartbeat);
2343
+ const result = await runDeterministicFold(params, heartbeat);
2344
+ // The fold resolved the run's snapshot under its hold; the emission renders
2345
+ // every packet and workload from the same tree.
2346
+ return { ...result, sourceRoot: requireSourceRoot(params) };
2249
2347
  }
2250
2348
  finally {
2251
2349
  heartbeat.stop();
@@ -2265,38 +2363,6 @@ async function runDeterministicFold(params, heartbeat) {
2265
2363
  const analyzersRef = {
2266
2364
  value: params.analyzers,
2267
2365
  };
2268
- // PREAMBLE — file-integrity re-intake (runs once, like remediate's
2269
- // forceReplan). When pending audit-task files have changed/vanished since the
2270
- // manifest was built, re-run intake so planning re-grounds. advanceAudit does
2271
- // not persist (only runAuditStep does), so this is the same diagnostic-then-
2272
- // reload the hand loop performed on its first iteration: the warning fires and
2273
- // the fold below starts from the freshly-loaded disk bundle.
2274
- {
2275
- const bundle = await loadArtifactBundle(params.artifactsDir);
2276
- if (bundle.audit_state?.status !== "complete" && bundle.repo_manifest) {
2277
- const pendingTasks = buildPendingAuditTasks(bundle);
2278
- const taskFiles = new Set();
2279
- for (const task of pendingTasks) {
2280
- for (const fp of Object.keys(task.file_line_counts ?? {}))
2281
- taskFiles.add(fp);
2282
- }
2283
- if (taskFiles.size > 0) {
2284
- const integrity = await checkFileIntegrity(params.root, bundle.repo_manifest, [...taskFiles]);
2285
- if (!integrity.is_clean) {
2286
- // Route this diagnostic OFF stdout: cmdNextStep emits the step
2287
- // contract as the sole stdout payload via console.log(JSON.stringify),
2288
- // so a console.log here would corrupt the JSON-on-stdout contract.
2289
- process.stderr.write(`[audit-code] nextStep: integrity check — ${integrity.changed_files.length} changed, ` +
2290
- `${integrity.missing_files.length} missing, ${integrity.io_errors.length} io-error(s); re-running intake.\n`);
2291
- await advanceAudit(bundle, {
2292
- root: params.root,
2293
- artifactsDir: params.artifactsDir,
2294
- preferredExecutor: "intake_executor",
2295
- });
2296
- }
2297
- }
2298
- }
2299
- }
2300
2366
  const ctx = {
2301
2367
  params,
2302
2368
  analyzersRef,
@@ -2330,8 +2396,18 @@ async function runDeterministicFold(params, heartbeat) {
2330
2396
  const holdStartMs = Date.now();
2331
2397
  let chargedExecutions;
2332
2398
  let ingestionExecutions = 0;
2399
+ let intakeExecutions = 0;
2333
2400
  await recoverStagedSubmissions(params.artifactsDir);
2334
2401
  const previousConsent = await readRunConsentUnlocked(params.root, params.artifactsDir);
2402
+ // The run's frozen snapshot, under the same hold as the consent record it
2403
+ // shares a lifetime with: created at run start, re-checked out on resume.
2404
+ // Every content read of this fold uses it (`requireSourceRoot`); the
2405
+ // complete-run rule lives in `ensureRunSourceRoot`, shared with every
2406
+ // standalone step command.
2407
+ params.sourceRoot = await ensureRunSourceRoot(params.root, params.artifactsDir);
2408
+ if (params.since !== undefined) {
2409
+ params.since = await resolveRunSince(params.root, params.artifactsDir, params.since);
2410
+ }
2335
2411
  const consent = params.autoFix?.enabled === undefined && !params.autoFix?.dryRun
2336
2412
  ? previousConsent
2337
2413
  : await updateRunConsentUnlocked(params.root, params.artifactsDir, {}, params.autoFix?.enabled, params.autoFix?.dryRun);
@@ -2352,6 +2428,26 @@ async function runDeterministicFold(params, heartbeat) {
2352
2428
  }
2353
2429
  ctx.currentBundleRef.value = startBundle;
2354
2430
  try {
2431
+ // Observe source integrity against the fresh carried authority, under
2432
+ // the same hold and before any old host submission can be ingested.
2433
+ if (startBundle.audit_state?.status !== "complete" && startBundle.repo_manifest) {
2434
+ const taskFiles = [...new Set(buildPendingAuditTasks(startBundle).flatMap((task) => task.file_paths))];
2435
+ if (taskFiles.length > 0) {
2436
+ const integrity = await checkFileIntegrity(requireSourceRoot(params), startBundle.repo_manifest, taskFiles);
2437
+ if (!integrity.is_clean) {
2438
+ process.stderr.write(`[audit-code] nextStep: integrity check - ${integrity.changed_files.length} changed, ` +
2439
+ `${integrity.missing_files.length} missing, ${integrity.io_errors.length} io-error(s); re-running intake.\n`);
2440
+ intakeExecutions = 1;
2441
+ const refreshed = await runSingleAdvanceStep(startBundle, {
2442
+ root: requireSourceRoot(params), repositoryRoot: params.root, artifactsDir: params.artifactsDir,
2443
+ preferredExecutor: "intake_executor", scopeIndexMemo: params.scopeIndexMemo,
2444
+ runLogger: foldLogger, heartbeat,
2445
+ });
2446
+ startBundle = refreshed.updated_bundle;
2447
+ ctx.currentBundleRef.value = startBundle;
2448
+ }
2449
+ }
2450
+ }
2355
2451
  if (startBundle.audit_tasks && startBundle.audit_state?.status !== "complete" &&
2356
2452
  deriveAuditState(startBundle, { emitStaleness: false }).obligations.find((obligation) => obligation.id === "planning_artifacts")?.state === "satisfied") {
2357
2453
  const beforeIngest = startBundle;
@@ -2364,13 +2460,13 @@ async function runDeterministicFold(params, heartbeat) {
2364
2460
  obligations: trackFoldBundle(buildAuditObligations()),
2365
2461
  }, startBundle, ctx, {
2366
2462
  maxTransitions: engineMaxTransitions(),
2367
- maxExecutions: MAX_DRAIN_STEPS - ingestionExecutions,
2463
+ maxExecutions: MAX_DRAIN_STEPS - ingestionExecutions - intakeExecutions,
2368
2464
  });
2369
- chargedExecutions = engineOutcome.executions + ingestionExecutions;
2465
+ chargedExecutions = engineOutcome.executions + ingestionExecutions + intakeExecutions;
2370
2466
  if (engineOutcome.step && engineOutcome.step.kind !== "terminal_intent" && ARCHITECTURE_WORK_STEPS.has(engineOutcome.step.kind) &&
2371
2467
  buildPendingAuditTasks(engineOutcome.state).length > 0) {
2372
2468
  const inspection = await ensureSemanticReviewRunUnlocked({
2373
- root: params.root, artifactsDir: params.artifactsDir,
2469
+ root: params.root, sourceRoot: requireSourceRoot(params), artifactsDir: params.artifactsDir,
2374
2470
  bundle: engineOutcome.state,
2375
2471
  state: deriveAuditState(engineOutcome.state, { emitStaleness: false }),
2376
2472
  obligationId: "audit_tasks_completed",
@@ -2423,14 +2519,14 @@ async function runDeterministicFold(params, heartbeat) {
2423
2519
  // obligation that produces most of it, is what makes "stated on exactly one
2424
2520
  // emitted step, WHICHEVER step that is" true for every kind. A terminal
2425
2521
  // intent is deliberately excluded: it is not a step, and the terminal
2426
- // conversion below promotes and deletes the artifacts dir — the carry has
2427
- // nowhere left to be stated, and the run is over.
2522
+ // conversion below promotes — the carry has nowhere left to be stated, and
2523
+ // the run is over.
2428
2524
  if (outcome.step && outcome.step.kind !== "terminal_intent") {
2429
2525
  return await withFoldAdvisories(outcome.step, ctx);
2430
2526
  }
2431
2527
  if (outcome.step) {
2432
2528
  // Guard terminals convert POST-commit, POST-hold: buildTerminalStep can
2433
- // promote, and promotion deletes artifactsDir.
2529
+ // promote, which writes outside the dir and removes a complete run's snapshot.
2434
2530
  return await buildTerminalStep(params, outcome.step.bundle, outcome.step.state, outcome.step.reason);
2435
2531
  }
2436
2532
  if (outcome.stopped === "budget") {