clearotron 0.4.1 → 0.5.0-beta.1

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 (104) hide show
  1. package/README.md +1 -0
  2. package/THIRD-PARTY-NOTICES.md +1 -1
  3. package/bin/start.mjs +33 -8
  4. package/build-info.json +2 -2
  5. package/driver/CHANGELOG.md +32 -0
  6. package/driver/ask-ledger.mjs +90 -16
  7. package/driver/band-shape.mjs +65 -11
  8. package/driver/band-size.mjs +10 -0
  9. package/driver/close-verify.mjs +29 -2
  10. package/driver/common-law-coverage-status.mjs +1 -1
  11. package/driver/connotation-search.mjs +7 -9
  12. package/driver/contract-e3-backlog.mjs +34 -34
  13. package/driver/contract-e3-baseline.json +5 -5
  14. package/driver/contract-vocabulary.mjs +12 -12
  15. package/driver/corrections-delivered.mjs +113 -0
  16. package/driver/corrections-feedforward.mjs +16 -6
  17. package/driver/corrective-arm.mjs +11 -8
  18. package/driver/coverage-form-io.mjs +2 -1
  19. package/driver/coverage-form.mjs +17 -2
  20. package/driver/coverage-union.mjs +11 -2
  21. package/driver/digest-queue.mjs +1 -1
  22. package/driver/disposition-call.mjs +1 -1
  23. package/driver/document-coverage.mjs +3 -2
  24. package/driver/effective-scope.mjs +12 -7
  25. package/driver/engine/mcp/band-server.mjs +77 -13
  26. package/driver/engine/mcp/dispositions-server.mjs +2 -2
  27. package/driver/engine/mcp/held-answers.mjs +128 -0
  28. package/driver/engine/mcp/recording-server.mjs +10 -6
  29. package/driver/engine/mcp/supplemental.mjs +74 -12
  30. package/driver/findings-model.mjs +16 -4
  31. package/driver/floor-duty.mjs +9 -0
  32. package/driver/gate-metrics.mjs +7 -1
  33. package/driver/gateway.mjs +28 -14
  34. package/driver/goods-evidence.mjs +295 -0
  35. package/driver/knockout-assess-record.mjs +1 -1
  36. package/driver/narrative-refutation-record.mjs +40 -2
  37. package/driver/package.json +1 -1
  38. package/driver/pipeline.mjs +1994 -323
  39. package/driver/placement-decisions.mjs +243 -0
  40. package/driver/placement-form.mjs +42 -8
  41. package/driver/placement-union.mjs +14 -6
  42. package/driver/provider-usage.mjs +10 -7
  43. package/driver/publish/audit-from-spine.mjs +5 -2
  44. package/driver/publish/index.mjs +19 -4
  45. package/driver/publish/xlsx.mjs +6 -6
  46. package/driver/recall-reconciliation.mjs +15 -5
  47. package/driver/record-carry.mjs +70 -6
  48. package/driver/record-discard.mjs +7 -1
  49. package/driver/record-fold.mjs +14 -0
  50. package/driver/record-goods.mjs +101 -5
  51. package/driver/register-plan.mjs +221 -6
  52. package/driver/registry-fidelity.mjs +34 -3
  53. package/driver/remedy-accounting.mjs +52 -14
  54. package/driver/repair-composers.mjs +265 -105
  55. package/driver/repair-scope.mjs +327 -0
  56. package/driver/repair-targets.mjs +92 -0
  57. package/driver/result-noun-fields.mjs +3 -3
  58. package/driver/scope-facts.mjs +6 -2
  59. package/driver/screen-gate.mjs +96 -0
  60. package/driver/skills/clearance-common-law/SKILL.md +8 -0
  61. package/driver/skills/clearance-register/SKILL.md +29 -21
  62. package/driver/skills/clearance-register/digest.md +22 -17
  63. package/driver/skills/clearance-register/providers/clarivate.md +4 -4
  64. package/driver/skills/clearance-register/providers/corsearch.md +1 -1
  65. package/driver/skills/clearance-register/providers/euipo.md +4 -4
  66. package/driver/skills/clearance-register/providers/free-tier.md +4 -4
  67. package/driver/skills/clearance-register/providers/uspto-local.md +3 -3
  68. package/driver/skills/clearance-register/register-recipes.md +6 -6
  69. package/driver/skills/clearance-register/unit.md +29 -22
  70. package/driver/skills/clearance-search/delivery-contract.md +2 -58
  71. package/driver/skills/clearance-search/report-prose.md +12 -3
  72. package/driver/skills/clearance-search/risk-framework-triage.md +1 -1
  73. package/driver/skills/clearance-search/synthesis-rules.md +10 -13
  74. package/driver/skills/clearance-variants/SKILL.md +4 -4
  75. package/driver/skills/knockout-assess/SKILL.md +9 -14
  76. package/driver/skills/knockout-frame/SKILL.md +5 -5
  77. package/driver/skills/narrative-refutation/SKILL.md +1 -1
  78. package/driver/skills/placement-inquiry/SKILL.md +1 -1
  79. package/driver/skills/whatif-memo/SKILL.md +1 -1
  80. package/driver/stage-context.mjs +2 -1
  81. package/driver/stages-knockout.mjs +1 -1
  82. package/driver/stages.mjs +96 -61
  83. package/driver/suite-census.json +415 -25
  84. package/driver/synthesis-record.mjs +310 -8
  85. package/driver/terminal-clamp.mjs +53 -2
  86. package/driver/verify-knockout.mjs +22 -4
  87. package/driver/verify.mjs +23 -3
  88. package/mcp-server/CHANGELOG.md +8 -0
  89. package/mcp-server/lib/driver.mjs +5 -0
  90. package/mcp-server/lib/events.mjs +25 -4
  91. package/mcp-server/lib/findings.mjs +4 -2
  92. package/mcp-server/package.json +1 -1
  93. package/package.json +1 -1
  94. package/portal-ui/package.json +1 -1
  95. package/providers/_shared/enumerate.mjs +12 -6
  96. package/providers/_shared/execute-plan.mjs +25 -1
  97. package/providers/_shared/follow-up-searches.mjs +139 -0
  98. package/providers/jx/src/core.js +3 -4
  99. package/providers/jx/src/judge.js +2 -2
  100. package/providers/jx/src/nativeread.js +1 -2
  101. package/providers/oauth-mcp-bridge/CHANGELOG.md +8 -0
  102. package/providers/oauth-mcp-bridge/package.json +1 -1
  103. package/scripts/e2e-first-time.mjs +30 -13
  104. package/scripts/release-await-cut.mjs +4 -2
@@ -12,7 +12,7 @@ import { join, dirname, basename, resolve } from "node:path"; // resolve: the
12
12
  import { driverDir, driverRel, ensureDriverDir } from "../shared/driver-dir.mjs";
13
13
  import { REVIEWER_OPEN_QUESTIONS_FILE } from "./reviewer-open-points.mjs"; // the run-record file the reviewer's open points go to, and the only thing that reads them now
14
14
  import { goodsOf } from "./queue-markers.mjs"; // — one reading of "does this job name goods", shared with the intake gate
15
- import { terminalClampDecision, orderClausesForLede, clientConditions, clauseForDefect } from "./terminal-clamp.mjs"; // — deliver and clamp, never withhold
15
+ import { terminalClampDecision, orderClausesForLede, clientConditions, clauseForDefect, gapPositivelyClosed, verdictWithoutTheClosedGap, jointUnaccounted } from "./terminal-clamp.mjs"; // — deliver and clamp, never withhold
16
16
  import { recordSpan } from "./attributed-span.mjs"; // — driver work the decomposition can attribute
17
17
  import { fileURLToPath } from "node:url";
18
18
  import { runStage, correctionHint, gridLedgerNameFor, draftCarryEligible, toolWrittenArtifact, selectEngine, saidBackOnly } from "./gateway.mjs";
@@ -33,14 +33,15 @@ import { buildRunContext, deriveSlug, kebab } from "./phase0.mjs";
33
33
  import { paths, STAGES, axisTier, decideAxes, assertTierSanity, assertEffectiveTier, lines, AGENT_WHATSAPP, whatsappRouting,
34
34
  chainEntries, stageOrdinal, stageInputs, stageOutputs, dependencyOrder, REGISTER_AXES, REGISTER_ENUMERATE_TOOL,
35
35
  buildEscalationFollowup, buildEnvelopeCloseFollowup, buildFrameReopenFollowup,
36
- buildFrameReopenRetryMessage, thinkingFor, composeFollowup, stampDispatchBlocks, recordEmptyReturn, nothingFound, nothingToRead, PROVIDER_META, proseRungDirective, inquiryRungDirective } from "./stages.mjs"; import { bandSizeForStage, derivedLimitSec, limitExceedsCeiling, ceilingRefusal } from "./band-size.mjs";
36
+ buildFrameReopenRetryMessage, thinkingFor, composeFollowup, stampDispatchBlocks, recordEmptyReturn, nothingFound, nothingToRead, PROVIDER_META, proseRungDirective, inquiryRungDirective } from "./stages.mjs"; import { bandSizeForStage, derivedLimitSec, limitExceedsCeiling, ceilingRefusal, stagesRefusingBand } from "./band-size.mjs";
37
37
  import { IDENTITY_FILE as REPORT_IDENTITY_FILE } from "./report-overview-record.mjs";
38
38
  import { dispatchRows, clearedSignatures } from "./seat-attempts.mjs";
39
39
  import { CONTEXT_DERIVATIONS, DISPATCH_EXTRAS, INLINE_CONTEXT, sandboxManifest, sandboxGaps, derivationsFor } from "./stage-context.mjs"; // — what a stage is actually handed
40
- import { parseVerdict, countCitedDefects, parseCorrectionKinds, parseCorrections, validators, findReviewerCoherenceFlags, lateReviewAgainst } from "./verify.mjs";
41
- import { readAcceptedFlags } from "./narrative-refutation-record.mjs"; // T3b — the typed flags, not the re-parse
40
+ import { parseVerdict, countCitedDefects, parseCorrectionKinds, parseCorrections, validators, findReviewerCoherenceFlags, lateReviewAgainst, VERDICT_RANK } from "./verify.mjs";
41
+ import { readAcceptedFlags, nextRefutationSeq, readAcceptedReviewSince } from "./narrative-refutation-record.mjs"; // T3b — the typed flags, not the re-parse
42
42
  import { evidenceClaimViolations, evidenceClaimTable } from "./evidence-claim-invariant.mjs"; //
43
43
  import { buildCorrectionsApplied, correctionsWorklist, correctionsAppliedTable, correctionScope, scopeDrift, unresolvedFlags, reportLines, linesOf, REPORT_LINE_KEY, REPORT_LINE_LABEL } from "./corrections-feedforward.mjs";
44
+ import { correctionTargets, deliveredOutcomes } from "./corrections-delivered.mjs"; // whether each correction reached the record the report was built from
44
45
  import { parseCoverageLedgerJson, parseCoverageLedgerFull, deriveCoverageStatus, classTokensFromScopeText, coerceToolAbsenceDeferred, applyTaintDeferred, decideRegisterGap, splitDeferredByCloseability, formRowUnitKey, coverageLedgerTableRows, coverageUnitLabel, NON_MATERIAL_AXES, COVERAGE_STATUSES } from "./coverage-ledger.mjs";
45
46
  import { receiptSettled, readEnvelopeDecision, settleReceipt, settledDeferralsSection, readStickyGaps } from "./envelope-settle.mjs";
46
47
  import { readRegisterTaint, readActiveTaintAxes } from "./register-taint.mjs";
@@ -58,7 +59,7 @@ import { reconcileRecall, parseFindingsEndings, parseCrowdRulings, readOkRecordU
58
59
  import { buildCrowdContext, CROWD_CONTEXT_AXIS } from "./crowd-context.mjs";
59
60
  import { writeUpForm, gradedWriteUpRung } from "./write-up-form.mjs"; // §8/§9 — which findings earn a full card
60
61
  import { shortEntryBody, groupEntries, buildGradedEntriesSection } from "./write-up-render.mjs";
61
- import { compileRegisterPlan, parseRegisterPlan, resolvePlanAgainstStore, joinPlanToBands, deriveCoverageSkeleton, validatePlanFeasibility, feasibilityMessages, foldSupplementalEntries, partitionFoldDirectives, screenThenCap, fullyDeferredAxes, variantTermIssue, searchedJurisdictionsFromPlan, PLAN_AUDIT_HEAD, PLAN_AUDIT_CLASSES, deferExhaustedProviderErrors, ladderExhaustedQids, isProviderHardErrorReason } from "./register-plan.mjs";
62
+ import { compileRegisterPlan, parseRegisterPlan, resolvePlanAgainstStore, joinPlanToBands, crowdNarrowing, blocksByQidOf, deriveCoverageSkeleton, validatePlanFeasibility, feasibilityMessages, foldSupplementalEntries, withheldFamilyTwins, pendingRequiredQids, refuseWithheldFamilies, partitionFoldDirectives, screenThenCap, fullyDeferredAxes, variantTermIssue, searchedJurisdictionsFromPlan, PLAN_AUDIT_HEAD, PLAN_AUDIT_CLASSES, deferExhaustedProviderErrors, ladderExhaustedQids, isProviderHardErrorReason } from "./register-plan.mjs";
62
63
  // — ONE binding of the office split to this box's env, shared with Depth 2's two lanes.
63
64
  import { registerCapabilities, registerUnavailableOffices } from "./register-unreachable.mjs";
64
65
  import { parseVariantManifestModel, famousMarkElements } from "./variant-manifest-model.mjs";
@@ -76,9 +77,25 @@ import { buildCoverageAbsenceForm, coverageAbsenceGaps, coverageFormAbsence, cov
76
77
  import { unionCoverageForm } from "./coverage-union.mjs";
77
78
  import { armCoverageForm, coverageFormInput, coverageFormPaths, coverageFormStamp, readCoverageForm, readCoverageFormInput, waitingFamilyStates, writeCoverageForm } from "./coverage-form-io.mjs";
78
79
  import { releasedFamilyQids, readWithheldFamilies } from "./withheld-families.mjs"; // the waiting families the reading turn released join as dictated entries
80
+ import { heldAnswers, refuseHeldQuestions } from "./engine/mcp/held-answers.mjs"; // one answer per question per run, in the re-sweep too
81
+ import { readOutstanding, FOLLOW_UP_SEARCH_LIMIT, followUpSearchesSent } from "../providers/_shared/follow-up-searches.mjs";
82
+ // The plan join's options, one place: the families the reading turn released, the follow-ups the run's
83
+ // safety net did not send, which join as questions not asked rather than missing (follow-up-searches.mjs), and the run's
84
+ // main markets, the one market list a crowd of the identical mark is read in (crowdNarrowing): the order's
85
+ // countries, else the customer's priority markets, else the major markets.
86
+ const mainMarketsOf = (ctx) => {
87
+ try { const { scope, priorities, major } = narrowingMarkets(instructedOrder(ctx.paths, ctx.job), ctx.profile); return scope.length ? scope : priorities.length ? priorities : major; }
88
+ catch { return []; }
89
+ };
90
+ const joinOptions = (ctx) => ({ released: releasedFamilyQids(ctx.paths.runDir),
91
+ outstanding: new Set(readOutstanding(driverDir(ctx.paths.runDir)).map((r) => String(r.qid))), mainMarkets: mainMarketsOf(ctx) });
79
92
  import { unionPlacementForm } from "./placement-union.mjs";
93
+ import { placedUriRows, standingSetAside, placementSetAsideGrounds, placementFloorDecisionLines, placementSetAsideLines, placementAuditRows,
94
+ placementRecallEndings, writePlacementDecisions, rankingNow, rankedRecords, recordsReRanked, rankingToJson, rankingFromJson } from "./placement-decisions.mjs"; // placement's standing decisions, one check for every reader
95
+ export { placementSetAsideGrounds, placementFloorDecisionLines, placementSetAsideLines, placementAuditRows, placementRecallEndings,
96
+ standingPlacementDecisions, writePlacementDecisions } from "./placement-decisions.mjs";
80
97
  import { readPlacementForm, readPlacementFormInput, writePlacementForm } from "./placement-form-io.mjs";
81
- import { buildSelectionIndex, setAsideGrounds, setAsideKeysOf, ownerFates, isSetAsideGroupRow } from "./placement-form.mjs";
98
+ import { buildSelectionIndex, setAsideKeysOf, ownerFates, servedLines, lineKey } from "./placement-form.mjs";
82
99
  import { dictatedPaths, findStrayArtifacts, treeSnapshot, findStrayInTree, matterSiblings, findStrayMatterSiblings } from "./stray-artifacts.mjs"; // — a run dir holds no document no stage dictated; — nor does the doctrine tree
83
100
  import { resolveProfile, resolveEffectiveProfile, derivedFloor, derivedBatchSize, gridBatchFor, applicantMatchesProfile, NEUTRAL_DELIVERY, deliveryForRun, recipeProseGuard, withRunPlatforms, profileStoreResolution } from "./profiles.mjs"; // adds profileStoreResolution — the CONFIG store's identity, beside the doctrine tree's
84
101
  import { resolveSearchPolicy, gateResolvedPolicy, loadRecipes, policyFor, isRegisterOnly, reportIdentityFor, depthFor } from "./search-policy.mjs";
@@ -87,7 +104,7 @@ import { profileOrdinals } from "./profile-selection.mjs"; // lever 3 — driv
87
104
  // CLIENT REPORT through the script-scope coverage row, which is why it is imported rather than typed:
88
105
  // the row used to name "Depth 5 (clearance-jx)" — a retired key on a retired ladder — as its remedy.
89
106
  import { NATIVE_LANGUAGE_REMEDY } from "./products.mjs";
90
- import { resolveTerritories, defaultTerritoryState, clientMarkets, orderTerritories } from "./effective-scope.mjs"; // the ONE territory ladder (the geography stamp included) + the stored-defaults reading
107
+ import { resolveTerritories, defaultTerritoryState, clientMarkets, orderTerritories, narrowingMarkets } from "./effective-scope.mjs"; // the ONE territory ladder (the geography stamp included) + the stored-defaults reading
91
108
  import { acquireSlot, releaseSlot } from "./slot-lock.mjs";
92
109
  import { mintSupplementalEntries, withRejected } from "./engine/mcp/supplemental.mjs";
93
110
  import { runLog, note, fileMeta, outputMeta, stageLog } from "./log.mjs"; import { plainDirective, deferralCoverageRow } from "./deferral-row.mjs"; import { degradedParts, writeDegradedParts, PART_NAMES, NOT_COMPLETED } from "./degraded-parts.mjs"; import { armProduced, readArmSurfaces, producedNothingLine } from "./experiment-honesty.mjs"; import { correctiveReadiness, correctiveRefusalLine, correctivePassState } from "./corrective-arm.mjs";
@@ -103,7 +120,7 @@ import { parseCaseLawProfiles, joinCaseLawProfiles } from "./publish/parse.mjs";
103
120
  import { buildAuditMd, parseSpineFindingBlocks } from "./publish/audit-from-spine.mjs";
104
121
  import { deriveRegisterPresence } from "./publish/register-presence.mjs"; // — the audit stores every live in-scope record
105
122
  import { lastAcceptedMatterFrame, withFrameClasses, frameIdentifiedClassRows, frameHouseElementCandidate } from "./matter-frame-record.mjs"; // — the frame's inferred scope, when nothing was instructed; and the classes it judged necessary beyond the instructed ones, each with its reason, which the plan compile gives one identical-mark question apiece (decision 18)
106
- import { bandShapeGoods, instructedOrder } from "./record-goods.mjs"; // — the band's goods, read from the run's own records, so the shape can sort by the client's goods
123
+ import { bandShapeGoods, instructedOrder, goodsEvidence } from "./record-goods.mjs"; // — the band's goods, read from the run's own records, so the shape can sort by the client's goods
107
124
  import { frameWebChoiceFor, frameAskedForWebGrid, webGridOf, closureBlocksOf } from "./web-grid.mjs"; // — the web grid the matter frame decides: its stores, its forms, what it set aside
108
125
  import { romanizedTermsFromPlan, mintSupplementalQid } from "./register-plan.mjs";
109
126
  import { excludeHouseElement, verifyHouseElementOwnership, resolveRegions as resolvePlanRegions, HOUSE_ELEMENT_RECEIPT } from "./register-plan.mjs";
@@ -124,11 +141,12 @@ import { mintPresenceDoubts } from "./presence-reconciliation.mjs"; // presenc
124
141
  import { escalatedAxes } from "./skeptic-record.mjs"; // THE escalation parse — shared with the record_skeptic transport so the rendered shape and this read cannot drift
125
142
  // — every placed candidate ends somewhere a reader can see; the ones that do not are counted by name
126
143
  import { PLACEMENT_CARRY_SCHEMA_VERSION, reconcilePlacementCarry, placementCarryEvent, mintPlacementCarryDoubts, entryUris } from "./placement-carry.mjs";
127
- import { FLOOR_DUTY_SCHEMA_VERSION, reconcileFloorDuty, floorDutyEvent, armFloorDuty, floorDutyArmed, floorDutyBlock, floorDutyBlocksSkip } from "./floor-duty.mjs"; // — the era stamp that turns disclosure into a delivery floor
128
- import { synthesisDutyForRun } from "./synthesis-record.mjs"; // — the duty checked against the DELIVERED document
129
- import { RECORD_CARRY_SCHEMA_VERSION, traceRecordCarry, parseStageOutcomes, recordCarryEvent, mintRecordCarryDoubts, bandRecordUri, placementIndex, findingUris , silentlyLostFindings, statedDivergenceFindings } from "./record-carry.mjs"; import { pickingExits, notesExits, notesPageRows, exitsForLog } from "./hand-off-exits.mjs";
144
+ import { FLOOR_DUTY_SCHEMA_VERSION, reconcileFloorDuty, floorDutyEvent, armFloorDuty, floorDutyArmed, floorDutyBlock, floorDutyClosed, floorDutyBlocksSkip } from "./floor-duty.mjs"; // — the era stamp that turns disclosure into a delivery floor
145
+ import { synthesisDutyForRun, snapshotSynthesisBase, restoreSynthesisBase, rebaseSynthesis, lastAcceptedCall, acceptSynthesis, driverReadsFor } from "./synthesis-record.mjs"; // — the duty checked against the DELIVERED document; the merge base a corrective pass holds and puts back
146
+ import { RECORD_CARRY_SCHEMA_VERSION, traceRecordCarry, parseStageOutcomes, recordCarryEvent, mintRecordCarryDoubts, recordCarryDoubtsAgain, bandRecordUri, placementIndex, findingUris , silentlyLostFindings, statedDivergenceFindings } from "./record-carry.mjs"; import { pickingExits, notesExits, notesPageRows, exitsForLog } from "./hand-off-exits.mjs";
130
147
  import { reconcileSurfaceDuty, surfaceDutyNote } from "./surface-duty.mjs"; // item 3 — silence at the findings surface, read off the rows above
131
148
  import { DISCARD_LEDGER_NAME, seamRows, appendDiscardRows, foldDiscardLedger } from "./record-discard.mjs";
149
+ import { deriveGoodsEvidence, recordGoodsEvidence, heldRecordProvenance, recordFetchOutcomes, readRunCallRows, recordReads, linesRead, registerCallAccount, readGoodsEvidence, GOODS_EVIDENCE_FILE, goodsFromLabel } from "./goods-evidence.mjs"; // — what each band record's goods rest on, written beside the shape and served by no band tool
132
150
  import { readDeclinations } from "./declination-tool.mjs"; // — synthesis's own stated declines
133
151
  import { seamReasonFor } from "./declination-call.mjs"; // — the per-record judgment, pure and separately tested
134
152
  import { traceCommonLawCarry, commonLawCarryEvent, mintCommonLawCarryDoubts, reconciliationVerdict, planVsExecutedChannels, CL_STAGES } from "./commonlaw-carry.mjs";
@@ -139,31 +157,33 @@ const COMMONLAW_CARRY_FLOOR = (() => {
139
157
  try { return JSON.parse(readFileSync(new URL("./commonlaw-carry-floor.json", import.meta.url), "utf8")); }
140
158
  catch { return null; }
141
159
  })();
142
- import { accountRemedyTerms, domClosedOverAccounted, remedyAccountingEvent, mintRemedyAccountingDoubts } from "./remedy-accounting.mjs"; // — every remedy term ends accounted (2026-08-03)
143
- import { tallyRegisterCalls, DEFAULT_LEDGER_PATH, fetchedRecordUris, countLaneCalls } from "./provider-usage.mjs";
160
+ import { accountRemedyTerms, domClosedOverAccounted, remedyAccountingEvent, mintRemedyAccountingDoubts, SETTLED_CLASSES } from "./remedy-accounting.mjs"; // — every remedy term ends accounted (2026-08-03)
161
+ import { tallyRegisterCalls, DEFAULT_LEDGER_PATH, countLaneCalls } from "./provider-usage.mjs";
144
162
  // — the run's own record log. Every driver-side register call that can write a record BODY carries
145
163
  // this path, because this process runs several runs' pipelines at once and a global address would file
146
164
  // one run's evidence under another.
147
165
  import { runRecordLogPath } from "../providers/_shared/ledger-path.mjs";
148
166
  import { beginAnswerMemory, endAnswerMemory } from "../providers/_shared/answer-memory.mjs";
149
- import { findScreenGateViolations, findScreenGateParseGaps, screenGateZeroCause } from "./screen-gate.mjs";
167
+ import { findScreenGateViolations, findScreenGateParseGaps, screenGateZeroCause, screenGateEvidence, policedDropUris } from "./screen-gate.mjs";
150
168
  import { emptyQueue, coerceQueue, mintItem, pendingItems, markFlushed, receiptKeyFor,
151
169
  buildFlushFollowup, runPostFlushGateRepair } from "./digest-queue.mjs"; // (t1cd) — the digest-trigger funnel
152
- import { foldFindingsFile, foldAfterSave, openPass, modelRecordPath, snapshotFold, restoreFold } from "./record-fold.mjs"; // the folded record from the delivery seam on
153
- import { writeStamp, stageStaleness, restamp, restampStage, staleOnPath, reconcileStamps, shaOf } from "./stage-freshness.mjs";
170
+ import { foldFindingsFile, foldAfterSave, openPass, modelRecordPath, snapshotFold, restoreFold, readFold, keptModelOrdinal } from "./record-fold.mjs"; // the folded record from the delivery seam on
171
+ import { writeStamp, stageStaleness, restamp, restampStage, staleOnPath, reconcileStamps, shaOf, readStamp } from "./stage-freshness.mjs";
172
+ import { locateReportFaults, reportPartsOf } from "./repair-targets.mjs"; // a delivery-check fault reaches the part of the report that holds it
154
173
  import { parseManifestVariants, variantsParseFailure, findCoverageLimitedCells, partitionClosableCells, findSimilarListingSignals,
155
174
  GRID_HALVES, GRID_SEATS, MEANING_SEAT, splitGridSpec, halfOfTerm, balanceClosureCells, mergeGrids, mergeCommonLawFindings, openChannelRows,
156
175
  routeHalfTermScopes, findDroppedConnotationQueries, findErroredConnotationQueries, findGridCandidateOmissions } from "./common-law-receipts.mjs";
157
176
  import { stampTokenRollup } from "./tokens.mjs";
158
177
  import { recordRunConsumption } from "./consumption-ledger.mjs";
159
178
  import { quoteForJob, reconcileTurnaround } from "./run-quote.mjs";
160
- import { assembleRunRecords, readRecordArtifacts, findRegistryArithmeticIssues, findRegistryViolations, applyRegistryCorrections, extractEnforcerSignals, collectOppositionDeadlines, normalizeRecordUri } from "./registry-fidelity.mjs";
179
+ import { assembleRunRecords, recordBodyHasContent, readRecordArtifacts, findRegistryArithmeticIssues, findRegistryViolations, applyRegistryCorrections, extractEnforcerSignals, collectOppositionDeadlines, normalizeRecordUri } from "./registry-fidelity.mjs";
161
180
  import { recallReceiptForOwnCompany } from "./recall-receipt.mjs"; // a past run's recall receipt, as one company's audit may list it
162
181
  import { runLint, flagLines, properNameCandidates } from "./predelivery-lint.mjs";
163
182
  import { parsePlacementsJson } from "./placement-model.mjs"; // B2 — the structured tier mirror; the lint reads it only to flag an EMPTY one
164
183
  import { readAnchors } from "./anchor-reader.mjs";
165
184
  import { findEngagementReceipts } from "./engagement-receipt.mjs";
166
- import { plainRegisterFlags } from "./plain-register.mjs";
185
+ import { plainRegisterFlags, SENTENCE_WORD_LIMIT } from "./plain-register.mjs";
186
+ import { snapshotRepairBase, readRepairBase, repairBasePath, changedContent, isEmptyChange, restingOn, scopeItems, anyRenumbered, classifyPoints, changedInputsOf, listForReview, writePostRepairRecord, postRepairRecordPath, NARRATIVE_PROSE, sectionText } from "./repair-scope.mjs";
167
187
  import { parseFindingsJson, parseFindingsJsonLenient, consolidateFindings, deriveDisplayVerdict, bindRecommendation, compareBlockingPower, inDispositionMode, DISPOSITION_GROUP, deriveActionConditions, cardedParties, actionPartyReferences, quarantinedConditionRows, salvageRepairTargets, riskStatement, verdictStance, remapActionOrdinals, joinAskToAnswer, stripAskLabel, bandBorderlineDeclarations, reasonedNegativeGroups, blankForeignRecordLinks } from "./findings-model.mjs";
168
188
  import { foldCaption, foldCardRead } from "./card-budget.mjs";
169
189
  // S2 — the report card's mechanical frame, composed from the record instead of dictated (see below).
@@ -598,7 +618,22 @@ export function writePlanExecutionReceipt(ctx, joinRes) { // @internal
598
618
  // are instead of letting `missing: []` imply none.
599
619
  const answeredRows = (joinRes.executed ?? []).filter((e) => String(e?.state ?? "").toLowerCase() !== "incomplete");
600
620
  const census = { answered: answeredRows.length, incomplete: (joinRes.executed ?? []).length - answeredRows.length };
601
- const receipt = { plan_version: ctx.registerPlan.plan_version, ...joinRes, ...census, ...(supplemental.length ? { supplemental } : {}), skeleton };
621
+ // THE SAFETY NET UNDER THE FOLLOW-UP SEARCHES (follow-up-searches.mjs), on the run log and nowhere a model reads: the
622
+ // limit, the follow-up searches sent and how many follow-ups it did not send. Logged once each time those figures move.
623
+ try {
624
+ const { sent } = followUpSearchesSent(driverDir(P.runDir));
625
+ // a question held back once and asked later (a count file unreadable for a moment) was asked
626
+ const answered = new Set((joinRes.executed ?? []).map((e) => String(e?.qid)));
627
+ const notSent = readOutstanding(driverDir(P.runDir)).filter((r) => !answered.has(String(r.qid))).length;
628
+ if (sent > 0 || notSent > 0) {
629
+ const net = { limit: FOLLOW_UP_SEARCH_LIMIT, sent: Number.isFinite(sent) ? sent : null, reached: !Number.isFinite(sent) || sent >= FOLLOW_UP_SEARCH_LIMIT, not_sent: notSent };
630
+ const seen = `${net.sent}:${net.not_sent}`;
631
+ if (ctx.followUpNetLogged !== seen) { runLog(P.runDir, { event: "register-follow-up-searches", ...net }); ctx.followUpNetLogged = seen; }
632
+ }
633
+ } catch { /* the run log line is a record only */ }
634
+ // the run's main markets, the one market list its crowd endings were read against: a run constant the coverage form reads
635
+ const receipt = { plan_version: ctx.registerPlan.plan_version, ...joinRes, ...census, ...(supplemental.length ? { supplemental } : {}),
636
+ main_markets: mainMarketsOf(ctx), skeleton };
602
637
  writeFileSync(`${P.planExecution}.tmp`, JSON.stringify(receipt, null, 2) + "\n");
603
638
  renameSync(`${P.planExecution}.tmp`, P.planExecution);
604
639
  ctx.planExecution = receipt;
@@ -634,6 +669,7 @@ export function settleDerivedBases(ctx, at) { // @internal
634
669
  });
635
670
  let applied = 0;
636
671
  let unwritable = 0;
672
+ let heldBase = null; // what the synthesis merge base took, when there was a demotion to give it
637
673
  if (demotions.length) {
638
674
  let onDisk = null;
639
675
  try { onDisk = JSON.parse(raw); } catch { onDisk = null; }
@@ -648,6 +684,23 @@ export function settleDerivedBases(ctx, at) { // @internal
648
684
  const next = applyDerivedBases(onDisk, demotions);
649
685
  applied = next.applied;
650
686
  atomicWrite(P.findings, JSON.stringify(next.doc, null, 2) + "\n");
687
+ // THE HELD BASE TAKES THE SAME DEMOTIONS. Every accepted synthesis patch merges onto
688
+ // `_driver/synthesis-calls/accepted.json` and re-renders the findings it does not name from there, so a
689
+ // demotion written to this file alone came back with the next patch, and the evidence check then told
690
+ // the reviewer the stamp had returned. In the model's numbering: under a fold, a demotion lands on the
691
+ // member the fold kept, whose object the folded finding is. The edit is judged as a call is; a refusal
692
+ // leaves the base byte-identical and is recorded below, and the run goes on as it did before.
693
+ if (applied) {
694
+ const fold = readFold(P.runDir);
695
+ const inModel = demotions.map((d) => ({ ...d, ordinal: keptModelOrdinal(fold, d.ordinal) })).filter((d) => d.ordinal != null);
696
+ const r = rebaseSynthesis(P.runDir, (held) => applyDerivedBases(held, inModel).doc);
697
+ heldBase = r.ok ? { base: r.changed ? "demoted" : "unchanged" }
698
+ : /^no_base:/.test(String(r.reason)) ? { base: "no-base" } : { base: "refused", baseReason: String(r.reason).slice(0, 200) };
699
+ if (heldBase.base === "refused") {
700
+ note(`basis derivation (${at}): the synthesis merge base could not take the demotions (${heldBase.baseReason.slice(0, 100)}) — `
701
+ + `it is left as it was, and the next accepted patch re-renders the stamps it holds`);
702
+ }
703
+ }
651
704
  // A SHORTFALL IS A FINDING. The demotions are computed against the lenient parse, which normalises
652
705
  // — so a raw finding missing the `ordinal` the match keys on would leave an unprovable claim
653
706
  // standing while `demoted` and `rows` sat next to each other looking consistent. Say it.
@@ -671,10 +724,11 @@ export function settleDerivedBases(ctx, at) { // @internal
671
724
  .filter((m) => m?.basis === "verified-from-record").length, 0);
672
725
  const record = { ts: new Date().toISOString(), at,
673
726
  stamped_before: stampedBefore, stamped_after: stampedBefore - applied, demoted: applied,
674
- ...(unwritable ? { unwritable } : {}),
727
+ ...(unwritable ? { unwritable } : {}), ...(heldBase ?? {}),
675
728
  rows: demotions.map((d) => ({ ordinal: d.ordinal, mark: d.mark, meter: d.meter, uri: d.uri, why: d.why })) };
676
729
  atomicWrite(P.basisDerivation, JSON.stringify(record, null, 2) + "\n");
677
730
  runLog(P.runDir, { event: "basis-derivation", at, stamped_after: stampedBefore - applied, demoted: applied,
731
+ ...(heldBase ? { base: heldBase.base } : {}),
678
732
  never_fetched: demotions.filter((d) => d.why === "record-never-fetched").length,
679
733
  never_read: demotions.filter((d) => d.why === "record-on-disk-never-read").length });
680
734
  return record;
@@ -739,16 +793,102 @@ export function refreshSupplementalExecution(ctx) { // @internal
739
793
  const added = foldSupplementalProposals(ctx);
740
794
  const stale = ctx.planExecution && ctx.planExecution.plan_version !== ctx.registerPlan.plan_version;
741
795
  if (added.length || stale) {
742
- writePlanExecutionReceipt(ctx, joinPlanToBands(ctx.registerPlan, readRegisterBands(ctx.paths, ctx.axes ?? []), { released: releasedFamilyQids(ctx.paths.runDir) }));
796
+ writePlanExecutionReceipt(ctx, joinPlanToBands(ctx.registerPlan, readRegisterBands(ctx.paths, ctx.axes ?? []), joinOptions(ctx)));
743
797
  runLog(ctx.paths.runDir, { event: "plan-execution-refresh", added: added.length });
744
798
  }
745
799
  } catch (e) { note(`supplemental receipt refresh skipped (${String(e.message).slice(0, 80)}) — never-kill`); }
746
800
  }
747
801
 
748
- function deriveNamedBand(ctx) {
802
+ /** One axis band's bytes, or null where it has none: what a re-run's change is measured against. */
803
+ function bandBytesOf(P, axis) { try { return readFileSync(P.registerBand(axis), "utf8"); } catch { return null; } }
804
+
805
+ /** A fingerprint of every axis band file, in axis order: what a merge read. */
806
+ function axisBandsFingerprint(P, axes = []) {
807
+ const h = createHash("sha256");
808
+ for (const a of axes ?? []) { h.update(`${a}\0`); h.update(bandBytesOf(P, a) ?? "\0absent"); h.update("\0"); }
809
+ return h.digest("hex");
810
+ }
811
+
812
+ /**
813
+ * THE NAMED BAND MERGED AGAIN WHEN AN AXIS BAND MOVED SINCE THE LAST MERGE. A unit re-run in its own session after the
814
+ * fan-in (the cross-check's questions, a frame-reopen sweep, the skeptic's escalation, the envelope close) can add blocks
815
+ * to its axis band whatever it does to its notes, and placement, the reading step and the writer read the named band.
816
+ * Only the code-side recheck merged again, so the records those re-runs found reached none of the three. Code only:
817
+ * nothing is dispatched. Returns whether the band was merged again.
818
+ *
819
+ * A BAND A READING STAGE WOULD REFUSE IS NOT ADOPTED. Placement, the reading step and the writer each refuse a band past
820
+ * the size ceiling before they dispatch, and that refusal ends the run with no report. These re-runs alone can grow a
821
+ * band the first merge kept under it past it, where without this merge the run would have gone on reading the band as
822
+ * first merged. So the band is measured before it is adopted, by the test each stage applies at dispatch, and a band any
823
+ * of the three would refuse is not adopted: the earlier merge stands, and the records the re-runs found stay unread. They
824
+ * are not lost silently: the records the refused band held and the band standing does not are kept, and the recall gate
825
+ * counts them into its approved sentence for records neither addressed nor set aside. `refusing` is that test,
826
+ * injectable so a test can drive a refusal without a band of that size.
827
+ */
828
+ export function remergeIfBandsMoved(ctx, why, { refusing = (bytes) => stagesRefusingBand(bytes, (s) => STAGES[s]?.timeoutSec) } = {}) { // @internal — driven by its test with a run's own files
829
+ const P = ctx.paths;
830
+ if (ctx.bandsMergedAt == null) return false; // nothing merged yet this pass: the fan-in merge reads every band
831
+ const now = axisBandsFingerprint(P, ctx.axes);
832
+ if (now === ctx.bandsMergedAt || now === ctx.bandsRefusedAt) return false; // unmoved, or already refused as it stands
833
+ try {
834
+ const merged = mergeRegisterBands(P, ctx.axes ?? []);
835
+ if (!merged.invalid.length) {
836
+ const bytes = Buffer.byteLength(namedBandText(merged.band));
837
+ const stages = refusing(bytes);
838
+ if (stages.length) {
839
+ ctx.bandsRefusedAt = now;
840
+ const left = recordRefusedRecords(P, merged.band);
841
+ runLog(P.runDir, { event: "named-band-remerge-over-ceiling", why, bytes, stages, records: left });
842
+ note(`${why}: the band merged again would be ${(bytes / (1024 * 1024)).toFixed(1)} MB, more than ${stages.join(", ")} can be dispatched over — the earlier merge stands, and the records the re-run found stay unread`);
843
+ return false;
844
+ }
845
+ }
846
+ deriveNamedBand(ctx, merged); runLog(P.runDir, { event: "named-band-remerged", why }); return true;
847
+ }
848
+ catch (e) { note(`${why}: the named band could not be merged again (${String(e?.message ?? e).slice(0, 100)}) — the earlier merge stands`); return false; }
849
+ }
850
+
851
+ /** Where the records a refused merge left out are kept, for the recall gate. */
852
+ const refusedRecordsPath = (P) => driverDir(P.runDir, "band-refused-records.json");
853
+
854
+ /**
855
+ * THE RECORDS A REFUSED MERGE LEAVES OUT: the ids the band merged again holds and the band standing does not, added to
856
+ * those an earlier refusal left out. Returns how many are kept, or null when they could not be written, which the run
857
+ * record names. Never throws.
858
+ */
859
+ function recordRefusedRecords(P, band) {
860
+ try {
861
+ const key = (r) => String(r?.record_id ?? "").toLowerCase();
862
+ const standing = new Set((safeReadJson(P.registerNamedBand)?.enumerated ?? []).map(key));
863
+ const ids = [...new Set([...(safeReadJson(refusedRecordsPath(P))?.ids ?? []),
864
+ ...(band?.enumerated ?? []).map(key).filter((id) => id && !standing.has(id))])];
865
+ atomicWrite(refusedRecordsPath(P), JSON.stringify({ ids, count: ids.length }, null, 2) + "\n");
866
+ return ids.length;
867
+ } catch (e) {
868
+ runLog(P.runDir, { event: "band-refused-records-failed", reason: String(e?.message ?? e).slice(0, 160) });
869
+ return null;
870
+ }
871
+ }
872
+
873
+ /**
874
+ * The records a refused merge left out that are still outside the named band: a record a later merge took in is read,
875
+ * and is not counted as left out. Never throws.
876
+ */
877
+ export function refusedRecordsLeftOut(P) { // @internal — driven by its test with a run's own files
878
+ const ids = safeReadJson(refusedRecordsPath(P))?.ids;
879
+ if (!Array.isArray(ids) || !ids.length) return [];
880
+ const inBand = new Set((safeReadJson(P.registerNamedBand)?.enumerated ?? []).map((r) => String(r?.record_id ?? "").toLowerCase()));
881
+ return ids.map((x) => String(x).toLowerCase()).filter((id) => id && !inBand.has(id));
882
+ }
883
+
884
+ /** The named band as it is written to disk: what a dispatch over it measures. */
885
+ const namedBandText = (band) => JSON.stringify(band, null, 2) + "\n";
886
+
887
+ export function deriveNamedBand(ctx, merged = null) { // @internal — driven by its test with a run's own files
749
888
  const P = ctx.paths;
750
889
  const axes = ctx.axes ?? [];
751
- const { band, axes: ok, invalid } = mergeRegisterBands(P, axes);
890
+ const { band, axes: ok, invalid } = merged ?? mergeRegisterBands(P, axes); // a caller that measured the merge first hands it in
891
+ const mergedFrom = axisBandsFingerprint(P, axes);
752
892
  if (invalid.length) {
753
893
  const detail = invalid.map((x) => `${x.axis}:${x.reason}`).join("; ");
754
894
  runLog(P.runDir, { event: "named-band-invalid", invalid });
@@ -756,8 +896,9 @@ function deriveNamedBand(ctx) {
756
896
  }
757
897
  try {
758
898
  const tmp = P.registerNamedBand + ".tmp";
759
- writeFileSync(tmp, JSON.stringify(band, null, 2) + "\n");
899
+ writeFileSync(tmp, namedBandText(band));
760
900
  renameSync(tmp, P.registerNamedBand);
901
+ ctx.bandsMergedAt = mergedFrom;
761
902
  runLog(P.runDir, { event: "named-band-merged", axes: ok, enumerated: band.enumerated.length, crowds: band.crowds.length });
762
903
  } catch (e) {
763
904
  note(`named-band merge write failed (non-fatal): ${String(e.message).slice(0, 100)}`);
@@ -923,6 +1064,7 @@ function recordDetailIndex(runDir) {
923
1064
 
924
1065
  function deriveBandShape(ctx, band) {
925
1066
  const P = ctx.paths;
1067
+ let evidenceDoc = null;
926
1068
  try {
927
1069
  const targets = [];
928
1070
  const markNames = Array.isArray(ctx.job?.marks)
@@ -959,7 +1101,18 @@ function deriveBandShape(ctx, band) {
959
1101
  runLog(P.runDir, { event: "register-positions-failed", fail: String(e?.message ?? e).slice(0, 160) });
960
1102
  note(`register-positions derivation failed (non-fatal — the per-registration band stands): ${String(e?.message ?? e).slice(0, 100)}`);
961
1103
  }
962
- const { shape, md } = buildBandShape(band, { targets: targets.slice(0, 64), inScopeClasses, crowdContext, positions, ...bandShapeGoods(P), markets: clientMarkets(instructedOrder(P, ctx.job), ctx.profile), territories: orderTerritories(instructedOrder(P, ctx.job), ctx.profile), keysOf: setAsideKeysOf(positions?.positions ?? null) });
1104
+ const shapeGoods = bandShapeGoods(P, band?.enumerated ?? []);
1105
+ // What each record's goods rest on, derived first so the shape says it of each line the goods test could not
1106
+ // place; the same record is written beside the shape below.
1107
+ try { evidenceDoc = goodsEvidenceDocFor(ctx, band, shapeGoods.goods); } catch { evidenceDoc = null; }
1108
+ const evidenceBy = evidenceDoc ? new Map(evidenceDoc.records.map((r) => [r.uri, r])) : null;
1109
+ const evidenceOf = evidenceBy ? (r) => evidenceBy.get(normalizeRecordUri(r?.record_id) || String(r?.record_id ?? "")) ?? null : null;
1110
+ // what was retrieved under each crowd, from the per-axis blocks on disk now: the shape never carries a record older than its band
1111
+ const endings = (() => {
1112
+ if (!ctx.registerPlan || !(band?.crowds?.length)) return [];
1113
+ try { return crowdNarrowing(ctx.registerPlan, blocksByQidOf(readRegisterBands(P, ctx.axes ?? [])), { mainMarkets: mainMarketsOf(ctx) }).rows; } catch { return []; }
1114
+ })();
1115
+ const { shape, md } = buildBandShape(band, { crowdNarrowing: endings, targets: targets.slice(0, 64), inScopeClasses, crowdContext, positions, ...shapeGoods, markets: clientMarkets(instructedOrder(P, ctx.job), ctx.profile), territories: orderTerritories(instructedOrder(P, ctx.job), ctx.profile), keysOf: setAsideKeysOf(positions?.positions ?? null), evidenceOf });
963
1116
  mkdirSync(dirname(P.bandShape), { recursive: true });
964
1117
  atomicWrite(P.bandShape, JSON.stringify(shape, null, 2) + "\n");
965
1118
  atomicWrite(P.bandShapeMd, md + "\n");
@@ -967,6 +1120,8 @@ function deriveBandShape(ctx, band) {
967
1120
  runLog(P.runDir, { event: "band-shape-derived", records: shape.totals.records, crowds: shape.totals.crowds,
968
1121
  tiers: shape.totals.by_tier, floors: shape.floors.in_class_identical_or_near.length,
969
1122
  goods_test: tested.source, goods_test_words: tested.words.length,
1123
+ // what each record's goods rest on: a held body, its band row's whole statements, statements cut short, or none
1124
+ goods_evidence: goodsEvidence(band?.enumerated ?? [], shapeGoods.goods),
970
1125
  blind_spots: shape.blind_spots.map((b) => ({ kind: b.kind, count: b.count })) });
971
1126
  // The goods test falls back where the variants stage named no product words; the run says so.
972
1127
  if (tested.source !== "product_words")
@@ -975,6 +1130,48 @@ function deriveBandShape(ctx, band) {
975
1130
  runLog(P.runDir, { event: "band-shape-failed", fail: String(e?.message ?? e).slice(0, 200) });
976
1131
  note(`band-shape derivation failed (non-fatal — judgment still holds band_lookup over the band; machine checks read the complete band): ${String(e?.message ?? e).slice(0, 120)}`);
977
1132
  }
1133
+ recordGoodsEvidenceFor(ctx, band, evidenceDoc);
1134
+ // Placement's standing decisions are read off the shape, so a shape derived again can change them with no placement
1135
+ // pass: a group's row checked again on the new shape may cover other lines, or stand no longer, and the floors it
1136
+ // leaves can grow. The decisions file the reading step, the writer and the review declare is written again with the
1137
+ // shape, so such a change makes a stage that already read them stale. Only once placement has written it.
1138
+ if (existsSync(P.placementDecisions)) {
1139
+ try { writePlacementDecisions(P); }
1140
+ catch (e) { runLog(P.runDir, { event: "placement-decisions-failed", at: "band-shape", reason: String(e?.message ?? e).slice(0, 120) }); }
1141
+ }
1142
+ }
1143
+
1144
+ /**
1145
+ * WHAT EACH BAND RECORD'S GOODS REST ON, written beside the shape after every derivation (goods-evidence.mjs): the
1146
+ * goods reader the shape tests with, given the band's records so that a reader which reads a band row's own goods
1147
+ * reads them here too; the official records the run holds, with where each came from; and this run's record fetches,
1148
+ * from the register call ledger. The shape says the same of each line from the same derivation (its goods cell and
1149
+ * `goods_evidence`); the file itself is served by no band tool, gates nothing, and a failure costs the run this
1150
+ * record and nothing else.
1151
+ */
1152
+ function goodsEvidenceDocFor(ctx, band, goods = null) {
1153
+ const P = ctx.paths;
1154
+ const runPrefix = ctx.run?.slug && ctx.run?.codename ? `clearance-${ctx.run.slug}-${ctx.run.codename}-` : null;
1155
+ const calls = runPrefix ? readRunCallRows(DEFAULT_LEDGER_PATH, runPrefix) : { rows: [], error: null };
1156
+ const records = Array.isArray(band?.enumerated) ? band.enumerated : [];
1157
+ const doc = deriveGoodsEvidence({ records, goods: goods ?? bandShapeGoods(P, records).goods, held: heldRecordProvenance(P.runDir),
1158
+ fetches: recordFetchOutcomes(calls.rows, { runPrefix }) });
1159
+ // Whether fetches could be read is part of the record: without the ledger, "not held" may hide a failed fetch.
1160
+ doc.fetches_read = runPrefix ? (calls.error ? `the register call ledger could not be read: ${calls.error}` : true) : "no run prefix";
1161
+ return doc;
1162
+ }
1163
+
1164
+ function recordGoodsEvidenceFor(ctx, band, derived = null) {
1165
+ const P = ctx.paths;
1166
+ try {
1167
+ const doc = derived ?? goodsEvidenceDocFor(ctx, band);
1168
+ ensureDriverDir(P.runDir);
1169
+ atomicWrite(driverDir(P.runDir, GOODS_EVIDENCE_FILE), JSON.stringify(doc, null, 2) + "\n");
1170
+ runLog(P.runDir, { event: "goods-evidence", records: doc.totals.records, by_state: doc.totals.by_state,
1171
+ by_source: doc.totals.by_source, unreadable: doc.totals.unreadable, fetches_read: doc.fetches_read });
1172
+ } catch (e) {
1173
+ runLog(P.runDir, { event: "goods-evidence-failed", fail: String(e?.message ?? e).slice(0, 160) });
1174
+ }
978
1175
  }
979
1176
 
980
1177
  // The DETERMINISTIC GRID CONTRACT — extracted from pipelineInner unchanged (item).
@@ -1310,6 +1507,10 @@ export function composeDispatchExtra(name, ctx, opts = {}) { // @internal
1310
1507
  const parts = [], ids = [], failed = [], empty = [];
1311
1508
  for (const x of DISPATCH_EXTRAS) {
1312
1509
  if (x.stage !== name) continue;
1510
+ // `skip`: blocks a dispatch deliberately does not send — the review after a delivery-check repair leaves
1511
+ // out the plain-words list, which covers every line a client meets and points at a check that review is
1512
+ // not given. Skipped is not built and not failed: it appears in neither list.
1513
+ if (Array.isArray(opts.skip) && opts.skip.includes(x.id)) continue;
1313
1514
  try {
1314
1515
  const built = DISPATCH_EXTRA_BUILDERS[x.id](ctx, opts);
1315
1516
  recordEmptyReturn(built, x.id, { parts, ids, empty }); // EVERY builder that ran is recorded, not only those that produced text: `if (built)` alone put an empty return in neither list, so a block that ran with nothing to flag read exactly like one that never ran — and only the second is a defect. The three-way sort lives in stages.mjs beside the sentinels it reads.
@@ -2975,13 +3176,15 @@ function derivePlacementCarry(ctx, trigger) {
2975
3176
  //
2976
3177
  // NEVER THROWS and never gates — same doctrine as the trace it feeds. A ledger that cannot be written
2977
3178
  // costs the run a `basis: "recorded"`, which the trace then states, and costs it nothing else.
2978
- function recordSeamDiscards(ctx, { seam, stage: stageName, trigger = null, completed = true,
3179
+ function recordSeamDiscards(ctx, { seam, stage: stageName, trigger = null, completed = true, skipped = false,
2979
3180
  saw = [], carried = [], reasonFor = null, evidence = "" } = {}) {
2980
3181
  const P = ctx.paths;
2981
3182
  try {
2982
3183
  ctx._discardPass ??= {};
2983
3184
  const pass = (ctx._discardPass[seam] = (ctx._discardPass[seam] ?? 0) + 1);
2984
- const rows = seamRows({ seam, stage: stageName, trigger, pass, completed, saw, carried, reasonFor, evidence });
3185
+ // A skipped pass leaves standing what an earlier pass recorded (record-discard.mjs, `seamRows`).
3186
+ const prior = skipped ? foldDiscardLedger(safeReadText(driverDir(P.runDir, DISCARD_LEDGER_NAME))) : null;
3187
+ const rows = seamRows({ seam, stage: stageName, trigger, pass, completed, skipped, prior, saw, carried, reasonFor, evidence });
2985
3188
  const w = appendDiscardRows(P.runDir, rows);
2986
3189
  const discarded = rows.filter((r) => r.verdict === "discarded").length;
2987
3190
  runLog(P.runDir, {
@@ -3005,18 +3208,6 @@ function bandUriRows(P) {
3005
3208
  } catch { return []; }
3006
3209
  }
3007
3210
 
3008
- /** Every record uri a placements.json names. */
3009
- function placedUriRows(P) {
3010
- try {
3011
- const placements = parsePlacementsJson(readFileSync(P.placementModel, "utf8")).placements;
3012
- const out = [];
3013
- for (const p of Array.isArray(placements) ? placements : []) {
3014
- for (const r of Array.isArray(p?.records) ? p.records : []) out.push({ uri: r, tier: p?.tier ?? null });
3015
- }
3016
- return out;
3017
- } catch { return []; }
3018
- }
3019
-
3020
3211
  /**
3021
3212
  * PLACEMENT. Everything in the band was in front of this pass; what it placed is in placements.json.
3022
3213
  * Recorded whether the pass completed or not — an incomplete pass discards nothing, and its rows say so
@@ -3027,7 +3218,7 @@ function recordPlacementSeam(ctx, r, trigger = null) {
3027
3218
  const saw = bandUriRows(P);
3028
3219
  if (!saw.length) return null;
3029
3220
  return recordSeamDiscards(ctx, {
3030
- seam: "placement", stage: "placement-inquiry", trigger, completed: r?.ok === true && !r?.skipped,
3221
+ seam: "placement", stage: "placement-inquiry", trigger, completed: r?.ok === true && !r?.skipped, skipped: r?.skipped === true,
3031
3222
  saw, carried: placedUriRows(P),
3032
3223
  evidence: r?.skipped ? "the pass was SKIPPED on an artifact already on disk, so it made no decision this pass" : (r?.fail ? String(r.fail).slice(0, 80) : ""),
3033
3224
  reasonFor: placementSeamReason(placementSetAsideGrounds(P)),
@@ -3035,28 +3226,75 @@ function recordPlacementSeam(ctx, r, trigger = null) {
3035
3226
  }
3036
3227
 
3037
3228
  /**
3038
- * The form's set-aside entries as they stand on the shape now on disk. A ground by name stands as stored. A
3039
- * group's row is checked again exactly as the union took it, on its count and on every line read, because the
3040
- * shape may have been derived again since: a resumed run derives it afresh, and a record body fetched after
3041
- * placement can move a line out of the list the goods test could not place and into a group. A row taken on
3042
- * the earlier shape then covers no line the later one added. Nothing is written back. PURE.
3229
+ * WHAT ONE PLACEMENT PASS DID, RECORDED: the ground every band record left it with (the seam rows) and the floors
3230
+ * it answered (the floor duty). The main path and the delivery gate's repeat of the stage both record through
3231
+ * here, so the ledger and the floor duty describe the placements the report is built from. Returns the pass
3232
+ * result.
3233
+ *
3234
+ * THE STAMP IS THE MAIN PATH'S. On the main path, a pass that actually ran arms the floor its account may be held
3235
+ * to. A SKIPPED pass leaves `placements.json` written by an earlier seat (on a resume, possibly one that never saw
3236
+ * the floors order), so it arms nothing, and its seam rows leave the earlier pass's grounds standing. The delivery
3237
+ * gate's repeat (`stale-repair`) arms nothing either: the floor it is held to is the one the run was already held
3238
+ * to, applied again to the repeated pass's account where the gate rebuilds the report from it. A run whose only
3239
+ * pass under the floors order is that repeat is held to no floor, as before.
3043
3240
  */
3044
- function standingSetAside(entries, input) {
3045
- const all = Array.isArray(entries) ? entries : [];
3046
- const grouped = all.filter(isSetAsideGroupRow);
3047
- if (!grouped.length) return all;
3048
- const { form } = unionPlacementForm({ rows: [], set_aside: grouped }, null, input);
3049
- return [...all.filter((e) => !isSetAsideGroupRow(e)), ...form.set_aside_groups];
3241
+ export function recordPlacementPass(ctx, r, trigger = null) { // @internal — driven by its test with a run's own files
3242
+ recordPlacementSeam(ctx, r, trigger);
3243
+ deriveFloorDuty(ctx, r, trigger);
3244
+ // The decisions as they stand after this pass, where the reading step and the writer declare them: a pass that
3245
+ // changes only a set-aside's ground or a group's membership leaves a reader that already ran stale.
3246
+ try { writePlacementDecisions(ctx.paths); }
3247
+ catch (e) { runLog(ctx.paths.runDir, { event: "placement-decisions-failed", trigger, reason: String(e?.message ?? e).slice(0, 120) }); }
3248
+ if (r?.ok === true && r?.skipped !== true) recordPlacementBandIds(ctx.paths);
3249
+ if (r?.ok === true && r?.skipped !== true && trigger !== "stale-repair") armFloorDuty(ctx.paths.runDir);
3250
+ return r;
3050
3251
  }
3051
3252
 
3052
- /** The set-aside grounds the placement form holds, by name and by group, as uri → entry, through the form's own selection index. */
3053
- export function placementSetAsideGrounds(P) { // @internal — driven by its test with a run's own files
3253
+ const bandRecordIds = (band) => [...new Set((band?.enumerated ?? []).map((r) => String(r?.record_id ?? "").toLowerCase()).filter(Boolean))];
3254
+
3255
+ /** The named band's record ids a placement pass that ran read, so a record the band gains after it is known as new. */
3256
+ function recordPlacementBandIds(P) {
3054
3257
  try {
3055
- const form = readPlacementForm(P.runDir);
3056
- if (!form.set_aside?.length) return () => null;
3057
- const input = readPlacementFormInput(P.runDir);
3058
- return setAsideGrounds(standingSetAside(form.set_aside, input), buildSelectionIndex(input), input.groups, input.floors);
3059
- } catch { return () => null; }
3258
+ const ids = bandRecordIds(safeReadJson(P.registerNamedBand)).sort();
3259
+ atomicWrite(driverDir(P.runDir, "placement-band-ids.json"), JSON.stringify({ count: ids.length, ids }) + "\n");
3260
+ } catch (e) { runLog(P.runDir, { event: "placement-band-ids-failed", reason: String(e?.message ?? e).slice(0, 120) }); }
3261
+ }
3262
+
3263
+ /**
3264
+ * The records the named band holds that the last placement pass that ran never read. [] when it read every one, and
3265
+ * when no pass recorded what it read (a run predating the record assesses nothing late). Ids, never the band's bytes:
3266
+ * a count that moved is not a record placement has not seen. PURE over the run's files.
3267
+ */
3268
+ export function lateRecordIds(P) { // @internal — driven by its test with a run's own files
3269
+ const seen = safeReadJson(driverDir(P.runDir, "placement-band-ids.json"));
3270
+ if (!Array.isArray(seen?.ids)) return [];
3271
+ const had = new Set(seen.ids);
3272
+ return bandRecordIds(safeReadJson(P.registerNamedBand)).filter((id) => !had.has(id));
3273
+ }
3274
+
3275
+ /**
3276
+ * THE LATE RECORDS THE REPORT DOES NOT ADDRESS: records the band holds that no placement pass read (`lateRecordIds`),
3277
+ * which neither the reading step's account nor the writer's findings name, and which no other clause names
3278
+ * (`alreadyNamed`, lowercased). A record either step names is addressed there, whatever placement did. Lowercased ids,
3279
+ * in the band's order. Never throws. PURE over the run's files.
3280
+ */
3281
+ export function lateRecordsUnaddressed(P, alreadyNamed = new Set()) { // @internal — driven by its test with a run's own files
3282
+ const late = lateRecordIds(P);
3283
+ if (!late.length) return [];
3284
+ let endings = { carried: new Set(), dropRows: new Set() };
3285
+ try { endings = parseFindingsEndings(readFileSync(P.registerFindings, "utf8")); } catch { /* no account from the reading step */ }
3286
+ let named = new Set();
3287
+ try { named = new Set([...findingUris(parseFindingsJsonLenient(readFileSync(P.findings, "utf8"))?.findings ?? []).keys()].map((u) => String(u).toLowerCase())); }
3288
+ catch { /* no findings from the writer */ }
3289
+ return late.filter((id) => !endings.carried.has(id) && !endings.dropRows.has(id) && !named.has(id) && !alreadyNamed.has(id));
3290
+ }
3291
+
3292
+ /** The records the writer was owed, by id: the population its duty is out of. Null without a spec. Never throws. */
3293
+ function owedUrisFor(runDir) {
3294
+ try {
3295
+ const owed = driverReadsFor(runDir).owed;
3296
+ return Array.isArray(owed) ? owed.map((row) => String(normalizeRecordUri(row?.uri ?? row?.record_id) ?? "").toLowerCase()).filter(Boolean) : null;
3297
+ } catch { return null; }
3060
3298
  }
3061
3299
 
3062
3300
  /**
@@ -3068,8 +3306,16 @@ export function placementOwnerFates(P, placements) { // @internal — driven b
3068
3306
  try {
3069
3307
  const input = readPlacementFormInput(P.runDir);
3070
3308
  if (!input.groups) return { absent: "the band shape lists no groups" };
3071
- const counted = ownerFates({ groups: input.groups, entries: standingSetAside(readPlacementForm(P.runDir).set_aside, input), index: buildSelectionIndex(input),
3072
- placedUris: (Array.isArray(placements) ? placements : []).flatMap((p) => (Array.isArray(p?.records) ? p.records : [])) });
3309
+ const index = buildSelectionIndex(input);
3310
+ // What placement read, by its own reading log, and what each line's goods rest on, where the run recorded it.
3311
+ const evidence = readGoodsEvidence(P.runDir);
3312
+ const stateOf = Array.isArray(evidence?.records) ? new Map(evidence.records.map((r) => [normalizeRecordUri(r?.uri), r?.state])) : null;
3313
+ const placedUris = (Array.isArray(placements) ? placements : []).flatMap((p) => (Array.isArray(p?.records) ? p.records : []));
3314
+ const held = readPlacementForm(P.runDir);
3315
+ const counted = ownerFates({ groups: input.groups, entries: standingSetAside(held.set_aside, input, { placedUris, rows: held.rows }), index,
3316
+ placedUris,
3317
+ served: servedLines(input.groups, input.served, index),
3318
+ lineEvidence: stateOf ? (l) => stateOf.get(normalizeRecordUri(l?.record_id)) ?? null : null });
3073
3319
  return counted ? { counted } : { absent: "the band shape's groups could not be counted" };
3074
3320
  } catch (e) { return { absent: `an input could not be read: ${String(e?.message ?? e).slice(0, 120)}` }; }
3075
3321
  }
@@ -3112,7 +3358,7 @@ function recordDigestSeam(ctx, r, trigger = null) {
3112
3358
  if (c === "carried") carried.push(row);
3113
3359
  }
3114
3360
  return recordSeamDiscards(ctx, {
3115
- seam: "digest", stage: "register-digest", trigger, completed: r?.ok === true && !r?.skipped,
3361
+ seam: "digest", stage: "register-digest", trigger, completed: r?.ok === true && !r?.skipped, skipped: r?.skipped === true,
3116
3362
  saw, carried,
3117
3363
  evidence: r?.fail ? String(r.fail).slice(0, 80) : "",
3118
3364
  reasonFor: (rec, uri) => {
@@ -3183,6 +3429,28 @@ function findingsSurfaceRows(P) {
3183
3429
  * `scope` is instructed-scope.json, JOB-authored before any model ran. It is the only thing the
3184
3430
  * contradiction refusals rest on, which is why it is read here rather than accepted from the seat.
3185
3431
  */
3432
+ // THE WRITE-UP IS TOLD WHAT EACH RECORD'S GOODS REST ON, beside its position (goods-evidence.mjs `goodsFromLabel`),
3433
+ // so a record declined on goods is declined knowing whether those goods were the official record's, a search answer's,
3434
+ // cut, or none. Only the dispatch's copy carries it; the spec the declination tool reads is the positions alone.
3435
+ // A list the label could not be added to keeps its lines as they were, and the run says so: a write-up handed no goods
3436
+ // source must not read as one whose records have none.
3437
+ export function withGoodsFrom(ctx, P, rows) { // @internal — driven by its test with a run's own files
3438
+ const unlabelled = (reason) => {
3439
+ try { runLog(P.runDir, { event: "write-up-goods-from-failed", rows: rows.length, reason: String(reason).slice(0, 120) }); } catch { /* the list still goes */ }
3440
+ return rows;
3441
+ };
3442
+ try {
3443
+ const runPrefix = ctx.run?.slug && ctx.run?.codename ? `clearance-${ctx.run.slug}-${ctx.run.codename}-` : null;
3444
+ if (!runPrefix) return unlabelled("the run has no session prefix to read its evidence by");
3445
+ const uris = rows.filter((r) => r?.kind !== "page" && r?.uri).map((r) => r.uri);
3446
+ const evidence = goodsEvidenceOfDrops(P, runPrefix, uris);
3447
+ return rows.map((r) => {
3448
+ const e = r?.kind !== "page" && r?.uri ? evidence.get(r.uri) : null;
3449
+ return e ? { ...r, goods_from: goodsFromLabel({ state: e.goods, source: e.source }) } : r;
3450
+ });
3451
+ } catch (e) { return unlabelled(e?.message ?? e); }
3452
+ }
3453
+
3186
3454
  export function prepareDeclinationSpec(ctx, P) { // @internal
3187
3455
  const rows = [...findingsSurfaceRows(P), ...notesPageRows(existsSync(P.commonLaw) ? readFileSync(P.commonLaw, "utf8") : null)]; // records, then the web notes' pages
3188
3456
  if (!rows.length) {
@@ -3200,7 +3468,7 @@ export function prepareDeclinationSpec(ctx, P) { // @internal
3200
3468
  ensureDriverDir(P.runDir);
3201
3469
  atomicWrite(driverDir(P.runDir, "declination-spec.json"),
3202
3470
  JSON.stringify({ ts: new Date().toISOString(), rows, scope: { marks: scope.marks ?? [], classes: scope.classes ?? [] } }, null, 2) + "\n");
3203
- ctx.findingsSurface = rows;
3471
+ ctx.findingsSurface = withGoodsFrom(ctx, P, rows);
3204
3472
  } catch (e) {
3205
3473
  // The dispatch block is gated on ctx.findingsSurface, so a failed write leaves the stage UNORDERED
3206
3474
  // rather than ordered to call a tool whose spec it cannot read. Loud in the log, never a dead order.
@@ -3214,8 +3482,17 @@ export function prepareDeclinationSpec(ctx, P) { // @internal
3214
3482
  * on a fenced or prose-wrapped file, and a synthesis pass whose output is merely untidy would otherwise
3215
3483
  * record every record it delivered as discarded — the defect rebuilt one seam later.
3216
3484
  */
3485
+ /** Where the ranking the writer last read is kept, for the sentence on records re-ranked after it wrote. */
3486
+ const rankingReadByWriterPath = (P) => driverDir(P.runDir, "ranking-read-by-writer.json");
3487
+ /** Keeps where each record stood when a writer's pass completed. Never throws. */
3488
+ function keepRankingReadByWriter(P) {
3489
+ try { atomicWrite(rankingReadByWriterPath(P), JSON.stringify(rankingToJson(rankingNow(P)), null, 2) + "\n"); }
3490
+ catch (e) { runLog(P.runDir, { event: "ranking-read-by-writer-failed", reason: String(e?.message ?? e).slice(0, 160) }); }
3491
+ }
3492
+
3217
3493
  function recordSynthesisSeam(ctx, r, trigger = null) {
3218
3494
  const P = ctx.paths;
3495
+ if (r?.ok) keepRankingReadByWriter(P); // where each record stood as this writer read it
3219
3496
  const declined = readDeclinations(P.runDir); // — three-valued: absent ledger ≠ empty ledger
3220
3497
  // — ONE DERIVATION, and it is CALLED rather than copied. This used to rebuild the carried set
3221
3498
  // inline, in three lines that happened to match `findingsSurfaceRows`'s. They must match: that function
@@ -3232,7 +3509,7 @@ function recordSynthesisSeam(ctx, r, trigger = null) {
3232
3509
  }
3233
3510
  } catch { /* validators.findings' business; this seam records what it can */ }
3234
3511
  return recordSeamDiscards(ctx, {
3235
- seam: "synthesis", stage: "synthesis", trigger, completed: r?.ok === true && !r?.skipped,
3512
+ seam: "synthesis", stage: "synthesis", trigger, completed: r?.ok === true && !r?.skipped, skipped: r?.skipped === true,
3236
3513
  saw, carried: delivered,
3237
3514
  evidence: r?.fail ? String(r.fail).slice(0, 80) : "",
3238
3515
  reasonFor: (rec, uri) => {
@@ -3618,7 +3895,8 @@ function deriveRecallReconciliation(ctx, trigger) {
3618
3895
  }
3619
3896
  }
3620
3897
  if (!positions) return notComputable("no exact-identity positions projection — the join's unit is the POSITION and record-unit would block a compliant one-URI position row");
3621
- const artifact = reconcileRecall({ candidates, endings, crowdRulings, positions, dominantElement, inScopeClasses });
3898
+ const placement = placementRecallEndings(P, candidates.map((c) => c.record_id));
3899
+ const artifact = reconcileRecall({ candidates, endings, crowdRulings, positions, dominantElement, inScopeClasses, placement });
3622
3900
  writeArtifact(artifact);
3623
3901
  runLog(P.runDir, recallReconciliationEvent({ trigger, artifact }));
3624
3902
  if (artifact.totals.unended) note(`recall-reconciliation: ${artifact.totals.unended} screened-live dominant-element position(s) end NOWHERE (${artifact.totals.candidates} candidate record(s) in ${artifact.totals.positions} position(s); crowd declares ${artifact.crowd.declared} vs residual ${artifact.crowd.residual})`);
@@ -4436,6 +4714,22 @@ function clearedForSeat(runDir, label) {
4436
4714
  catch { return []; }
4437
4715
  }
4438
4716
 
4717
+ /**
4718
+ * The context a stage's message is composed from. The review's floor check lists placement's standing decisions, and
4719
+ * they are read here, from the run's files, each time its message is built: the review, its recheck, the delivery
4720
+ * loop's whole review after a repeated placement pass and a review dispatched from a rebuilt context all read the
4721
+ * decisions as they stand then, never a copy an earlier pass left on the context, and never "(none)" from a context
4722
+ * that never set one. The reading step and the writer read placement's accepted set-asides the same way. Every
4723
+ * other stage composes from the context as it is.
4724
+ */
4725
+ export function messageCtxFor(name, ctx) { // @internal — driven by its test
4726
+ if (!ctx?.paths) return ctx;
4727
+ if (name === "narrative-refutation") return { ...ctx, placementFloorDecisions: placementFloorDecisionLines(ctx.paths) };
4728
+ // The reading step and the writer are told placement's accepted set-asides the same way, read when the message is built.
4729
+ if (name === "register-digest" || name === "synthesis") return { ...ctx, placementSetAsides: placementSetAsideLines(ctx.paths) };
4730
+ return ctx;
4731
+ }
4732
+
4439
4733
  async function stageOnce(name, ctx, opts = {}) {
4440
4734
  const def = STAGES[name];
4441
4735
  if (!def) throw new Error(`unknown stage ${name}`);
@@ -4615,8 +4909,15 @@ async function stageOnce(name, ctx, opts = {}) {
4615
4909
  // is read HERE because this is the one site that composes a followup. NEVER-KILL: an unreadable
4616
4910
  // or absent seat log yields no constraint and the dispatch is exactly what it was before.
4617
4911
  cleared: clearedForSeat(P.runDir, label),
4912
+ // The review after a delivery-check repair names the files that repair changed; every other dispatch
4913
+ // passes nothing and keeps its own inputs line.
4914
+ changedInputs: opts.changedInputs ?? null,
4915
+ // The reading step's and the writer's fresh sessions are given placement's accepted set-asides as they stand,
4916
+ // except a narrow repair: it fixes only the faults or objects it names, and the general order to check each
4917
+ // ground would widen it. Its inputs, the decisions file among them, are named as for any other.
4918
+ decisions: NARROW_REPAIR_TRIGGERS.has(trigger) ? null : (messageCtxFor(name, ctx)?.placementSetAsides ?? null),
4618
4919
  })
4619
- : (def.message(ctx) + (opts.extra ? `\n\n${opts.extra}` : ""));
4920
+ : (def.message(messageCtxFor(name, ctx)) + (opts.extra ? `\n\n${opts.extra}` : ""));
4620
4921
  // Every stage runs as the forwarding agent (derived from the queue location). An explicit opts.agent wins.
4621
4922
  const execAgent = opts.agent ?? ctx.agent;
4622
4923
  // thinking: ONLY Anthropic models get a --thinking token; a cross-provider model (gpt/gemini) NEVER does, so
@@ -4683,7 +4984,7 @@ async function stageOnce(name, ctx, opts = {}) {
4683
4984
  // seat answering something it was no longer asked about is served from the ledger rather than refused
4684
4985
  // as out-of-list — and `still_open` keeps naming everything genuinely open.
4685
4986
  ...(patch == null && typeof def.refreshCtx === "function"
4686
- ? { refreshMessage: () => def.message(def.refreshCtx(ctx)) + (opts.extra ? `\n\n${opts.extra}` : "") }
4987
+ ? { refreshMessage: () => def.message(messageCtxFor(name, def.refreshCtx(ctx))) + (opts.extra ? `\n\n${opts.extra}` : "") }
4687
4988
  : {}),
4688
4989
  // #5b: a followup is a warm session resume — tell runStage so a hard-wall timeout breaks after ONE attempt
4689
4990
  // (an already-over-budget resume can't fit the 1.5× extension; this restores ~1× the wall, was ~2.5×).
@@ -5972,7 +6273,7 @@ const UPSTREAM_STALE_REPAIR = {
5972
6273
  // re-run could leave outvoting its prose, and it is re-runnable by contract (no one-shot receipt — the
5973
6274
  // assertReceiptSettled call ahead of its normal dispatch settles the plan receipt, which is already
5974
6275
  // settled by the time the delivery gate asks).
5975
- "placement-inquiry": (ctx) => stage("placement-inquiry", ctx, { force: true, trigger: "stale-repair" }),
6276
+ "placement-inquiry": async (ctx) => recordPlacementPass(ctx, await stage("placement-inquiry", ctx, { force: true, trigger: "stale-repair" }), "stale-repair"),
5976
6277
  // frame-diff is DELIBERATELY ABSENT and must stay absent. It is one-shot by contract (no in-process
5977
6278
  // re-diff), so an entry here would re-dispatch a stage the design says runs once, for a model call
5978
6279
  // that cannot change anything: nothing declares frame-diff's outputs as a stage input, so by the time
@@ -5994,14 +6295,16 @@ export function partitionDeliveryStale(staleStages) { // @internal
5994
6295
  // file is byte-unchanged (or every named finding's object is) and no corrections marker landed, fire
5995
6296
  // ONE warm followup demanding the re-emit; a terminal miss NEVER withholds (never-withhold) — it logs,
5996
6297
  // flags the internal review bar, and closes the CLIENT export via _driver/corrections-state.json.
5997
- function snapshotFindingsForCorrections(P, runDir) {
6298
+ export function snapshotFindingsForCorrections(P, runDir) { // @internal
5998
6299
  let raw = null;
5999
6300
  try { raw = readFileSync(P.findings, "utf8"); } catch { /* legacy / not-yet-populated */ }
6000
6301
  if (raw == null) return null;
6001
6302
  const sha = createHash("sha256").update(raw).digest("hex");
6002
6303
  // THE RECORD IN THE MODEL'S NUMBERING, beside the one on disk. The review's flags name findings in the
6003
6304
  // model's numbering, so the corrective body compares the record the model wrote; once the delivered
6004
- // record is folded the two differ (record-fold.mjs). The fold is held too, for a rollback to put back.
6305
+ // record is folded the two differ (record-fold.mjs). The fold is held too, for a rollback to put back,
6306
+ // and so are narrative.md and the transport's merge base (`base`): a rollback that put back findings.json
6307
+ // alone left the next patch to merge onto the failed pass's record.
6005
6308
  const modelAt = modelRecordPath(runDir, P.findings);
6006
6309
  let modelRaw = raw;
6007
6310
  if (modelAt !== P.findings) { try { modelRaw = readFileSync(modelAt, "utf8"); } catch { modelRaw = raw; } }
@@ -6016,7 +6319,7 @@ function snapshotFindingsForCorrections(P, runDir) {
6016
6319
  writeFileSync(tmp, JSON.stringify({ ts: new Date().toISOString(), sha, text: raw }, null, 2));
6017
6320
  renameSync(tmp, driverDir(runDir, "findings-pre-corrective.json"));
6018
6321
  } catch { /* advisory sidecar */ }
6019
- return { raw, sha, model, fold };
6322
+ return { raw, sha, model, fold, base: snapshotSynthesisBase(runDir) };
6020
6323
  }
6021
6324
  /**
6022
6325
  *, T3b — THE CORRECTIVE PASS FAILED VALIDATION. RESTORE THE LAST GOOD STATE.
@@ -6042,6 +6345,11 @@ function snapshotFindingsForCorrections(P, runDir) {
6042
6345
  * is the document every earlier stage's output was checked against. It is not a guess at a good state;
6043
6346
  * it is the last one this run demonstrated.
6044
6347
  *
6348
+ * The narrative beside it and the transport's merge base come back with it, whether or not findings.json
6349
+ * moved: a pass can save a narrative change alone, and a base left at the failed pass's record is what the
6350
+ * next patch would merge onto, which applies the discarded pass again. The snapshot holds both
6351
+ * (`snapshotFindingsForCorrections`); a snapshot taken without them leaves both files as they are.
6352
+ *
6045
6353
  * ── TWO CASES THAT MUST STILL THROW, BECAUSE SWALLOWING THEM LOSES MORE THAN IT SAVES ────────────
6046
6354
  *
6047
6355
  * rate-limited The run is PARKED and auto-resumes after the cap resets, with every stage intact.
@@ -6066,8 +6374,10 @@ export function rollbackCorrectivePass(P, runDir, pre, fail) { // @internal
6066
6374
  atomicWrite(P.findings, pre.raw);
6067
6375
  if (pre.fold) restoreFold(runDir, pre.fold); // the record the model wrote and the map, as they stood
6068
6376
  }
6377
+ // `restored` keeps speaking for findings.json alone; what happened to the narrative and the base is its own field.
6378
+ const base = restoreSynthesisBase(runDir, pre.base);
6069
6379
  const record = { ts: new Date().toISOString(), reason: String(fail?.fail ?? "failed").slice(0, 200),
6070
- restored: changed, preSha: pre.sha, failedSha: nowSha };
6380
+ restored: changed, preSha: pre.sha, failedSha: nowSha, ...(base ? { base } : {}) };
6071
6381
  try { atomicWrite(driverDir(runDir, "corrective-rollback.json"), JSON.stringify(record, null, 2) + "\n"); }
6072
6382
  catch { /* sidecar is best-effort; the runLog event below is the record that decides */ }
6073
6383
  return record;
@@ -6154,26 +6464,35 @@ export function repairUnnamedRemovals(P, runDir, pre, namedOrdinals, namedMarks,
6154
6464
 
6155
6465
  if (!restoredFindings.length && !restoredKeys.length && !restoredRows.length) return null;
6156
6466
 
6157
- const merged = { ...postDoc };
6158
- for (const k of restoredKeys) merged[k] = preDoc[k];
6159
- for (const [reg, back] of Object.entries(rowsBack)) {
6160
- // In the snapshot's order, the post-pass version of every row it kept, then any row the pass added.
6161
- const keyOf = REPORT_LINE_KEY[reg];
6162
- const post = new Map(postDoc[reg].map((r) => [keyOf(r), r]));
6163
- const restored = new Set(back);
6164
- const inPre = new Set(preDoc[reg].map(keyOf));
6165
- merged[reg] = [
6166
- ...preDoc[reg].flatMap((r) => restored.has(r) ? [r] : post.has(keyOf(r)) ? [post.get(keyOf(r))] : []),
6167
- ...postDoc[reg].filter((r) => !inPre.has(keyOf(r))),
6168
- ];
6169
- }
6170
- if (restoredFindings.length) {
6171
- // Back in the order the reviewer read them, by ordinal — a restored finding appended to the end
6172
- // would be a different document from the one that was correct.
6173
- const byOrdinal = new Map(preList.map((f, i) => [String(f?.ordinal), i]));
6174
- merged.findings = [...postList, ...restoredFindings]
6175
- .sort((a, b) => (byOrdinal.get(String(a?.ordinal)) ?? 0) - (byOrdinal.get(String(b?.ordinal)) ?? 0));
6176
- }
6467
+ // ONE RESTORATION, applied to a record in the model's numbering: the file's here, and the merge base's
6468
+ // below. Whatever a record already holds stays as it holds it; only what came back is added.
6469
+ const byOrdinal = new Map(preList.map((f, i) => [String(f?.ordinal), i]));
6470
+ const restoreInto = (doc) => {
6471
+ const merged = { ...doc };
6472
+ for (const k of restoredKeys) if (!(k in merged)) merged[k] = preDoc[k];
6473
+ for (const [reg, back] of Object.entries(rowsBack)) {
6474
+ // In the snapshot's order, the post-pass version of every row it kept, then any row the pass added.
6475
+ const keyOf = REPORT_LINE_KEY[reg];
6476
+ const now = Array.isArray(doc[reg]) ? doc[reg] : [];
6477
+ const post = new Map(now.map((r) => [keyOf(r), r]));
6478
+ const restored = new Set(back);
6479
+ const inPre = new Set(preDoc[reg].map(keyOf));
6480
+ merged[reg] = [
6481
+ ...preDoc[reg].flatMap((r) => restored.has(r) ? [r] : post.has(keyOf(r)) ? [post.get(keyOf(r))] : []),
6482
+ ...now.filter((r) => !inPre.has(keyOf(r))),
6483
+ ];
6484
+ }
6485
+ if (restoredFindings.length) {
6486
+ // Back in the order the reviewer read them, by ordinal — a restored finding appended to the end
6487
+ // would be a different document from the one that was correct.
6488
+ const list = Array.isArray(doc.findings) ? doc.findings : [];
6489
+ const has = new Set(list.map((f) => String(f?.ordinal)));
6490
+ merged.findings = [...list, ...restoredFindings.filter((f) => !has.has(String(f?.ordinal)))]
6491
+ .sort((a, b) => (byOrdinal.get(String(a?.ordinal)) ?? 0) - (byOrdinal.get(String(b?.ordinal)) ?? 0));
6492
+ }
6493
+ return merged;
6494
+ };
6495
+ const merged = restoreInto(postDoc);
6177
6496
  const brief = (f) => ({ ordinal: f?.ordinal ?? null, mark: f?.mark ?? null });
6178
6497
  const record = {
6179
6498
  ts: new Date().toISOString(),
@@ -6184,11 +6503,33 @@ export function repairUnnamedRemovals(P, runDir, pre, namedOrdinals, namedMarks,
6184
6503
  };
6185
6504
  atomicWrite(P.findings, JSON.stringify(merged, null, 2) + "\n");
6186
6505
  foldAfterSave(runDir, P.findings); // after the seam the repaired record is folded again, as a save's is
6506
+ // THE MERGE BASE TAKES THE SAME RESTORATION. The patch that follows (the corrections gate's re-emit) merges
6507
+ // onto the base and re-renders the file from it, so a restore written to the file alone is dropped again.
6508
+ // The edit is judged as a call is; a refusal leaves the base as it was, and the record says so.
6509
+ record.base = rebaseSynthesis(runDir, restoreInto);
6187
6510
  try { atomicWrite(driverDir(runDir, "corrective-repair.json"), JSON.stringify(record, null, 2) + "\n"); }
6188
6511
  catch { /* sidecar is best-effort; the runLog event is the record that decides */ }
6189
6512
  return record;
6190
6513
  }
6191
6514
 
6515
+ /**
6516
+ * THE REPAIR'S RUN-LOG ROW, built from what `repairUnnamedRemovals` returned. PURE.
6517
+ *
6518
+ * `base` says what the merge base took, in the words the basis derivation's row uses for the same edit:
6519
+ * `restored`, or `unchanged` where the base already held what came back; `refused` where the acceptance boundary
6520
+ * refused the edit and the base was left as it was, so the next accepted patch drops the restore again; and
6521
+ * `no-base` where the run holds no base, so a later patch is refused for having none. The sidecar keeps the reason.
6522
+ */
6523
+ export function removalRepairedRow(repaired) { // @internal
6524
+ const b = repaired?.base;
6525
+ const base = !b ? null : b.ok ? (b.changed ? "restored" : "unchanged") : /^no_base:/.test(String(b.reason)) ? "no-base" : "refused";
6526
+ return { event: "corrective-unnamed-removal-repaired", defect: true,
6527
+ restored: repaired.restoredFindings.length, keys: repaired.restoredKeys.length,
6528
+ rows: repaired.restoredRows.length,
6529
+ leftRemoved: repaired.leftRemoved.length,
6530
+ findings: repaired.restoredFindings.map((f) => `${f.ordinal}:${f.mark}`), ...(base ? { base } : {}) };
6531
+ }
6532
+
6192
6533
  /**
6193
6534
  * The ordinals the reviewer's flags name, from the typed call where the run has one and the rendered
6194
6535
  * parse where it does not — the same two sources, in the same order, that `correctionsExtra` uses to
@@ -6240,7 +6581,9 @@ export function restoredFindingsTable(repair) { // @internal
6240
6581
  function correctionNamedOrdinals(P) {
6241
6582
  const out = [];
6242
6583
  try {
6243
- const typed = readAcceptedFlags(P.runDir);
6584
+ // `P.reviewFlags`: the pass after a delivery-check repair applies a LATER review's flags, which
6585
+ // `readAcceptedFlags` (the first review's call) cannot see. Absent everywhere else.
6586
+ const typed = Array.isArray(P.reviewFlags) ? P.reviewFlags : readAcceptedFlags(P.runDir);
6244
6587
  if (typed) { for (const f of typed) if (Array.isArray(f?.on)) out.push(...f.on); return out; }
6245
6588
  } catch { /* fall through to the rendered parse, exactly as the worklist does */ }
6246
6589
  try {
@@ -6301,6 +6644,426 @@ function correctionNamedSet(P, doc0 = null) {
6301
6644
  }
6302
6645
  return [...named];
6303
6646
  }
6647
+ // ── THE REVIEW AFTER A DELIVERY-CHECK REPAIR, AND ITS ONE CORRECTION ─────────────────────────────────────
6648
+ //
6649
+ // When the delivery check finds a fault in the narrative, the writer repairs its record and the narrative's
6650
+ // bytes move, so the reviewer reads stale. It used to be sent the whole review again, cold, and nothing applied
6651
+ // what it flagged then. Now, where the scope can be established, it reads what the repair changed and every
6652
+ // statement resting on it, worked out by value from the writer's record before and after the repair
6653
+ // (repair-scope.mjs), and its flags go to ONE correction by the writer that nothing reviews again.
6654
+ //
6655
+ // THE FIRST REVIEW STAYS WHOLE, and so does this one whenever the scope is not certain: no record kept from
6656
+ // before the repair, another of the reviewer's inputs moved, a whole re-synthesis ran in this pass, or the
6657
+ // record's version or framework changed. A rewrite with the same values is no change and no review runs.
6658
+
6659
+ /** Which review the reviewer's staleness after a delivery-check repair calls for. Reads the run; never throws. */
6660
+ /**
6661
+ * A SAVE TRIED AGAIN BEFORE IT IS GIVEN UP. Calls `save` up to `attempts` times, waiting `waitMs` between tries, and
6662
+ * says how it went: `{ ok, attempts, error }`. Never throws. `wait` is injectable so a test does not sleep.
6663
+ */
6664
+ export function saveWithRetries(save, { attempts = 3, waitMs = 250, wait = (ms) => Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms) } = {}) { // @internal — driven by its test
6665
+ let error = null;
6666
+ for (let n = 1; n <= attempts; n++) {
6667
+ try { save(); return { ok: true, attempts: n, error }; }
6668
+ catch (e) { error = e; if (n < attempts) wait(waitMs); }
6669
+ }
6670
+ return { ok: false, attempts, error };
6671
+ }
6672
+
6673
+ /**
6674
+ * THE STAGES STALE ON WHAT PLACEMENT WROTE, which join the chain of a changed decision whoever moved it: the delivery
6675
+ * check's own repeat, the decisions written again when the shape is derived again, or a late pass whose readers had
6676
+ * already run. `names` are the base names of placement's files. PURE.
6677
+ */
6678
+ export function stagesStaleOnPlacement(stale = [], names = new Set()) { // @internal — driven by its test
6679
+ return stale.filter((x) => (x?.changed ?? []).some((c) => names.has(String(c?.name ?? "")))).map((x) => x.label);
6680
+ }
6681
+
6682
+ /**
6683
+ * A CLAUSE JUDGED AGAIN REPLACES THE ONE IT REPEATS, BY ITS KIND. Withdraws every entry whose reason opens with
6684
+ * `<token>:` from the index-aligned reasons and clauses, and a withdrawn clause from the guard set once no entry left
6685
+ * carries it. Returns the arrays as they now stand and how many entries went. PURE.
6686
+ */
6687
+ export function withdrawByToken(reasons = [], clauses = [], guard = new Set(), token) { // @internal — driven by its test
6688
+ const keep = reasons.map((r) => !String(r ?? "").startsWith(`${token}:`));
6689
+ const out = { reasons: reasons.filter((_, i) => keep[i]), clauses: clauses.filter((_, i) => keep[i]), withdrawn: keep.filter((k) => !k).length };
6690
+ for (const [i, c] of clauses.entries()) if (!keep[i] && !out.clauses.includes(c)) guard.delete(c);
6691
+ return out;
6692
+ }
6693
+
6694
+ /**
6695
+ * WHAT A REPAIR IN THE DELIVERY CHECK LEFT STALE, AND WHAT OF IT IS QUEUED. A stage is this repair's when it is stale now
6696
+ * and was not before the repair, or is stale on an input that has moved since; a stage stale before for the same reasons
6697
+ * is not, and stays the guard's. Every stage the repair left stale joins the chain, so its own re-run is followed in turn.
6698
+ * Of those, a stage already queued ahead runs where it stands, and one this pass already re-did is re-done once more at
6699
+ * most. PURE.
6700
+ */
6701
+ export function whatTheRepairLeftStale({ before = [], after = [], repaired, ahead = new Set(), processed = new Set(), requeued = new Set() }) { // @internal — driven by its test
6702
+ const keys = (x) => (x?.changed ?? []).map((c) => `${c?.name}@${c?.now}`);
6703
+ const was = new Map(before.map((x) => [x.label, new Set(keys(x))]));
6704
+ const chain = [], queue = [], requeue = [];
6705
+ for (const x of after) {
6706
+ if (x.label === repaired) continue;
6707
+ const prior = was.get(x.label);
6708
+ if (prior && keys(x).every((k) => prior.has(k))) continue; // stale before, for the same reasons: not this repair's
6709
+ chain.push(x.label);
6710
+ if (ahead.has(x.label)) continue;
6711
+ if (processed.has(x.label)) { if (requeued.has(x.label)) continue; requeue.push(x.label); }
6712
+ queue.push(x);
6713
+ }
6714
+ return { chain, queue, requeue };
6715
+ }
6716
+
6717
+ export function postRepairScope(runDir, P, s2, { staleRepaired = [], baseKept = false } = {}) { // @internal
6718
+ if (!baseKept) return { mode: "whole", why: "no-repair-base" };
6719
+ const narrative = basename(String(P?.narrative ?? "narrative.md"));
6720
+ const moved = (s2?.changed ?? []).filter((c) => c?.via !== "repair-record" && basename(String(c?.name ?? "")) !== narrative);
6721
+ if (moved.length) return { mode: "whole", why: "another-input-moved", moved: moved.map((c) => String(c?.name ?? "")) };
6722
+ if (staleRepaired.includes("synthesis")) return { mode: "whole", why: "re-synthesised" };
6723
+ // Any other step this loop re-ran ahead of the reviewer (the digest, placement, the skeptic) moved what the review
6724
+ // stands on beyond the repair's own change, so the scope the queue captured no longer says what changed.
6725
+ const upstream = staleRepaired.filter((l) => l !== "narrative-refutation" && !/^report-(overview|card:)/.test(l));
6726
+ if (upstream.length) return { mode: "whole", why: "upstream-re-run", upstream };
6727
+ let before = null, after = null;
6728
+ try { before = readRepairBase(runDir); after = lastAcceptedCall(runDir); } catch { /* read as no base */ }
6729
+ const changed = changedContent(before, after);
6730
+ if (changed.whole) return { mode: "whole", why: changed.why };
6731
+ if (isEmptyChange(changed)) return { mode: "none", why: "repair-changed-no-content" };
6732
+ return { mode: "narrowed", changed, after, findingOnly: queuedForTheRecordOnly(s2) };
6733
+ }
6734
+
6735
+ /**
6736
+ * Whether the delivery-check repair's change to the findings record is owed a review when the narrative did not move:
6737
+ * any change but the same values saved again. A change the scope cannot narrow (a moved version or framework, or a
6738
+ * record that could not be compared) is owed one too, and the loop gives it the whole review. Never throws.
6739
+ */
6740
+ export function recordChangeOwesReview(runDir) { // @internal
6741
+ let c = null;
6742
+ try { c = changedContent(readRepairBase(runDir), lastAcceptedCall(runDir)); } catch { c = null; }
6743
+ return !isEmptyChange(c);
6744
+ }
6745
+
6746
+ /**
6747
+ * An open ask as the closure step is shown it: its id, where it was raised and its text, and for a request kept as
6748
+ * parts, which part it is and the questions that part holds, so two parts of one request never read as one line.
6749
+ */
6750
+ export function openAskForClosure(a) { // @internal — driven by its test
6751
+ return { ask_id: a.ask_id, born: a.born, ask: a.ask,
6752
+ ...(a.part ? { part: a.part, qids: Array.isArray(a.qids) ? a.qids : [] } : {}), ...(a.family ? { family: a.family } : {}) };
6753
+ }
6754
+
6755
+ /** The change a whole review's record lists when its scope could not be compared: none. */
6756
+ const NO_CHANGE = Object.freeze({ findings: [], lines: [], registers: [], sections: [], whole: true, why: "not-compared" });
6757
+
6758
+ /** Whether the reviewer was queued for a change to the findings record only: its narrative did not move. */
6759
+ function queuedForTheRecordOnly(s2) {
6760
+ const changed = s2?.changed ?? [];
6761
+ return changed.length > 0 && changed.every((c) => c?.via === "repair-record");
6762
+ }
6763
+
6764
+ /** What the record of an unread review says its change was: findings only, or more. null with no unread record. */
6765
+ function unreadRecordFindingOnly(runDir) {
6766
+ try {
6767
+ const r = JSON.parse(readFileSync(postRepairRecordPath(runDir), "utf8"));
6768
+ return r?.status === "unread" && typeof r.findingOnly === "boolean" ? r.findingOnly : null;
6769
+ } catch { return null; }
6770
+ }
6771
+
6772
+ /**
6773
+ * A review after a repair that could not be read, ON RECORD. Its change went unreviewed, and the reviewer's own stamp
6774
+ * cannot be trusted to say so: a findings-only change does not move the files the reviewer declares, and a stage
6775
+ * that ends with no accepted call still reads ok whenever the earlier review's file stands, so its stamp is
6776
+ * refreshed over the repaired narrative. This record is never handed to the delivery guard: a run is not held on an
6777
+ * unread review (owner's ruling, 2026-10-19), and such a run delivers as the released engine delivers it. The one
6778
+ * boundary is the released engine's own: a reviewer whose re-run fails outright leaves its stamp stale on a changed
6779
+ * narrative, and the freshness guard decides that, as it always has. The record keeps what went unreviewed, and a
6780
+ * resume for any other cause reviews that change whole, until a review that is read replaces the record.
6781
+ */
6782
+ export function unreadRecordReview(runDir) { // @internal
6783
+ try { return JSON.parse(readFileSync(postRepairRecordPath(runDir), "utf8"))?.status === "unread"; }
6784
+ catch { return false; }
6785
+ }
6786
+
6787
+ const reviewBeforeRepairPath = (runDir) => driverDir(runDir, "review-before-repair.md");
6788
+ const reviewAfterRepairPath = (runDir) => driverDir(runDir, "review-after-repair.md");
6789
+ const postRepairCorrectionsPath = (runDir) => driverDir(runDir, "post-repair-corrections.json");
6790
+ const scopeRecord = (scope, items) => ({
6791
+ findingOnly: Boolean(scope.findingOnly),
6792
+ changed: { findings: scope.changed.findings.map((f) => ({ ordinal: f.ordinal, change: f.change, fields: f.fields })),
6793
+ lines: scope.changed.lines.map((l) => ({ area: l.area, change: l.change })),
6794
+ sections: scope.changed.sections.map((x) => x.title), registers: scope.changed.registers.map((r) => r.label) },
6795
+ items: items.map((i) => ({ kind: i.kind, role: i.role, label: i.label, ...(i.delivered != null ? { delivered: i.delivered } : {}) })),
6796
+ });
6797
+
6798
+ /**
6799
+ * The narrowed review. Keeps the whole review aside first, so whatever this one writes, the run can put the
6800
+ * whole review back: `senior-eye-review.md` holds the review whose verdict the run carries, and the caller
6801
+ * decides that after the late-verdict rule. Returns `{reviewed: false}` when no accepted review came back —
6802
+ * the label then stays stale and the delivery guard decides, as for a whole review that failed.
6803
+ */
6804
+ async function runPostRepairReview(ctx, P, run, scope) {
6805
+ const runDir = run.runDir;
6806
+ const kept = existsSync(P.seniorEyeReview) ? readFileSync(P.seniorEyeReview, "utf8") : null;
6807
+ if (kept != null) atomicWrite(reviewBeforeRepairPath(runDir), kept);
6808
+ const putBack = () => {
6809
+ if (kept == null) return;
6810
+ let now = null;
6811
+ try { now = readFileSync(P.seniorEyeReview, "utf8"); } catch { now = null; }
6812
+ if (now !== kept) atomicWrite(P.seniorEyeReview, kept);
6813
+ };
6814
+ const seq0 = nextRefutationSeq(runDir);
6815
+ const fold = readFold(runDir);
6816
+ const resting = restingOn(scope.changed, scope.after);
6817
+ const items = scopeItems(scope.changed, resting, fold?.map);
6818
+ const list = listForReview(scope.changed, resting, fold?.map);
6819
+ const changedInputs = changedInputsOf(scope.changed, { narrative: P.narrative, findings: P.findings });
6820
+ const message = repairFollowup("narrative-refutation:post-repair-review", {
6821
+ findings: P.findings, ...list, registerOnly: Boolean(ctx.registerOnly), job: ctx.job ?? {},
6822
+ skepticFlags: P.skepticFlags, frameReopenReceipt: P.frameReopenReceipt, floorDecisions: placementFloorDecisionLines(P),
6823
+ });
6824
+ const { text: blocks } = composeDispatchExtra("narrative-refutation", ctx, { skip: ["refute-plain-register"] });
6825
+ const t0 = Date.now();
6826
+ const r = await stage("narrative-refutation", ctx, {
6827
+ force: true, trigger: "post-repair-review", freshMessage: message, ...(blocks ? { extra: blocks } : {}), changedInputs,
6828
+ });
6829
+ const rec = r?.ok ? readAcceptedReviewSince(runDir, seq0) : null;
6830
+ const base = { seq0, ...scopeRecord(scope, items), changedInputs: changedInputs.map((x) => basename(String(x))), ms: Date.now() - t0 };
6831
+ if (!rec) {
6832
+ // NOT A PASS: no review the driver can act on came back. The whole review goes back in place, the record
6833
+ // says the review could not be read, and the label stays stale for the delivery guard.
6834
+ putBack();
6835
+ writePostRepairRecord(runDir, { status: "unread", ...base, fail: r?.ok ? "no-accepted-call" : String(r?.fail ?? "failed") });
6836
+ runLog(runDir, { event: "post-repair-review", read: false, fail: r?.ok ? "no-accepted-call" : String(r?.fail ?? "failed").slice(0, 120) });
6837
+ note(`[post-repair] the review after the delivery-check repair could not be read back — the whole review stays in place and the delivery guard decides`);
6838
+ return { reviewed: false };
6839
+ }
6840
+ const reviewText = readFileSync(P.seniorEyeReview, "utf8");
6841
+ atomicWrite(reviewAfterRepairPath(runDir), reviewText);
6842
+ const points = classifyPoints(rec.flags, items);
6843
+ const by = points.reduce((a, x) => ((a[x.class] = (a[x.class] ?? 0) + 1), a), {});
6844
+ writePostRepairRecord(runDir, { status: "read", ...base, verdict: rec.verdict, seq: rec.seq,
6845
+ points: points.map((x) => ({ n: x.n, kind: x.kind, on: x.on, target: x.target.label, class: x.class, text: String(rec.flags[x.n - 1]?.text ?? "") })) });
6846
+ runLog(runDir, { event: "post-repair-review", read: true, verdict: rec.verdict, flags: rec.flags.length, classes: by,
6847
+ changed: { findings: scope.changed.findings.length, lines: scope.changed.lines.length, sections: scope.changed.sections.length, registers: scope.changed.registers.length },
6848
+ findingOnly: Boolean(scope.findingOnly), ms: base.ms });
6849
+ return { reviewed: true, rec, reviewText, items, points, putBack, base };
6850
+ }
6851
+
6852
+ /**
6853
+ * THE WHOLE REVIEW OF A REPAIR'S CHANGE: when the narrowed one could not be read, and when the change cannot be
6854
+ * narrowed at all (a change to the record the scope cannot compare, or one an earlier review could not read, as on
6855
+ * a resume). It returns what runPostRepairReview returns, so its points go to the same one correction; adopting its
6856
+ * verdict alone would leave its flags unapplied. When it cannot be read, the record says the change was not reviewed
6857
+ * (`unreadRecordReview`). No unread review is a pass, and the record never holds the run; a re-run that failed outright
6858
+ * leaves the reviewer's stamp as the released engine leaves it, for the freshness guard. `rerun`
6859
+ * is the reviewer's own stale-repair arm.
6860
+ */
6861
+ async function runWholeReviewForRecord(ctx, P, run, scope, rerun) {
6862
+ const runDir = run.runDir;
6863
+ const seq0 = nextRefutationSeq(runDir);
6864
+ const t0 = Date.now();
6865
+ const r = await rerun();
6866
+ const rec = r?.ok ? readAcceptedReviewSince(runDir, seq0) : null;
6867
+ const fold = readFold(runDir);
6868
+ const items = scopeItems(scope.changed, restingOn(scope.changed, scope.after), fold?.map);
6869
+ const base = { seq0, ...scopeRecord(scope, items), whole: true, ms: Date.now() - t0 };
6870
+ if (!rec) {
6871
+ const fail = r?.ok ? "no-accepted-call" : String(r?.fail ?? "failed");
6872
+ writePostRepairRecord(runDir, { status: "unread", ...base, fail });
6873
+ runLog(runDir, { event: "post-repair-review", read: false, whole: true, fail: fail.slice(0, 120) });
6874
+ note(`[post-repair] neither review of what the repair changed could be read back — it was not reviewed, and the run's record says so; ${r?.ok ? "the run delivers" : "the reviewer's re-run failed, so its stamp stays as it was and the freshness guard decides, as in the released engine"}`);
6875
+ return { reviewed: false };
6876
+ }
6877
+ const reviewText = readFileSync(P.seniorEyeReview, "utf8");
6878
+ atomicWrite(reviewAfterRepairPath(runDir), reviewText);
6879
+ const points = classifyPoints(rec.flags, items);
6880
+ writePostRepairRecord(runDir, { status: "read", ...base, verdict: rec.verdict, seq: rec.seq,
6881
+ points: points.map((x) => ({ n: x.n, kind: x.kind, on: x.on, target: x.target.label, class: x.class, text: String(rec.flags[x.n - 1]?.text ?? "") })) });
6882
+ runLog(runDir, { event: "post-repair-review", read: true, whole: true, verdict: rec.verdict, flags: rec.flags.length, ms: base.ms });
6883
+ return { reviewed: true, rec, reviewText, items, points, putBack: () => {}, base };
6884
+ }
6885
+
6886
+ /** Where the state the review after a repair reads is kept whole, before the review and its correction run. */
6887
+ const postRepairStatePath = (runDir) => driverDir(runDir, "post-repair-state.json");
6888
+
6889
+ /**
6890
+ * THE STATE THE REVIEW AFTER A REPAIR READS, KEPT WHOLE before the review and its correction can lose any of it: the
6891
+ * findings record, its narrative, the merge base the next patch builds on, and the fold, in one file under `_driver/`,
6892
+ * so the four are kept together or not at all. Where a part cannot be read, no state is kept, and an earlier one is
6893
+ * removed so that it never stands for this review. Returns whether a state was kept. Never throws.
6894
+ */
6895
+ function retainStateBeforeReview(runDir, P) {
6896
+ try {
6897
+ const findings = readFileSync(P.findings, "utf8");
6898
+ const base = snapshotSynthesisBase(runDir);
6899
+ if (base.accepted == null || base.narrative == null) throw new Error("a part of the state could not be read");
6900
+ atomicWrite(postRepairStatePath(runDir), JSON.stringify({ ts: new Date().toISOString(), findings, narrative: base.narrative,
6901
+ accepted: base.accepted, fold: snapshotFold(runDir) }) + "\n");
6902
+ return true;
6903
+ } catch (e) {
6904
+ try { rmSync(postRepairStatePath(runDir), { force: true }); } catch { /* nothing to remove */ }
6905
+ runLog(runDir, { event: "post-repair-state-not-kept", reason: String(e?.message ?? e).slice(0, 160) });
6906
+ return false;
6907
+ }
6908
+ }
6909
+
6910
+ /**
6911
+ * THE TRUSTED STATE BEFORE THE CORRECTION AFTER A POST-REPAIR REVIEW, for its rollback, from the first source that
6912
+ * stands, in this order:
6913
+ * 1. the findings record as it stands, with its fold, narrative and base (the usual snapshot);
6914
+ * 2. the state kept whole before the review (`retainStateBeforeReview`), byte for byte;
6915
+ * 3. the save the review read (`before`, read before the correction starts), rebuilt through the driver's own
6916
+ * acceptance, never from a save the correction may write afterwards;
6917
+ * 4. the save kept from before the delivery-check repair, rebuilt the same way.
6918
+ * A save that the run's CURRENT duty refuses is not lost: it was accepted when it was made, and the records owed since
6919
+ * are evidence added later. It is rebuilt with the duty set aside and marked so (`duty: "relaxed"`); once the correction
6920
+ * settles, the writer's duty is judged again on the record that stands, and names what it leaves unaccounted in the
6921
+ * approved sentence. A save that cannot be read, or that
6922
+ * the acceptance refuses for anything else, is not a source. Null only when none of the four stands. Never throws.
6923
+ */
6924
+ function trustedStateBeforeCorrection(Pv, runDir, before) {
6925
+ const held = snapshotFindingsForCorrections(Pv, runDir);
6926
+ if (held) return held;
6927
+ const sha = (t) => createHash("sha256").update(t).digest("hex");
6928
+ try {
6929
+ const k = JSON.parse(readFileSync(postRepairStatePath(runDir), "utf8"));
6930
+ if (typeof k?.findings === "string" && typeof k.narrative === "string" && typeof k.accepted === "string") {
6931
+ const h = sha(k.findings);
6932
+ return { raw: k.findings, sha: h, model: { raw: k.findings, sha: h }, fold: k.fold ?? null,
6933
+ base: { accepted: k.accepted, narrative: k.narrative }, from: "state-before-the-review" };
6934
+ }
6935
+ } catch { /* no state kept, or it does not read: the saves below */ }
6936
+ const reads = (() => { try { return driverReadsFor(runDir); } catch { return null; } })();
6937
+ const fromSave = (params, accepted, from) => {
6938
+ if (!params || accepted == null || !reads) return null;
6939
+ const accept = (r) => { try { return acceptSynthesis(params, r); } catch { return null; } };
6940
+ let v = accept(reads), duty = "current", refused = null;
6941
+ if (!v?.ok && reads.owed != null) {
6942
+ refused = v?.reason ?? null;
6943
+ const w = accept({ ...reads, owed: null, declined: null });
6944
+ if (w?.ok) { v = w; duty = "relaxed"; }
6945
+ }
6946
+ if (!v?.ok || typeof v.findings !== "string" || typeof v.narrative !== "string") return null;
6947
+ const h = sha(v.findings);
6948
+ return { raw: v.findings, sha: h, model: { raw: v.findings, sha: h }, fold: null, base: { accepted, narrative: v.narrative }, from, duty,
6949
+ ...(duty === "relaxed" ? { refusedUnderCurrentDuty: String(refused ?? "").slice(0, 160) } : {}) };
6950
+ };
6951
+ const bytes = (at) => { try { return readFileSync(at, "utf8"); } catch { return null; } };
6952
+ return fromSave(before, snapshotSynthesisBase(runDir).accepted, "accepted-save")
6953
+ ?? fromSave(readRepairBase(runDir), bytes(repairBasePath(runDir)), "save-before-the-repair");
6954
+ }
6955
+
6956
+ /**
6957
+ * A FAILED CORRECTION AFTER A POST-REPAIR REVIEW NEVER ENDS THE RUN (the owner's ruling of 2026-10-19: a run is never
6958
+ * held). The trusted state comes back whole: the record, its narrative and the merge base, then the fold and the seam's
6959
+ * writes made again over a record rebuilt from a save, as after any save, so the delivered verdict is derived from the
6960
+ * record that stands. The correction's points stay in the run's open-questions record. A rate-limited correction is
6961
+ * not waited for either: it is rolled back as a failed one is, since the report it would correct is already reviewed,
6962
+ * and waiting for the reset would hold the run.
6963
+ *
6964
+ * WITH NO TRUSTED STATE, THIS PASS DOES NOT DELIVER. That is the precise limit: the record, the state kept whole before
6965
+ * the review, the save the review read and the save kept from before the repair are all unreadable or refused, a
6966
+ * storage failure. Nothing the failed pass wrote is adopted: the narrative and the base
6967
+ * go back to what they were, and the failed pass's record is set aside under `_driver/` as evidence. A pass that went on from there delivered a
6968
+ * report with no findings, labelled with the verdict as it stood (measured on the mock pipeline, 2026-10-19): a clear
6969
+ * result over a record nobody can read. So this case alone keeps the failure it always had. Returns the rollback
6970
+ * record, or null for this case.
6971
+ */
6972
+ async function recoverFromFailedCorrection(ctx, Pv, runDir, pre, pass, { baseBefore, findingsBefore }) {
6973
+ // A rate limit is a failure of this pass like any other here: the shared rollback would leave it to wait.
6974
+ const failure = pass?.fail === "rate_limited" || pass?.resetsAt ? { fail: "rate_limited, not waited for" } : pass;
6975
+ if (pre?.raw) {
6976
+ const rollback = rollbackCorrectivePass(Pv, runDir, pre, failure);
6977
+ if (rollback && pre.from) rollback.from = pre.from;
6978
+ if (rollback && pre.duty) rollback.duty = pre.duty;
6979
+ if (rollback && (pre.from === "accepted-save" || pre.from === "save-before-the-repair")) {
6980
+ // A record rebuilt from a save is folded, and given the seam's writes, as the save itself was.
6981
+ try { foldAfterSave(runDir, Pv.findings); } catch (e) { note(`[post-repair] the restored record could not be folded (${String(e?.message ?? e).slice(0, 100)})`); }
6982
+ try { await ctx.afterSynthesisSave?.(); } catch (e) { note(`[post-repair] the seam's writes could not be made again over the restored record (${String(e?.message ?? e).slice(0, 100)})`); }
6983
+ }
6984
+ return rollback;
6985
+ }
6986
+ // NO TRUSTED STATE: nothing the failed pass wrote is adopted, and the pass ends as it always did.
6987
+ const base = restoreSynthesisBase(runDir, baseBefore);
6988
+ let findings = "unchanged";
6989
+ if (!findingsBefore) {
6990
+ let now = null;
6991
+ try { now = readFileSync(Pv.findings, "utf8"); } catch { now = null; }
6992
+ // Set aside, never deleted: the failed pass's record stays beside the run's record for whoever diagnoses the run.
6993
+ if (now != null) {
6994
+ try { renameSync(Pv.findings, driverDir(runDir, "findings-failed-correction.json")); findings = "set-aside"; }
6995
+ catch { try { rmSync(Pv.findings, { force: true }); findings = "removed"; } catch { findings = "failed"; } }
6996
+ }
6997
+ }
6998
+ runLog(runDir, { event: "post-repair-correction-unrecoverable", reason: String(failure?.fail ?? "failed").slice(0, 200), findings, ...(base ? { base } : {}) });
6999
+ note(`[post-repair] the correction after the review of the repair failed with no trusted state to restore (the record and both saves unreadable) — nothing it wrote is kept, and this pass does not deliver a report without its record`);
7000
+ return null;
7001
+ }
7002
+
7003
+ /**
7004
+ * The one correction after that review: every accepted point goes to it, whatever its class, and nothing
7005
+ * reviews it again. It runs the first correction's own backstops — the rollback, the put-back of a removal no
7006
+ * flag named, the follow-up when a correction missed the findings — on a view of the run's paths that points at
7007
+ * the review after the repair, and records what each flag led to beside the first correction's record.
7008
+ */
7009
+ async function runPostRepairCorrection(ctx, P, run, { rec, items, points, grounds = false, whole = false, synthesisRef }) {
7010
+ const runDir = run.runDir;
7011
+ const Pv = { ...P, seniorEyeReview: reviewAfterRepairPath(runDir), correctionsApplied: postRepairCorrectionsPath(runDir), reviewFlags: rec.flags };
7012
+ const named = [...new Set(points.filter((x) => x.target.kind !== "none").map((x) => x.target.label))];
7013
+ const rows = rec.flags.map((f, i) => ({ n: i + 1, kind: f.kind, text: f.text, fix: f.fix ?? null,
7014
+ ordinals: Array.isArray(f.on) && f.on.length ? f.on : null }));
7015
+ const review = readFileSync(Pv.seniorEyeReview, "utf8");
7016
+ const rulingsTail = extractRulingsTail(existsSync(P.placement) ? readFileSync(P.placement, "utf8") : "");
7017
+ const followup = repairFollowup("synthesis:post-repair-corrective", {
7018
+ narrative: P.narrative, findings: P.findings, placement: P.placement, rulingsTail,
7019
+ items: named, renumbered: anyRenumbered(items), worklist: correctionsWorklist(rows, { afterRepair: true }), review, whole,
7020
+ });
7021
+ const before = lastAcceptedCall(runDir);
7022
+ // What stood before the correction, held before it can alter any of it: the files as they are, for a pass that
7023
+ // fails with no trusted state to restore, and the trusted state itself.
7024
+ const baseBefore = snapshotSynthesisBase(runDir);
7025
+ const findingsBefore = (() => { try { readFileSync(Pv.findings); return true; } catch { return false; } })();
7026
+ const pre = trustedStateBeforeCorrection(Pv, runDir, before);
7027
+ prepareDeclinationSpec(ctx, P);
7028
+ const pass = await stage("synthesis", ctx, { force: true, followup: followup + stageCharter("synthesis", ctx.depth, ctx.framework), sessionKey: synthesisRef.key,
7029
+ model: synthesisRef.model, thinking: synthesisRef.thinking, trigger: "post-repair-corrective" });
7030
+ recordSynthesisSeam(ctx, pass, "post-repair-corrective");
7031
+ const rollback = pass.ok ? null : await recoverFromFailedCorrection(ctx, Pv, runDir, pre, pass, { baseBefore, findingsBefore });
7032
+ if (rollback) {
7033
+ runLog(runDir, { event: "corrective-rollback", reason: rollback.reason, restored: rollback.restored, pass: "post-repair", from: rollback.from ?? "record",
7034
+ ...(rollback.duty ? { duty: rollback.duty } : {}) });
7035
+ note(`[post-repair] the correction after the review of the repair failed (${rollback.reason}) — the record it started from is back, and the run delivers with the points recorded`);
7036
+ } else {
7037
+ must(pass, "synthesis(post-repair)");
7038
+ let preDocForNames = null;
7039
+ try { preDocForNames = pre ? parseFindingsJsonLenient(pre.raw) : null; } catch { /* fall back to the file */ }
7040
+ const repaired = repairUnnamedRemovals(Pv, runDir, pre, correctionNamedOrdinals(Pv), correctionNamedSet(Pv, preDocForNames), correctionNamedLines(Pv, preDocForNames));
7041
+ if (repaired) runLog(runDir, removalRepairedRow(repaired));
7042
+ await enforceCorrectionsReachFindings(ctx, Pv, pre, { synthesisKey: synthesisRef.key, synthesisModel: synthesisRef.model, synthesisThinking: synthesisRef.thinking });
7043
+ }
7044
+ let applied = null;
7045
+ try {
7046
+ const preDoc = pre ? parseFindingsJsonLenient(pre.model?.raw ?? pre.raw) : null;
7047
+ let postDoc = null;
7048
+ try { postDoc = parseFindingsJsonLenient(readFileSync(modelRecordPath(runDir, P.findings), "utf8")); } catch { /* shape defects ride the normal ladder */ }
7049
+ const after = lastAcceptedCall(runDir);
7050
+ const sections = points.filter((x) => x.target.kind === "section").map((x) => {
7051
+ const sec = NARRATIVE_PROSE.find((y) => y.key === x.target.key);
7052
+ return { n: x.n, label: x.target.label, before: sectionText(sec?.read(before?.narrative)), after: sectionText(sec?.read(after?.narrative)) };
7053
+ });
7054
+ applied = buildCorrectionsApplied(rows, preDoc, postDoc, { sections });
7055
+ // `grounds`: the review these rows answer hardened the run's verdict, so its points are that verdict's grounds.
7056
+ atomicWrite(Pv.correctionsApplied, JSON.stringify({ ts: new Date().toISOString(), pass: "post-repair", ...(grounds ? { grounds: true } : {}),
7057
+ rows: applied, classes: points.map((x) => ({ n: x.n, class: x.class, target: x.target.label })) }, null, 2) + "\n");
7058
+ const outcomes = applied.reduce((a, x) => ((a[x.outcome] = (a[x.outcome] ?? 0) + 1), a), {});
7059
+ runLog(runDir, { event: "post-repair-fix-pass", handed: rows.length, ok: Boolean(pass.ok), rolledBack: Boolean(rollback), outcomes });
7060
+ } catch (e) {
7061
+ note(`[post-repair] the observation of the correction could not be built (${String(e?.message ?? e).slice(0, 100)}) — its points print as not checked`);
7062
+ runLog(runDir, { event: "post-repair-fix-pass", handed: rows.length, ok: Boolean(pass.ok), rolledBack: Boolean(rollback), observation: false });
7063
+ }
7064
+ return { ran: true, ok: Boolean(pass.ok), rollback, applied };
7065
+ }
7066
+
6304
7067
  async function enforceCorrectionsReachFindings(ctx, P, pre, resume) {
6305
7068
  if (!pre) return; // no findings.json before the pass — nothing to enforce
6306
7069
  const { run } = ctx;
@@ -6318,7 +7081,7 @@ async function enforceCorrectionsReachFindings(ctx, P, pre, resume) {
6318
7081
  await stage("synthesis", ctx, {
6319
7082
  force: true, sessionKey: resume.synthesisKey, model: resume.synthesisModel, thinking: resume.synthesisThinking,
6320
7083
  trigger: "corrective-findings",
6321
- followup: repairFollowup("synthesis:corrective-findings", { findings: P.findings, named }) + stageCharter("synthesis", ctx.depth, ctx.framework),
7084
+ followup: repairFollowup("synthesis:corrective-findings", { findings: P.findings, named, seniorEyeReview: P.seniorEyeReview }) + stageCharter("synthesis", ctx.depth, ctx.framework),
6322
7085
  });
6323
7086
  if (readState().stale) {
6324
7087
  runLog(run.runDir, { event: "corrective-findings-stale", named });
@@ -6534,15 +7297,22 @@ function plainRegisterExtra(ctx) {
6534
7297
  // The surfaces a clearance reader meets before opening anything, named one by one so adding a
6535
7298
  // field to the page is a deliberate addition here too.
6536
7299
  const visible = [];
6537
- const add = (where, v) => { const t = String(v ?? "").trim(); if (t) visible.push({ where, text: t }); };
6538
- for (const f of doc.findings ?? []) add(`conflict ${f?.ordinal ?? "?"}'s one sentence`, f?.net);
7300
+ const add = (where, v, field = null) => { const t = String(v ?? "").trim(); if (t) visible.push({ where, text: t, field }); };
7301
+ for (const f of doc.findings ?? []) add(`conflict ${f?.ordinal ?? "?"}'s finding sentence`, f?.net, "net");
6539
7302
  // The lines that are not a finding, labelled by `reportLines` — the labels the corrective cycle's
6540
7303
  // observation joins a flag back to, so what the reviewer is handed and what the driver reads agree.
6541
7304
  for (const l of reportLines(doc)) add(l.label, l.text);
6542
7305
 
7306
+ // A FINDING SENTENCE IS AT MOST TWO SENTENCES, so its length hit cannot say "split it": a finding sentence
7307
+ // already at two would be told to grow a third. What does not fit goes to the legal and practical
7308
+ // positions, in the approved paragraph's own words. Every other visible line, and the knockout's, keeps the
7309
+ // shared hit word for word — `plainRegisterFlags` is not changed.
7310
+ const findingSentenceLength = (n) => `${n} words in one sentence, and a visible line takes ${SENTENCE_WORD_LIMIT}. The finding sentence is at most two sentences: every fact that does not fit goes in the legal and practical positions, never dropped.`;
6543
7311
  const hits = [];
6544
- for (const { where, text } of visible) {
6545
- for (const flag of plainRegisterFlags(text, about)) hits.push(`- ${where} — ${flag.say}`);
7312
+ for (const { where, text, field } of visible) {
7313
+ for (const flag of plainRegisterFlags(text, about)) {
7314
+ hits.push(`- ${where} — ${field === "net" && flag.kind === "length" ? findingSentenceLength(flag.words) : flag.say}`);
7315
+ }
6546
7316
  }
6547
7317
  if (!hits.length) return nothingFound(`no plain-words flag over ${visible.length} visible line(s)`); // a NEGATIVE FINDING, and the count travels with it so a clean read is not a read of nothing
6548
7318
 
@@ -7483,6 +8253,79 @@ export function injectLaneDepthCoverage(P, runDir, note) { // @internal
7483
8253
  }
7484
8254
  }
7485
8255
 
8256
+ /**
8257
+ * WHAT EACH POLICED GOODS DROP RESTS ON, recorded beside the screen gate (screen-gate.mjs `screenGateEvidence`).
8258
+ * Taken in two halves: `screenGateSnapshot` at the moment the gate judges, before it fetches anything (the findings
8259
+ * it judged, each policed drop's goods evidence, and the reading log), and the record after the gate's fetches,
8260
+ * which reads each drop's goods again only where the gate fetched. Disclosure only: the gate's rule and its
8261
+ * outcome are decided above and nothing here changes them.
8262
+ */
8263
+ function goodsEvidenceOfDrops(P, runPrefix, uris, gateFetched = null) {
8264
+ const out = new Map();
8265
+ if (!uris.length) return out;
8266
+ const calls = readRunCallRows(DEFAULT_LEDGER_PATH, runPrefix);
8267
+ const held = heldRecordProvenance(P.runDir);
8268
+ // Each drop's own band row, so that a goods reader which reads a row's goods reads this one's too.
8269
+ const band = safeReadJson(P.registerNamedBand);
8270
+ const bandRows = Array.isArray(band?.enumerated) ? band.enumerated : [];
8271
+ const rowOf = new Map(bandRows.map((r) => [normalizeRecordUri(r?.record_id), r]));
8272
+ const goods = bandShapeGoods(P, bandRows).goods;
8273
+ const fetches = recordFetchOutcomes(calls.rows, { runPrefix });
8274
+ // The gate's own fetches are this run's too, recorded by the gate whatever the register's ledger kept of them.
8275
+ for (const [u, r] of gateFetched ?? []) {
8276
+ const key = normalizeRecordUri(u) || String(u ?? "").toLowerCase();
8277
+ const o = fetches.get(key) ?? { sent: 0, from_memory: 0, failed: 0, ok: false, last_status: null };
8278
+ if (r?.ok === true) o.ok = true; else o.failed = Math.max(o.failed, 1);
8279
+ fetches.set(key, o);
8280
+ }
8281
+ for (const uri of uris) {
8282
+ const e = recordGoodsEvidence(rowOf.get(normalizeRecordUri(uri)) ?? { record_id: uri }, { goods, held, fetches });
8283
+ out.set(uri, { goods: e.state, source: e.source });
8284
+ }
8285
+ return out;
8286
+ }
8287
+
8288
+ function screenGateSnapshot(ctx, runPrefix) {
8289
+ const P = ctx.paths;
8290
+ try {
8291
+ const findings = existsSync(P.registerFindings) ? readFileSync(P.registerFindings, "utf8") : "";
8292
+ let readingLog = "";
8293
+ try { readingLog = readFileSync(P.readingLog, "utf8"); } catch { readingLog = ""; }
8294
+ return { findings, readingLog, goods: goodsEvidenceOfDrops(P, runPrefix, policedDropUris(findings)) };
8295
+ } catch (e) {
8296
+ return { findings: "", readingLog: "", goods: new Map(), error: String(e?.message ?? e).slice(0, 160) };
8297
+ }
8298
+ }
8299
+
8300
+ function recordScreenGateEvidence(ctx, { runPrefix, before, heldBeforeGate, heldAfterGate, gateFetched }) {
8301
+ const P = ctx.paths;
8302
+ try {
8303
+ const uris = policedDropUris(before.findings);
8304
+ const after = gateFetched.size ? goodsEvidenceOfDrops(P, runPrefix, uris, gateFetched) : before.goods;
8305
+ // The line each drop's record sits on, as placement's fate count joins it, so a part of the shape or a page
8306
+ // of a group that served the line counts as the goods words shown.
8307
+ let lineOf = () => null, lineReads = "read";
8308
+ try {
8309
+ const index = buildSelectionIndex(readPlacementFormInput(P.runDir));
8310
+ lineOf = (uri) => lineKey(index.resolve(uri));
8311
+ } catch (e) { lineReads = `the band shape's lines could not be read, so only record opens and lookups count: ${String(e?.message ?? e).slice(0, 100)}`; }
8312
+ const ev = screenGateEvidence({ findingsContent: before.findings, heldBefore: heldBeforeGate, heldAfter: heldAfterGate,
8313
+ before: (uri) => before.goods.get(uri), after: (uri) => after.get(uri),
8314
+ reads: recordReads(before.readingLog), lines: linesRead(before.readingLog), lineOf, readerStage: "register-digest", gateFetched });
8315
+ const calls = readRunCallRows(DEFAULT_LEDGER_PATH, runPrefix);
8316
+ const gateCalls = registerCallAccount(calls.rows.filter((r) => String(r?.sessionKey ?? "").includes("screen-gate-refetch")));
8317
+ const doc = { schema_version: 2, ...ev, gate_calls: gateCalls, line_reads: lineReads,
8318
+ reading_note: "A drop with no recorded read has no band_record open or search-answer serve, and no lookup, part of the shape or page of a group serving its line, in the reading log for the reading step. The log is best-effort: this does not show the goods were never read.",
8319
+ ...(before.error ? { snapshot_error: before.error } : {}), ...(calls.error ? { call_ledger_error: calls.error } : {}) };
8320
+ ensureDriverDir(P.runDir);
8321
+ atomicWrite(driverDir(P.runDir, "screen-gate-evidence.json"), JSON.stringify({ ts: new Date().toISOString(), ...doc }, null, 2) + "\n");
8322
+ runLog(P.runDir, { event: "screen-gate-evidence", ...ev.counts, unnamed: ev.unnamed, fetches: ev.fetches,
8323
+ alternatives: ev.alternatives, gate_calls: gateCalls });
8324
+ } catch (e) {
8325
+ runLog(P.runDir, { event: "screen-gate-evidence-failed", fail: String(e?.message ?? e).slice(0, 160) });
8326
+ }
8327
+ }
8328
+
7486
8329
  // ── screen-gate unexamined-disclosure readers ────────────────────────────────────────────────────────
7487
8330
  // The screen-gate's disclose-and-continue terminal writes _driver/screen-gate-unresolved.json (atomic,
7488
8331
  // the receipt idiom) the moment its post-repair re-check still fails, so the disclosure survives a
@@ -7536,7 +8379,7 @@ export function injectScreenGateCoverage(P, runDir, note, ctx = {}) { // @inte
7536
8379
  // the EARLIEST qualifying window end. Code fills ABSENCE only (an authored deadline always wins);
7537
8380
  // out-of-window dates never inject (an archived 2015 window is history, not urgency). Receipt:
7538
8381
  // _driver/deadline-enrich.json. NEVER-KILL: any defect leaves findings.json untouched.
7539
- function enrichFindingDeadlines(P, runDir, note, { nowMs = Date.now(), withinDays = 60, graceDays = 14 } = {}) {
8382
+ export function enrichFindingDeadlines(P, runDir, note, { nowMs = Date.now(), withinDays = 60, graceDays = 14 } = {}) { // @internal
7540
8383
  try {
7541
8384
  if (!existsSync(P.findings)) return;
7542
8385
  const deadlines = collectOppositionDeadlines(runDir);
@@ -7560,7 +8403,8 @@ function enrichFindingDeadlines(P, runDir, note, { nowMs = Date.now(), withinDay
7560
8403
  if (!enriched.length) return;
7561
8404
  parseFindingsJson(JSON.stringify(doc)); // re-validate the shape (throws → catch keeps original)
7562
8405
  atomicWrite(P.findings, `${JSON.stringify(doc, null, 2)}\n`); // B5 — a crash mid-write must never leave a truncated findings.json
7563
- atomicWrite(driverDir(runDir, "deadline-enrich.json"), JSON.stringify({ schema_version: 1, ts: new Date().toISOString(), enriched }, null, 2) + "\n");
8406
+ const base = rebaseEnrichedDeadlines(runDir, enriched); // a later patch keeps what the file now carries
8407
+ atomicWrite(driverDir(runDir, "deadline-enrich.json"), JSON.stringify({ schema_version: 1, ts: new Date().toISOString(), judged_at: new Date(nowMs).toISOString(), enriched, base }, null, 2) + "\n");
7564
8408
  note(`[deadline-enrich] ${enriched.length} finding(s) gained the register's own opposition window (${enriched.map((e) => `${e.mark}: ${e.date}`).join("; ").slice(0, 140)}) — a recorded deadline never silently disappears`);
7565
8409
  } catch (e) {
7566
8410
  note(`[deadline-enrich] skipped: ${String(e?.message || e).replace(/\s+/g, " ").slice(0, 100)}`);
@@ -7843,6 +8687,8 @@ export function buildOnlyYouSection(actions, findings, { nowMs = Date.now(), wit
7843
8687
  //
7844
8688
  // BLOCKING every ground the reviewer cites — unchanged, the reviewer refused to sign
7845
8689
  // ANY VERDICT plus every objection the run tried and could not show it closed
8690
+ // A HARDENED VERDICT plus every point of the review after a repair that hardened it, even one its correction
8691
+ // changed: nothing reviewed that change, so the label keeps a visible ground (`ground`)
7846
8692
  //
7847
8693
  // The second source is `corrections-applied.json`, the driver's own flag-by-flag observation,
7848
8694
  // which already exists and is already written on both the CONDITIONAL and the BLOCKING path. What
@@ -7867,7 +8713,7 @@ export function buildOnlyYouSection(actions, findings, { nowMs = Date.now(), wit
7867
8713
  export function buildReviewerOpenPointsSection(reviewMd, appliedRows = null) { // @internal
7868
8714
  const blocking = parseVerdict(reviewMd) === "BLOCKING";
7869
8715
  const cited = blocking ? parseCorrections(reviewMd) : [];
7870
- const unfixed = unresolvedFlags(appliedRows);
8716
+ const unfixed = (Array.isArray(appliedRows) ? appliedRows : []).filter((r) => r?.ground === true || unresolvedFlags([r]).length);
7871
8717
  const seen = new Set();
7872
8718
  const rows = [];
7873
8719
  for (const r of [...cited, ...unfixed]) {
@@ -7917,6 +8763,8 @@ export function buildReviewerOpenPointsSection(reviewMd, appliedRows = null) {
7917
8763
  "not-entity-scoped": "the run could not check whether this was addressed",
7918
8764
  "not-checkable": "the run could not check whether this was addressed",
7919
8765
  "line-unchanged": "the line this named was left as written",
8766
+ // A narrative section a flag of the review after a repair named, left as written: the shipped words.
8767
+ "section-unchanged": "the line this named was left as written",
7920
8768
  "line-removed": "the line this named was removed rather than corrected",
7921
8769
  };
7922
8770
  const items = rows.map((r) => {
@@ -7924,7 +8772,9 @@ export function buildReviewerOpenPointsSection(reviewMd, appliedRows = null) {
7924
8772
  if (Array.isArray(r.ordinals) && r.ordinals.length) bits.push(`finding ${r.ordinals.join(", ")}`);
7925
8773
  // An outcome nobody has classified takes the unchecked wording, never the checked one — the same
7926
8774
  // direction `unresolvedFlags` fails in, for the same reason.
7927
- if (r.outcome) bits.push(QUALIFIER[r.outcome] ?? "the run could not check whether this was addressed");
8775
+ // A ground the correction changed keeps its observed outcome in the record and prints as unchecked: no review
8776
+ // read the change, so the run cannot say it was addressed, and "did not change" would be false.
8777
+ if (r.outcome) bits.push((r.ground === true ? null : QUALIFIER[r.outcome]) ?? "the run could not check whether this was addressed");
7928
8778
  const tail = bits.length ? ` *(${bits.join(" — ")})*` : "";
7929
8779
  return `- ${ADVISORY_TAG["client-fact"]} ${String(r.text).trim()}${tail}`;
7930
8780
  });
@@ -8060,6 +8910,54 @@ export function buildReasonedNegativesSection(findings) { // @internal
8060
8910
  return `${CODE_BUILT_SECTIONS.reasonedNegatives}\n\n${lines.join("\n\n")}`;
8061
8911
  }
8062
8912
 
8913
+ // ── WHETHER EACH REVIEWER CORRECTION REACHED THE RECORD THE REPORT WAS BUILT FROM ───────────────────────
8914
+ //
8915
+ // The corrective pass keeps a basis — what each flag changed in the fields a report prints from the record —
8916
+ // in `_driver/corrections-delivered.json`, and every assembly reads it against the record the report was
8917
+ // built from and writes the outcome beside it (corrections-delivered.mjs). The last assembly's answer stands.
8918
+ // corrections-applied.json, which the open points and the verdict re-check read, is not touched.
8919
+ export const CORRECTIONS_DELIVERED_FILE = "corrections-delivered.json";
8920
+
8921
+ /** Keep the basis at the corrective pass, from the two records the applied table was built from. Throws on a write failure; the caller is never-kill. */
8922
+ export function recordCorrectionsBasis(runDir, rows, preDoc, postDoc) { // @internal
8923
+ const basis = correctionTargets(rows, preDoc, postDoc);
8924
+ atomicWrite(driverDir(runDir, CORRECTIONS_DELIVERED_FILE), JSON.stringify({
8925
+ _provenance: "whether each reviewer correction reached the record the report was built from: the basis is kept at the corrective pass, and every assembly reads it against the delivered record (corrections-delivered.mjs)",
8926
+ basisAt: new Date().toISOString(), basis,
8927
+ }, null, 2) + "\n");
8928
+ return basis;
8929
+ }
8930
+
8931
+ /**
8932
+ * Read the basis against the record this assembly was built from, and record the outcome: the file, and one
8933
+ * run-log row. A run with no basis records nothing. A delivered record that cannot be read is recorded as a
8934
+ * failure to look, never as an outcome. Never throws.
8935
+ */
8936
+ export function recordCorrectionsDelivered(P) { // @internal
8937
+ if (!P?.runDir) return null;
8938
+ const at = driverDir(P.runDir, CORRECTIONS_DELIVERED_FILE);
8939
+ let held = null;
8940
+ try { held = JSON.parse(readFileSync(at, "utf8")); } catch { return null; } // no corrective basis on this run
8941
+ if (!Array.isArray(held?.basis)) return null;
8942
+ let deliveredDoc = null, reportMd = null;
8943
+ try { deliveredDoc = parseFindingsJsonLenient(readFileSync(P.findings, "utf8")); } catch { deliveredDoc = null; }
8944
+ try { reportMd = readFileSync(P.report, "utf8"); } catch { reportMd = null; }
8945
+ let foldMap = null;
8946
+ try { foldMap = readFold(P.runDir)?.map ?? null; } catch { foldMap = null; }
8947
+ const rows = deliveredOutcomes({ basis: held.basis, deliveredDoc, foldMap, reportMd });
8948
+ const { rows: _rows, error: _error, assembledAt: _assembledAt, ...kept } = held;
8949
+ const out = { ...kept, assembledAt: new Date().toISOString(), rows: rows ?? null,
8950
+ ...(rows ? {} : { error: "the record the report was built from could not be read, so no correction was compared" }) };
8951
+ try { atomicWrite(at, JSON.stringify(out, null, 2) + "\n"); } catch { /* the run-log row below is the record */ }
8952
+ try {
8953
+ const outcomes = (rows ?? []).reduce((a, r) => (a[r.outcome] = (a[r.outcome] ?? 0) + 1, a), {});
8954
+ runLog(P.runDir, rows
8955
+ ? { event: "corrections-delivered", ok: true, flags: rows.length, outcomes, reportMdRead: reportMd != null }
8956
+ : { event: "corrections-delivered", ok: false, error: out.error });
8957
+ } catch { /* a log that cannot be written must not stop the report it describes */ }
8958
+ return out;
8959
+ }
8960
+
8063
8961
  export function assembleReportMd(P, findings, cardOrdinals, { grouped = [], byRight = false } = {}) { // @internal
8064
8962
  const overviewRaw = existsSync(P.reportOverview) ? readFileSync(P.reportOverview, "utf8") : "---\n---\n";
8065
8963
  let overview = overviewRaw.split(/^#\s+Marks\b/m)[0].replace(/\s*$/, ""); // defensive: overview owns down to # Marks
@@ -8164,6 +9062,26 @@ export function assembleReportMd(P, findings, cardOrdinals, { grouped = [], byRi
8164
9062
  let applied = null;
8165
9063
  try { applied = JSON.parse(readFileSync(P.correctionsApplied, "utf8"))?.rows ?? null; }
8166
9064
  catch { /* absent or malformed — the record falls back to the review alone */ }
9065
+ // …and the correction after the review of a delivery-check repair, which records its rows beside them. Its
9066
+ // points print through the same rule: anything not positively resolved. Which review raised a point stays in
9067
+ // the run's own records (_driver/post-repair-review.json); the printed line adds nothing.
9068
+ // Where that review hardened the verdict its points are the verdict's grounds, and print even when changed.
9069
+ try {
9070
+ const doc = JSON.parse(readFileSync(driverDir(dirname(P.report), "post-repair-corrections.json"), "utf8"));
9071
+ const after = doc?.rows;
9072
+ if (Array.isArray(after) && after.length) applied = [...(applied ?? []), ...(doc.grounds === true ? after.map((r) => ({ ...r, ground: true })) : after)];
9073
+ } catch { /* no correction after a repair on this run */ }
9074
+ // …and a review after a repair that was read and never corrected: the run was parked before its one correction,
9075
+ // and nothing on a resume repeats it. Its points are not shown closed, so they print through the same rule.
9076
+ try {
9077
+ const runDir = dirname(P.report);
9078
+ const rec = JSON.parse(readFileSync(driverDir(runDir, "post-repair-review.json"), "utf8"));
9079
+ if (rec?.status === "read" && !existsSync(driverDir(runDir, "post-repair-corrections.json"))) {
9080
+ const rows = (Array.isArray(rec.points) ? rec.points : []).filter((x) => String(x?.text ?? "").trim())
9081
+ .map((x) => ({ text: String(x.text), kind: x.kind ?? null, outcome: "not-checkable" }));
9082
+ if (rows.length) applied = [...(applied ?? []), ...rows];
9083
+ }
9084
+ } catch { /* no review after a repair on this run */ }
8167
9085
  const openPoints = existsSync(P.seniorEyeReview)
8168
9086
  ? buildReviewerOpenPointsSection(readFileSync(P.seniorEyeReview, "utf8"), applied)
8169
9087
  : "";
@@ -8268,6 +9186,9 @@ export function assembleReportMd(P, findings, cardOrdinals, { grouped = [], byRi
8268
9186
  ensureDriverDir(P.runDir);
8269
9187
  writeFileSync(driverDir(P.runDir, "card-folds.json"), `${JSON.stringify({ schema: 1, folds }, null, 2)}\n`);
8270
9188
  } catch { /* observability only */ }
9189
+ // Whether each reviewer correction reached the record this report was built from: rewritten on every
9190
+ // assembly, like the fold record above, so the last assembly's answer stands. Nothing without a basis.
9191
+ try { recordCorrectionsDelivered(P); } catch { /* observability only: the report is assembled either way */ }
8271
9192
  return { cards: cards.length, folds };
8272
9193
  }
8273
9194
 
@@ -10219,9 +11140,10 @@ async function pipelineInner(job, opts = {}) {
10219
11140
  if (unitKey[axis]) try {
10220
11141
  const byQid = new Map(ctx.registerPlan.entries.map((e) => [e.qid, e]));
10221
11142
  const from = new Map([...(Array.isArray(prior?.directives) ? prior.directives : []), ...kept].map((d) => [d.qid, d.source?.url ?? null]));
10222
- const followup = repairFollowup("register-unit:xcheck-decide", { axis, entries: waiting.map((q) => ({ ...byQid.get(q), from: from.get(q) ?? null })) });
11143
+ const followup = repairFollowup("register-unit:xcheck-decide", { axis, registerPlan: P.registerPlan, bandPath: P.registerBand(axis), entries: waiting.map((q) => ({ ...byQid.get(q), from: from.get(q) ?? null })) });
10223
11144
  const r = await stage("register-unit", { ...ctx, axis }, { force: true, followup, sessionKey: unitKey[axis], trigger: "xcheck-decide" });
10224
11145
  asked = r?.ok === true;
11146
+ remergeIfBandsMoved(ctx, "xcheck-decide");
10225
11147
  } catch (e) { runLog(run.runDir, { event: "register-xcheck-decide-failed", fail: String(e?.message ?? e).slice(0, 160) }); }
10226
11148
  const released = releasedFamilyQids(run.runDir), withheld = readWithheldFamilies(run.runDir);
10227
11149
  const ran = undecidedCrossChecks(ctx.registerPlan, { released, withheld });
@@ -10277,7 +11199,7 @@ async function pipelineInner(job, opts = {}) {
10277
11199
  await dispatchPlanQids(axis, qids, "early-envelope-close", 2);
10278
11200
  } : null,
10279
11201
  rejoin: planExec ? async () => {
10280
- writeExecution(joinPlanToBands(ctx.registerPlan, readBands(), { released: releasedFamilyQids(ctx.paths.runDir) }));
11202
+ writeExecution(joinPlanToBands(ctx.registerPlan, readBands(), joinOptions(ctx)));
10281
11203
  deriveNamedBand(ctx); // a close landed band blocks — re-merge so Layer B reads them
10282
11204
  return readPlanExecution(ctx);
10283
11205
  } : null,
@@ -10318,7 +11240,7 @@ async function pipelineInner(job, opts = {}) {
10318
11240
  if (held !== j) runLog(run.runDir, { event: "plan-qids-sticky-gap", qids: j.missing.filter((q) => !held.missing.includes(q)), action: "held-from-missing" });
10319
11241
  return held;
10320
11242
  };
10321
- let joinRes = holdStickyGaps(joinPlanToBands(ctx.registerPlan, readBands(), { released: releasedFamilyQids(ctx.paths.runDir) }));
11243
+ let joinRes = holdStickyGaps(joinPlanToBands(ctx.registerPlan, readBands(), joinOptions(ctx)));
10322
11244
  const missingEntriesByAxis = () => {
10323
11245
  const byQid = new Map(ctx.registerPlan.entries.map((e) => [e.qid, e]));
10324
11246
  const m = new Map();
@@ -10340,7 +11262,7 @@ async function pipelineInner(job, opts = {}) {
10340
11262
  runLog(run.runDir, { event: "plan-qids-missing", axis: a, qids, action: "plan-direct-execute" });
10341
11263
  await dispatchPlanQids(a, qids, "plan-direct-execute", 2);
10342
11264
  }
10343
- joinRes = holdStickyGaps(joinPlanToBands(ctx.registerPlan, readBands(), { released: releasedFamilyQids(ctx.paths.runDir) }));
11265
+ joinRes = holdStickyGaps(joinPlanToBands(ctx.registerPlan, readBands(), joinOptions(ctx)));
10344
11266
  if (!joinRes.missing.length) deriveNamedBand(ctx); // dispatch landed blocks — re-merge so Layer B reads them
10345
11267
  }
10346
11268
  if (joinRes.missing.length) {
@@ -10365,7 +11287,7 @@ async function pipelineInner(job, opts = {}) {
10365
11287
  const wf = await stage("register-unit", { ...ctx, axis: a }, { force: true, followup, sessionKey: unitKey[a], trigger: "plan-join" });
10366
11288
  if (!wf.ok) note(`register-unit ${a}: warm plan-join followup failed (${wf.fail}) — the plan-unexecuted StageFailure below holds the line`);
10367
11289
  }
10368
- joinRes = holdStickyGaps(joinPlanToBands(ctx.registerPlan, readBands(), { released: releasedFamilyQids(ctx.paths.runDir) }));
11290
+ joinRes = holdStickyGaps(joinPlanToBands(ctx.registerPlan, readBands(), joinOptions(ctx)));
10369
11291
  deriveNamedBand(ctx); // the followup appended band blocks — re-merge so Layer B reads them
10370
11292
  }
10371
11293
  const skeleton = writeExecution(joinRes);
@@ -10922,7 +11844,7 @@ async function pipelineInner(job, opts = {}) {
10922
11844
  // (`x:0` vs `x:null` must differ by value, never by presence). The demote paths carry the SAME
10923
11845
  // rows forward unchanged, deliberately — a failed re-digest demotes CLOSURE, not searchedness:
10924
11846
  // a query that ran still ran.
10925
- const writeReopenReceipt = (swept, deferrals, dom, partials = [], accounting = null, answered = []) => {
11847
+ const writeReopenReceipt = (swept, deferrals, dom, partials = [], accounting = null, answered = [], withheld = []) => {
10926
11848
  try {
10927
11849
  writeFileSync(reopenReceiptPath, JSON.stringify({
10928
11850
  ts: new Date().toISOString(), requested: firing.map(reopenKey),
@@ -10930,6 +11852,9 @@ async function pipelineInner(job, opts = {}) {
10930
11852
  ...(partials.length ? { partials } : {}),
10931
11853
  // a closed directive whose slice the fold refused as already-asked, with the plan row that answered it
10932
11854
  ...(answered.length ? { answered_elsewhere: answered } : {}),
11855
+ // a closed directive with a slice settled by the reading turn's judgment: the slice and the family
11856
+ // it repeats, and nothing else. The reason stays on the turn's own record.
11857
+ ...(withheld.length ? { withheld_by_judgment: withheld } : {}),
10933
11858
  dominant_element_gap, domClosed: dom,
10934
11859
  directive_qids: reopenQids, born: reopenBorn,
10935
11860
  remedy_terms: accounting?.computable === true ? accounting.rows : null,
@@ -10976,6 +11901,7 @@ async function pipelineInner(job, opts = {}) {
10976
11901
  // folded into the defer reason otherwise), never dropped into the aggregate count alone.
10977
11902
  const regPartialMint = new Map();
10978
11903
  const regAnsweredBy = new Map(); // reopenKey → the plan rows that answered its slices the fold refused as already-asked
11904
+ const regWithheldBy = new Map(); // reopenKey → its slices refused as families the reading turn withheld, with each family
10979
11905
  // — the per-TERM rows the remedy ledger accounts. One row per remedy term per directive,
10980
11906
  // recorded at the mint seam (the only place the term→qid mapping exists) so the receipt can
10981
11907
  // later say what each term asked and what came back. reopenKey → [{term, predicates, qids,
@@ -11093,18 +12019,35 @@ async function pipelineInner(job, opts = {}) {
11093
12019
  note(`frame-reopen: ${regDirectives.length} register omission(s) → ${allMinted.length} supplemental entr${allMinted.length === 1 ? "y" : "ies"} minted (structured remedy), dispatched as pure code (no agent sweep)`);
11094
12020
  const beforeBand = existsSync(P.registerBand(a)) ? readFileSync(P.registerBand(a), "utf8") : null;
11095
12021
  if (allMinted.length) {
11096
- const { plan, added, refused } = foldSupplementalEntries(ctx.registerPlan, allMinted);
12022
+ // A QUESTION THE READING TURN WITHHELD IS NOT ASKED AGAIN HERE. The fold lets a question through
12023
+ // when it matches a waiting family, because the reading turn's own ask is what the family waits
12024
+ // for; this sweep asks on nobody's judgment, so its exact repeat of a family the turn withheld is
12025
+ // refused before the fold, with the family as its twin and the turn's recorded reason. The record
12026
+ // is read here, at the fold, so a family the turn has since released is asked as before.
12027
+ const withheldTwins = withheldFamilyTwins(ctx.registerPlan, readWithheldFamilies(run.runDir), releasedFamilyQids(run.runDir));
12028
+ const { kept: toFold, refused: withheldRefused } = refuseWithheldFamilies(allMinted, withheldTwins, ctx.registerPlan);
12029
+ // ONE ANSWER PER QUESTION PER RUN (held-answers.mjs): a question another worker or pass has answered is
12030
+ // refused as already asked, its twin the row that answered it, and the directive is checked on that row.
12031
+ let toFoldHeld = toFold, heldRefused = [];
12032
+ try {
12033
+ const planRegions = (ctx.registerPlan.regions ?? []).map(String);
12034
+ ({ kept: toFoldHeld, refused: heldRefused } = refuseHeldQuestions(toFold, heldAnswers(run.runDir, { planRegions, withheld: readWithheldFamilies(run.runDir) }), planRegions));
12035
+ } catch { toFoldHeld = toFold; heldRefused = []; } // an economy: without it every question is folded as before
12036
+ const folded = foldSupplementalEntries(ctx.registerPlan, toFoldHeld);
12037
+ const { plan, added } = folded;
12038
+ const refused = [...withheldRefused, ...heldRefused, ...folded.refused];
11097
12039
  if (added.length) {
11098
12040
  writeFileSync(`${P.registerPlan}.tmp`, JSON.stringify(plan, null, 2) + "\n");
11099
12041
  renameSync(`${P.registerPlan}.tmp`, P.registerPlan);
11100
12042
  ctx.registerPlan = plan;
11101
12043
  }
11102
- // — expected empty: mintSupplementalEntries runs entryTermIssues at the mint and the
11103
- // rejects are already in the sidecar above. Logged anyway, because "expected empty" is a
11104
- // belief about two screens agreeing, and the day they stop agreeing this is the only place
11105
- // that would say so.
12044
+ // — expected to hold only the two refusals that mean the question is settled already: a
12045
+ // duplicate of the plan's own question, and a family the reading turn withheld. Every other kind
12046
+ // is a term mintSupplementalEntries should have caught (it runs entryTermIssues at the mint, and
12047
+ // its rejects are already in the sidecar above). Logged anyway, because that is a belief about
12048
+ // two screens agreeing, and the day they stop agreeing this is the only place that would say so.
11106
12049
  if (refused.length) {
11107
- for (const r of refused) if (r?.qid) regFoldRefusals.set(r.qid, { kind: r.kind ?? null, twin: r.twin ?? null });
12050
+ for (const r of refused) if (r?.qid) regFoldRefusals.set(r.qid, { kind: r.kind ?? null, twin: r.twin ?? null, reason: r.reason ?? null });
11108
12051
  // THE REASON THE FOLD GAVE IS LOGGED, not just the qid it refused. The fold is the only
11109
12052
  // place that knows WHY, and for a duplicate it knows WHICH plan row already holds the
11110
12053
  // answer. Logging the qid alone threw that away: recovering it on one run meant
@@ -11119,20 +12062,31 @@ async function pipelineInner(job, opts = {}) {
11119
12062
  // in the plan and its existing row carries the coverage. An identity collision or a
11120
12063
  // malformed term IS the mint screen and the fold screen disagreeing, because the mint
11121
12064
  // runs entryTermIssues and should have caught it.
12065
+ // A withheld family's refusal is neither: it is the reading turn's decision standing, recorded on
12066
+ // the run log row above with its kind and its family, and settled by that turn's reason.
11122
12067
  const dupes = refused.filter((r) => r.kind === "duplicate-question");
11123
- const faults = refused.filter((r) => r.kind !== "duplicate-question");
12068
+ const faults = refused.filter((r) => r.kind !== "duplicate-question" && r.kind !== "withheld-by-judgment");
11124
12069
  if (dupes.length) note(`frame-reopen: ${dupes.length} minted entr${dupes.length === 1 ? "y" : "ies"} refused at the fold as already-asked — the plan row${dupes.length === 1 ? "" : "s"} ${dupes.map((r) => `"${r.twin ?? "(twin unstated)"}"`).join(", ")} carr${dupes.length === 1 ? "ies" : "y"} that coverage; the term is answered, not unsearched`);
11125
12070
  if (faults.length) note(`frame-reopen: ${faults.length} minted entr${faults.length === 1 ? "y" : "ies"} REFUSED at the fold (${[...new Set(faults.map((r) => r.kind ?? "unstated"))].join(", ")}) — the mint screen and the fold screen disagree, which is a wiring finding`);
11126
12071
  }
11127
12072
  // Dispatch → derive band → VERIFY per directive (qid-landed + non-collapse + class-scope).
11128
12073
  // A byte-changed band with only a wrong-scope/empty/error block closes NOTHING.
11129
12074
  const dispatchAndVerify = async (qids) => {
11130
- const outcome = await dispatchPlanQids(a, qids, "frame-reopen-sweep", 2);
12075
+ // NOTHING ADDED, NOTHING REPEATED. The executor reads an empty question list as every entry on the
12076
+ // axis, so a fold that refused every slice (the plan's own questions, or families the reading
12077
+ // turn withheld) handed the whole primary sweep back to the executor to run again. It now sends
12078
+ // only the axis's required questions that have not landed (pendingRequiredQids), so a required
12079
+ // search still gets its retry and nothing landed is asked twice. The verify below reads the join
12080
+ // either way, so a refused slice is still checked through its twin or its family.
12081
+ let send = qids;
12082
+ if (!send.length) send = pendingRequiredQids(ctx.registerPlan, a, readRegisterBands(P, axes), releasedFamilyQids(ctx.paths.runDir));
12083
+ const outcome = send.length ? await dispatchPlanQids(a, send, "frame-reopen-sweep", 2) : "ok";
11131
12084
  refreshSupplementalExecution(ctx);
11132
12085
  try { deriveNamedBand(ctx); } catch (e) { regMechFail = `named-band:${String(e.message).slice(0, 80)}`; }
11133
12086
  const bands = readRegisterBands(P, axes);
11134
- const executedQids = new Set(joinPlanToBands(ctx.registerPlan, bands, { released: releasedFamilyQids(ctx.paths.runDir) }).executed.map((x) => x.qid));
11135
- const blocksByQid = new Map((bands[a] ?? []).filter((b) => b && b.qid).map((b) => [b.qid, b]));
12087
+ const executedQids = new Set(joinPlanToBands(ctx.registerPlan, bands, joinOptions(ctx)).executed.map((x) => x.qid));
12088
+ // every axis's blocks: a slice refused as already answered is checked on its twin, which may be another axis's row
12089
+ const blocksByQid = blocksByQidOf(bands);
11136
12090
  return { outcome, executedQids, blocksByQid };
11137
12091
  };
11138
12092
  // A slice the fold refused as already-asked is checked on the row that answered it, as the remedy
@@ -11143,6 +12097,7 @@ async function pipelineInner(job, opts = {}) {
11143
12097
  executedQids: ev.executedQids, blocksByQid: ev.blocksByQid, foldRefusals: regFoldRefusals,
11144
12098
  });
11145
12099
  if (v.closed && v.answered_by) regAnsweredBy.set(reopenKey(d), v.answered_by);
12100
+ if (v.closed && v.withheld_by_judgment) regWithheldBy.set(reopenKey(d), v.withheld_by_judgment);
11146
12101
  return v;
11147
12102
  };
11148
12103
  const mintedDirectives = regDirectives.filter((d) => (directiveQids.get(reopenKey(d)) ?? []).length);
@@ -11204,6 +12159,7 @@ async function pipelineInner(job, opts = {}) {
11204
12159
  const a = "primary-sweep";
11205
12160
  const regDom = regDirectives.filter((d) => d.severity === "dominant-element");
11206
12161
  const beforeUnit = existsSync(P.registerUnit(a)) ? readFileSync(P.registerUnit(a), "utf8") : null;
12162
+ const beforeBand = bandBytesOf(P, a); // a sweep that adds blocks moves the band, whatever it does to the notes
11207
12163
  note(`frame-reopen: ${regDirectives.length} register omission(s) → resuming ${a} to ${regDom.length ? "ENUMERATE the dominant-element floor to exhaustion" : "fold in a targeted supplemental sweep"}`);
11208
12164
  // Lane flag read from the SAME key the tool exclusion keys on (see the stage dispatcher) —
11209
12165
  // this arm is reachable on a lane run whenever the code-side dispatch arm above could not
@@ -11212,7 +12168,7 @@ async function pipelineInner(job, opts = {}) {
11212
12168
  supplementalLane: !!ctx.registerPlan?.contract?.supplemental_lane });
11213
12169
  const r = await stage("register-unit", { ...ctx, axis: a }, { force: true, followup, sessionKey: unitKey[a], trigger: "frame-reopen" });
11214
12170
  if (r.ok) {
11215
- if (readFileSync(P.registerUnit(a), "utf8") !== beforeUnit) { changedAxes.push(a); regChanged = true; }
12171
+ if (readFileSync(P.registerUnit(a), "utf8") !== beforeUnit || bandBytesOf(P, a) !== beforeBand) { changedAxes.push(a); regChanged = true; }
11216
12172
  else note(`frame-reopen: ${a} unchanged after the supplemental sweep`);
11217
12173
  } else {
11218
12174
  regMechFail = r.fail; note(`frame-reopen: ${a} supplemental sweep failed mechanically (${r.fail})`);
@@ -11226,7 +12182,7 @@ async function pipelineInner(job, opts = {}) {
11226
12182
  const retryMsg = repairFollowup("register-unit:frame-reopen-retry", { paths: P, axis: a, directives: regDirectives, reopenFetchCap,
11227
12183
  supplementalLane: !!ctx.registerPlan?.contract?.supplemental_lane });
11228
12184
  const r2 = await stage("register-unit", { ...ctx, axis: a }, { force: true, freshMessage: retryMsg, sessionKey: `clearance-${ctx.run.slug}-${ctx.run.codename}-register-unit-${a}-reopen-retry`, trigger: "frame-reopen-retry" });
11229
- if (r2.ok && existsSync(P.registerUnit(a)) && readFileSync(P.registerUnit(a), "utf8") !== beforeUnit) {
12185
+ if (r2.ok && existsSync(P.registerUnit(a)) && (readFileSync(P.registerUnit(a), "utf8") !== beforeUnit || bandBytesOf(P, a) !== beforeBand)) {
11230
12186
  changedAxes.push(a); regChanged = true; regMechFail = null;
11231
12187
  note(`frame-reopen: ${a} fresh scoped retry closed the gap`);
11232
12188
  } else if (!r2.ok) {
@@ -11240,6 +12196,7 @@ async function pipelineInner(job, opts = {}) {
11240
12196
  regNoSession = true;
11241
12197
  note(`frame-reopen: ${regDirectives.length} register omission(s) — no resumable primary-sweep/digest session in this process`);
11242
12198
  }
12199
+ if (regArm === "resume") remergeIfBandsMoved(ctx, "frame-reopen");
11243
12200
 
11244
12201
  // SOURCE channels: a warm RESUME is taken when the common-law session is live; with no
11245
12202
  // resumable session the omission becomes a DISCLOSED DEFERRAL (open coverage row + the
@@ -11431,7 +12388,7 @@ async function pipelineInner(job, opts = {}) {
11431
12388
  return "unchanged-after-resume";
11432
12389
  });
11433
12390
  // — THE REMEDY TERM LEDGER, built before the closure read because the closure read now
11434
- // depends on it. Every term a firing directive restated ends in one of five named states, and
12391
+ // depends on it. Every term a firing directive restated ends in one of six named states, and
11435
12392
  // `searched-empty` carries the executed query and its counted zero — the state that used to
11436
12393
  // leave no trace anywhere. Non-fatal end to end: a ledger that cannot build leaves the
11437
12394
  // receipt's fields NULL (never absent) and the closure read falls back to its prior rule,
@@ -11486,7 +12443,8 @@ async function pipelineInner(job, opts = {}) {
11486
12443
  const partials = swept.filter((k) => regPartialMint.has(k))
11487
12444
  .map((k) => ({ directive: k, note: `verified-closed on the minted subset — ${regPartialMint.get(k)}` }));
11488
12445
  const answered = swept.filter((k) => regAnsweredBy.has(k)).map((k) => ({ directive: k, slices: regAnsweredBy.get(k) }));
11489
- writeReopenReceipt(swept, deferrals, domClosed, partials, remedyAccounting, answered);
12446
+ const withheldBy = swept.filter((k) => regWithheldBy.has(k)).map((k) => ({ directive: k, slices: regWithheldBy.get(k) }));
12447
+ writeReopenReceipt(swept, deferrals, domClosed, partials, remedyAccounting, answered, withheldBy);
11490
12448
  runLog(run.runDir, { event: "frame-reopen", requested: firing.length, swept: swept.length, deferred: deferrals.length, remaining: deferrals.length, dominant_element_gap, domClosed, reopenFetchCap: regDirectives.some((d) => d.severity === "dominant-element") ? reopenFetchCap : undefined });
11491
12449
  // C2 (review fix, 2026-07-29) — the redigest-fail demotion invariant, LOCKED path. The
11492
12450
  // unlocked path demotes verified-closed directives inline when its pre-synthesis flush
@@ -11583,23 +12541,10 @@ async function pipelineInner(job, opts = {}) {
11583
12541
  // Step 2C placement → Step 2D digest
11584
12542
  await assertReceiptSettled(ctx, "placement-inquiry");
11585
12543
  const placementPass = await stage("placement-inquiry", ctx);
11586
- // half one — what THIS pass did to every record in the band, recorded before anything else can
11587
- // overwrite placements.json. Ahead of `must` deliberately: a pass that failed or skipped on a partial
11588
- // artifact is exactly the pass whose account is worth having, and `must` throws.
11589
- recordPlacementSeam(ctx, placementPass);
11590
- // — and whether the floors came back, on the SAME seam and for the same reason: before
11591
- // anything can overwrite placements.json, and ahead of `must`, because a pass that failed or
11592
- // skipped is exactly the pass whose floor account is worth having.
11593
- deriveFloorDuty(ctx, placementPass);
11594
- // ── — ARM THE FLOOR, AND ONLY ON A PASS THAT ACTUALLY RAN ─────────────────────────────────
11595
- //
11596
- // The stamp says "this run's placement ran under code carrying the floors order", which is the one
11597
- // fact a delivery floor may rest on. A SKIPPED pass leaves `placements.json` written by an earlier
11598
- // seat — on a resume, potentially one that never saw the dictation — so arming there would hold a
11599
- // seat to an order it never got. `ok:false` never reaches here anyway (`must` throws on the next
11600
- // line), and it is written out rather than relied on, because the reason it cannot reach here is a
11601
- // property of the line below rather than of this one.
11602
- if (placementPass?.ok === true && placementPass?.skipped !== true) armFloorDuty(P.runDir);
12544
+ // What THIS pass did, recorded before anything else can overwrite placements.json, and ahead of `must`
12545
+ // deliberately: a pass that failed or skipped on a partial artifact is exactly the pass whose account is
12546
+ // worth having, and `must` throws. The delivery gate's repeat of this stage records through the same helper.
12547
+ recordPlacementPass(ctx, placementPass);
11603
12548
  must(placementPass, "placement-inquiry");
11604
12549
  await checkLateBind("pre-digest"); // B5b checkpoint 2: a bind here rides the digest message as a re-classification block
11605
12550
  const digestKey = must(await runDigest(ctx), "register-digest").sessionKey;
@@ -11766,6 +12711,7 @@ async function pipelineInner(job, opts = {}) {
11766
12711
  // Only axes whose unit file MATERIALLY changed stay in escalated[]; content-compare is conservative —
11767
12712
  // any byte difference re-fires, so a genuine recall fix is never skipped. Floor-neutral.
11768
12713
  const beforeUnit = existsSync(P.registerUnit(a)) ? readFileSync(P.registerUnit(a), "utf8") : null;
12714
+ const beforeBand = bandBytesOf(P, a); // a narrow sub-query adds blocks to the band, whatever it does to the notes
11769
12715
  note(`skeptic escalated ${a} → resuming its session to defend/adjust (warm, same tier)`);
11770
12716
  let followup = repairFollowup("register-unit:escalation", { paths: P, axis: a, flags,
11771
12717
  supplementalLane: !!ctx.registerPlan?.contract?.supplemental_lane });
@@ -11783,7 +12729,7 @@ async function pipelineInner(job, opts = {}) {
11783
12729
  const r = await stage("register-unit", { ...ctx, axis: a }, { force: true, followup, sessionKey: unitKey[a], trigger: "escalation" });
11784
12730
  if (!r.ok) return { axis: a, changed: false, ok: false };
11785
12731
  const afterUnit = readFileSync(P.registerUnit(a), "utf8");
11786
- const changed = afterUnit !== beforeUnit;
12732
+ const changed = afterUnit !== beforeUnit || bandBytesOf(P, a) !== beforeBand;
11787
12733
  if (!changed) {
11788
12734
  note(`escalation ${a}: unit defended in place (unchanged) — Opus re-digest not needed`);
11789
12735
  runLog(run.runDir, { event: "escalation-noop", axis: a });
@@ -11791,6 +12737,7 @@ async function pipelineInner(job, opts = {}) {
11791
12737
  return { axis: a, changed, ok: true };
11792
12738
  });
11793
12739
  const escalated = escResults.filter((e) => e.changed).map((e) => e.axis);
12740
+ remergeIfBandsMoved(ctx, "escalation");
11794
12741
  // W-3 input (D1): persist the escalation outcome so the client-export gate can WITHHOLD when a
11795
12742
  // load-bearing escalation the system ITSELF raised did not complete. requested = axes attempted this
11796
12743
  // session; failed = those whose register-unit stage returned !r.ok (never ran to a result). The
@@ -11882,6 +12829,7 @@ async function pipelineInner(job, opts = {}) {
11882
12829
  if (closeableAxes.length && decision.close && !digestLockedEnv) {
11883
12830
  const closeResults = await runBatched(closeableAxes, config.gatherConcurrency, async (a) => {
11884
12831
  const beforeUnit = existsSync(P.registerUnit(a)) ? readFileSync(P.registerUnit(a), "utf8") : null;
12832
+ const beforeBand = bandBytesOf(P, a); // a close that runs the deferred slices adds blocks to the band
11885
12833
  // quote the axis's own deferred ledger rows + ⭐-floor breach rows verbatim so the unit closes
11886
12834
  // exactly what was deferred (or mislabelled away). WS-A: the quoted rows come from the SAME
11887
12835
  // loadCoverageLedger read the gate decided on — never a separate prose grep that could diverge.
@@ -11903,8 +12851,9 @@ async function pipelineInner(job, opts = {}) {
11903
12851
  const r = await stage("register-unit", { ...ctx, axis: a }, { force: true, followup, sessionKey: unitKey[a], trigger: "envelope" });
11904
12852
  if (!r.ok) return { axis: a, changed: false, fail: r.fail };
11905
12853
  const afterUnit = readFileSync(P.registerUnit(a), "utf8");
11906
- return { axis: a, changed: afterUnit !== beforeUnit, fail: null };
12854
+ return { axis: a, changed: afterUnit !== beforeUnit || bandBytesOf(P, a) !== beforeBand, fail: null };
11907
12855
  });
12856
+ remergeIfBandsMoved(ctx, "envelope");
11908
12857
  const closedChanged = closeResults.filter((e) => e.changed).map((e) => e.axis);
11909
12858
  const closeFails = closeResults.filter((e) => e.fail);
11910
12859
  if (closedChanged.length) {
@@ -12007,14 +12956,17 @@ async function pipelineInner(job, opts = {}) {
12007
12956
  // Re-read the findings + recompute the fetched universe on EACH check (the recovery re-digest rewrites
12008
12957
  // the findings AND may record_fetch the flagged URIs — both must be picked up on the post-recovery
12009
12958
  // check). V4-1: the fetched universe is the DELIVERED ARTIFACT SET's records (run-dir _records/ ∪ this
12010
- // session's ledger rows) ∪ this session's fetch attempts — not the session-prefixed ledger alone.
12959
+ // session's ledger rows) — not the session-prefixed ledger alone. A failed fetch is not a retrieval, and a
12960
+ // body that carries no record is not one either (ruled 2026-10-18): neither answers the gate, so such a drop
12961
+ // gets the gate's own recovery fetch and, failing that, its disclosure. A valid copy the run already holds
12962
+ // still answers it, whatever a later attempt did. A call the register answered `ok` is no record on its own:
12963
+ // every register files the body it fetched, and a 200 carrying an empty record files an empty one, so the
12964
+ // body is what is read, never the call's status.
12965
+ const fetchedUniverse = () => new Set(
12966
+ [...assembleRunRecords(run.runDir, runPrefix).records].filter(([, body]) => recordBodyHasContent(body)).map(([uri]) => uri));
12011
12967
  const checkScreenGate = () => {
12012
12968
  const findings = existsSync(P.registerFindings) ? readFileSync(P.registerFindings, "utf8") : "";
12013
- const fetched = new Set([
12014
- ...fetchedRecordUris(DEFAULT_LEDGER_PATH, runPrefix),
12015
- ...assembleRunRecords(run.runDir, runPrefix).records.keys(),
12016
- ]);
12017
- return findScreenGateViolations(findings, fetched);
12969
+ return findScreenGateViolations(findings, fetchedUniverse());
12018
12970
  };
12019
12971
 
12020
12972
  // PARSE-GAP LEDGER (2026-07-21): records the gate is blind to because URI_RE's `[a-z]{2}` cannot read
@@ -12114,7 +13066,7 @@ async function pipelineInner(job, opts = {}) {
12114
13066
  // Re-stamp the disclosure to the CURRENT leftover set — the flush may have corrected some
12115
13067
  // rows while new ones appeared; the floor and the sidecar must describe what is true now.
12116
13068
  // A row already disclosed keeps its original (more specific) fetch-failure cause.
12117
- const priorCause = new Map((ctx.screenGateUnresolved ?? []).map((u) => [u.uri ?? u.mark, u.cause]));
13069
+ const priorCause = new Map((ctx.screenGateUnresolved ?? []).map((u) => [u.uri ?? u.mark, u]));
12118
13070
  // — the PROVIDER's own words first. `runPostFlushGateRepair` now returns what each fetch
12119
13071
  // said instead of discarding it, so a row disclosed on this path names the register's error
12120
13072
  // rather than the driver's generic string. `cause_source` says which of the three it is,
@@ -12125,7 +13077,10 @@ async function pipelineInner(job, opts = {}) {
12125
13077
  const fresh = v.uri ? postFlushFailures?.get(v.uri) : null;
12126
13078
  const prior = priorCause.get(key);
12127
13079
  if (fresh) return { mark: v.mark, uri: v.uri ?? null, cause: fresh, cause_source: "provider" };
12128
- if (prior) return { mark: v.mark, uri: v.uri ?? null, cause: prior, cause_source: "provider-earlier-pass" };
13080
+ // An earlier pass's cause keeps the source it was given: a refusal is the provider's on that earlier
13081
+ // pass, and a fetch that reported success, or a row naming no record, stays what it was.
13082
+ if (prior?.cause) return { mark: v.mark, uri: v.uri ?? null, cause: prior.cause,
13083
+ cause_source: !prior.cause_source || prior.cause_source === "provider" ? "provider-earlier-pass" : prior.cause_source };
12129
13084
  return {
12130
13085
  mark: v.mark, uri: v.uri ?? null,
12131
13086
  cause: v.uri
@@ -12150,6 +13105,10 @@ async function pipelineInner(job, opts = {}) {
12150
13105
  }
12151
13106
  };
12152
13107
  let violations = enforcedViolations();
13108
+ // What the gate's answer stood on before it fetched anything, and each fetch it makes: the evidence below.
13109
+ const heldBeforeGate = fetchedUniverse();
13110
+ const gateBefore = screenGateSnapshot(ctx, runPrefix);
13111
+ const gateFetched = new Map();
12153
13112
  if (violations.length) {
12154
13113
  // REPAIR-OR-DISCLOSE, NEVER BLOCK (2026-06-18). The digest intermittently drops an in-scope-live mark on
12155
13114
  // goods WITHOUT record_fetching it, and a generic re-digest re-runs identically (the model skips the same
@@ -12172,7 +13131,8 @@ async function pipelineInner(job, opts = {}) {
12172
13131
  try {
12173
13132
  const r = await fetcher(v.uri, { agentId: agent, sessionKey: `${runPrefix}screen-gate-refetch`, recordLog: runRecordLogPath(run.runDir) });
12174
13133
  if (!r?.ok) fetchFailures.set(v.uri, r?.cause ?? "record_fetch returned not-ok");
12175
- } catch (e) { fetchFailures.set(v.uri, String(e?.message ?? e).slice(0, 120)); }
13134
+ gateFetched.set(v.uri, { ok: r?.ok === true, cause: r?.ok ? null : (r?.cause ?? "record_fetch returned not-ok") });
13135
+ } catch (e) { fetchFailures.set(v.uri, String(e?.message ?? e).slice(0, 120)); gateFetched.set(v.uri, { ok: false, cause: String(e?.message ?? e).slice(0, 120) }); }
12176
13136
  }
12177
13137
  // The re-decide text — shared verbatim between the legacy immediate re-digest (funnel OFF) and
12178
13138
  // the queued settlement-flush section (funnel ON).
@@ -12244,6 +13204,7 @@ async function pipelineInner(job, opts = {}) {
12244
13204
  ...screenGateZeroCause({ findingsPresent: fPresent,
12245
13205
  findingsContent: fPresent ? readFileSync(P.registerFindings, "utf8") : "", unnamedArmed }) });
12246
13206
  }
13207
+ recordScreenGateEvidence(ctx, { runPrefix, before: gateBefore, heldBeforeGate, heldAfterGate: fetchedUniverse(), gateFetched });
12247
13208
  }
12248
13209
 
12249
13210
  // ──: STANDALONE SETTLEMENT / LATE FLUSH ───────────────────────────────────────────────────
@@ -12261,6 +13222,32 @@ async function pipelineInner(job, opts = {}) {
12261
13222
  // never re-fire on any later resume.
12262
13223
  if (!ctx.digestSettled) {
12263
13224
  const digestLockedFlush = existsSync(P.narrative) && !forceFromActive(ctx, "synthesis");
13225
+ // ── LATE EVIDENCE IS ASSESSED BEFORE THE READING STEP'S LAST PASS AND THE WRITER (owner's ruling, 2026-10-19) ──
13226
+ // A record the band gained after placement's pass (a unit's re-run, the code-side recheck) is placed or set aside
13227
+ // by placement itself, through its own stage, before the flush below hands the reading step the placements and
13228
+ // before the writer reads them. The form is a union, so every row placement accepted stands. Only ids placement
13229
+ // never read run it again: a band whose counts alone moved costs nothing. Every path that grows the band after
13230
+ // placement queues the reading step's re-run, so the flush below follows; one that did not is recorded.
13231
+ if (!digestLockedFlush) {
13232
+ const late = lateRecordIds(P);
13233
+ if (late.length) {
13234
+ runLog(run.runDir, { event: "late-evidence", records: late.length });
13235
+ note(`late evidence: ${late.length} record(s) reached the band after placement's pass — placement assesses them before the reading step's next pass and the writer`);
13236
+ // A PASS THAT FAILS, OR IS REFUSED BEFORE IT STARTS, DOES NOT END THE RUN HERE. A run is never held for a recovery
13237
+ // that could not run (the owner's ruling of 2026-10-19): placement's earlier account stands, and the floor and the
13238
+ // recall gate state what it leaves open. The stage can throw before it dispatches, as it does for a band past the
13239
+ // size ceiling, so the throw is caught and recorded as the failure it is.
13240
+ let pass;
13241
+ try { pass = recordPlacementPass(ctx, await stage("placement-inquiry", ctx, { force: true, trigger: "late-evidence" }), "late-evidence"); }
13242
+ catch (e) { pass = { ok: false, fail: String(e?.message ?? e) }; }
13243
+ if (!pass?.ok) {
13244
+ runLog(run.runDir, { event: "late-evidence-failed", records: late.length, fail: String(pass?.fail ?? "failed").slice(0, 200) });
13245
+ ctx.latePassFailed = { records: late.length, fail: String(pass?.fail ?? "failed").slice(0, 200) }; // the attempt, for the delivery record
13246
+ note(`late evidence: placement's pass did not complete (${String(pass?.fail ?? "").slice(0, 100)}) — its earlier account stands, and the floor and the recall gate state what it leaves open`);
13247
+ }
13248
+ if (!pendingItems(ctx.digestQueue).length) runLog(run.runDir, { event: "late-evidence-no-digest-work", records: late.length });
13249
+ }
13250
+ }
12264
13251
  if (!digestLockedFlush) await flushDigestQueue("pre-synthesis");
12265
13252
  else if (pendingItems(ctx.digestQueue).length) await flushDigestQueue("late");
12266
13253
  ctx.digestSettled = true;
@@ -12410,8 +13397,9 @@ async function pipelineInner(job, opts = {}) {
12410
13397
  const { text: synthExtra } = composeDispatchExtra("synthesis", ctx);
12411
13398
  const synth = await quarantineSynth(await stage("synthesis", ctx, synthExtra ? { extra: synthExtra } : {}), ctx, "synthesis"); // A3: last-resort quarantine instead of must(); A4: one targeted re-emit inside
12412
13399
  recordSynthesisSeam(ctx, synth); // half one — which surfaced records this pass delivered, and which it did not
12413
- // The corrective re-synthesis must RESUME the WINNING attempt — its key AND model/thinking. If synthesis
12414
- // fell over to a cross-provider model, resuming the base key on opus would cold-cache + mismatch the model.
13400
+ // The corrective re-synthesis is dispatched on the WINNING attempt's key AND model/thinking. A followup's first
13401
+ // attempt starts fresh, and it never resumes the synthesis session (no engine resumes by key); but if synthesis
13402
+ // fell over to a cross-provider model, a dispatch on the base model would correct a record another model wrote.
12415
13403
  const synthesisKey = synth.sessionKey, synthesisModel = synth.model, synthesisThinking = synth.thinking;
12416
13404
 
12417
13405
  // A2/A4 — the v3 evidence gates (verified-needs-source, symmetric use-check, client-tier
@@ -12475,7 +13463,7 @@ async function pipelineInner(job, opts = {}) {
12475
13463
  await stage("synthesis", ctx, {
12476
13464
  force: true, sessionKey: synthesisKey, model: synthesisModel, thinking: synthesisThinking,
12477
13465
  trigger: "actions-missing",
12478
- followup: repairFollowup("synthesis:actions-missing", { findings: P.findings }) + stageCharter("synthesis", ctx.depth, ctx.framework),
13466
+ followup: repairFollowup("synthesis:actions-missing", { findings: P.findings, narrative: P.narrative }) + stageCharter("synthesis", ctx.depth, ctx.framework),
12479
13467
  });
12480
13468
  const after = readActionsPresence();
12481
13469
  if (after && !after.hasActions) runLog(run.runDir, { event: "findings-actions-absent" });
@@ -12658,8 +13646,9 @@ async function pipelineInner(job, opts = {}) {
12658
13646
  note(`corrective cycle already settled for these exact review/narrative bytes — not re-firing (a stale-artifact recovery re-does what went stale, never the settled corrective loop)`);
12659
13647
  }
12660
13648
  }
12661
- // One corrective re-synthesis on CONDITIONAL/BLOCKING, feeding the reviewer's flags back in. Resume the
12662
- // WINNING synthesis attempt (key + model + thinking) so a fallback-won synthesis is corrected warm, in place.
13649
+ // One corrective re-synthesis on CONDITIONAL/BLOCKING, feeding the reviewer's flags back in, dispatched on the
13650
+ // WINNING synthesis attempt's key, model and thinking, so the model that wrote a fallback-won synthesis corrects
13651
+ // it. Its first attempt is a fresh turn handed the flags and the inputs; it never resumes the synthesis session.
12663
13652
  let correctionsApplied = null; // — the driver's flag-by-flag observation, shared by both legs
12664
13653
  let evidenceViolations = []; // — claims that moved against their own evidence, carried to the recheck
12665
13654
  let correctionsScope = null; // — the declared per-finding scope, and whether the pass held to it
@@ -12719,11 +13708,7 @@ async function pipelineInner(job, opts = {}) {
12719
13708
  // A DEFECT SIGNAL, not a success. After the schema fix a corrective pass sends a targeted edit
12720
13709
  // and cannot remove a finding at all, so e2e asserts this count is ZERO — a firing reports the
12721
13710
  // primary fix not holding, never this backstop working.
12722
- runLog(run.runDir, { event: "corrective-unnamed-removal-repaired", defect: true,
12723
- restored: repaired.restoredFindings.length, keys: repaired.restoredKeys.length,
12724
- rows: repaired.restoredRows.length,
12725
- leftRemoved: repaired.leftRemoved.length,
12726
- findings: repaired.restoredFindings.map((f) => `${f.ordinal}:${f.mark}`) });
13711
+ runLog(run.runDir, removalRepairedRow(repaired)); // with `base`: whether the merge base took the restore
12727
13712
  note(`[corrections] the corrective pass removed ${repaired.restoredFindings.length} finding(s) `
12728
13713
  + `no flag named — restored whole from the pre-corrective snapshot: `
12729
13714
  + `${repaired.restoredFindings.map((f) => `#${f.ordinal} ${f.mark}`).join(", ")}`
@@ -12791,7 +13776,16 @@ async function pipelineInner(job, opts = {}) {
12791
13776
  runLog(run.runDir, { event: "corrections-applied", flags: correctionsApplied.length, outcomes: by,
12792
13777
  scoped: correctionsScope.scoped, named: correctionsScope.named.length,
12793
13778
  movedOutsideScope: correctionsScope.moved.length, unboundOrdinals: correctionsScope.unbound.length });
13779
+ // The basis for the delivery check each assembly makes (recordCorrectionsDelivered): what each flag
13780
+ // changed in the fields a report prints from the record, from the same two records. Its own file.
13781
+ try { recordCorrectionsBasis(run.runDir, correctionsApplied, preDoc, postDoc); }
13782
+ catch (e) {
13783
+ try { rmSync(driverDir(run.runDir, CORRECTIONS_DELIVERED_FILE), { force: true }); } catch { /* best-effort */ }
13784
+ note(`[corrections] the delivery basis could not be kept (${String(e?.message ?? e).slice(0, 100)}) — this pass has no delivery record`);
13785
+ }
12794
13786
  } catch (e) {
13787
+ // A basis from an earlier pass would be read against this pass's report as if it were this pass's.
13788
+ try { rmSync(driverDir(run.runDir, CORRECTIONS_DELIVERED_FILE), { force: true }); } catch { /* best-effort */ }
12795
13789
  correctionsApplied = null;
12796
13790
  note(`[corrections] the applied table could not be built (${String(e?.message ?? e).slice(0, 100)}) — the recheck reads both files, as before`);
12797
13791
  runLog(run.runDir, { event: "corrections-applied-failed", error: String(e?.message ?? e).slice(0, 160) });
@@ -12801,9 +13795,10 @@ async function pipelineInner(job, opts = {}) {
12801
13795
  // read, so a recheck spends a dispatch to re-derive the verdict it already gave. The entry verdict
12802
13796
  // stands, which is also the fail-safe direction this block uses everywhere else.
12803
13797
  if (!correctiveRollback && !correctiveCycleSettled && (verdict === "CONDITIONAL" || verdict === "BLOCKING")) {
12804
- // A1: re-check after the corrective pass by RESUMING the refutation's winning session (warm cache, same
12805
- // model) with a lean follow-up — NOT a cold re-run on the base key, and NOT re-trying deepseek when sonnet
12806
- // won. (On a crash-resume skip with no live session, fall back to a clean forced re-run on the same model.)
13798
+ // A1: re-check after the corrective pass with a lean follow-up on the refutation's winning key and model —
13799
+ // NOT a full re-run of the stage, and NOT the base model when another model won. The follow-up's first
13800
+ // attempt starts fresh, and it never resumes the refutation's session (no engine resumes by key). (With no
13801
+ // winning key, as on a crash-resume skip, a clean forced re-run on the same model.)
12807
13802
  // A1: the recheck also runs on the CONDITIONAL path (the VENZY shipping path — previously the
12808
13803
  // corrective output was never re-verified there) and reads the updated FINDINGS too, since the client
12809
13804
  // surfaces render from findings.json, not the narrative. Failure falls back to the ENTRY verdict
@@ -12953,31 +13948,12 @@ async function pipelineInner(job, opts = {}) {
12953
13948
  // are settled by now; the corrective ladder above re-emitted findings last).
12954
13949
  enrichFindingDeadlines(P, run.runDir, note);
12955
13950
 
12956
- // ── P2-A pre-verdict floor: the reconciliation's hard discrepancy list BLOCKS delivery ──────────
12957
- // screened-live dominant-element candidates ∖ endings must be EMPTY before a report ships. The
12958
- // digest seam already spent its warm followups (runDigest → enforceRecallReconciliation, re-run
12959
- // after every rewrite incl. flushes); a non-empty list here means the run kept losing screened
12960
- // records after repair — that is a defect to fail loudly on, never residue to disclose around
12961
- // (charter P2a; the Round-2 ruling: silent death of a retrieved record is the one class that
12962
- // never ships). Absent/not-computable artifacts never block (legacy runs, prose-only manifests,
12963
- // register-gap runs — the gate polices only what this code derived).
12964
- //
12965
- // ROUND-2 FIX (review problem 7): the throw stamps failClass "deterministic", like every other
12966
- // code-derived floor of this kind (the fan-in throws at :320/:3723/:4061). The list is computed
12967
- // from the run's own artifacts by pure code, so a fresh sample re-derives it identically — the
12968
- // park the "unknown" classification used to buy (this reason text misses repairs.mjs
12969
- // DETERMINISTIC_RE, so classifyFailureReason returned "unknown" and decideRecovery granted a full
12970
- // pipeline resume) is provably futile. Throw-site stamping always wins over the text guess, which
12971
- // is why the token is NOT added to DETERMINISTIC_RE.
12972
- {
12973
- const rec = safeReadJson(P.recallReconciliation);
12974
- if (rec?.computable === true && (rec.totals?.unended ?? 0) > 0) {
12975
- const sample = (rec.unended ?? []).slice(0, 4).map((u) => `${u.mark_text} (${u.record_id})`).join("; ");
12976
- throw new StageFailure("recall-reconciliation",
12977
- `recall_reconciliation_unended:${rec.totals.unended} — retrieved and screened in-scope-live on the dominant element but ending nowhere a reader can see (no finding row, no drop row, no counted crowd ruling): ${sample}${rec.totals.unended > 4 ? " …" : ""} — a report never ships losing screened candidates silently`,
12978
- undefined, { failClass: "deterministic" });
12979
- }
12980
- }
13951
+ // ── P2-A pre-verdict floor: the reconciliation's hard discrepancy list ─────────────────────────
13952
+ // screened-live dominant-element candidates ∖ endings is read here as it always was, and it no longer THROWS:
13953
+ // the owner's ruling of 2026-10-19 that a run is never held replaces the Round-2 rule that this one class never
13954
+ // ships. The run first recovers what it can in-run (the digest seam's warm followups; placement assessing late
13955
+ // evidence before the writer); whatever is still unended ships NAMED, through the terminal guards' deliver-and-clamp
13956
+ // (`clampOnTheRecallGate`, beside the floor duty's clamp below), never as a run that delivers nothing.
12981
13957
 
12982
13958
  // ── — THE TERMINAL GUARDS DELIVER AND CLAMP; THEY DO NOT WITHHOLD ───────────
12983
13959
  //
@@ -13002,6 +13978,11 @@ async function pipelineInner(job, opts = {}) {
13002
13978
  // run earlier in this function. The sidecar write sorts guard clauses AFTER the opinion's, stated
13003
13979
  // there; this set is how it tells them apart without a parallel index to keep aligned.
13004
13980
  const guardClauses = new Set();
13981
+ // THE ACCEPTED ASSESSMENT: the result the review reached, before any guard or floor clamped it. It moves with the
13982
+ // review alone (the degenerate re-ask, a late review that is harsher), and a gap a guard judged again finds closed is
13983
+ // withdrawn against it (ruled 2026-10-19).
13984
+ let assessed = verdict;
13985
+ let restoredClear = false; // a closed gap took this run back to CLEAR, so a later writer's judgment meets the floor again
13005
13986
 
13006
13987
  /**
13007
13988
  * Deliver, clamp conservatively, and name the defect in the run record.
@@ -13018,14 +13999,20 @@ async function pipelineInner(job, opts = {}) {
13018
13999
  // could only be pinned by source text, and a source-text pin cannot tell an armed check from one
13019
14000
  // somebody disarmed while leaving its words in place. This closure is the wiring: run record, note,
13020
14001
  // clamp lists, status. The judgement is all in the module.
13021
- const deliverAndClamp = ({ event, defect, reason, clause, detail = {} }) => {
14002
+ const deliverAndClamp = ({ event, defect, reason, clause, detail = {}, again = false, statedElsewhere = false }) => {
14003
+ // A GUARD JUDGED AGAIN in this run says nothing new when its judgment is unchanged, and otherwise replaces its
14004
+ // earlier clause of the same kind, so a record is never counted twice and no count stands from an earlier pass.
14005
+ if (again && clampReasons.includes(String(reason ?? "").trim())) return;
14006
+ if (again) replaceClauses(String(defect ?? "").split(":")[0]);
13022
14007
  // — reason and clause are SEPARATE inputs now: the run record and the ops note keep
13023
14008
  // the token sentence; the clause list (which riskStatement renders into the client's Verdict row)
13024
14009
  // receives only the reader's sentence. terminalClampDecision refuses a fused clause by shape.
13025
14010
  const d = terminalClampDecision({ verdict, defect, reason, clause, detail });
13026
14011
  runLog(run.runDir, { event, ...d.record });
13027
14012
  note(d.reason);
13028
- if (!clampReasons.includes(d.reason)) { clampClauses.push(d.clause); clampReasons.push(d.reason); guardClauses.add(d.clause); }
14013
+ // A gap stated in another entry's sentence keeps its reason for the run record, and its clause is that sentence's.
14014
+ const stored = statedElsewhere ? null : d.clause;
14015
+ if (!clampReasons.includes(d.reason)) { clampClauses.push(stored); clampReasons.push(d.reason); guardClauses.add(stored); }
13029
14016
  if (d.clamped) {
13030
14017
  runLog(run.runDir, { event: "terminal-guard-clamp", defect: d.record.defect, from: verdict, to: d.verdict });
13031
14018
  verdict = d.verdict;
@@ -13033,31 +14020,96 @@ async function pipelineInner(job, opts = {}) {
13033
14020
  }
13034
14021
  };
13035
14022
 
13036
- // ── — THE FLOOR DUTY, DISCLOSED AND CLAMPED AT DELIVERY (it BLOCKED until) ───
13037
- //
13038
- // The floors are placement-inquiry's one UNCAPPED obligation — every live in-class identical or
13039
- // near-identical record, listed individually — and the duty has been measured and never enforced.
13040
- // Measured on two delivered runs before it was even derived: 45 of 207 floors on one and 99 of 225
13041
- // on the next never reached the placement form, every one a LIVE filing.
13042
- //
13043
- // ARMED BY THE STAMP, NOT BY THE ARTIFACT'S PRESENCE. Across the 24 run directories on the test box,
13044
- // 19 carry no floor-duty artifact at all — and on this box "absent" means a knockout run far more
13045
- // often than an old one, so absence cannot be read as either "clean" or "old". The stamp is written
13046
- // only by a placement pass that actually ran under the code carrying the order.
13047
- //
13048
- // IT COUNTS THE SEAT'S OWN SHARE. A floor row the band gave no record id is undischarged and
13049
- // UNANSWERABLE — no seat behaviour could have closed it, and the repair it implies can be performed
13050
- // by nobody. Blocking on it would fail a run for a band defect under a rule about seat behaviour, so
13051
- // it is disclosed in the message and excluded from the count that blocks. That is a stated limit,
13052
- // not an oversight: if the unanswerable count is ever the large one, it wants its own issue against
13053
- // the band rather than a wider floor here.
13054
- //
13055
- // NO REPAIR RUNG, deliberately and on a measurement (ruling relayed 2026-08-27). Every
13056
- // registered repair composer belongs to another stage; placement has none, and building one is a
13057
- // larger piece of work than this floor. The blast radius said it could wait: on the day this was
13058
- // written today's run discharged all 60 of its floors, and the only two runs with undischarged rows
13059
- // carried 5 and 9 out of 222 and 352. A rung is a follow-up, never a precondition.
13060
- {
14023
+ // A CLAUSE JUDGED AGAIN REPLACES THE ONE IT REPEATS. A guard judged a second time in this run, after a repeat or a
14024
+ // re-run, withdraws its earlier clause of the same kind before it states the current one, so a record is never
14025
+ // counted twice and a count never stands from an earlier judgment. Returns how many entries it withdrew.
14026
+ const replaceClauses = (token, { event = "guard-clause-replaced" } = {}) => {
14027
+ const w = withdrawByToken(clampReasons, clampClauses, guardClauses, token);
14028
+ if (!w.withdrawn) return 0;
14029
+ clampReasons = w.reasons;
14030
+ clampClauses = w.clauses;
14031
+ runLog(run.runDir, { event, token, withdrawn: w.withdrawn });
14032
+ return w.withdrawn;
14033
+ };
14034
+
14035
+ // A GAP JUDGED AGAIN AND POSITIVELY CLOSED takes its own clause with it, and the result is recomputed from the
14036
+ // accepted assessment and the conditions still standing (ruled 2026-10-19). Only a CONDITIONAL that this run's
14037
+ // guards alone made goes back to CLEAR, and that CLEAR then meets the same deliver-conditional floor any CLEAR meets.
14038
+ // A gap whose account is absent, unreadable or not computable is not closed: its clause stands.
14039
+ const withdrawClosedGap = (token) => {
14040
+ if (!replaceClauses(token, { event: "guard-gap-closed" })) return;
14041
+ const now = verdictWithoutTheClosedGap({ verdict, assessed, reasons: clampReasons });
14042
+ if (now === verdict) return;
14043
+ runLog(run.runDir, { event: "verdict-recomputed-gap-closed", token, from: verdict, to: now });
14044
+ note(`the ${token.split("_")[0]} gap this run named is closed, and nothing else conditions the result — ${verdict} goes back to ${now}, the result the review reached`);
14045
+ verdict = now;
14046
+ restoredClear = now === "CLEAR";
14047
+ writeRunStatus(ctx, signoffPatch(verdict));
14048
+ applyCoverageFloor(); // the floor any CLEAR meets: the opinion's forward actions, the coverage gaps
14049
+ };
14050
+
14051
+ // THE RECALL GATE, DELIVERED AND CLAMPED (the owner's ruling of 2026-10-19: a run is never held). A retrieved record still
14052
+ // ending nowhere a reader can see after the run's recovery ships named, in the approved sentence for the same state,
14053
+ // counted in records out of the records the gate weighed. A record another clause already names (the writer's duty,
14054
+ // the floor) is left to it, so no record is counted twice. Returns the count it clamped on, or 0.
14055
+ // THE RECALL GATE'S GAP AS IT STANDS, judged and not yet clamped, so the writer's duty can be stated with it.
14056
+ const recallGapNow = (duty = null) => {
14057
+ const rec = safeReadJson(P.recallReconciliation);
14058
+ const computable = rec?.computable === true;
14059
+ // A record the writer's duty or the floor's clamp already names is left to that clause: one record, one sentence.
14060
+ // The floor's rows count only where its clamp fires, so a record is never left unnamed by both.
14061
+ const floorNames = floorDutyBlock(safeReadJson(P.floorDuty), { armed: floorDutyArmed(run.runDir) })
14062
+ ? (safeReadJson(P.floorDuty)?.rows ?? []).filter((r) => r?.disposition !== "accounted").map((r) => String(r?.record_id ?? "").toLowerCase())
14063
+ : [];
14064
+ const alreadyNamed = new Set([...(duty?.computable === true ? duty.unaccounted : []).map((u) => String(u?.uri ?? "").toLowerCase()), ...floorNames]);
14065
+ // A RECORD A REFUSED MERGE LEFT OUT IS UNREAD, and it is counted here, in the same sentence, out of a total that
14066
+ // includes it: the band could not take it without passing the size its readers can be dispatched over.
14067
+ const left = refusedRecordsLeftOut(P);
14068
+ // A RECORD NO PLACEMENT PASS READ, and that the report does not address, is counted in the same sentence: the band
14069
+ // gained it after placement's last pass, and the pass that would have assessed it did not complete.
14070
+ const late = lateRecordsUnaddressed(P, alreadyNamed);
14071
+ const unended = (computable ? rec.unended ?? [] : []).filter((u) => !(u.candidate_records ?? [u.record_id])
14072
+ .some((x) => alreadyNamed.has(String(x).toLowerCase())));
14073
+ const weighedIds = new Set(unended.flatMap((u) => u.candidate_records ?? [u.record_id]).map((x) => String(x).toLowerCase()));
14074
+ const records = [...new Set([...weighedIds, ...left, ...late])];
14075
+ // Out of the records the gate weighed or, where it could weigh none, the records the band holds; and the records
14076
+ // left out besides. A late record outside the records the gate weighed is added to the total; where the gate
14077
+ // weighed none, the band's count already holds it.
14078
+ const base = computable ? Number(rec.totals?.candidates ?? 0) : (safeReadJson(P.registerNamedBand)?.enumerated ?? []).length;
14079
+ const weighed = base + left.length + (computable ? late.filter((id) => !weighedIds.has(id)).length : 0);
14080
+ // The same population by id, where the gate kept it: what a sentence stated with the writer's duty counts once.
14081
+ const population = computable
14082
+ ? (Array.isArray(rec.candidate_ids) ? [...rec.candidate_ids, ...left, ...late] : null)
14083
+ : [...(safeReadJson(P.registerNamedBand)?.enumerated ?? []).map((r) => String(r?.record_id ?? "").toLowerCase()), ...left];
14084
+ return { rec, computable, left, late, unended, records, base, weighed, population };
14085
+ };
14086
+ // THE RECALL GATE, DELIVERED AND CLAMPED (the owner's ruling of 2026-10-19: a run is never held). A retrieved record still
14087
+ // ending nowhere a reader can see after the run's recovery ships named, in the approved sentence for the same state,
14088
+ // counted in records out of the records the gate weighed. A record another clause already names (the writer's duty,
14089
+ // the floor) is left to it, so no record is counted twice. Where the writer's duty names records too, the one sentence
14090
+ // is the duty's, stated with these (`joint`). Returns the count it clamped on, or 0.
14091
+ const clampOnTheRecallGate = (duty = null, { again = false, gap = recallGapNow(duty), joint = null } = {}) => {
14092
+ const { rec, computable, left, late, unended, records, base, weighed } = gap;
14093
+ if (!left.length && !late.length && (!computable || !((rec.totals?.unended ?? 0) > 0))) {
14094
+ if (again && gapPositivelyClosed("recall_reconciliation_unended", { recall: rec, leftOut: [...left, ...late] })) withdrawClosedGap("recall_reconciliation_unended");
14095
+ return 0;
14096
+ }
14097
+ // Every record the gate would name is named by a clause standing now: its own earlier clause goes, so the record
14098
+ // is counted once. The gap is not closed, and the result is not recomputed.
14099
+ if (again && !records.length && unended.length < (rec?.unended ?? []).length) replaceClauses("recall_reconciliation_unended", { event: "guard-clause-named-elsewhere" });
14100
+ if (!records.length || !(weighed > 0)) return 0;
14101
+ const sample = [...unended.slice(0, 4).map((u) => `${u.mark_text} (${u.record_id})`), ...left.slice(0, 2), ...late.slice(0, 2)].join("; ");
14102
+ deliverAndClamp({
14103
+ event: "recall-reconciliation-unended-delivered",
14104
+ defect: `recall_reconciliation_unended:${records.length}`,
14105
+ detail: { positions: unended.length, records: records.length, candidates: base, refused: left.length, late: late.length, ...(joint ? { stated_with_duty: joint } : {}) },
14106
+ reason: `recall_reconciliation_unended:${records.length} of ${weighed} record(s) — retrieved and screened in-scope-live on the dominant element but ending nowhere a reader can see (no finding row, no drop row, no counted crowd ruling) after the run's recovery${left.length ? `, ${left.length} of them found by a unit's re-run and left out of a band its readers could not have been dispatched over` : ""}${late.length ? `, ${late.length} of them reaching the band after placement's last pass, whose pass to assess them did not complete` : ""}: ${sample}. The report ships with these named rather than being withheld.${joint ? ` Stated with the writer's duty, once, as ${joint.n} of ${joint.m} record(s).` : ""}`,
14107
+ again,
14108
+ clause: clauseForDefect("recall_reconciliation_unended", records.length, weighed),
14109
+ statedElsewhere: Boolean(joint),
14110
+ });
14111
+ return records.length;
14112
+ };
13061
14113
  // ── — THE DECLINATION DUTY, CHECKED AGAINST THE DELIVERED DOCUMENT ────────────────────────
13062
14114
  //
13063
14115
  // A SECOND NET OVER ONE REACHABLE PATH, and it exists because I argued it could not fire and was
@@ -13070,10 +14122,19 @@ async function pipelineInner(job, opts = {}) {
13070
14122
  // boundary ever saw a call about the records added since, because the only call ordered against them
13071
14123
  // was the one that was refused.
13072
14124
  //
14125
+ // JUDGED AGAIN after the delivery check repeats placement or re-runs the writer, with `again`: a gap that remains
14126
+ // replaces its earlier clause with the current count, and a gap positively closed withdraws it (withdrawClosedGap).
14127
+ //
13073
14128
  // NO ERA STAMP, and it needs none: an archived run carries no declination spec, so `owed` is null,
13074
14129
  // the reconcile is `computable:false` and this never blocks. Archive compatibility by construction.
13075
- {
14130
+ const clampOnTheWritersDuty = ({ again = false } = {}) => {
13076
14131
  const duty = synthesisDutyForRun(run.runDir);
14132
+ // THE TWO GAPS STATED ONCE when both name records: the approved sentence, with each record counted once out of the
14133
+ // records either required to be assessed (ruled 2026-10-19). The run record keeps each gap's own counts.
14134
+ const gap = recallGapNow(duty);
14135
+ const joint = duty?.computable === true && duty.unaccounted.length
14136
+ ? jointUnaccounted({ dutyIds: duty.unaccounted.map((u) => u.uri), owedIds: owedUrisFor(run.runDir), recallIds: gap.records, weighedIds: gap.population })
14137
+ : null;
13077
14138
  if (duty?.computable === true && duty.unaccounted.length) {
13078
14139
  const sample = duty.unaccounted.slice(0, 4).map((u) => `${u.mark ? `${u.mark} ` : ""}(${u.uri})`).join("; ");
13079
14140
  // DELIVERS AND CLAMPS. It used to throw. The defect is real and stays
@@ -13083,18 +14144,48 @@ async function pipelineInner(job, opts = {}) {
13083
14144
  event: "synthesis-duty-undischarged",
13084
14145
  defect: `synthesis_unaccounted_delivered:${duty.unaccounted.length}`,
13085
14146
  detail: { unaccounted: duty.unaccounted.length, owed: duty.totals.owed, uris: duty.unaccounted.slice(0, 6).map((u) => u.uri) },
13086
- reason: `synthesis_unaccounted_delivered:${duty.unaccounted.length} of ${duty.totals.owed} record(s) reached the findings surface and the delivered document accounts for none of them — neither a finding that names them nor a declination with a ground: ${sample}${duty.unaccounted.length > 4 ? " …" : ""}. The report ships with this named rather than being withheld; these records are open points a reader must weigh.`,
14147
+ reason: `synthesis_unaccounted_delivered:${duty.unaccounted.length} of ${duty.totals.owed} record(s) reached the findings surface and the delivered document accounts for none of them — neither a finding that names them nor a declination with a ground: ${sample}${duty.unaccounted.length > 4 ? " …" : ""}. The report ships with this named rather than being withheld; these records are open points a reader must weigh.${joint ? ` Stated with the recall gate's records, once, as ${joint.n} of ${joint.m} record(s).` : ""}`,
14148
+ again,
13087
14149
  // — the READER's sentence: what is open, in a lawyer's nouns. Counts survive;
13088
14150
  // the token, the record ids and the engine's nouns stay in `reason` and the run record.
13089
14151
  // COMPOSED BY THE AUTHORITY, NOT SPELLED HERE. The same sentence has to be reachable from a
13090
14152
  // run that stored no clause, where these two objects are long gone and only the reason's own
13091
14153
  // counts survive; `terminal-clamp.mjs` composes it from the counts either way, so the fresh
13092
14154
  // run and the republished archive cannot drift apart.
13093
- clause: clauseForDefect("synthesis_unaccounted_delivered", duty.unaccounted.length, duty.totals.owed),
14155
+ clause: joint ? clauseForDefect("synthesis_unaccounted_delivered", joint.n, joint.m)
14156
+ : clauseForDefect("synthesis_unaccounted_delivered", duty.unaccounted.length, duty.totals.owed),
13094
14157
  });
13095
- }
13096
- }
13097
-
14158
+ } else if (again && gapPositivelyClosed("synthesis_unaccounted_delivered", { duty })) withdrawClosedGap("synthesis_unaccounted_delivered");
14159
+ const recall = clampOnTheRecallGate(duty, { again, gap, joint }); // the recall gate's gap, beside the writer's duty
14160
+ return { duty, recall };
14161
+ };
14162
+ // ── — THE FLOOR DUTY, DISCLOSED AND CLAMPED AT DELIVERY (it BLOCKED until) ───
14163
+ //
14164
+ // The floors are placement-inquiry's one UNCAPPED obligation — every live in-class identical or
14165
+ // near-identical record, listed individually — and the duty has been measured and never enforced.
14166
+ // Measured on two delivered runs before it was even derived: 45 of 207 floors on one and 99 of 225
14167
+ // on the next never reached the placement form, every one a LIVE filing.
14168
+ //
14169
+ // ARMED BY THE STAMP, NOT BY THE ARTIFACT'S PRESENCE. Across the 24 run directories on the test box,
14170
+ // 19 carry no floor-duty artifact at all — and on this box "absent" means a knockout run far more
14171
+ // often than an old one, so absence cannot be read as either "clean" or "old". The stamp is written
14172
+ // only by a placement pass that actually ran under the code carrying the order.
14173
+ //
14174
+ // IT COUNTS THE SEAT'S OWN SHARE. A floor row the band gave no record id is undischarged and
14175
+ // UNANSWERABLE — no seat behaviour could have closed it, and the repair it implies can be performed
14176
+ // by nobody. Blocking on it would fail a run for a band defect under a rule about seat behaviour, so
14177
+ // it is disclosed in the message and excluded from the count that blocks. That is a stated limit,
14178
+ // not an oversight: if the unanswerable count is ever the large one, it wants its own issue against
14179
+ // the band rather than a wider floor here.
14180
+ //
14181
+ // NO REPAIR RUNG, deliberately and on a measurement (ruling relayed 2026-08-27). Every
14182
+ // registered repair composer belongs to another stage; placement has none, and building one is a
14183
+ // larger piece of work than this floor. The blast radius said it could wait: on the day this was
14184
+ // written today's run discharged all 60 of its floors, and the only two runs with undischarged rows
14185
+ // carried 5 and 9 out of 222 and 352. A rung is a follow-up, never a precondition.
14186
+ // THE FLOOR DUTY'S CLAMP, callable: applied where the terminal checks run, and again to a placement pass the
14187
+ // delivery gate repeats, whose account the report is then rebuilt from. Returns the block it clamped on, or null.
14188
+ const clampOnTheFloorDuty = ({ again = false } = {}) => {
13098
14189
  // THE PREDICATE IS `floorDutyBlock`, called not copied — it lives in floor-duty.mjs so the arms can
13099
14190
  // DRIVE it. Inline here it could only be pinned by source text, and a source-text pin cannot tell
13100
14191
  // an armed check from one somebody disarmed while leaving its words in place.
@@ -13113,6 +14204,7 @@ async function pipelineInner(job, opts = {}) {
13113
14204
  + `${block.named_without_ground ? ` (${block.named_without_ground} named with no ground)` : ""}`
13114
14205
  + `${block.unanswerable ? ` — a further ${block.unanswerable} floor row(s) carry no record id at all and are excluded from this count: the band gave the seat nothing to name, so those are a band defect and not this seat's to answer` : ""}`
13115
14206
  + `. The report ships with these named rather than being withheld.`,
14207
+ again,
13116
14208
  // — the READER's sentence. "Floor row" is an engine noun; what the fact IS for a
13117
14209
  // lawyer: live registrations identical or near-identical to the mark that the report does not
13118
14210
  // individually address. Counts survive; token, ids and engine nouns stay in `reason`.
@@ -13120,7 +14212,14 @@ async function pipelineInner(job, opts = {}) {
13120
14212
  // gives: a republished pre-split run has to reach the same sentence from the counts alone.
13121
14213
  clause: clauseForDefect("floor_duty_undischarged", block.undischarged, block.floors),
13122
14214
  });
13123
- }
14215
+ } else if (again && gapPositivelyClosed("floor_duty_undischarged", { floorClosed: floorDutyClosed(safeReadJson(P.floorDuty), { armed: floorDutyArmed(run.runDir) }) }))
14216
+ withdrawClosedGap("floor_duty_undischarged");
14217
+ return block;
14218
+ };
14219
+ {
14220
+ clampOnTheWritersDuty(); // the writer's duty and, beside it, the recall gate (defined above the floor's block)
14221
+
14222
+ clampOnTheFloorDuty();
13124
14223
  }
13125
14224
 
13126
14225
  // PR-3 (report voice) — the factual open-state clause per reason, INDEX-ALIGNED with clampReasons.
@@ -13350,7 +14449,7 @@ async function pipelineInner(job, opts = {}) {
13350
14449
  // MANDATORY now (was best-effort): the sidecar IS the single authority; a failed write is a run
13351
14450
  // failure, never a silent fallback to divergent labels. Re-invoked after findings consolidation
13352
14451
  // (the delivery block) so the sidecar always reflects the FINAL findings set.
13353
- const writeVerdictSidecar = () => {
14452
+ const composeVerdictSidecar = () => {
13354
14453
  let findingsArr = [], sv = 1, actionsArr = null, actionsDropped = [];
13355
14454
  try {
13356
14455
  const doc = parseFindingsJsonLenient(readFileSync(P.findings, "utf8"));
@@ -13447,10 +14546,16 @@ async function pipelineInner(job, opts = {}) {
13447
14546
  // consumer keys on (never the statement's wording — the magic-string coupling is retired).
13448
14547
  const statement = riskStatement({ tier: derived.tier, verdict, reasons: reasonsOut, clauses: clausesOut,
13449
14548
  basis: isRegisterOnly(ctx.searchPolicy) ? "register-only" : null });
13450
- const tmp = driverDir(run.runDir, "verdict.json.tmp");
13451
- writeFileSync(tmp, JSON.stringify({ ts: new Date().toISOString(), verdict, reasons: reasonsOut, clauses: clausesOut, kinds: kindsOut,
14549
+ return { derived, statement, doc: { ts: new Date().toISOString(), verdict, reasons: reasonsOut, clauses: clausesOut, kinds: kindsOut,
13452
14550
  tier: derived.tier, badge: derived.badge, gaugeIndex: derived.gaugeIndex, maxComposite: derived.maxComposite,
13453
- band: derived.band ?? null, statement, stance: verdictStance(verdict) }, null, 2));
14551
+ band: derived.band ?? null, statement, stance: verdictStance(verdict) } };
14552
+ };
14553
+ // The label, composed above and written here. Composing is apart so the delivery check can show the stages it re-runs
14554
+ // the label it could not yet save.
14555
+ const writeVerdictSidecar = () => {
14556
+ const { derived, statement, doc } = composeVerdictSidecar();
14557
+ const tmp = driverDir(run.runDir, "verdict.json.tmp");
14558
+ writeFileSync(tmp, JSON.stringify(doc, null, 2));
13454
14559
  renameSync(tmp, driverDir(run.runDir, "verdict.json"));
13455
14560
  // — THE STATUS RECORD CARRIES THE BAND AND THE SENTENCE, so a reader of the run does not
13456
14561
  // have to reach for `verdict` to say something about the outcome. `verdict` is the gate's decision
@@ -13460,6 +14565,26 @@ async function pipelineInner(job, opts = {}) {
13460
14565
  writeRunStatus(ctx, { tier: derived.tier ?? null, statement: statement ?? null });
13461
14566
  return derived;
13462
14567
  };
14568
+ // THE LABEL, SAVED AGAIN IN THE DELIVERY CHECK, WITHOUT ENDING THE RUN (2026-10-19). After a clamp there the label
14569
+ // has to reach every surface the report is published from, and they all read it from the one file. A save that
14570
+ // fails is tried again. One that still fails is owed: the stages the check re-runs are shown the label as composed,
14571
+ // and the save is made once more before anything is published, so no surface is built from the label settled
14572
+ // before the clamp. Returns whether it is saved.
14573
+ let labelOwed = null;
14574
+ let rankingBeforeRepeat = null; // where each record stood before the delivery check repeated placement
14575
+ const saveTheLabel = (why) => {
14576
+ const saved = saveWithRetries(() => writeVerdictSidecar());
14577
+ if (saved.ok) {
14578
+ if (saved.attempts > 1) runLog(run.runDir, { event: "verdict-write-retried", why, attempts: saved.attempts });
14579
+ try { displayVerdict = JSON.parse(readFileSync(driverDir(run.runDir, "verdict.json"), "utf8")); } catch { /* the label is re-read at reassembly */ }
14580
+ return true;
14581
+ }
14582
+ labelOwed = labelOwed ?? why;
14583
+ runLog(run.runDir, { event: "verdict-write-deferred", why, attempts: saved.attempts, fail: String(saved.error?.message ?? saved.error).slice(0, 160) });
14584
+ note(`delivery: the label could not be saved ${why} (${String(saved.error?.message ?? saved.error).slice(0, 100)}) — it is saved again before the report is published`);
14585
+ try { displayVerdict = composeVerdictSidecar().doc; } catch { /* composed again when it is saved */ }
14586
+ return false;
14587
+ };
13463
14588
  try { writeVerdictSidecar(); }
13464
14589
  catch (e) { throw new StageFailure("verdict", `verdict sidecar write failed (the single label authority): ${String(e.message).slice(0, 120)}`); }
13465
14590
 
@@ -13524,6 +14649,7 @@ async function pipelineInner(job, opts = {}) {
13524
14649
  reviewLedger.record("reviewer-degenerate-reask", run.codename, rr.ok ? "ok" : `failed: ${rr.fail}`, { max: degenerateReaskMax });
13525
14650
  if (rr.ok) {
13526
14651
  verdict = parseVerdict(readReview()) ?? "BLOCKING";
14652
+ assessed = verdict; // the re-asked review is the assessment now
13527
14653
  runLog(run.runDir, { event: "verdict-3", verdict, trigger: "degenerate-reask" });
13528
14654
  writeRunStatus(ctx, signoffPatch(verdict));
13529
14655
  applyCoverageFloor(); // a re-asked CLEAR passes the same deliver-conditional floor
@@ -13632,12 +14758,19 @@ async function pipelineInner(job, opts = {}) {
13632
14758
  catch (e2) { throw new StageFailure("verdict", `verdict sidecar refresh failed after consolidation: ${String(e2.message).slice(0, 120)}`); }
13633
14759
  }
13634
14760
  };
14761
+ // THE RECORD THE CARDS ARE BUILT ON CARRIES EVERY DEMOTION. The derivation settled the record after the first
14762
+ // synthesis save; a corrective pass saves again before this seam, and a whole re-send brings back every stamp it had
14763
+ // demoted. Built on those, every card whose finding carried one read as stale after the derivation before delivery
14764
+ // demoted them again, and was rebuilt with the overview for a finding nothing had changed. Settled here, once more,
14765
+ // before the overview and the cards; idempotent on a record that already carries every demotion.
14766
+ settleDerivedBases(ctx, "seam");
13635
14767
  await seamWrites();
13636
14768
  // Every write above is idempotent: each injection skips a row the record already carries, the stamp
13637
14769
  // replaces its rows, and the fold leaves a folded record as it is.
13638
14770
  ctx.afterSynthesisSave = async () => {
13639
14771
  note(`[seam] a synthesis save after the seam rewrote findings.json — re-applying the seam's writes`);
13640
14772
  runLog(run.runDir, { event: "seam-reapplied", after: "synthesis-save" });
14773
+ rewriteFromBeforeTheSeam(ctx); // the basis derivation and the deadline enrichment, as the pass made them
13641
14774
  await seamWrites();
13642
14775
  };
13643
14776
  // wp50/wi2 — the overview PROSE must speak the derived tier (VENZY: caption said "High risk" while
@@ -13703,13 +14836,11 @@ async function pipelineInner(job, opts = {}) {
13703
14836
  // plausible zero, and a zero here means every floor row quietly loses the full card the owner made
13704
14837
  // non-negotiable. An absent band-shape yields an empty map and every finding reads as holding no
13705
14838
  // floor — which is what it did before this existed.
13706
- const floorTiers = floorTierByMark(safeReadJson(P.bandShape));
13707
- const formOf = (f) => writeUpForm(ctx.depth, f, { floorTier: floorTiers.get(floorMarkKey(f?.mark)) ?? null });
13708
- const byOrdinal = new Map((findingsForCards ?? []).filter((f) => f?.ordinal != null).map((f) => [f.ordinal, f]));
13709
- const proseOrdinals = fullProseOrdinals(findingsForCards);
13710
- const cardOrdinals = proseOrdinals.filter((ord) => formOf(byOrdinal.get(ord)) === "full");
13711
- // The remainder, in the same order, for the short-entry and grouped-row render below.
13712
- const entryOrdinals = proseOrdinals.filter((ord) => formOf(byOrdinal.get(ord)) !== "full");
14839
+ // The plan is made in one place (planWriteUps), the same way here and wherever a later save changes the record:
14840
+ // the full cards in the record's order, then the remainder, in the same order, for the short-entry and grouped-row
14841
+ // render below.
14842
+ const writeUps = planWriteUps(findingsForCards, ctx.depth, safeReadJson(P.bandShape));
14843
+ const { cardOrdinals, entryOrdinals } = writeUps;
13713
14844
  // §9 — WRITTEN ON EVERY GRADED PRODUCT, INCLUDING ONE WHOSE ENTRY COUNT IS 0. The old gate was
13714
14845
  // `entryOrdinals.length`, which conflated two different empties: one country is empty BY CONSTRUCTION
13715
14846
  // (every form is `full`, so the row would report the product's own definition back), while a graded run
@@ -13780,17 +14911,11 @@ async function pipelineInner(job, opts = {}) {
13780
14911
  // The bodies are written to the SAME card files a dispatched card would use, so `assembleReportMd`
13781
14912
  // frames them with the same composer and the ordinal binding is untouched. What they never get is a
13782
14913
  // model turn — the whole saving.
13783
- const entryFindings = entryOrdinals.map((ord) => byOrdinal.get(ord)).filter(Boolean);
13784
- const byRight = String(ctx.depth?.narrativeProse ?? "") === "adversarial";
13785
- const { groups: entryGroups, singles: entrySingles } = groupEntries(entryFindings, { byRight });
13786
- for (const f of entrySingles) {
13787
- try { writeFileSync(P.reportCard(String(f.ordinal)), `${shortEntryBody(f)}\n`); }
13788
- catch (e) { note(`short entry ${f.ordinal} not written (${String(e?.message ?? e).slice(0, 80)}) — it renders structured-only, as an absent card always has`); }
13789
- }
14914
+ const { entryFindings, byRight, entrySingles } = writeUps;
14915
+ writeShortEntries(P, entrySingles);
13790
14916
  // Assembly sees full cards AND short entries; the DISPATCH set and the freshness stage list stay
13791
14917
  // `cardOrdinals`, because a short entry has no stage and never had one.
13792
- const assembledOrdinals = [...cardOrdinals, ...entrySingles.map((f) => f.ordinal)];
13793
- const groupedMembers = entryGroups.flatMap((g) => g.findings);
14918
+ const { assembledOrdinals, groupedMembers } = writeUps;
13794
14919
  if (entryFindings.length)
13795
14920
  note(`write-up forms: ${cardOrdinals.length} full card(s), ${entrySingles.length} short entr(y/ies), ${groupedMembers.length} grouped line(s)`);
13796
14921
  assembleReportMd(P, findingsForCards, assembledOrdinals, { grouped: groupedMembers, byRight });
@@ -13912,6 +15037,9 @@ async function pipelineInner(job, opts = {}) {
13912
15037
  // A2 — full audit: DETERMINISTIC CODE (parse the spine tables → audit.md, count-guarded). Replaces the LLM
13913
15038
  // audit-emit (output varied 47↔69 on identical input). NON-FATAL: if it fails, the report still ships (publish
13914
15039
  // renders the report with no xlsx when audit.md is absent), and the audit self-recovers on a re-run.
15040
+ // `rebuildTheDeliveredRecord` is set by the block once the audit is written: the delivery check calls it after a repair.
15041
+ let rebuildTheDeliveredRecord = null;
15042
+ let auditWrittenThisPass = false, earlierPairKept = null;
13915
15043
  try {
13916
15044
  // One resolution per claim: hand the builder the run's OWN dispositions so a withdrawn conflict reads
13917
15045
  // as withdrawn on the audit surface too (it already does on report + client summary). Best-effort —
@@ -13932,6 +15060,8 @@ async function pipelineInner(job, opts = {}) {
13932
15060
  // mint below, the predelivery lint's `recordCarry` arms) reads THIS artifact rather than deriving
13933
15061
  // its own: two derivations from different inputs is what let the doubt ledger be roughly right
13934
15062
  // while the artifact and every lint arm were inverted.
15063
+ // A resumed pass writes both again: what the earlier pass wrote is kept under _history first.
15064
+ earlierPairKept = keepDeliveredRecordAsHistory(P, "earlier-pass");
13935
15065
  deriveRecordCarry(ctx, "publish", { findings: auditRunFindings ?? [] });
13936
15066
  // ── — AND NOW THE JOIN BETWEEN THE TWO ARTIFACTS THAT WERE EACH RIGHT ────────
13937
15067
  //
@@ -14062,6 +15192,7 @@ async function pipelineInner(job, opts = {}) {
14062
15192
  supplementalPlans,
14063
15193
  xcheck: readJson(driverDir(run.runDir, "register-xcheck.json")),
14064
15194
  recall: recallReceiptForOwnCompany(readJson(driverDir(run.runDir, "register-recall.json"))), // never another company's recall line
15195
+ withheldFamilies: readWithheldFamilies(run.runDir), // the reading turn's reasons, for a frame ask its judgment settled
14065
15196
  planExecution: ctx.planExecution ?? readJson(P.planExecution),
14066
15197
  }, { ts: askTs });
14067
15198
  } catch (e) {
@@ -14163,12 +15294,13 @@ async function pipelineInner(job, opts = {}) {
14163
15294
  // a record of what the EXECUTOR did, and nothing after the reopen can retroactively make an
14164
15295
  // undispatched term dispatched, so the receipt is the authority and re-deriving would need band
14165
15296
  // blocks the audit seam has no reason to hold. A `searched-empty` term mints nothing — it is
14166
- // answered. A run with no reopen, or a legacy receipt with no term rows, mints nothing.
15297
+ // answered — and nor does one the reading turn's recorded judgment settled (SETTLED_CLASSES). A run
15298
+ // with no reopen, or a legacy receipt with no term rows, mints nothing.
14167
15299
  try {
14168
15300
  const fr = JSON.parse(readFileSync(driverDir(run.runDir, "frame-reopen.json"), "utf8"));
14169
15301
  if (Array.isArray(fr?.remedy_terms)) {
14170
15302
  doubts.push(...mintRemedyAccountingDoubts(
14171
- { not_accounted: fr.remedy_terms.filter((r) => !["found", "searched-empty"].includes(r?.class)) },
15303
+ { not_accounted: fr.remedy_terms.filter((r) => !SETTLED_CLASSES.includes(r?.class)) },
14172
15304
  { sourceName: "_driver/frame-reopen.json" },
14173
15305
  ));
14174
15306
  }
@@ -14223,7 +15355,7 @@ async function pipelineInner(job, opts = {}) {
14223
15355
  ? openAfterStitch.filter((d) => doubtSel.ids.includes(d.id))
14224
15356
  : openAfterStitch;
14225
15357
  ctx.openDoubts = dispatchDoubts.map((d) => ({ id: d.id, birth: d.birth, subject: d.subject }));
14226
- ctx.openAsks = openAsksForStage.map((a) => ({ ask_id: a.ask_id, born: a.born, ask: a.ask }));
15358
+ ctx.openAsks = openAsksForStage.map(openAskForClosure);
14227
15359
  // THE SPEC SIDECAR, WRITTEN BEFORE THE DISPATCH. The seat supplies none of its own context:
14228
15360
  // the ids it may speak about and the files it may cite are the driver's, so a doubt it was not
14229
15361
  // given is refused by name and a file it was not given cannot be expressed at all. Both
@@ -14326,16 +15458,7 @@ async function pipelineInner(job, opts = {}) {
14326
15458
  // _driver/reading-log.jsonl). Passed on any run that derived a band shape — an EMPTY array renders
14327
15459
  // the honest zero ("no lookups recorded"), because a band-consuming stage that read nothing is a
14328
15460
  // fact the audit must show, not hide. Legacy runs (no shape) pass null → no section, byte-identical.
14329
- let readingLog = null;
14330
- if (existsSync(P.bandShape)) {
14331
- readingLog = [];
14332
- try {
14333
- for (const line of readFileSync(P.readingLog, "utf8").split("\n")) {
14334
- if (!line.trim()) continue;
14335
- try { readingLog.push(JSON.parse(line)); } catch { /* a torn tail line never kills the audit */ }
14336
- }
14337
- } catch { /* no lookups recorded — the empty array IS the disclosure */ }
14338
- }
15461
+ const readingLog = readReadingLog(P);
14339
15462
  // item 33 — hand the builder the machine grid so the common-law search log is derived from the
14340
15463
  // tool-written per-term cells rather than from a heading the tagger has to match. Absent (pre-grid
14341
15464
  // archive, register-only run) it falls back to the prose tables, which is honest: those runs have
@@ -14377,13 +15500,25 @@ async function pipelineInner(job, opts = {}) {
14377
15500
  runLog(run.runDir, { event: "register-presence-skipped", reason: "no register-named-band.json" });
14378
15501
  }
14379
15502
  const a = buildAuditMd(registerFindingsText, commonLawText,
14380
- { findings: auditRunFindings, doubts: runDoubts, asks: runAsks, readingLog, commonLawGrid, doubtTruncations, registerPresence });
15503
+ { findings: auditRunFindings, doubts: runDoubts, asks: runAsks, readingLog, commonLawGrid, doubtTruncations, registerPresence,
15504
+ placementRows: placementAuditRows(P) });
14381
15505
  if (!a.counts.findings) throw new Error("0 findings parsed from the spine tables"); // count-guard
14382
15506
  writeFileSync(P.audit, a.md);
15507
+ auditWrittenThisPass = true;
14383
15508
  runLog(run.runDir, { event: "audit", ...a.counts }); // — counts now carry doubtsOmitted
15509
+ // The trace and the Audit Trail, written again over a repair (`rebuildDeliveredRecord`), from the inputs this
15510
+ // pass built them from; the doubts minted from the trace follow the trace written again.
15511
+ rebuildTheDeliveredRecord = (trigger) => rebuildDeliveredRecord(ctx,
15512
+ { commonLawText, doubts: runDoubts, asks: runAsks, commonLawGrid, doubtTruncations, registerPresence }, trigger);
14384
15513
  note(`audit (code): ${a.counts.findings} findings, ${a.counts.negatives} negatives, ${a.counts.audit} audit-trail${a.counts.withdrawnStamped ? `, ${a.counts.withdrawnStamped} withdrawn-stamped` : ""}${a.counts.resolutionStamped ? `, ${a.counts.resolutionStamped} resolution-stamped` : ""}${runDoubts ? `, doubts ${a.counts.doubtsSettled} settled / ${a.counts.doubtsOpen} open` : ""}${a.counts.presenceRows != null ? `, register presence ${a.counts.presenceRows} row(s)` : ""}`);
14385
15514
  } catch (e) {
14386
15515
  note(`audit build failed (NON-FATAL — report still ships, audit will regenerate on re-run): ${e.message}`);
15516
+ // A findings record with no finding row trips the count guard, and on a clean run it can: that is recorded apart,
15517
+ // as a note on the run's content, and only a build that broke is recorded as a failure.
15518
+ const noFindings = /^0 findings parsed from the spine tables/.test(String(e?.message ?? ""));
15519
+ runLog(run.runDir, { event: noFindings ? "audit-not-built-no-findings" : "audit-build-failed", reason: String(e?.message ?? e).slice(0, 160) });
15520
+ // An audit an earlier pass wrote describes that pass, not this one: the report ships without it, kept under _history.
15521
+ if (!auditWrittenThisPass) withdrawSupersededAudit(P, "audit-build-failed", { keep: !earlierPairKept });
14387
15522
  }
14388
15523
 
14389
15524
  // ── Spec A1 + A2: pre-delivery verification — pure code, before ANYTHING outward-facing. Per the
@@ -14667,6 +15802,9 @@ async function pipelineInner(job, opts = {}) {
14667
15802
  // (composeEmailHtml opts.verdict, from _driver/verdict.json), the pre-delivery lint below, and
14668
15803
  // the client gate. On the R2 evidence run this ladder was where the cost landed: three
14669
15804
  // byte-identical failures before it exhausted.
15805
+ // The writer's accepted record as it stood just before the delivery-check repair, kept only when that
15806
+ // repair runs: the review after the repair is handed what changed between the two (repair-scope.mjs).
15807
+ let repairBaseKept = false;
14670
15808
  if (lint.failures.length) {
14671
15809
  runLog(run.runDir, { event: "predelivery-lint-failed", failures: lint.failures.map((f) => f.id) });
14672
15810
  const bySurface = (s) => lint.failures.filter((f) => f.surface === s || (s === "report" && f.surface === "all"));
@@ -14684,13 +15822,13 @@ async function pipelineInner(job, opts = {}) {
14684
15822
  // `Write` and `Edit` out of its grant. Same class as the ten anchor sites and the max-tokens
14685
15823
  // corrective before it: a retirement reaches the first-attempt surfaces and misses a correction
14686
15824
  // surface. Keyed on the ARTIFACT rather than on the label, so conversions 5 and 6 inherit it.
14687
- const redo = async (label, failures, key, model, thinking, file) => {
15825
+ const redo = async (label, failures, key, model, thinking, file, more = {}) => {
14688
15826
  if (!failures.length || !key) return;
14689
15827
  note(`pre-delivery lint: ${failures.length} failed check(s) on ${label} — one warm named-correction redo`);
14690
15828
  const toolWritten = file ? toolWrittenArtifact(file) : null;
14691
15829
  const r = await stage(label, ctx, {
14692
15830
  force: true, sessionKey: key, model, thinking, trigger: "lint-repair",
14693
- followup: repairFollowup("*:lint-repair", { label, file, toolWritten, failures })
15831
+ followup: repairFollowup("*:lint-repair", { label, file, toolWritten, failures, ...more })
14694
15832
  + stageCharter(label, ctx.depth, ctx.framework),
14695
15833
  });
14696
15834
  if (r.ok) repaired.push(label);
@@ -14705,7 +15843,14 @@ async function pipelineInner(job, opts = {}) {
14705
15843
  // finding-provenance bleed) re-renders THAT card alone, FRESH (isolated from its own record, so the bleed
14706
15844
  // cannot recur); a shell failure (no ordinal) re-runs report-overview. Then re-assemble. Bounded: one pass.
14707
15845
  let reassembled = false;
14708
- const badOrdinals = [...new Set(reportFixable.map((f) => f.ordinal).filter((o) => o != null))];
15846
+ // Each fault goes to the part that holds it (repair-targets.mjs): a card's to that card, with the detail its
15847
+ // check gives on that card alone; the overview's to the overview; and a fault only the lines composed from the
15848
+ // record hold to the writer's repair below, since the record is the writer's. One no single part holds keeps the
15849
+ // overview's repair.
15850
+ const targets = locateReportFaults(reportFixable, reportPartsOf(P, findingsForCards, writeUps));
15851
+ if (reportFixable.length) runLog(run.runDir, { event: "report-repair-targets", cards: [...targets.cards.keys()],
15852
+ overview: targets.overview.map((f) => f.id), stamped: targets.stamped.map((f) => f.id), unlocated: targets.unlocated.map((f) => f.id) });
15853
+ const badOrdinals = [...targets.cards.keys()];
14709
15854
  for (const ord of badOrdinals) {
14710
15855
  const f = findingsForCards.find((x) => x.ordinal === ord);
14711
15856
  if (!f) continue;
@@ -14718,7 +15863,7 @@ async function pipelineInner(job, opts = {}) {
14718
15863
  // isolation from the contaminated rendering is the entire point — patching the bad card would
14719
15864
  // preserve exactly what the re-render exists to discard. The redo() ladder below is the opposite
14720
15865
  // case (a warm resume of a session that owns its file) and patches.
14721
- const why = reportFixable.filter((x) => x.ordinal === ord).map((x) => x.detail).filter(Boolean);
15866
+ const why = targets.cards.get(ord).map((x) => x.detail).filter(Boolean);
14722
15867
  const rc = await stage("report-card", { ...ctx, axis: String(ord), finding: f }, {
14723
15868
  force: true, trigger: "lint-repair",
14724
15869
  ...(why.length ? { extra: lines(
@@ -14729,14 +15874,26 @@ async function pipelineInner(job, opts = {}) {
14729
15874
  });
14730
15875
  if (rc.ok) { repaired.push(`report-card:${ord}`); reassembled = true; }
14731
15876
  }
14732
- const shellFixable = reportFixable.filter((f) => f.ordinal == null);
15877
+ const shellFixable = targets.overview;
14733
15878
  if (shellFixable.length) { await redo("report-overview", shellFixable, rov.sessionKey, rov.model, rov.thinking, P.reportOverview); if (repaired.includes("report-overview")) reassembled = true; }
14734
15879
  // — THE NARRATIVE REDO ROUTE. The same mechanism report-overview already has, re-aimed at
14735
15880
  // the seat that wrote narrative.md. Without it a depth check could only FLAG, and a flag on a
14736
15881
  // narrative that broke the rules is the run shipping the defect with a note attached. ONE warm
14737
15882
  // pass, named violations; a second overrun ships and the lint row is the record.
14738
- const narrativeFixable = modelFixable(bySurface("narrative"));
14739
- if (narrativeFixable.length) await redo("synthesis", narrativeFixable, synthesisKey, synthesisModel, synthesisThinking, P.narrative);
15883
+ //
15884
+ // A FAULT IN A LINE COMPOSED FROM THE RECORD (a finding's own sentence, a short entry, a grouped line) is the
15885
+ // writer's too: no card or overview repair can change what the renderer stamps from the record. It joins the
15886
+ // narrative's faults in the writer's one repair, each named by the finding that owns it, in the numbering the
15887
+ // writer's save takes. Its consumers then rebuild, and its change is reviewed, as for any repair of the record.
15888
+ const fold = readFold(run.runDir);
15889
+ const sentenceFixable = targets.stamped.flatMap((f) => (f.parts ?? []).map((x) => ({ ...f, finding: keptModelOrdinal(fold, x.ordinal), detail: x.detail })));
15890
+ const narrativeFixable = [...modelFixable(bySurface("narrative")), ...sentenceFixable];
15891
+ if (narrativeFixable.length && synthesisKey) repairBaseKept = snapshotRepairBase(run.runDir);
15892
+ // A writer fault with no writer session to repair it is not repaired, and the run says so.
15893
+ if (narrativeFixable.length && !synthesisKey)
15894
+ runLog(run.runDir, { event: "lint-redo-skipped", surface: "writer", why: "no-writer-session", failures: narrativeFixable.map((f) => f.id) });
15895
+ if (narrativeFixable.length) await redo("synthesis", narrativeFixable, synthesisKey, synthesisModel, synthesisThinking, P.narrative,
15896
+ { findings: modelRecordPath(run.runDir, P.findings), narrative: P.narrative });
14740
15897
  if (reassembled) assembleReportMd(P, findingsForCards, assembledOrdinals, { grouped: groupedMembers, byRight });
14741
15898
  // The client-summary redo arm is retired with the stage (2026-08-01). Its `email`-surface
14742
15899
  // failures have not gone unowned: the `:email` reference check now runs over the report-derived
@@ -14763,16 +15920,35 @@ async function pipelineInner(job, opts = {}) {
14763
15920
  // lint-repair-tax metric: recording the verdict here means no consumer can read the failure list of
14764
15921
  // a stale-blocked run and mistake it for a delivery-quality signal. The receipt itself stays
14765
15922
  // ALWAYS-WRITTEN (spec A2 auditor evidence) — blocking a run must not cost us the evidence of why.
14766
- const deliveryPathStages = [...new Set([
15923
+ // The cards on the path are the ones the record owes as it stands, read again each time the path is judged: a
15924
+ // synthesis save after the cards can add a finding that is owed one, or withdraw one whose card is owed no longer.
15925
+ const deliveryPathStages = () => [...new Set([
14767
15926
  ...(ctx.skippedStages ?? []), "register-digest", "synthesis", "narrative-refutation",
14768
- "report-overview", ...cardOrdinals.map((o) => `report-card:${o}`),
15927
+ "report-overview", ...owedCardOrdinals(P, ctx.depth).map((o) => `report-card:${o}`),
14769
15928
  ])];
14770
15929
  const deliveryInputsFor = (label) => {
14771
15930
  const bare = label.replace(/:.*$/, "");
14772
15931
  const axis = label.includes(":") ? label.slice(label.indexOf(":") + 1) : null;
14773
15932
  try { return stageInputs(bare, P, { axes: ctx.axes, axis, registerOnly: ctx.registerOnly }); } catch { return []; }
14774
15933
  };
14775
- staleStages = staleOnPath(run.runDir, deliveryPathStages, deliveryInputsFor, { project: projectStageInput });
15934
+ staleStages = [...staleOnPath(run.runDir, deliveryPathStages(), deliveryInputsFor, { project: projectStageInput }),
15935
+ ...cardsNeverBuilt(P, ctx.depth, cardOrdinals)]; // a finding a later save added is owed its one card call
15936
+ // A DELIVERY-CHECK REPAIR THAT CHANGED ONLY THE FINDINGS RECORD leaves the narrative's bytes, and so the
15937
+ // reviewer, fresh — the reviewer does not declare findings.json. The change is still content no review has
15938
+ // read, so the reviewer is queued for the review after a repair, marked as the record's change.
15939
+ if (repairBaseKept && !staleStages.some((s2) => s2.label === "narrative-refutation")) {
15940
+ if (recordChangeOwesReview(run.runDir)) {
15941
+ // At the FRONT, so the dependency order below puts it after the writer and ahead of the overview and the
15942
+ // cards, which then rebuild from whatever its correction changes.
15943
+ staleStages.unshift({ label: "narrative-refutation", changed: [{ name: basename(P.findings), via: "repair-record" }] });
15944
+ runLog(run.runDir, { event: "post-repair-review-queued", why: "the repair changed the findings record only" });
15945
+ }
15946
+ }
15947
+ // A RESUME AFTER SUCH A CHANGE WENT UNREVIEWED: its record queues the reviewer again, so this pass reviews it.
15948
+ if (unreadRecordReview(run.runDir) && !staleStages.some((s2) => s2.label === "narrative-refutation")) {
15949
+ staleStages.unshift({ label: "narrative-refutation", changed: [{ name: basename(P.findings), via: "repair-record" }] });
15950
+ runLog(run.runDir, { event: "post-repair-review-queued", why: "a change to the findings record went unreviewed" });
15951
+ }
14776
15952
  // ── AD-2 A1 (E2E-R2, ordering) — repair-owned staleness is re-done IN-PASS, never parked. The
14777
15953
  // pre-delivery repair can move an input under the tail this pass already built (the observed
14778
15954
  // case: the report-overview lint redo rewrote findings.json, staling every report card + the
@@ -14816,20 +15992,232 @@ async function pipelineInner(job, opts = {}) {
14816
15992
  if (existsSync(P.caseLaw)) caseLawNow = joinCaseLawProfiles(parseCaseLawProfiles(readFileSync(P.caseLaw, "utf8")), findingsNow);
14817
15993
  } catch { /* no case-law layer */ }
14818
15994
  let reassemble = false;
15995
+ // THE LATE VERDICT, ONE RULE FOR BOTH REVIEWS THIS LOOP CAN RUN — the whole review and the review after
15996
+ // a delivery-check repair. A late review may only harden the run's verdict, and a softer one is logged,
15997
+ // never adopted. Returns what it found, so the review after a repair can keep the review whose verdict
15998
+ // the run carries in senior-eye-review.md.
15999
+ const adoptLateReview = (reviewNow, stageLabel) => {
16000
+ const { hardened, softened: reviewSays } = lateReviewAgainst(verdict, reviewNow);
16001
+ // A late review harsher than the assessment is a ground of its own: a gap closed later never lifts it.
16002
+ const says = parseVerdict(reviewNow);
16003
+ if ((VERDICT_RANK[says] ?? -1) > (VERDICT_RANK[assessed] ?? -1)) assessed = says;
16004
+ // A REVIEW THAT SOFTENED IS NOT ADOPTED, AND THE RUN SAYS SO. The ratchet below only
16005
+ // tightens, so a re-review that comes back softer leaves the settled verdict in force while
16006
+ // senior-eye-review.md, rewritten by this repair, opens with the softer word. That is the
16007
+ // design; recording nothing was the defect: a reader of the run found two answers and no
16008
+ // line saying which one governs (measured in testing, 2026-09-23: BLOCKING recorded, CONDITIONAL on disk).
16009
+ if (reviewSays) {
16010
+ runLog(run.runDir, { event: "verdict-softening-not-adopted", was: verdict, reviewSays, stage: stageLabel });
16011
+ note(`stale-repair: the reviewer now returns ${reviewSays}; the run keeps ${verdict}, because a late re-review may only harden the verdict — senior-eye-review.md now disagrees with verdict.json, and verdict.json governs`);
16012
+ }
16013
+ if (hardened) {
16014
+ // ── T3a — THE LATE VERDICT IS ADOPTED, NOT THROWN, AND ADOPTION IS THE WHOLE JOB ──
16015
+ //
16016
+ // built the throw here because a late hardening was an input the gate had been
16017
+ // DENIED: the verdict settles thousands of lines earlier, nothing re-reads the review,
16018
+ // so a reviewer refusing to sign DURING this repair "is written to disk and never
16019
+ // heard". Measured on `bf21580e` — the narrative printed a registration date of
16020
+ // 1 March 2011 against a record reading 25 February 2011, the reviewer said so, and
16021
+ // the run delivered anyway.
16022
+ //
16023
+ // DELETING THE THROW WITHOUT A DESTINATION RE-OPENS EXACTLY THAT HOLE, and that is
16024
+ // why this branch is not the same edit as the gate's. The owner's ruling removes the
16025
+ // REFUSAL; it does not say the reviewer stops being heard. So the late verdict is
16026
+ // adopted as the run's own and everything derived from it is rebuilt:
16027
+ //
16028
+ // · `verdict` and status.json — the run's own record
16029
+ // · verdict.json — "the single label authority" (its words)
16030
+ // · `reassemble` — assembleReportMd re-runs, so the open-points
16031
+ // section is built from THIS review, and
16032
+ // applyVerdictFrontMatter re-reads the sidecar
16033
+ // and re-stamps overall_label / overall_badge
16034
+ //
16035
+ // Without the sidecar write the report would ship BADGED WITH THE VERDICT SETTLED
16036
+ // BEFORE THE REVIEWER HARDENED — refused by the reviewer, labelled CONDITIONAL. That
16037
+ // is worse than either behaviour this replaces, and it is `bf21580e`'s shape one
16038
+ // layer up: the reviewer's word on disk and the client's document disagreeing.
16039
+ //
16040
+ // `verdictHardenedTo` still ratchets ONE WAY, so a review that SOFTENED during the
16041
+ // repair still cannot lift a clamp. This branch only ever tightens.
16042
+ runLog(run.runDir, { event: "verdict-hardened-by-repair", was: verdict, now: hardened, stage: stageLabel, adopted: true });
16043
+ note(`stale-repair: the reviewer returned ${hardened} — adopting it as the run's verdict (the one settled earlier was decided against a review this repair has since rewritten) and rebuilding the label from it`);
16044
+ verdict = hardened;
16045
+ writeRunStatus(ctx, signoffPatch(verdict));
16046
+ try { writeVerdictSidecar(); }
16047
+ catch (e) { throw new StageFailure("verdict", `verdict sidecar write failed (the single label authority): ${String(e.message).slice(0, 120)}`); }
16048
+ reassemble = true;
16049
+ }
16050
+ return { hardened, softened: reviewSays };
16051
+ };
16052
+ const processedLabels = new Set(); // the labels this loop has re-done, for the re-queue after a correction
16053
+ const requeuedLabels = new Set();
16054
+ const repairFailed = new Map(); // label → why its re-run in this loop did not complete
16055
+ // A CHANGED DECISION IS READ AGAIN IN THIS PASS. A repeated placement pass whose decisions moved leaves the reading
16056
+ // step, the writer and the review stale, and each of them leaves what reads it stale in turn. They are queued here,
16057
+ // in dependency order after the stage that moved them, each re-done at most once more; one already queued runs where
16058
+ // it stands, and a stage stale before the repair for the same reasons is not the repair's. The reviewer gets the whole
16059
+ // review and its one correction, and the writer is judged again against its duty and the recall gate. The pass
16060
+ // then ends with all of them rebuilt from the decision that stands. The guard below used to park the run on them,
16061
+ // and the resume re-ran them, until the owner's ruling of 2026-10-19 that a run is never held. A stage still stale
16062
+ // after its re-run still falls to the guard: no approved sentence states that case yet.
16063
+ const chain = new Set();
16064
+ const staleNow = () => staleOnPath(run.runDir, deliveryPathStages(), deliveryInputsFor, { project: projectStageInput });
16065
+ const queueWhatItLeftStale = (s2, before) => {
16066
+ const at = staleStages.indexOf(s2) + 1;
16067
+ const left = whatTheRepairLeftStale({ before, after: staleNow(), repaired: s2.label,
16068
+ ahead: new Set(staleStages.slice(at).map((x) => x.label)), processed: processedLabels, requeued: requeuedLabels });
16069
+ for (const l of left.chain) chain.add(l);
16070
+ for (const l of left.requeue) requeuedLabels.add(l);
16071
+ if (!left.queue.length) return;
16072
+ const rest = [...staleStages.slice(at), ...left.queue];
16073
+ const order = [...new Set(dependencyOrder(rest.map((x) => x.label), P, { axes: ctx.axes }))]; // each label once
16074
+ staleStages.splice(at, staleStages.length - at, ...order.map((l) => rest.find((x) => x.label === l)).filter(Boolean));
16075
+ runLog(run.runDir, { event: "delivery-stale-requeued", after: s2.label, stages: left.queue.map((x) => x.label) });
16076
+ };
16077
+ // A STAGE STALE ON WHAT PLACEMENT WROTE IS THE CHAIN'S FROM THE START, whoever moved it: the decisions written
16078
+ // again when the shape is derived again, or a late pass whose readers had already run, as much as this check's
16079
+ // own repeat. Its re-run is followed, and a re-run that does not complete delivers.
16080
+ const placementWrote = new Set([...stageOutputs("placement-inquiry", P, { axes: ctx.axes }).map((f) => basename(f)), basename(P.placementDecisions)]);
16081
+ for (const l of stagesStaleOnPlacement(staleStages, placementWrote)) chain.add(l);
14819
16082
  for (const s2 of staleStages) {
16083
+ processedLabels.add(s2.label);
14820
16084
  if (s2.label === "report-overview") {
14821
16085
  const r = await stage("report-overview", { ...ctx, displayVerdict }, { force: true, trigger: "stale-repair" });
14822
16086
  if (r.ok) { staleRepaired.push(s2.label); reassemble = true; }
14823
16087
  else note(`stale-repair: report-overview re-emit failed (${r.fail}) — stays stale, the guard below decides`);
16088
+ } else if (s2.label === "narrative-refutation"
16089
+ && (chain.has(s2.label) || postRepairScope(run.runDir, P, s2, { staleRepaired, baseKept: repairBaseKept }).mode !== "whole"
16090
+ || queuedForTheRecordOnly(s2) || unreadRecordReview(run.runDir))) {
16091
+ // A REVIEWER A CHANGED DECISION QUEUED is given the whole review and its one correction here, as the pass that
16092
+ // re-ran it on a resume gave it a review and a correction.
16093
+ // THE REVIEW AFTER A DELIVERY-CHECK REPAIR (see runPostRepairReview). Reached where the scope is certain, and
16094
+ // where a change to the record, or one an earlier review could not read, is reviewed whole: that review's
16095
+ // flags go to the same one correction. Every other staleness of the reviewer takes the arm below, as before.
16096
+ // A CHANGE NO REVIEW READ IS REVIEWED WHOLE, whatever this pass's own repair changed: the base this pass
16097
+ // kept follows the earlier change, so a narrowed review would read only the later one, and saving the same
16098
+ // values again would settle a stamp over a change nobody read.
16099
+ const unreadOnEntry = unreadRecordReview(run.runDir);
16100
+ const narrowedScope = postRepairScope(run.runDir, P, s2, { staleRepaired, baseKept: repairBaseKept });
16101
+ const scope = unreadOnEntry && narrowedScope.mode !== "whole" ? { mode: "whole", why: "an earlier change went unreviewed" } : narrowedScope;
16102
+ if (scope.mode === "none") {
16103
+ // A FILE WRITTEN AGAIN WITH THE SAME VALUES IS NOT A CHANGE: no review runs, and the reviewer's stamp
16104
+ // takes the rewrite as accounted for, so the delivery guard does not block on it.
16105
+ settleOneShotStamp(run.runDir, "narrative-refutation", [P.narrative], "repair-changed-no-content");
16106
+ runLog(run.runDir, { event: "post-repair-review-not-needed", why: scope.why });
16107
+ note(`[post-repair] the repair saved the same values again — no review needed`);
16108
+ staleRepaired.push(s2.label);
16109
+ continue;
16110
+ }
16111
+ const rerun = () => UPSTREAM_STALE_REPAIR[s2.label](ctx, { displayVerdict });
16112
+ // THE STATE THIS REVIEW READS IS KEPT WHOLE before it runs, so a correction that fails after it has a trusted
16113
+ // state to come back to even where the record itself is lost meanwhile.
16114
+ retainStateBeforeReview(run.runDir, P);
16115
+ // A scope that cannot be narrowed is reviewed whole at once, and its record lists no change it could compare.
16116
+ let review = scope.mode === "whole"
16117
+ // A resume reviews the change its earlier record names, so that record says what the change was.
16118
+ ? await runWholeReviewForRecord(ctx, P, run, { ...scope, changed: scope.changed ?? NO_CHANGE,
16119
+ findingOnly: unreadRecordFindingOnly(run.runDir) ?? queuedForTheRecordOnly(s2) }, rerun)
16120
+ : await runPostRepairReview(ctx, P, run, scope);
16121
+ // A narrowed review that could not be read falls back to the whole review, whose points go to the same one
16122
+ // correction below: neither the stamp of a findings-only change nor that of a stage that ends ok on the
16123
+ // earlier review's file can show the change unread. When the whole review cannot be read either, its
16124
+ // record keeps the change no review read; the record holds nothing (see `unreadRecordReview`).
16125
+ if (!review.reviewed && scope.mode !== "whole") review = await runWholeReviewForRecord(ctx, P, run, scope, rerun);
16126
+ if (!review.reviewed) continue;
16127
+ staleRepaired.push(s2.label);
16128
+ reassemble = true;
16129
+ // senior-eye-review.md HOLDS THE REVIEW WHOSE VERDICT THE RUN CARRIES. A narrowed review that hardened
16130
+ // the verdict stays in place, so the open questions print its grounds; otherwise the whole review goes
16131
+ // back, and the narrowed one is kept under _driver/.
16132
+ const { hardened } = adoptLateReview(review.reviewText, s2.label);
16133
+ if (!hardened) review.putBack();
16134
+ if (review.rec.flags.length) {
16135
+ const pass = await runPostRepairCorrection(ctx, P, run, { rec: review.rec, items: review.items, points: review.points,
16136
+ grounds: Boolean(hardened), whole: review.base?.whole === true, synthesisRef: { key: synthesisKey, model: synthesisModel, thinking: synthesisThinking } });
16137
+ // NOTHING REVIEWS THE CORRECTION: the reviewer's stamp takes what it changed as accounted for, and so
16138
+ // does the first cycle's receipt, so a resume re-fires neither.
16139
+ settleOneShotStamp(run.runDir, "narrative-refutation", [P.narrative], "post-repair-fix");
16140
+ try {
16141
+ const at = driverDir(run.runDir, "corrective-cycle.json");
16142
+ let receipt = null;
16143
+ try { receipt = JSON.parse(readFileSync(at, "utf8")); } catch { receipt = null; }
16144
+ atomicWrite(at, JSON.stringify({ ...(receipt ?? { entryVerdict: verdict }), ts: new Date().toISOString(), verdict,
16145
+ shas: { review: shaOf(P.seniorEyeReview), narrative: shaOf(P.narrative) }, settledBy: "post-repair-cycle" }, null, 2) + "\n");
16146
+ } catch (e) { note(`[post-repair] the cycle receipt could not be rewritten (${String(e?.message ?? e).slice(0, 100)}) — a resume may re-fire the first correction`); }
16147
+ // ITS CONSUMERS REBUILD FROM WHAT IT WROTE: re-read the findings, and queue the overview and any card it
16148
+ // left stale that this loop has not queued already.
16149
+ readNow = readFindingsForReport(P);
16150
+ findingsNow = readNow.findings;
16151
+ ordinalsNow = fullProseOrdinals(findingsNow);
16152
+ try {
16153
+ if (existsSync(P.caseLaw)) caseLawNow = joinCaseLawProfiles(parseCaseLawProfiles(readFileSync(P.caseLaw, "utf8")), findingsNow);
16154
+ } catch { /* no case-law layer */ }
16155
+ // After a correction, and after a rollback that rebuilt the record from a save, what reads the record is
16156
+ // rebuilt from it; a rollback that put the same bytes back leaves nothing stale, and queues nothing.
16157
+ if (pass.ok || pass.rollback) {
16158
+ // A label not queued yet is appended; one this loop already re-done is re-queued ONCE, so the
16159
+ // pass stays bounded and still ends with every surface rebuilt from the corrected record.
16160
+ const queued = new Set(staleStages.map((x) => x.label));
16161
+ // A finding the correction's save added is owed its one card call, as after any later save.
16162
+ for (const x of [...staleOnPath(run.runDir, deliveryPathStages(), deliveryInputsFor, { project: projectStageInput }),
16163
+ ...cardsNeverBuilt(P, ctx.depth, cardOrdinals)]) {
16164
+ if (!(x.label === "report-overview" || x.label.startsWith("report-card:"))) continue;
16165
+ if (!queued.has(x.label)) { staleStages.push(x); queued.add(x.label); }
16166
+ else if (processedLabels.has(x.label) && !requeuedLabels.has(x.label)) { requeuedLabels.add(x.label); staleStages.push(x); }
16167
+ }
16168
+ }
16169
+ // THE WRITER'S DUTY, JUDGED AGAIN ONCE THE CORRECTION HAS SETTLED, rollback included. The terminal guards judged
16170
+ // it on the record before this correction. A correction that rolled back restores a record that the duty as it
16171
+ // now stands may not be met by: a save restored as accepted work, or records owed since the save was made. The
16172
+ // approved clause names what it leaves unaccounted, and a gap it closed withdraws its clause, as after the
16173
+ // writer's re-run in this loop; the label is saved again before anything is published.
16174
+ {
16175
+ const was = verdict, stood = JSON.stringify(clampReasons);
16176
+ const judged = clampOnTheWritersDuty({ again: true });
16177
+ if (restoredClear && verdict === "CLEAR") applyCoverageFloor();
16178
+ if (JSON.stringify(clampReasons) !== stood || verdict !== was) saveTheLabel("after the correction after a post-repair review");
16179
+ runLog(run.runDir, { event: "writer-duty-after-correction", was, now: verdict, rolledBack: Boolean(pass.rollback),
16180
+ owed: judged.duty?.computable === true ? judged.duty.totals?.owed ?? null : null,
16181
+ unaccounted: judged.duty?.computable === true ? judged.duty.unaccounted.length : null, recall: judged.recall });
16182
+ }
16183
+ }
16184
+ continue;
14824
16185
  } else if (UPSTREAM_STALE_REPAIR[s2.label]) {
14825
16186
  // The upstream arm (item 15a). Each of these re-runs the SAME stage the pipeline would have
14826
16187
  // run, forced, with a `stale-repair` trigger so the telemetry says why. Dependency order put
14827
16188
  // it ahead of anything that reads what it writes, so the tail arms below rebuild from the
14828
16189
  // repaired artifact rather than from the one that moved.
16190
+ const reviewSeq0 = s2.label === "narrative-refutation" ? nextRefutationSeq(run.runDir) : null;
16191
+ const staleBefore = s2.label === "placement-inquiry" || chain.has(s2.label) ? staleNow() : null; // what was stale before this one
16192
+ if (s2.label === "placement-inquiry" && !rankingBeforeRepeat) rankingBeforeRepeat = rankingNow(P); // as the writer last read it
14829
16193
  const r = await UPSTREAM_STALE_REPAIR[s2.label](ctx, { displayVerdict });
14830
- if (r?.ok) {
16194
+ // NOT A PASS: a reviewer that ends with no accepted call reads ok whenever the earlier review's file
16195
+ // stands, and its stamp is refreshed over the change it never read. The record says so, the run delivers,
16196
+ // and a resume for any other cause reviews the change whole.
16197
+ if (r?.ok && s2.label === "narrative-refutation" && !readAcceptedReviewSince(run.runDir, reviewSeq0)) {
16198
+ writePostRepairRecord(run.runDir, { status: "unread", whole: true, findingOnly: queuedForTheRecordOnly(s2), by: "stale-repair", fail: "no-accepted-call" });
16199
+ runLog(run.runDir, { event: "post-repair-review", read: false, whole: true, fail: "no-accepted-call" });
16200
+ note(`stale-repair: the reviewer's re-run recorded no review — the change it was queued for is unreviewed; the run's record says so, and the run delivers`);
16201
+ } else if (r?.ok) {
14831
16202
  staleRepaired.push(s2.label);
14832
16203
  reassemble = true;
16204
+ // THE FLOOR, APPLIED AGAIN TO THE PLACEMENT THE REPORT IS NOW BUILT FROM. The delivery floor was judged on
16205
+ // the placement account before this repair; the repeated pass has recorded its own floor duty, and the
16206
+ // stages after it rebuild from it in this loop. The same rule is applied to that account, through the
16207
+ // same clamp, so the delivered verdict, its floor and its record describe one pass. It only tightens.
16208
+ if (s2.label === "placement-inquiry") {
16209
+ const was = verdict, stood = JSON.stringify(clampReasons);
16210
+ clampOnTheFloorDuty({ again: true });
16211
+ clampOnTheWritersDuty({ again: true }); // the recall gate leaves out the floor that now stands
16212
+ // A save that fails here is tried again, then owed, never the end of the run: see saveTheLabel.
16213
+ if (JSON.stringify(clampReasons) !== stood || verdict !== was) saveTheLabel("after the repeated placement pass");
16214
+ runLog(run.runDir, { event: "floor-duty-after-repair", was, now: verdict });
16215
+ }
16216
+ // A whole review that is read reviews the change an earlier unread one left: its record says so, and
16217
+ // its points stand under the late-review rule below, as any whole review's do. (The arm above has
16218
+ // already set aside a stage that read ok without an accepted review.)
16219
+ if (s2.label === "narrative-refutation" && unreadRecordReview(run.runDir))
16220
+ writePostRepairRecord(run.runDir, { status: "read", whole: true, by: "stale-repair" });
14833
16221
  // its consumers rebuild from what it JUST wrote — re-read, do not carry the pre-repair copy
14834
16222
  readNow = readFindingsForReport(P);
14835
16223
  findingsNow = readNow.findings;
@@ -14837,6 +16225,7 @@ async function pipelineInner(job, opts = {}) {
14837
16225
  try {
14838
16226
  if (existsSync(P.caseLaw)) caseLawNow = joinCaseLawProfiles(parseCaseLawProfiles(readFileSync(P.caseLaw, "utf8")), findingsNow);
14839
16227
  } catch { /* no case-law layer */ }
16228
+ for (const owed of cardsNeverBuilt(P, ctx.depth, cardOrdinals)) if (!staleStages.some((x) => x.label === owed.label)) staleStages.push(owed);
14840
16229
  // — AND THE REVIEW IS ALSO SOMETHING THIS PASS JUST WROTE.
14841
16230
  //
14842
16231
  // The re-reads above are the artifacts a repaired stage's CONSUMERS need. narrative-refutation
@@ -14854,56 +16243,28 @@ async function pipelineInner(job, opts = {}) {
14854
16243
  // stays where it is; this restores the input it was denied. `verdictHardenedTo` ratchets one
14855
16244
  // way, so a review that softened during the repair cannot lift a clamp.
14856
16245
  if (s2.label === "narrative-refutation") {
14857
- const reviewNow = existsSync(P.seniorEyeReview) ? readFileSync(P.seniorEyeReview, "utf8") : "";
14858
- const { hardened, softened: reviewSays } = lateReviewAgainst(verdict, reviewNow);
14859
- // A REVIEW THAT SOFTENED IS NOT ADOPTED, AND THE RUN SAYS SO. The ratchet below only
14860
- // tightens, so a re-review that comes back softer leaves the settled verdict in force while
14861
- // senior-eye-review.md, rewritten by this repair, opens with the softer word. That is the
14862
- // design; recording nothing was the defect: a reader of the run found two answers and no
14863
- // line saying which one governs (measured in testing, 2026-09-23: BLOCKING recorded, CONDITIONAL on disk).
14864
- if (reviewSays) {
14865
- runLog(run.runDir, { event: "verdict-softening-not-adopted", was: verdict, reviewSays, stage: s2.label });
14866
- note(`stale-repair: the reviewer now returns ${reviewSays}; the run keeps ${verdict}, because a late re-review may only harden the verdict — senior-eye-review.md now disagrees with verdict.json, and verdict.json governs`);
14867
- }
14868
- if (hardened) {
14869
- // ── T3a — THE LATE VERDICT IS ADOPTED, NOT THROWN, AND ADOPTION IS THE WHOLE JOB ──
14870
- //
14871
- // built the throw here because a late hardening was an input the gate had been
14872
- // DENIED: the verdict settles thousands of lines earlier, nothing re-reads the review,
14873
- // so a reviewer refusing to sign DURING this repair "is written to disk and never
14874
- // heard". Measured on `bf21580e` — the narrative printed a registration date of
14875
- // 1 March 2011 against a record reading 25 February 2011, the reviewer said so, and
14876
- // the run delivered anyway.
14877
- //
14878
- // DELETING THE THROW WITHOUT A DESTINATION RE-OPENS EXACTLY THAT HOLE, and that is
14879
- // why this branch is not the same edit as the gate's. The owner's ruling removes the
14880
- // REFUSAL; it does not say the reviewer stops being heard. So the late verdict is
14881
- // adopted as the run's own and everything derived from it is rebuilt:
14882
- //
14883
- // · `verdict` and status.json — the run's own record
14884
- // · verdict.json — "the single label authority" (its words)
14885
- // · `reassemble` — assembleReportMd re-runs, so the open-points
14886
- // section is built from THIS review, and
14887
- // applyVerdictFrontMatter re-reads the sidecar
14888
- // and re-stamps overall_label / overall_badge
14889
- //
14890
- // Without the sidecar write the report would ship BADGED WITH THE VERDICT SETTLED
14891
- // BEFORE THE REVIEWER HARDENED — refused by the reviewer, labelled CONDITIONAL. That
14892
- // is worse than either behaviour this replaces, and it is `bf21580e`'s shape one
14893
- // layer up: the reviewer's word on disk and the client's document disagreeing.
14894
- //
14895
- // `verdictHardenedTo` still ratchets ONE WAY, so a review that SOFTENED during the
14896
- // repair still cannot lift a clamp. This branch only ever tightens.
14897
- runLog(run.runDir, { event: "verdict-hardened-by-repair", was: verdict, now: hardened, stage: s2.label, adopted: true });
14898
- note(`stale-repair: the reviewer returned ${hardened} — adopting it as the run's verdict (the one settled earlier was decided against a review this repair has since rewritten) and rebuilding the label from it`);
14899
- verdict = hardened;
14900
- writeRunStatus(ctx, signoffPatch(verdict));
14901
- try { writeVerdictSidecar(); }
14902
- catch (e) { throw new StageFailure("verdict", `verdict sidecar write failed (the single label authority): ${String(e.message).slice(0, 120)}`); }
14903
- reassemble = true;
14904
- }
16246
+ adoptLateReview(existsSync(P.seniorEyeReview) ? readFileSync(P.seniorEyeReview, "utf8") : "", s2.label);
14905
16247
  }
14906
- } else note(`stale-repair: ${s2.label} re-run failed (${r?.fail}) — stays stale, the guard below decides`);
16248
+ // THE WRITER, JUDGED AGAIN WHEN A CHANGED DECISION RE-RAN IT. Its duty and the recall gate were judged on the
16249
+ // writer before it; a gap this one opens is named through the same clamps, and one already named stays named.
16250
+ // The label is saved again as the floor's is above, through saveTheLabel.
16251
+ if (s2.label === "synthesis") keepRankingReadByWriter(P); // where each record stood as this writer read it
16252
+ if (s2.label === "synthesis" && chain.has(s2.label)) {
16253
+ const was = verdict, stood = JSON.stringify(clampReasons);
16254
+ const judged = clampOnTheWritersDuty({ again: true });
16255
+ // A CLEAR a closed gap gave back meets the floor again on what this writer now says: its forward actions
16256
+ // are a ground of their own.
16257
+ if (restoredClear && verdict === "CLEAR") applyCoverageFloor();
16258
+ if (JSON.stringify(clampReasons) !== stood || verdict !== was) saveTheLabel("after the writer's re-run");
16259
+ runLog(run.runDir, { event: "writer-duty-after-repair", was, now: verdict,
16260
+ owed: judged.duty?.computable === true ? judged.duty.totals?.owed ?? null : null,
16261
+ unaccounted: judged.duty?.computable === true ? judged.duty.unaccounted.length : null, recall: judged.recall });
16262
+ }
16263
+ if (staleBefore) queueWhatItLeftStale(s2, staleBefore);
16264
+ } else {
16265
+ repairFailed.set(s2.label, String(r?.fail ?? "failed").slice(0, 200));
16266
+ note(`stale-repair: ${s2.label} re-run failed (${r?.fail}) — stays stale, the guard below decides`);
16267
+ }
14907
16268
  } else if (s2.label.startsWith("report-card:")) {
14908
16269
  const ord = Number(s2.label.slice("report-card:".length));
14909
16270
  const f = findingsNow.find((x) => x.ordinal === ord);
@@ -14923,12 +16284,19 @@ async function pipelineInner(job, opts = {}) {
14923
16284
  }
14924
16285
  }
14925
16286
  if (reassemble) {
14926
- assembleReportMd(P, findingsNow, ordinalsNow);
16287
+ reassembleFromTheRecord(P, findingsNow, ctx.depth); // the plan the first assembly used, made from the record as it stands
14927
16288
  applyVerdictFrontMatter(); // the reassembly rewrote report.md — re-apply every driver-owned
14928
16289
  // field, exactly as the lint-repair branch above does
14929
16290
  writeScopeFacts();
14930
16291
  applyScopeFrontMatter();
14931
16292
  applyRatedUnderFrontMatter();
16293
+ // A repair that re-ran a stage the trace and the Audit Trail report has both written again, before the lint
16294
+ // below reads the trace. A rebuild that fails withdraws the audit written before the repair, as a failed first
16295
+ // build ships none.
16296
+ if (rebuildTheDeliveredRecord && staleRepaired.some((l) => REBUILDS_THE_DELIVERED_RECORD.has(l))) {
16297
+ const rebuilt = rebuildTheDeliveredRecord("stale-repair");
16298
+ if (!rebuilt.ok) note(`stale-repair: the audit could not be written again (${String(rebuilt.reason).slice(0, 100)}) — the one written before the repair is withdrawn and kept under _history; the report ships without an audit`);
16299
+ }
14932
16300
  }
14933
16301
  // The AD-2 in-pass stale-repair arm for client-summary is retired with the stage
14934
16302
  // (2026-08-01): nothing generates that label, so it can never turn up stale here.
@@ -14936,7 +16304,65 @@ async function pipelineInner(job, opts = {}) {
14936
16304
  // freshness question — one bounded pass; anything still stale blocks below, exactly as before.
14937
16305
  lint = lintNow();
14938
16306
  autoCorrectRegistry();
14939
- staleStages = staleOnPath(run.runDir, deliveryPathStages, deliveryInputsFor, { project: projectStageInput });
16307
+ staleStages = staleOnPath(run.runDir, deliveryPathStages(), deliveryInputsFor, { project: projectStageInput });
16308
+ // A REPAIR WHOSE REVIEW COULD NOT BE READ IS NOT REVIEWED, and its record says so (`unreadRecordReview`). The guard
16309
+ // is never handed that record: a run is not held on an unread review (owner's ruling, 2026-10-19). What the guard
16310
+ // reads here is staleness alone, as in the released engine.
16311
+ // A CHANGED DECISION STILL UNREAD AFTER ITS RE-RUN IS DELIVERED, NOT HELD (the owner's ruling of 2026-10-19 that a
16312
+ // run is never held). When every stage still stale is one the changed decision left stale and whose re-run in
16313
+ // this pass did not complete, the run delivers what it has. The writer's duty and the recall gate are judged
16314
+ // again on the writer delivered, and name in their approved sentences what they find. Staleness for any other
16315
+ // reason still falls to the guard.
16316
+ // LATE RECORDS NO PLACEMENT PASS COULD ASSESS ARE DELIVERED, NOT HELD (ruled 2026-10-19). The band gained records
16317
+ // after placement's last pass, and neither the pass that would have assessed them nor the delivery check's repeat
16318
+ // of placement completed. The report stands as the last accepted passes built it. An account from before the late
16319
+ // records cannot show they were assessed, so the floor and the recall gate are judged again on the band as it
16320
+ // stands, and the late records the report does not address are counted in the approved sentence for records
16321
+ // neither addressed nor set aside. The attempts and the records are kept in the run record.
16322
+ const unassessed = repairFailed.has("placement-inquiry") ? lateRecordIds(P) : [];
16323
+ if (unassessed.length && staleStages.some((x) => x.label === "placement-inquiry")
16324
+ && staleStages.every((x) => x.label === "placement-inquiry" || chain.has(x.label))) {
16325
+ const was = verdict, stood = JSON.stringify(clampReasons);
16326
+ clampOnTheFloorDuty({ again: true });
16327
+ const judged = clampOnTheWritersDuty({ again: true });
16328
+ if ((JSON.stringify(clampReasons) !== stood || verdict !== was) && saveTheLabel("after late records went unassessed")) applyVerdictFrontMatter();
16329
+ runLog(run.runDir, { event: "late-evidence-unassessed-delivered", was, now: verdict, records: unassessed.length, ids: unassessed.slice(0, 20),
16330
+ stated: judged.recall, latePass: ctx.latePassFailed ?? null, repeat: repairFailed.get("placement-inquiry") });
16331
+ note(`late evidence: ${unassessed.length} record(s) reached the band after placement's last pass, and neither its pass for them nor the delivery check's repeat completed — the run delivers what the last accepted passes built, and the approved clauses state what is open`);
16332
+ staleStages = staleStages.filter((x) => x.label !== "placement-inquiry");
16333
+ }
16334
+ if (staleStages.length && staleStages.every((x) => chain.has(x.label))) {
16335
+ const was = verdict, named = clampReasons.length, stood = JSON.stringify(clampReasons);
16336
+ clampOnTheWritersDuty({ again: true });
16337
+ // THE RECORDS RE-RANKED SINCE THE WRITER LAST WROTE, in the owner's words of 2026-10-19. The report states the
16338
+ // ranking the writer last read; the audit trail, written again after the repeat, states the one that stands.
16339
+ const readByWriter = rankingFromJson(safeReadJson(rankingReadByWriterPath(P))) ?? rankingBeforeRepeat;
16340
+ if (readByWriter && staleStages.some((x) => x.label === "synthesis")) {
16341
+ const now = rankingNow(P), n = recordsReRanked(readByWriter, now);
16342
+ const m = Math.max(rankedRecords(readByWriter), rankedRecords(now), n);
16343
+ if (n > 0) deliverAndClamp({
16344
+ event: "ranking-changed-unread-delivered",
16345
+ defect: `ranking_changed_unread:${n}`,
16346
+ detail: { records: n, of: m },
16347
+ reason: `ranking_changed_unread:${n} of ${m} record(s) — the delivery check's repeat of placement changed where these records stand, and the writer's re-run for the change did not complete, so the report gives the ranking it last read and the audit trail the one that stands. The report ships with this named rather than being withheld.`,
16348
+ clause: clauseForDefect("ranking_changed_unread", n, m),
16349
+ });
16350
+ }
16351
+ if ((JSON.stringify(clampReasons) !== stood || verdict !== was) && saveTheLabel("after a changed decision went unread")) applyVerdictFrontMatter();
16352
+ runLog(run.runDir, { event: "changed-decision-unread-delivered", was, now: verdict, named: clampReasons.length - named,
16353
+ stages: staleStages.map((x) => ({ label: x.label, changed: (x.changed ?? []).map((c) => c.name) })) });
16354
+ note(`stale-repair: ${staleStages.map((x) => x.label).join(", ")} could not be re-run for the changed decision — the run delivers, and the approved clauses name what they find`);
16355
+ staleStages = [];
16356
+ }
16357
+ // THE LABEL OWED IS SAVED NOW, before anything is published from it, and the report's own label follows it. Only
16358
+ // a run directory that refuses every save of it ends the run here, and then nothing the run writes can be published.
16359
+ if (labelOwed) {
16360
+ const owed = labelOwed;
16361
+ labelOwed = null;
16362
+ if (!saveTheLabel(`${owed}, saved again before delivery`))
16363
+ throw new StageFailure("verdict", `the run directory refused every save of the result's label (${owed}) — no surface can be published with it`);
16364
+ applyVerdictFrontMatter();
16365
+ }
14940
16366
  note(`stale-repair: re-did ${staleRepaired.length} stage(s) in-pass (${staleRepaired.join(", ") || "none"}) — ${staleStages.length ? `${staleStages.length} still stale, the delivery guard blocks` : "the delivery path is fresh"}`);
14941
16367
  }
14942
16368
  // P2 — record the delivery-BLOCKING stale labels the moment they are known (not only at the throw
@@ -16421,12 +17847,12 @@ async function runExperimentInner(job, opts) {
16421
17847
 
16422
17848
  // — THE CORRECTIVE PASS, on a COLD turn, and it says which pass it reproduced.
16423
17849
  //
16424
- // Production's corrective pass RESUMES the synthesis session and sends only these blocks. An arm builds
16425
- // a fresh key in a sandbox and cannot resume anything; resuming the real session was rejected because
16426
- // "canonical run untouched" is the arm's whole contract. So the arm carries the same INPUTS on a cold
16427
- // turn, composed by the same function production uses — reaching the seam, not reproducing the
16428
- // resumption. The receipt says `dispatched-cold`, never `dispatched-warm`, so the two cannot be read as
16429
- // one another.
17850
+ // Production's corrective pass is a followup dispatch on the run's own synthesis key, and its message is
17851
+ // these blocks: its first attempt starts fresh, and it never resumes the synthesis session (no engine resumes
17852
+ // by key). An arm builds a fresh key in a sandbox, and dispatching on the real run's key was rejected because
17853
+ // "canonical run untouched" is the arm's whole contract. So the arm carries the same INPUTS on a cold turn,
17854
+ // composed by the same function production uses — reaching the seam, not reproducing production's dispatch.
17855
+ // The receipt says `dispatched-cold`, never `dispatched-warm`, so the two cannot be read as one another.
16430
17856
  const P = shadowCtx.paths;
16431
17857
  const correctiveReady = correctiveReadiness({
16432
17858
  preCorrective: existsSync(driverDir(shadowDir, "findings-pre-corrective.json")),
@@ -17060,6 +18486,52 @@ async function afterLateSynthesisSave(name, ctx, r) {
17060
18486
  if (r?.ok && !r.skipped && name === "synthesis" && typeof ctx?.afterSynthesisSave === "function") await ctx.afterSynthesisSave();
17061
18487
  }
17062
18488
 
18489
+ /**
18490
+ * THE DRIVER'S WRITES FROM BEFORE THE SEAM, MADE AGAIN AFTER A SYNTHESIS SAVE THAT FOLLOWS IT.
18491
+ *
18492
+ * Two writes into the findings record run before the seam rather than at it: the basis derivation, which demotes a
18493
+ * `verified-from-record` stamp the reading log cannot back, and the deadline enrichment, which gives a finding with
18494
+ * no deadline the register's own opposition window. A save after the seam renders the record from the model's call.
18495
+ * A patch keeps both, because each also lands in the merge base the patch is merged onto; a whole re-send replaces
18496
+ * that base, and they went with it. The seam's own writes were made again after such a save, and these two were not,
18497
+ * so the delivered record claimed reads no step had made, and every card whose finding had carried a demoted stamp
18498
+ * read as stale and was rebuilt for a finding the save had not changed. They run again here, in the order the pass
18499
+ * ran them, before the seam's writes. Each leaves a record that already carries it as it is, and neither throws.
18500
+ */
18501
+ export function rewriteFromBeforeTheSeam(ctx) { // @internal
18502
+ settleDerivedBases(ctx, "late-save");
18503
+ enrichFindingDeadlines(ctx.paths, ctx.paths.runDir, note, { nowMs: deadlineClockOf(ctx.paths.runDir) });
18504
+ }
18505
+
18506
+ /**
18507
+ * The clock the run's deadline enrichment judged its windows by, so a window is judged again exactly as it was
18508
+ * judged the first time, never moved across its edge by the minutes a repair took. Now, on a run whose enrichment
18509
+ * recorded none: it judged nothing, so there is nothing to keep it consistent with. Never throws.
18510
+ */
18511
+ function deadlineClockOf(runDir) {
18512
+ try {
18513
+ const at = Date.parse(JSON.parse(readFileSync(driverDir(runDir, "deadline-enrich.json"), "utf8"))?.judged_at);
18514
+ return Number.isFinite(at) ? at : Date.now();
18515
+ } catch { return Date.now(); }
18516
+ }
18517
+
18518
+ /**
18519
+ * THE MERGE BASE TAKES THE ENRICHED DEADLINES. A patch after the enrichment is merged onto
18520
+ * `_driver/synthesis-calls/accepted.json` and re-renders every finding from there, so a deadline written to the
18521
+ * findings file alone was dropped by the next patch, and a card built with it was rebuilt without it. In the model's
18522
+ * numbering: under a fold, a deadline lands on the member the fold kept, whose object the folded finding is, and the
18523
+ * fold carries it again. Only a finding the base holds with no deadline of its own takes one, as in the file. The
18524
+ * edit is judged as a call is; a refusal leaves the base as it was, and the receipt says what happened. Never throws.
18525
+ */
18526
+ function rebaseEnrichedDeadlines(runDir, enriched) {
18527
+ const fold = readFold(runDir);
18528
+ const byOrdinal = new Map(enriched.map((e) => [keptModelOrdinal(fold, e.ordinal), e.date]).filter(([o]) => o != null));
18529
+ if (!byOrdinal.size) return "unmapped";
18530
+ const r = rebaseSynthesis(runDir, (held) => ({ ...held, findings: (Array.isArray(held?.findings) ? held.findings : [])
18531
+ .map((f) => (byOrdinal.has(f?.ordinal) && !f?.deadline?.date ? { ...f, deadline: { kind: "opposition", date: byOrdinal.get(f.ordinal) } } : f)) }));
18532
+ return r.ok ? (r.changed ? "enriched" : "unchanged") : /^no_base:/.test(String(r.reason)) ? "no-base" : "refused";
18533
+ }
18534
+
17063
18535
  /**
17064
18536
  * The reader's name for a planned search that never ran and that no open ledger row on its axis claims —
17065
18537
  * the orphan row of `coverageJudgmentRows`. Its area is the plan's qid, which a lawyer cannot read; the
@@ -17112,3 +18584,202 @@ export function foreignRecordLinksBlanked(r, ctx, name) { // exported for its
17112
18584
  runLog(ctx.paths.runDir, { event: "foreign-record-links-recheck", stage: name, ok: false, fail: String(v?.reason ?? "no validator").slice(0, 200) });
17113
18585
  return false;
17114
18586
  }
18587
+
18588
+ /**
18589
+ * THE WRITE-UPS A RECORD OWES, PLANNED IN ONE PLACE. PURE.
18590
+ *
18591
+ * §9: a finding owed prose earns a full card where its form is `full`, and the rest render code-side, as a short
18592
+ * entry or as a line in the grouped section (§8). The first assembly made this plan inline, and the delivery check's
18593
+ * repair, which reassembles the report after a synthesis save has moved the record, made its own: every finding owed
18594
+ * prose and no grouped section. So a report reassembled on a graded product lost its "Also on the register" section,
18595
+ * where its grouped findings are read by territory or by right, each short entry kept the text composed before the
18596
+ * save, and the cards a run owed stayed the ones the first plan named whatever the record became. One plan, made here
18597
+ * from the record as it stands, now serves the first build, the delivery path and every reassembly.
18598
+ *
18599
+ * The floor tier joins on `mark_text` (floorTierByMark); an absent band shape yields an empty map, and every finding
18600
+ * then reads as holding no floor. On one country every form is `full`, so the plan is `fullProseOrdinals` itself.
18601
+ */
18602
+ export function planWriteUps(findings, depth, bandShape) { // @internal
18603
+ const floorTiers = floorTierByMark(bandShape);
18604
+ const formOf = (f) => writeUpForm(depth, f, { floorTier: floorTiers.get(floorMarkKey(f?.mark)) ?? null });
18605
+ const byOrdinal = new Map((findings ?? []).filter((f) => f?.ordinal != null).map((f) => [f.ordinal, f]));
18606
+ const proseOrdinals = fullProseOrdinals(findings);
18607
+ const cardOrdinals = proseOrdinals.filter((ord) => formOf(byOrdinal.get(ord)) === "full");
18608
+ const entryOrdinals = proseOrdinals.filter((ord) => formOf(byOrdinal.get(ord)) !== "full");
18609
+ const entryFindings = entryOrdinals.map((ord) => byOrdinal.get(ord)).filter(Boolean);
18610
+ // Worldwide groups by the right, multi-country by territory: the depth row's prose rung says which.
18611
+ const byRight = String(depth?.narrativeProse ?? "") === "adversarial";
18612
+ const { groups, singles: entrySingles } = groupEntries(entryFindings, { byRight });
18613
+ return {
18614
+ cardOrdinals, entryOrdinals, entryFindings, byRight, entrySingles,
18615
+ // Assembly sees full cards AND short entries; a grouped finding is a line in its section, never a card.
18616
+ assembledOrdinals: [...cardOrdinals, ...entrySingles.map((f) => f.ordinal)],
18617
+ groupedMembers: groups.flatMap((g) => g.findings),
18618
+ };
18619
+ }
18620
+
18621
+ /** Each short entry's card file, composed by code from its finding as it stands. No model turn. */
18622
+ function writeShortEntries(P, entrySingles) {
18623
+ for (const f of entrySingles) {
18624
+ try { writeFileSync(P.reportCard(String(f.ordinal)), `${shortEntryBody(f)}\n`); }
18625
+ catch (e) { note(`short entry ${f.ordinal} not written (${String(e?.message ?? e).slice(0, 80)}) — it renders structured-only, as an absent card always has`); }
18626
+ }
18627
+ }
18628
+
18629
+ /** The full cards the record owes as it stands now, in its order. */
18630
+ function owedCardOrdinals(P, depth) {
18631
+ return planWriteUps(readFindingsForReport(P).findings, depth, safeReadJson(P.bandShape)).cardOrdinals;
18632
+ }
18633
+
18634
+ /**
18635
+ * THE CARD A FINDING ADDED AFTER THE CARDS IS OWED. A card with no stamp is never stale, so a finding that a
18636
+ * synthesis save after the cards made owed a full card, at an ordinal the first plan never dispatched one for (a
18637
+ * finding it added, or one it moved from a short entry to a full card), got no card call, and the report rendered it
18638
+ * structured-only or as the short entry. Each such card joins the delivery check's repair once, as owed and never
18639
+ * built; a card the first plan dispatched and that failed keeps that outcome, as a failed card always has. Returns
18640
+ * entries in the shape `staleOnPath` gives.
18641
+ */
18642
+ function cardsNeverBuilt(P, depth, firstPlan) {
18643
+ const dispatched = new Set(firstPlan ?? []);
18644
+ return owedCardOrdinals(P, depth)
18645
+ .filter((o) => !dispatched.has(o) && !readStamp(P.runDir, `report-card:${o}`))
18646
+ .map((o) => ({ label: `report-card:${o}`, changed: [{ name: `${o}.md`, was: null, now: null, via: "never-built" }] }));
18647
+ }
18648
+
18649
+ /**
18650
+ * The repairs that fix only what they name: a delivery lint fault, or an object of the writer's record that failed the
18651
+ * strict parse. They are not told placement's set-asides with the order to check each ground (ruled 2026-10-19); every
18652
+ * other follow-up of the reading step and the writer is.
18653
+ */
18654
+ export const NARROW_REPAIR_TRIGGERS = new Set(["lint-repair", "finding-reemit", "action-reemit", "ask-answer-reemit"]); // @internal — driven by its test
18655
+
18656
+ /** The repaired stages whose output the record-carry trace or the Audit Trail reports. */
18657
+ const REBUILDS_THE_DELIVERED_RECORD = new Set(["placement-inquiry", "register-digest", "synthesis", "narrative-refutation"]);
18658
+
18659
+ /**
18660
+ * Keep the Audit Trail, the record-carry trace and the doubt ledger as they stand, under _history/<ts>-audit-and-trace-
18661
+ * <reason>/, before any of them is written again, so a pass that writes them over a repair or a resume never loses what
18662
+ * the earlier state said. The pass that wrote them may have delivered nothing: a pass the delivery check held writes
18663
+ * them too. Returns the directory, or null when there was nothing to keep. Best-effort, like every snapshot.
18664
+ */
18665
+ export function keepDeliveredRecordAsHistory(P, reason) { // @internal — driven by its test with a run's own files
18666
+ const files = [P.audit, P.recordCarry, driverDir(P.runDir, "doubts.json")].filter((f) => existsSync(f));
18667
+ if (!files.length) return null;
18668
+ try {
18669
+ const ts = new Date().toISOString().replace(/[:.]/g, "-");
18670
+ const dest = join(P.runDir, "_history", `${ts}-audit-and-trace-${reason}`);
18671
+ mkdirSync(dest, { recursive: true });
18672
+ for (const f of files) copyFileSync(f, join(dest, basename(f)));
18673
+ return dest;
18674
+ } catch { return null; }
18675
+ }
18676
+
18677
+ /**
18678
+ * The reading audit's lookups (band-server appends every lookup to _driver/reading-log.jsonl). Read on any run that
18679
+ * derived a band shape, an empty list included, because a band-consuming stage that read nothing is a fact the audit
18680
+ * must show; null on a run with no shape, which renders no section. A torn tail line never kills the audit.
18681
+ */
18682
+ function readReadingLog(P) {
18683
+ if (!existsSync(P.bandShape)) return null;
18684
+ const log = [];
18685
+ try {
18686
+ for (const line of readFileSync(P.readingLog, "utf8").split("\n")) {
18687
+ if (!line.trim()) continue;
18688
+ try { log.push(JSON.parse(line)); } catch { /* a torn tail line never kills the audit */ }
18689
+ }
18690
+ } catch { /* no lookups recorded — the empty array IS the disclosure */ }
18691
+ return log;
18692
+ }
18693
+
18694
+ /**
18695
+ * Take an audit that no longer describes the run out of the place publishing reads it from, keeping it under _history
18696
+ * (unless the caller has just kept it). The report then ships without that Audit Trail, as it does when a first build
18697
+ * fails, never with an earlier one offered as current; the publisher still builds the workbook from the current
18698
+ * findings. Returns whether an audit was withdrawn.
18699
+ */
18700
+ export function withdrawSupersededAudit(P, reason, { keep = true } = {}) { // @internal — driven by its test with a run's own files
18701
+ if (!existsSync(P.audit)) return false;
18702
+ if (keep) keepDeliveredRecordAsHistory(P, reason);
18703
+ try { rmSync(P.audit, { force: true }); } catch { return false; }
18704
+ runLog(P.runDir, { event: "audit-withdrawn", reason });
18705
+ return true;
18706
+ }
18707
+
18708
+ /** The doubt ledger on disk, written again with the doubts the rebuilt audit carries; what it said of the closure stays. */
18709
+ function writeDoubtLedgerAgain(P, doubts, truncated) {
18710
+ const path = driverDir(P.runDir, "doubts.json");
18711
+ const earlier = safeReadJson(path) ?? {};
18712
+ const settled = doubts.filter((d) => d?.status === "checked-and-settled").length;
18713
+ const { doubts: _d, minted: _m, settled: _s, open: _o, truncated: _t, ...closure } = earlier;
18714
+ writeFileSync(path, JSON.stringify({ minted: doubts.length, settled, open: doubts.length - settled,
18715
+ ...(truncated?.length ? { truncated } : {}), ...closure, doubts }, null, 2));
18716
+ }
18717
+
18718
+ /**
18719
+ * THE TRACE AND THE AUDIT TRAIL, WRITTEN AGAIN OVER A REPAIR. Both describe a pass before the delivery check repairs
18720
+ * anything, and a stage that check re-runs can change what they report: a repeated placement pass can reverse a
18721
+ * set-aside or change its ground. This keeps the pair as it stands under _history, then writes the trace and the audit
18722
+ * again from the run's files, so what is delivered describes the decision that stands at delivery. `inputs` are what the
18723
+ * pass built its audit from. The doubts minted from the trace follow the trace written again (recordCarryDoubtsAgain);
18724
+ * every other doubt and ask stands as the pass raised and closed it, and nothing is dispatched again.
18725
+ * A rebuild that fails, the trace written and the audit not among the ways, withdraws the audit on disk: it describes
18726
+ * the state before the repair. Returns {ok, counts} or {ok:false, reason}. IMPURE (writes the run).
18727
+ */
18728
+ export function rebuildDeliveredRecord(ctx, inputs = {}, trigger = "stale-repair") { // @internal — driven by its test with a run's own files
18729
+ const P = ctx.paths;
18730
+ keepDeliveredRecordAsHistory(P, trigger);
18731
+ try {
18732
+ let findingsNow = [], actionsNow = [];
18733
+ try {
18734
+ if (existsSync(P.findings)) {
18735
+ const parsed = parseFindingsJsonLenient(readFileSync(P.findings, "utf8"));
18736
+ findingsNow = parsed.findings ?? [];
18737
+ actionsNow = parsed.actions ?? [];
18738
+ }
18739
+ } catch { /* the audit still builds without them */ }
18740
+ deriveRecordCarry(ctx, trigger, { findings: findingsNow });
18741
+ const registerFindingsText = readFileSync(P.registerFindings, "utf8");
18742
+ // THE DOUBTS MINTED FROM THE TRACE FOLLOW THE TRACE. The record-carry family was minted from the trace before the
18743
+ // repair; read against the trace written just above, a record the repair answered loses its doubt and one it left
18744
+ // unreasoned gains one, keyed by record so no closure the closure step recorded moves onto another record. Every
18745
+ // other family stands as the pass raised and closed it.
18746
+ let doubts = inputs.doubts ?? null, doubtTruncations = inputs.doubtTruncations ?? null;
18747
+ if (Array.isArray(doubts)) {
18748
+ const coverageRows = (() => { try { const rows = JSON.parse(readFileSync(P.registerCoverageLedger, "utf8")); return Array.isArray(rows) ? rows : []; } catch { return []; } })();
18749
+ const re = recordCarryDoubtsAgain(doubts, safeReadJson(P.recordCarry),
18750
+ { stitch: (ds) => stitchDoubts(ds, { findings: { findings: findingsNow, actions: actionsNow }, coverageRows, registerFindingsText }) });
18751
+ doubts = re.doubts;
18752
+ // A trace that could not be read says nothing about any record: the doubts stand as raised, and so does the count
18753
+ // of those the cap left out.
18754
+ if (!re.unavailable) doubtTruncations = [...(doubtTruncations ?? []).filter((t) => t?.source !== "record-carry"),
18755
+ ...(re.omitted ? [{ source: "record-carry", minted: re.minted, omitted: re.omitted }] : [])];
18756
+ writeDoubtLedgerAgain(P, doubts, doubtTruncations);
18757
+ if (re.fresh.length || re.dropped.length || re.unavailable)
18758
+ runLog(P.runDir, { event: "record-carry-doubts-again", trigger, fresh: re.fresh.length, dropped: re.dropped.length, ...(re.unavailable ? { unavailable: true } : {}) });
18759
+ }
18760
+ const again = buildAuditMd(registerFindingsText, inputs.commonLawText ?? "",
18761
+ { findings: findingsNow, doubts, asks: inputs.asks ?? null, readingLog: readReadingLog(P),
18762
+ commonLawGrid: inputs.commonLawGrid ?? null, doubtTruncations,
18763
+ registerPresence: inputs.registerPresence ?? null, placementRows: placementAuditRows(P) });
18764
+ if (!again.counts.findings) throw new Error("0 findings parsed from the spine tables");
18765
+ writeFileSync(P.audit, again.md);
18766
+ runLog(P.runDir, { event: "audit-rebuilt", trigger, ...again.counts });
18767
+ return { ok: true, counts: again.counts };
18768
+ } catch (e) {
18769
+ const reason = String(e?.message ?? e).slice(0, 160);
18770
+ runLog(P.runDir, { event: "audit-rebuild-failed", trigger, reason });
18771
+ withdrawSupersededAudit(P, `${trigger}-failed`, { keep: false }); // the pair as it stood was kept above
18772
+ return { ok: false, reason };
18773
+ }
18774
+ }
18775
+
18776
+ /**
18777
+ * Reassemble report.md from the record as it stands, as the first assembly did: the plan made again, each short entry
18778
+ * composed again from its finding, and the grouped section built. Returns the plan.
18779
+ */
18780
+ export function reassembleFromTheRecord(P, findings, depth) { // @internal
18781
+ const plan = planWriteUps(findings, depth, safeReadJson(P.bandShape));
18782
+ writeShortEntries(P, plan.entrySingles);
18783
+ assembleReportMd(P, findings, plan.assembledOrdinals, { grouped: plan.groupedMembers, byRight: plan.byRight });
18784
+ return plan;
18785
+ }