clearotron 0.4.1 → 0.5.0-beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (90) 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 +23 -0
  6. package/driver/ask-ledger.mjs +86 -15
  7. package/driver/band-shape.mjs +61 -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.mjs +3 -0
  19. package/driver/coverage-union.mjs +11 -2
  20. package/driver/digest-queue.mjs +1 -1
  21. package/driver/disposition-call.mjs +1 -1
  22. package/driver/document-coverage.mjs +3 -2
  23. package/driver/effective-scope.mjs +2 -2
  24. package/driver/engine/mcp/band-server.mjs +77 -13
  25. package/driver/engine/mcp/dispositions-server.mjs +2 -2
  26. package/driver/engine/mcp/recording-server.mjs +10 -6
  27. package/driver/findings-model.mjs +16 -4
  28. package/driver/floor-duty.mjs +9 -0
  29. package/driver/gate-metrics.mjs +7 -1
  30. package/driver/gateway.mjs +28 -14
  31. package/driver/goods-evidence.mjs +295 -0
  32. package/driver/knockout-assess-record.mjs +1 -1
  33. package/driver/narrative-refutation-record.mjs +40 -2
  34. package/driver/package.json +1 -1
  35. package/driver/pipeline.mjs +1945 -314
  36. package/driver/placement-decisions.mjs +243 -0
  37. package/driver/placement-form.mjs +42 -8
  38. package/driver/placement-union.mjs +14 -6
  39. package/driver/provider-usage.mjs +10 -7
  40. package/driver/publish/audit-from-spine.mjs +5 -2
  41. package/driver/publish/index.mjs +19 -4
  42. package/driver/publish/xlsx.mjs +6 -6
  43. package/driver/recall-reconciliation.mjs +15 -5
  44. package/driver/record-carry.mjs +70 -6
  45. package/driver/record-discard.mjs +7 -1
  46. package/driver/record-fold.mjs +14 -0
  47. package/driver/record-goods.mjs +101 -5
  48. package/driver/register-plan.mjs +89 -0
  49. package/driver/registry-fidelity.mjs +34 -3
  50. package/driver/remedy-accounting.mjs +52 -14
  51. package/driver/repair-composers.mjs +265 -105
  52. package/driver/repair-scope.mjs +327 -0
  53. package/driver/repair-targets.mjs +92 -0
  54. package/driver/result-noun-fields.mjs +3 -3
  55. package/driver/screen-gate.mjs +96 -0
  56. package/driver/skills/clearance-common-law/SKILL.md +8 -0
  57. package/driver/skills/clearance-register/SKILL.md +5 -5
  58. package/driver/skills/clearance-register/digest.md +14 -14
  59. package/driver/skills/clearance-register/unit.md +3 -3
  60. package/driver/skills/clearance-search/delivery-contract.md +2 -58
  61. package/driver/skills/clearance-search/report-prose.md +12 -3
  62. package/driver/skills/clearance-search/risk-framework-triage.md +1 -1
  63. package/driver/skills/clearance-search/synthesis-rules.md +10 -13
  64. package/driver/skills/clearance-variants/SKILL.md +4 -4
  65. package/driver/skills/knockout-assess/SKILL.md +9 -14
  66. package/driver/skills/knockout-frame/SKILL.md +5 -5
  67. package/driver/skills/narrative-refutation/SKILL.md +1 -1
  68. package/driver/skills/placement-inquiry/SKILL.md +1 -1
  69. package/driver/skills/whatif-memo/SKILL.md +1 -1
  70. package/driver/stage-context.mjs +2 -1
  71. package/driver/stages-knockout.mjs +1 -1
  72. package/driver/stages.mjs +95 -60
  73. package/driver/suite-census.json +367 -19
  74. package/driver/synthesis-record.mjs +310 -8
  75. package/driver/terminal-clamp.mjs +53 -2
  76. package/driver/verify-knockout.mjs +22 -4
  77. package/driver/verify.mjs +23 -3
  78. package/mcp-server/CHANGELOG.md +4 -0
  79. package/mcp-server/lib/driver.mjs +5 -0
  80. package/mcp-server/lib/events.mjs +25 -4
  81. package/mcp-server/lib/findings.mjs +4 -2
  82. package/mcp-server/package.json +1 -1
  83. package/package.json +1 -1
  84. package/portal-ui/package.json +1 -1
  85. package/providers/jx/src/core.js +3 -4
  86. package/providers/jx/src/judge.js +2 -2
  87. package/providers/jx/src/nativeread.js +1 -2
  88. package/providers/oauth-mcp-bridge/CHANGELOG.md +4 -0
  89. package/providers/oauth-mcp-bridge/package.json +1 -1
  90. package/scripts/e2e-first-time.mjs +29 -12
@@ -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, 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";
@@ -77,8 +78,12 @@ 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
79
80
  import { unionPlacementForm } from "./placement-union.mjs";
81
+ import { placedUriRows, standingSetAside, placementSetAsideGrounds, placementFloorDecisionLines, placementSetAsideLines, placementAuditRows,
82
+ placementRecallEndings, writePlacementDecisions, rankingNow, rankedRecords, recordsReRanked, rankingToJson, rankingFromJson } from "./placement-decisions.mjs"; // placement's standing decisions, one check for every reader
83
+ export { placementSetAsideGrounds, placementFloorDecisionLines, placementSetAsideLines, placementAuditRows, placementRecallEndings,
84
+ standingPlacementDecisions, writePlacementDecisions } from "./placement-decisions.mjs";
80
85
  import { readPlacementForm, readPlacementFormInput, writePlacementForm } from "./placement-form-io.mjs";
81
- import { buildSelectionIndex, setAsideGrounds, setAsideKeysOf, ownerFates, isSetAsideGroupRow } from "./placement-form.mjs";
86
+ import { buildSelectionIndex, setAsideKeysOf, ownerFates, servedLines, lineKey } from "./placement-form.mjs";
82
87
  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
88
  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
89
  import { resolveSearchPolicy, gateResolvedPolicy, loadRecipes, policyFor, isRegisterOnly, reportIdentityFor, depthFor } from "./search-policy.mjs";
@@ -103,7 +108,7 @@ import { parseCaseLawProfiles, joinCaseLawProfiles } from "./publish/parse.mjs";
103
108
  import { buildAuditMd, parseSpineFindingBlocks } from "./publish/audit-from-spine.mjs";
104
109
  import { deriveRegisterPresence } from "./publish/register-presence.mjs"; // — the audit stores every live in-scope record
105
110
  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
111
+ 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
112
  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
113
  import { romanizedTermsFromPlan, mintSupplementalQid } from "./register-plan.mjs";
109
114
  import { excludeHouseElement, verifyHouseElementOwnership, resolveRegions as resolvePlanRegions, HOUSE_ELEMENT_RECEIPT } from "./register-plan.mjs";
@@ -124,11 +129,12 @@ import { mintPresenceDoubts } from "./presence-reconciliation.mjs"; // presenc
124
129
  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
130
  // — every placed candidate ends somewhere a reader can see; the ones that do not are counted by name
126
131
  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";
132
+ 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
133
+ 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
134
+ 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
135
  import { reconcileSurfaceDuty, surfaceDutyNote } from "./surface-duty.mjs"; // item 3 — silence at the findings surface, read off the rows above
131
136
  import { DISCARD_LEDGER_NAME, seamRows, appendDiscardRows, foldDiscardLedger } from "./record-discard.mjs";
137
+ 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
138
  import { readDeclinations } from "./declination-tool.mjs"; // — synthesis's own stated declines
133
139
  import { seamReasonFor } from "./declination-call.mjs"; // — the per-record judgment, pure and separately tested
134
140
  import { traceCommonLawCarry, commonLawCarryEvent, mintCommonLawCarryDoubts, reconciliationVerdict, planVsExecutedChannels, CL_STAGES } from "./commonlaw-carry.mjs";
@@ -139,31 +145,33 @@ const COMMONLAW_CARRY_FLOOR = (() => {
139
145
  try { return JSON.parse(readFileSync(new URL("./commonlaw-carry-floor.json", import.meta.url), "utf8")); }
140
146
  catch { return null; }
141
147
  })();
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";
148
+ import { accountRemedyTerms, domClosedOverAccounted, remedyAccountingEvent, mintRemedyAccountingDoubts, SETTLED_CLASSES } from "./remedy-accounting.mjs"; // — every remedy term ends accounted (2026-08-03)
149
+ import { tallyRegisterCalls, DEFAULT_LEDGER_PATH, countLaneCalls } from "./provider-usage.mjs";
144
150
  // — the run's own record log. Every driver-side register call that can write a record BODY carries
145
151
  // this path, because this process runs several runs' pipelines at once and a global address would file
146
152
  // one run's evidence under another.
147
153
  import { runRecordLogPath } from "../providers/_shared/ledger-path.mjs";
148
154
  import { beginAnswerMemory, endAnswerMemory } from "../providers/_shared/answer-memory.mjs";
149
- import { findScreenGateViolations, findScreenGateParseGaps, screenGateZeroCause } from "./screen-gate.mjs";
155
+ import { findScreenGateViolations, findScreenGateParseGaps, screenGateZeroCause, screenGateEvidence, policedDropUris } from "./screen-gate.mjs";
150
156
  import { emptyQueue, coerceQueue, mintItem, pendingItems, markFlushed, receiptKeyFor,
151
157
  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";
158
+ import { foldFindingsFile, foldAfterSave, openPass, modelRecordPath, snapshotFold, restoreFold, readFold, keptModelOrdinal } from "./record-fold.mjs"; // the folded record from the delivery seam on
159
+ import { writeStamp, stageStaleness, restamp, restampStage, staleOnPath, reconcileStamps, shaOf, readStamp } from "./stage-freshness.mjs";
160
+ import { locateReportFaults, reportPartsOf } from "./repair-targets.mjs"; // a delivery-check fault reaches the part of the report that holds it
154
161
  import { parseManifestVariants, variantsParseFailure, findCoverageLimitedCells, partitionClosableCells, findSimilarListingSignals,
155
162
  GRID_HALVES, GRID_SEATS, MEANING_SEAT, splitGridSpec, halfOfTerm, balanceClosureCells, mergeGrids, mergeCommonLawFindings, openChannelRows,
156
163
  routeHalfTermScopes, findDroppedConnotationQueries, findErroredConnotationQueries, findGridCandidateOmissions } from "./common-law-receipts.mjs";
157
164
  import { stampTokenRollup } from "./tokens.mjs";
158
165
  import { recordRunConsumption } from "./consumption-ledger.mjs";
159
166
  import { quoteForJob, reconcileTurnaround } from "./run-quote.mjs";
160
- import { assembleRunRecords, readRecordArtifacts, findRegistryArithmeticIssues, findRegistryViolations, applyRegistryCorrections, extractEnforcerSignals, collectOppositionDeadlines, normalizeRecordUri } from "./registry-fidelity.mjs";
167
+ import { assembleRunRecords, recordBodyHasContent, readRecordArtifacts, findRegistryArithmeticIssues, findRegistryViolations, applyRegistryCorrections, extractEnforcerSignals, collectOppositionDeadlines, normalizeRecordUri } from "./registry-fidelity.mjs";
161
168
  import { recallReceiptForOwnCompany } from "./recall-receipt.mjs"; // a past run's recall receipt, as one company's audit may list it
162
169
  import { runLint, flagLines, properNameCandidates } from "./predelivery-lint.mjs";
163
170
  import { parsePlacementsJson } from "./placement-model.mjs"; // B2 — the structured tier mirror; the lint reads it only to flag an EMPTY one
164
171
  import { readAnchors } from "./anchor-reader.mjs";
165
172
  import { findEngagementReceipts } from "./engagement-receipt.mjs";
166
- import { plainRegisterFlags } from "./plain-register.mjs";
173
+ import { plainRegisterFlags, SENTENCE_WORD_LIMIT } from "./plain-register.mjs";
174
+ import { snapshotRepairBase, readRepairBase, repairBasePath, changedContent, isEmptyChange, restingOn, scopeItems, anyRenumbered, classifyPoints, changedInputsOf, listForReview, writePostRepairRecord, postRepairRecordPath, NARRATIVE_PROSE, sectionText } from "./repair-scope.mjs";
167
175
  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
176
  import { foldCaption, foldCardRead } from "./card-budget.mjs";
169
177
  // S2 — the report card's mechanical frame, composed from the record instead of dictated (see below).
@@ -634,6 +642,7 @@ export function settleDerivedBases(ctx, at) { // @internal
634
642
  });
635
643
  let applied = 0;
636
644
  let unwritable = 0;
645
+ let heldBase = null; // what the synthesis merge base took, when there was a demotion to give it
637
646
  if (demotions.length) {
638
647
  let onDisk = null;
639
648
  try { onDisk = JSON.parse(raw); } catch { onDisk = null; }
@@ -648,6 +657,23 @@ export function settleDerivedBases(ctx, at) { // @internal
648
657
  const next = applyDerivedBases(onDisk, demotions);
649
658
  applied = next.applied;
650
659
  atomicWrite(P.findings, JSON.stringify(next.doc, null, 2) + "\n");
660
+ // THE HELD BASE TAKES THE SAME DEMOTIONS. Every accepted synthesis patch merges onto
661
+ // `_driver/synthesis-calls/accepted.json` and re-renders the findings it does not name from there, so a
662
+ // demotion written to this file alone came back with the next patch, and the evidence check then told
663
+ // the reviewer the stamp had returned. In the model's numbering: under a fold, a demotion lands on the
664
+ // member the fold kept, whose object the folded finding is. The edit is judged as a call is; a refusal
665
+ // leaves the base byte-identical and is recorded below, and the run goes on as it did before.
666
+ if (applied) {
667
+ const fold = readFold(P.runDir);
668
+ const inModel = demotions.map((d) => ({ ...d, ordinal: keptModelOrdinal(fold, d.ordinal) })).filter((d) => d.ordinal != null);
669
+ const r = rebaseSynthesis(P.runDir, (held) => applyDerivedBases(held, inModel).doc);
670
+ heldBase = r.ok ? { base: r.changed ? "demoted" : "unchanged" }
671
+ : /^no_base:/.test(String(r.reason)) ? { base: "no-base" } : { base: "refused", baseReason: String(r.reason).slice(0, 200) };
672
+ if (heldBase.base === "refused") {
673
+ note(`basis derivation (${at}): the synthesis merge base could not take the demotions (${heldBase.baseReason.slice(0, 100)}) — `
674
+ + `it is left as it was, and the next accepted patch re-renders the stamps it holds`);
675
+ }
676
+ }
651
677
  // A SHORTFALL IS A FINDING. The demotions are computed against the lenient parse, which normalises
652
678
  // — so a raw finding missing the `ordinal` the match keys on would leave an unprovable claim
653
679
  // standing while `demoted` and `rows` sat next to each other looking consistent. Say it.
@@ -671,10 +697,11 @@ export function settleDerivedBases(ctx, at) { // @internal
671
697
  .filter((m) => m?.basis === "verified-from-record").length, 0);
672
698
  const record = { ts: new Date().toISOString(), at,
673
699
  stamped_before: stampedBefore, stamped_after: stampedBefore - applied, demoted: applied,
674
- ...(unwritable ? { unwritable } : {}),
700
+ ...(unwritable ? { unwritable } : {}), ...(heldBase ?? {}),
675
701
  rows: demotions.map((d) => ({ ordinal: d.ordinal, mark: d.mark, meter: d.meter, uri: d.uri, why: d.why })) };
676
702
  atomicWrite(P.basisDerivation, JSON.stringify(record, null, 2) + "\n");
677
703
  runLog(P.runDir, { event: "basis-derivation", at, stamped_after: stampedBefore - applied, demoted: applied,
704
+ ...(heldBase ? { base: heldBase.base } : {}),
678
705
  never_fetched: demotions.filter((d) => d.why === "record-never-fetched").length,
679
706
  never_read: demotions.filter((d) => d.why === "record-on-disk-never-read").length });
680
707
  return record;
@@ -745,10 +772,96 @@ export function refreshSupplementalExecution(ctx) { // @internal
745
772
  } catch (e) { note(`supplemental receipt refresh skipped (${String(e.message).slice(0, 80)}) — never-kill`); }
746
773
  }
747
774
 
748
- function deriveNamedBand(ctx) {
775
+ /** One axis band's bytes, or null where it has none: what a re-run's change is measured against. */
776
+ function bandBytesOf(P, axis) { try { return readFileSync(P.registerBand(axis), "utf8"); } catch { return null; } }
777
+
778
+ /** A fingerprint of every axis band file, in axis order: what a merge read. */
779
+ function axisBandsFingerprint(P, axes = []) {
780
+ const h = createHash("sha256");
781
+ for (const a of axes ?? []) { h.update(`${a}\0`); h.update(bandBytesOf(P, a) ?? "\0absent"); h.update("\0"); }
782
+ return h.digest("hex");
783
+ }
784
+
785
+ /**
786
+ * THE NAMED BAND MERGED AGAIN WHEN AN AXIS BAND MOVED SINCE THE LAST MERGE. A unit re-run in its own session after the
787
+ * fan-in (the cross-check's questions, a frame-reopen sweep, the skeptic's escalation, the envelope close) can add blocks
788
+ * to its axis band whatever it does to its notes, and placement, the reading step and the writer read the named band.
789
+ * Only the code-side recheck merged again, so the records those re-runs found reached none of the three. Code only:
790
+ * nothing is dispatched. Returns whether the band was merged again.
791
+ *
792
+ * A BAND A READING STAGE WOULD REFUSE IS NOT ADOPTED. Placement, the reading step and the writer each refuse a band past
793
+ * the size ceiling before they dispatch, and that refusal ends the run with no report. These re-runs alone can grow a
794
+ * band the first merge kept under it past it, where without this merge the run would have gone on reading the band as
795
+ * first merged. So the band is measured before it is adopted, by the test each stage applies at dispatch, and a band any
796
+ * of the three would refuse is not adopted: the earlier merge stands, and the records the re-runs found stay unread. They
797
+ * are not lost silently: the records the refused band held and the band standing does not are kept, and the recall gate
798
+ * counts them into its approved sentence for records neither addressed nor set aside. `refusing` is that test,
799
+ * injectable so a test can drive a refusal without a band of that size.
800
+ */
801
+ export function remergeIfBandsMoved(ctx, why, { refusing = (bytes) => stagesRefusingBand(bytes, (s) => STAGES[s]?.timeoutSec) } = {}) { // @internal — driven by its test with a run's own files
802
+ const P = ctx.paths;
803
+ if (ctx.bandsMergedAt == null) return false; // nothing merged yet this pass: the fan-in merge reads every band
804
+ const now = axisBandsFingerprint(P, ctx.axes);
805
+ if (now === ctx.bandsMergedAt || now === ctx.bandsRefusedAt) return false; // unmoved, or already refused as it stands
806
+ try {
807
+ const merged = mergeRegisterBands(P, ctx.axes ?? []);
808
+ if (!merged.invalid.length) {
809
+ const bytes = Buffer.byteLength(namedBandText(merged.band));
810
+ const stages = refusing(bytes);
811
+ if (stages.length) {
812
+ ctx.bandsRefusedAt = now;
813
+ const left = recordRefusedRecords(P, merged.band);
814
+ runLog(P.runDir, { event: "named-band-remerge-over-ceiling", why, bytes, stages, records: left });
815
+ 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`);
816
+ return false;
817
+ }
818
+ }
819
+ deriveNamedBand(ctx, merged); runLog(P.runDir, { event: "named-band-remerged", why }); return true;
820
+ }
821
+ 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; }
822
+ }
823
+
824
+ /** Where the records a refused merge left out are kept, for the recall gate. */
825
+ const refusedRecordsPath = (P) => driverDir(P.runDir, "band-refused-records.json");
826
+
827
+ /**
828
+ * THE RECORDS A REFUSED MERGE LEAVES OUT: the ids the band merged again holds and the band standing does not, added to
829
+ * those an earlier refusal left out. Returns how many are kept, or null when they could not be written, which the run
830
+ * record names. Never throws.
831
+ */
832
+ function recordRefusedRecords(P, band) {
833
+ try {
834
+ const key = (r) => String(r?.record_id ?? "").toLowerCase();
835
+ const standing = new Set((safeReadJson(P.registerNamedBand)?.enumerated ?? []).map(key));
836
+ const ids = [...new Set([...(safeReadJson(refusedRecordsPath(P))?.ids ?? []),
837
+ ...(band?.enumerated ?? []).map(key).filter((id) => id && !standing.has(id))])];
838
+ atomicWrite(refusedRecordsPath(P), JSON.stringify({ ids, count: ids.length }, null, 2) + "\n");
839
+ return ids.length;
840
+ } catch (e) {
841
+ runLog(P.runDir, { event: "band-refused-records-failed", reason: String(e?.message ?? e).slice(0, 160) });
842
+ return null;
843
+ }
844
+ }
845
+
846
+ /**
847
+ * The records a refused merge left out that are still outside the named band: a record a later merge took in is read,
848
+ * and is not counted as left out. Never throws.
849
+ */
850
+ export function refusedRecordsLeftOut(P) { // @internal — driven by its test with a run's own files
851
+ const ids = safeReadJson(refusedRecordsPath(P))?.ids;
852
+ if (!Array.isArray(ids) || !ids.length) return [];
853
+ const inBand = new Set((safeReadJson(P.registerNamedBand)?.enumerated ?? []).map((r) => String(r?.record_id ?? "").toLowerCase()));
854
+ return ids.map((x) => String(x).toLowerCase()).filter((id) => id && !inBand.has(id));
855
+ }
856
+
857
+ /** The named band as it is written to disk: what a dispatch over it measures. */
858
+ const namedBandText = (band) => JSON.stringify(band, null, 2) + "\n";
859
+
860
+ export function deriveNamedBand(ctx, merged = null) { // @internal — driven by its test with a run's own files
749
861
  const P = ctx.paths;
750
862
  const axes = ctx.axes ?? [];
751
- const { band, axes: ok, invalid } = mergeRegisterBands(P, axes);
863
+ const { band, axes: ok, invalid } = merged ?? mergeRegisterBands(P, axes); // a caller that measured the merge first hands it in
864
+ const mergedFrom = axisBandsFingerprint(P, axes);
752
865
  if (invalid.length) {
753
866
  const detail = invalid.map((x) => `${x.axis}:${x.reason}`).join("; ");
754
867
  runLog(P.runDir, { event: "named-band-invalid", invalid });
@@ -756,8 +869,9 @@ function deriveNamedBand(ctx) {
756
869
  }
757
870
  try {
758
871
  const tmp = P.registerNamedBand + ".tmp";
759
- writeFileSync(tmp, JSON.stringify(band, null, 2) + "\n");
872
+ writeFileSync(tmp, namedBandText(band));
760
873
  renameSync(tmp, P.registerNamedBand);
874
+ ctx.bandsMergedAt = mergedFrom;
761
875
  runLog(P.runDir, { event: "named-band-merged", axes: ok, enumerated: band.enumerated.length, crowds: band.crowds.length });
762
876
  } catch (e) {
763
877
  note(`named-band merge write failed (non-fatal): ${String(e.message).slice(0, 100)}`);
@@ -923,6 +1037,7 @@ function recordDetailIndex(runDir) {
923
1037
 
924
1038
  function deriveBandShape(ctx, band) {
925
1039
  const P = ctx.paths;
1040
+ let evidenceDoc = null;
926
1041
  try {
927
1042
  const targets = [];
928
1043
  const markNames = Array.isArray(ctx.job?.marks)
@@ -959,7 +1074,13 @@ function deriveBandShape(ctx, band) {
959
1074
  runLog(P.runDir, { event: "register-positions-failed", fail: String(e?.message ?? e).slice(0, 160) });
960
1075
  note(`register-positions derivation failed (non-fatal — the per-registration band stands): ${String(e?.message ?? e).slice(0, 100)}`);
961
1076
  }
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) });
1077
+ const shapeGoods = bandShapeGoods(P, band?.enumerated ?? []);
1078
+ // What each record's goods rest on, derived first so the shape says it of each line the goods test could not
1079
+ // place; the same record is written beside the shape below.
1080
+ try { evidenceDoc = goodsEvidenceDocFor(ctx, band, shapeGoods.goods); } catch { evidenceDoc = null; }
1081
+ const evidenceBy = evidenceDoc ? new Map(evidenceDoc.records.map((r) => [r.uri, r])) : null;
1082
+ const evidenceOf = evidenceBy ? (r) => evidenceBy.get(normalizeRecordUri(r?.record_id) || String(r?.record_id ?? "")) ?? null : null;
1083
+ const { shape, md } = buildBandShape(band, { 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
1084
  mkdirSync(dirname(P.bandShape), { recursive: true });
964
1085
  atomicWrite(P.bandShape, JSON.stringify(shape, null, 2) + "\n");
965
1086
  atomicWrite(P.bandShapeMd, md + "\n");
@@ -967,6 +1088,8 @@ function deriveBandShape(ctx, band) {
967
1088
  runLog(P.runDir, { event: "band-shape-derived", records: shape.totals.records, crowds: shape.totals.crowds,
968
1089
  tiers: shape.totals.by_tier, floors: shape.floors.in_class_identical_or_near.length,
969
1090
  goods_test: tested.source, goods_test_words: tested.words.length,
1091
+ // what each record's goods rest on: a held body, its band row's whole statements, statements cut short, or none
1092
+ goods_evidence: goodsEvidence(band?.enumerated ?? [], shapeGoods.goods),
970
1093
  blind_spots: shape.blind_spots.map((b) => ({ kind: b.kind, count: b.count })) });
971
1094
  // The goods test falls back where the variants stage named no product words; the run says so.
972
1095
  if (tested.source !== "product_words")
@@ -975,6 +1098,48 @@ function deriveBandShape(ctx, band) {
975
1098
  runLog(P.runDir, { event: "band-shape-failed", fail: String(e?.message ?? e).slice(0, 200) });
976
1099
  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
1100
  }
1101
+ recordGoodsEvidenceFor(ctx, band, evidenceDoc);
1102
+ // Placement's standing decisions are read off the shape, so a shape derived again can change them with no placement
1103
+ // pass: a group's row checked again on the new shape may cover other lines, or stand no longer, and the floors it
1104
+ // leaves can grow. The decisions file the reading step, the writer and the review declare is written again with the
1105
+ // shape, so such a change makes a stage that already read them stale. Only once placement has written it.
1106
+ if (existsSync(P.placementDecisions)) {
1107
+ try { writePlacementDecisions(P); }
1108
+ catch (e) { runLog(P.runDir, { event: "placement-decisions-failed", at: "band-shape", reason: String(e?.message ?? e).slice(0, 120) }); }
1109
+ }
1110
+ }
1111
+
1112
+ /**
1113
+ * WHAT EACH BAND RECORD'S GOODS REST ON, written beside the shape after every derivation (goods-evidence.mjs): the
1114
+ * goods reader the shape tests with, given the band's records so that a reader which reads a band row's own goods
1115
+ * reads them here too; the official records the run holds, with where each came from; and this run's record fetches,
1116
+ * from the register call ledger. The shape says the same of each line from the same derivation (its goods cell and
1117
+ * `goods_evidence`); the file itself is served by no band tool, gates nothing, and a failure costs the run this
1118
+ * record and nothing else.
1119
+ */
1120
+ function goodsEvidenceDocFor(ctx, band, goods = null) {
1121
+ const P = ctx.paths;
1122
+ const runPrefix = ctx.run?.slug && ctx.run?.codename ? `clearance-${ctx.run.slug}-${ctx.run.codename}-` : null;
1123
+ const calls = runPrefix ? readRunCallRows(DEFAULT_LEDGER_PATH, runPrefix) : { rows: [], error: null };
1124
+ const records = Array.isArray(band?.enumerated) ? band.enumerated : [];
1125
+ const doc = deriveGoodsEvidence({ records, goods: goods ?? bandShapeGoods(P, records).goods, held: heldRecordProvenance(P.runDir),
1126
+ fetches: recordFetchOutcomes(calls.rows, { runPrefix }) });
1127
+ // Whether fetches could be read is part of the record: without the ledger, "not held" may hide a failed fetch.
1128
+ doc.fetches_read = runPrefix ? (calls.error ? `the register call ledger could not be read: ${calls.error}` : true) : "no run prefix";
1129
+ return doc;
1130
+ }
1131
+
1132
+ function recordGoodsEvidenceFor(ctx, band, derived = null) {
1133
+ const P = ctx.paths;
1134
+ try {
1135
+ const doc = derived ?? goodsEvidenceDocFor(ctx, band);
1136
+ ensureDriverDir(P.runDir);
1137
+ atomicWrite(driverDir(P.runDir, GOODS_EVIDENCE_FILE), JSON.stringify(doc, null, 2) + "\n");
1138
+ runLog(P.runDir, { event: "goods-evidence", records: doc.totals.records, by_state: doc.totals.by_state,
1139
+ by_source: doc.totals.by_source, unreadable: doc.totals.unreadable, fetches_read: doc.fetches_read });
1140
+ } catch (e) {
1141
+ runLog(P.runDir, { event: "goods-evidence-failed", fail: String(e?.message ?? e).slice(0, 160) });
1142
+ }
978
1143
  }
979
1144
 
980
1145
  // The DETERMINISTIC GRID CONTRACT — extracted from pipelineInner unchanged (item).
@@ -1310,6 +1475,10 @@ export function composeDispatchExtra(name, ctx, opts = {}) { // @internal
1310
1475
  const parts = [], ids = [], failed = [], empty = [];
1311
1476
  for (const x of DISPATCH_EXTRAS) {
1312
1477
  if (x.stage !== name) continue;
1478
+ // `skip`: blocks a dispatch deliberately does not send — the review after a delivery-check repair leaves
1479
+ // out the plain-words list, which covers every line a client meets and points at a check that review is
1480
+ // not given. Skipped is not built and not failed: it appears in neither list.
1481
+ if (Array.isArray(opts.skip) && opts.skip.includes(x.id)) continue;
1313
1482
  try {
1314
1483
  const built = DISPATCH_EXTRA_BUILDERS[x.id](ctx, opts);
1315
1484
  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 +3144,15 @@ function derivePlacementCarry(ctx, trigger) {
2975
3144
  //
2976
3145
  // NEVER THROWS and never gates — same doctrine as the trace it feeds. A ledger that cannot be written
2977
3146
  // 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,
3147
+ function recordSeamDiscards(ctx, { seam, stage: stageName, trigger = null, completed = true, skipped = false,
2979
3148
  saw = [], carried = [], reasonFor = null, evidence = "" } = {}) {
2980
3149
  const P = ctx.paths;
2981
3150
  try {
2982
3151
  ctx._discardPass ??= {};
2983
3152
  const pass = (ctx._discardPass[seam] = (ctx._discardPass[seam] ?? 0) + 1);
2984
- const rows = seamRows({ seam, stage: stageName, trigger, pass, completed, saw, carried, reasonFor, evidence });
3153
+ // A skipped pass leaves standing what an earlier pass recorded (record-discard.mjs, `seamRows`).
3154
+ const prior = skipped ? foldDiscardLedger(safeReadText(driverDir(P.runDir, DISCARD_LEDGER_NAME))) : null;
3155
+ const rows = seamRows({ seam, stage: stageName, trigger, pass, completed, skipped, prior, saw, carried, reasonFor, evidence });
2985
3156
  const w = appendDiscardRows(P.runDir, rows);
2986
3157
  const discarded = rows.filter((r) => r.verdict === "discarded").length;
2987
3158
  runLog(P.runDir, {
@@ -3005,18 +3176,6 @@ function bandUriRows(P) {
3005
3176
  } catch { return []; }
3006
3177
  }
3007
3178
 
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
3179
  /**
3021
3180
  * PLACEMENT. Everything in the band was in front of this pass; what it placed is in placements.json.
3022
3181
  * Recorded whether the pass completed or not — an incomplete pass discards nothing, and its rows say so
@@ -3027,7 +3186,7 @@ function recordPlacementSeam(ctx, r, trigger = null) {
3027
3186
  const saw = bandUriRows(P);
3028
3187
  if (!saw.length) return null;
3029
3188
  return recordSeamDiscards(ctx, {
3030
- seam: "placement", stage: "placement-inquiry", trigger, completed: r?.ok === true && !r?.skipped,
3189
+ seam: "placement", stage: "placement-inquiry", trigger, completed: r?.ok === true && !r?.skipped, skipped: r?.skipped === true,
3031
3190
  saw, carried: placedUriRows(P),
3032
3191
  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
3192
  reasonFor: placementSeamReason(placementSetAsideGrounds(P)),
@@ -3035,28 +3194,75 @@ function recordPlacementSeam(ctx, r, trigger = null) {
3035
3194
  }
3036
3195
 
3037
3196
  /**
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.
3197
+ * WHAT ONE PLACEMENT PASS DID, RECORDED: the ground every band record left it with (the seam rows) and the floors
3198
+ * it answered (the floor duty). The main path and the delivery gate's repeat of the stage both record through
3199
+ * here, so the ledger and the floor duty describe the placements the report is built from. Returns the pass
3200
+ * result.
3201
+ *
3202
+ * THE STAMP IS THE MAIN PATH'S. On the main path, a pass that actually ran arms the floor its account may be held
3203
+ * to. A SKIPPED pass leaves `placements.json` written by an earlier seat (on a resume, possibly one that never saw
3204
+ * the floors order), so it arms nothing, and its seam rows leave the earlier pass's grounds standing. The delivery
3205
+ * gate's repeat (`stale-repair`) arms nothing either: the floor it is held to is the one the run was already held
3206
+ * to, applied again to the repeated pass's account where the gate rebuilds the report from it. A run whose only
3207
+ * pass under the floors order is that repeat is held to no floor, as before.
3043
3208
  */
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];
3209
+ export function recordPlacementPass(ctx, r, trigger = null) { // @internal — driven by its test with a run's own files
3210
+ recordPlacementSeam(ctx, r, trigger);
3211
+ deriveFloorDuty(ctx, r, trigger);
3212
+ // The decisions as they stand after this pass, where the reading step and the writer declare them: a pass that
3213
+ // changes only a set-aside's ground or a group's membership leaves a reader that already ran stale.
3214
+ try { writePlacementDecisions(ctx.paths); }
3215
+ catch (e) { runLog(ctx.paths.runDir, { event: "placement-decisions-failed", trigger, reason: String(e?.message ?? e).slice(0, 120) }); }
3216
+ if (r?.ok === true && r?.skipped !== true) recordPlacementBandIds(ctx.paths);
3217
+ if (r?.ok === true && r?.skipped !== true && trigger !== "stale-repair") armFloorDuty(ctx.paths.runDir);
3218
+ return r;
3050
3219
  }
3051
3220
 
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
3221
+ const bandRecordIds = (band) => [...new Set((band?.enumerated ?? []).map((r) => String(r?.record_id ?? "").toLowerCase()).filter(Boolean))];
3222
+
3223
+ /** The named band's record ids a placement pass that ran read, so a record the band gains after it is known as new. */
3224
+ function recordPlacementBandIds(P) {
3054
3225
  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; }
3226
+ const ids = bandRecordIds(safeReadJson(P.registerNamedBand)).sort();
3227
+ atomicWrite(driverDir(P.runDir, "placement-band-ids.json"), JSON.stringify({ count: ids.length, ids }) + "\n");
3228
+ } catch (e) { runLog(P.runDir, { event: "placement-band-ids-failed", reason: String(e?.message ?? e).slice(0, 120) }); }
3229
+ }
3230
+
3231
+ /**
3232
+ * The records the named band holds that the last placement pass that ran never read. [] when it read every one, and
3233
+ * when no pass recorded what it read (a run predating the record assesses nothing late). Ids, never the band's bytes:
3234
+ * a count that moved is not a record placement has not seen. PURE over the run's files.
3235
+ */
3236
+ export function lateRecordIds(P) { // @internal — driven by its test with a run's own files
3237
+ const seen = safeReadJson(driverDir(P.runDir, "placement-band-ids.json"));
3238
+ if (!Array.isArray(seen?.ids)) return [];
3239
+ const had = new Set(seen.ids);
3240
+ return bandRecordIds(safeReadJson(P.registerNamedBand)).filter((id) => !had.has(id));
3241
+ }
3242
+
3243
+ /**
3244
+ * THE LATE RECORDS THE REPORT DOES NOT ADDRESS: records the band holds that no placement pass read (`lateRecordIds`),
3245
+ * which neither the reading step's account nor the writer's findings name, and which no other clause names
3246
+ * (`alreadyNamed`, lowercased). A record either step names is addressed there, whatever placement did. Lowercased ids,
3247
+ * in the band's order. Never throws. PURE over the run's files.
3248
+ */
3249
+ export function lateRecordsUnaddressed(P, alreadyNamed = new Set()) { // @internal — driven by its test with a run's own files
3250
+ const late = lateRecordIds(P);
3251
+ if (!late.length) return [];
3252
+ let endings = { carried: new Set(), dropRows: new Set() };
3253
+ try { endings = parseFindingsEndings(readFileSync(P.registerFindings, "utf8")); } catch { /* no account from the reading step */ }
3254
+ let named = new Set();
3255
+ try { named = new Set([...findingUris(parseFindingsJsonLenient(readFileSync(P.findings, "utf8"))?.findings ?? []).keys()].map((u) => String(u).toLowerCase())); }
3256
+ catch { /* no findings from the writer */ }
3257
+ return late.filter((id) => !endings.carried.has(id) && !endings.dropRows.has(id) && !named.has(id) && !alreadyNamed.has(id));
3258
+ }
3259
+
3260
+ /** The records the writer was owed, by id: the population its duty is out of. Null without a spec. Never throws. */
3261
+ function owedUrisFor(runDir) {
3262
+ try {
3263
+ const owed = driverReadsFor(runDir).owed;
3264
+ return Array.isArray(owed) ? owed.map((row) => String(normalizeRecordUri(row?.uri ?? row?.record_id) ?? "").toLowerCase()).filter(Boolean) : null;
3265
+ } catch { return null; }
3060
3266
  }
3061
3267
 
3062
3268
  /**
@@ -3068,8 +3274,16 @@ export function placementOwnerFates(P, placements) { // @internal — driven b
3068
3274
  try {
3069
3275
  const input = readPlacementFormInput(P.runDir);
3070
3276
  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 : [])) });
3277
+ const index = buildSelectionIndex(input);
3278
+ // What placement read, by its own reading log, and what each line's goods rest on, where the run recorded it.
3279
+ const evidence = readGoodsEvidence(P.runDir);
3280
+ const stateOf = Array.isArray(evidence?.records) ? new Map(evidence.records.map((r) => [normalizeRecordUri(r?.uri), r?.state])) : null;
3281
+ const placedUris = (Array.isArray(placements) ? placements : []).flatMap((p) => (Array.isArray(p?.records) ? p.records : []));
3282
+ const held = readPlacementForm(P.runDir);
3283
+ const counted = ownerFates({ groups: input.groups, entries: standingSetAside(held.set_aside, input, { placedUris, rows: held.rows }), index,
3284
+ placedUris,
3285
+ served: servedLines(input.groups, input.served, index),
3286
+ lineEvidence: stateOf ? (l) => stateOf.get(normalizeRecordUri(l?.record_id)) ?? null : null });
3073
3287
  return counted ? { counted } : { absent: "the band shape's groups could not be counted" };
3074
3288
  } catch (e) { return { absent: `an input could not be read: ${String(e?.message ?? e).slice(0, 120)}` }; }
3075
3289
  }
@@ -3112,7 +3326,7 @@ function recordDigestSeam(ctx, r, trigger = null) {
3112
3326
  if (c === "carried") carried.push(row);
3113
3327
  }
3114
3328
  return recordSeamDiscards(ctx, {
3115
- seam: "digest", stage: "register-digest", trigger, completed: r?.ok === true && !r?.skipped,
3329
+ seam: "digest", stage: "register-digest", trigger, completed: r?.ok === true && !r?.skipped, skipped: r?.skipped === true,
3116
3330
  saw, carried,
3117
3331
  evidence: r?.fail ? String(r.fail).slice(0, 80) : "",
3118
3332
  reasonFor: (rec, uri) => {
@@ -3183,6 +3397,28 @@ function findingsSurfaceRows(P) {
3183
3397
  * `scope` is instructed-scope.json, JOB-authored before any model ran. It is the only thing the
3184
3398
  * contradiction refusals rest on, which is why it is read here rather than accepted from the seat.
3185
3399
  */
3400
+ // THE WRITE-UP IS TOLD WHAT EACH RECORD'S GOODS REST ON, beside its position (goods-evidence.mjs `goodsFromLabel`),
3401
+ // so a record declined on goods is declined knowing whether those goods were the official record's, a search answer's,
3402
+ // cut, or none. Only the dispatch's copy carries it; the spec the declination tool reads is the positions alone.
3403
+ // 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
3404
+ // source must not read as one whose records have none.
3405
+ export function withGoodsFrom(ctx, P, rows) { // @internal — driven by its test with a run's own files
3406
+ const unlabelled = (reason) => {
3407
+ try { runLog(P.runDir, { event: "write-up-goods-from-failed", rows: rows.length, reason: String(reason).slice(0, 120) }); } catch { /* the list still goes */ }
3408
+ return rows;
3409
+ };
3410
+ try {
3411
+ const runPrefix = ctx.run?.slug && ctx.run?.codename ? `clearance-${ctx.run.slug}-${ctx.run.codename}-` : null;
3412
+ if (!runPrefix) return unlabelled("the run has no session prefix to read its evidence by");
3413
+ const uris = rows.filter((r) => r?.kind !== "page" && r?.uri).map((r) => r.uri);
3414
+ const evidence = goodsEvidenceOfDrops(P, runPrefix, uris);
3415
+ return rows.map((r) => {
3416
+ const e = r?.kind !== "page" && r?.uri ? evidence.get(r.uri) : null;
3417
+ return e ? { ...r, goods_from: goodsFromLabel({ state: e.goods, source: e.source }) } : r;
3418
+ });
3419
+ } catch (e) { return unlabelled(e?.message ?? e); }
3420
+ }
3421
+
3186
3422
  export function prepareDeclinationSpec(ctx, P) { // @internal
3187
3423
  const rows = [...findingsSurfaceRows(P), ...notesPageRows(existsSync(P.commonLaw) ? readFileSync(P.commonLaw, "utf8") : null)]; // records, then the web notes' pages
3188
3424
  if (!rows.length) {
@@ -3200,7 +3436,7 @@ export function prepareDeclinationSpec(ctx, P) { // @internal
3200
3436
  ensureDriverDir(P.runDir);
3201
3437
  atomicWrite(driverDir(P.runDir, "declination-spec.json"),
3202
3438
  JSON.stringify({ ts: new Date().toISOString(), rows, scope: { marks: scope.marks ?? [], classes: scope.classes ?? [] } }, null, 2) + "\n");
3203
- ctx.findingsSurface = rows;
3439
+ ctx.findingsSurface = withGoodsFrom(ctx, P, rows);
3204
3440
  } catch (e) {
3205
3441
  // The dispatch block is gated on ctx.findingsSurface, so a failed write leaves the stage UNORDERED
3206
3442
  // rather than ordered to call a tool whose spec it cannot read. Loud in the log, never a dead order.
@@ -3214,8 +3450,17 @@ export function prepareDeclinationSpec(ctx, P) { // @internal
3214
3450
  * on a fenced or prose-wrapped file, and a synthesis pass whose output is merely untidy would otherwise
3215
3451
  * record every record it delivered as discarded — the defect rebuilt one seam later.
3216
3452
  */
3453
+ /** Where the ranking the writer last read is kept, for the sentence on records re-ranked after it wrote. */
3454
+ const rankingReadByWriterPath = (P) => driverDir(P.runDir, "ranking-read-by-writer.json");
3455
+ /** Keeps where each record stood when a writer's pass completed. Never throws. */
3456
+ function keepRankingReadByWriter(P) {
3457
+ try { atomicWrite(rankingReadByWriterPath(P), JSON.stringify(rankingToJson(rankingNow(P)), null, 2) + "\n"); }
3458
+ catch (e) { runLog(P.runDir, { event: "ranking-read-by-writer-failed", reason: String(e?.message ?? e).slice(0, 160) }); }
3459
+ }
3460
+
3217
3461
  function recordSynthesisSeam(ctx, r, trigger = null) {
3218
3462
  const P = ctx.paths;
3463
+ if (r?.ok) keepRankingReadByWriter(P); // where each record stood as this writer read it
3219
3464
  const declined = readDeclinations(P.runDir); // — three-valued: absent ledger ≠ empty ledger
3220
3465
  // — ONE DERIVATION, and it is CALLED rather than copied. This used to rebuild the carried set
3221
3466
  // inline, in three lines that happened to match `findingsSurfaceRows`'s. They must match: that function
@@ -3232,7 +3477,7 @@ function recordSynthesisSeam(ctx, r, trigger = null) {
3232
3477
  }
3233
3478
  } catch { /* validators.findings' business; this seam records what it can */ }
3234
3479
  return recordSeamDiscards(ctx, {
3235
- seam: "synthesis", stage: "synthesis", trigger, completed: r?.ok === true && !r?.skipped,
3480
+ seam: "synthesis", stage: "synthesis", trigger, completed: r?.ok === true && !r?.skipped, skipped: r?.skipped === true,
3236
3481
  saw, carried: delivered,
3237
3482
  evidence: r?.fail ? String(r.fail).slice(0, 80) : "",
3238
3483
  reasonFor: (rec, uri) => {
@@ -3618,7 +3863,8 @@ function deriveRecallReconciliation(ctx, trigger) {
3618
3863
  }
3619
3864
  }
3620
3865
  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 });
3866
+ const placement = placementRecallEndings(P, candidates.map((c) => c.record_id));
3867
+ const artifact = reconcileRecall({ candidates, endings, crowdRulings, positions, dominantElement, inScopeClasses, placement });
3622
3868
  writeArtifact(artifact);
3623
3869
  runLog(P.runDir, recallReconciliationEvent({ trigger, artifact }));
3624
3870
  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 +4682,22 @@ function clearedForSeat(runDir, label) {
4436
4682
  catch { return []; }
4437
4683
  }
4438
4684
 
4685
+ /**
4686
+ * The context a stage's message is composed from. The review's floor check lists placement's standing decisions, and
4687
+ * they are read here, from the run's files, each time its message is built: the review, its recheck, the delivery
4688
+ * loop's whole review after a repeated placement pass and a review dispatched from a rebuilt context all read the
4689
+ * decisions as they stand then, never a copy an earlier pass left on the context, and never "(none)" from a context
4690
+ * that never set one. The reading step and the writer read placement's accepted set-asides the same way. Every
4691
+ * other stage composes from the context as it is.
4692
+ */
4693
+ export function messageCtxFor(name, ctx) { // @internal — driven by its test
4694
+ if (!ctx?.paths) return ctx;
4695
+ if (name === "narrative-refutation") return { ...ctx, placementFloorDecisions: placementFloorDecisionLines(ctx.paths) };
4696
+ // The reading step and the writer are told placement's accepted set-asides the same way, read when the message is built.
4697
+ if (name === "register-digest" || name === "synthesis") return { ...ctx, placementSetAsides: placementSetAsideLines(ctx.paths) };
4698
+ return ctx;
4699
+ }
4700
+
4439
4701
  async function stageOnce(name, ctx, opts = {}) {
4440
4702
  const def = STAGES[name];
4441
4703
  if (!def) throw new Error(`unknown stage ${name}`);
@@ -4615,8 +4877,15 @@ async function stageOnce(name, ctx, opts = {}) {
4615
4877
  // is read HERE because this is the one site that composes a followup. NEVER-KILL: an unreadable
4616
4878
  // or absent seat log yields no constraint and the dispatch is exactly what it was before.
4617
4879
  cleared: clearedForSeat(P.runDir, label),
4880
+ // The review after a delivery-check repair names the files that repair changed; every other dispatch
4881
+ // passes nothing and keeps its own inputs line.
4882
+ changedInputs: opts.changedInputs ?? null,
4883
+ // The reading step's and the writer's fresh sessions are given placement's accepted set-asides as they stand,
4884
+ // except a narrow repair: it fixes only the faults or objects it names, and the general order to check each
4885
+ // ground would widen it. Its inputs, the decisions file among them, are named as for any other.
4886
+ decisions: NARROW_REPAIR_TRIGGERS.has(trigger) ? null : (messageCtxFor(name, ctx)?.placementSetAsides ?? null),
4618
4887
  })
4619
- : (def.message(ctx) + (opts.extra ? `\n\n${opts.extra}` : ""));
4888
+ : (def.message(messageCtxFor(name, ctx)) + (opts.extra ? `\n\n${opts.extra}` : ""));
4620
4889
  // Every stage runs as the forwarding agent (derived from the queue location). An explicit opts.agent wins.
4621
4890
  const execAgent = opts.agent ?? ctx.agent;
4622
4891
  // thinking: ONLY Anthropic models get a --thinking token; a cross-provider model (gpt/gemini) NEVER does, so
@@ -4683,7 +4952,7 @@ async function stageOnce(name, ctx, opts = {}) {
4683
4952
  // seat answering something it was no longer asked about is served from the ledger rather than refused
4684
4953
  // as out-of-list — and `still_open` keeps naming everything genuinely open.
4685
4954
  ...(patch == null && typeof def.refreshCtx === "function"
4686
- ? { refreshMessage: () => def.message(def.refreshCtx(ctx)) + (opts.extra ? `\n\n${opts.extra}` : "") }
4955
+ ? { refreshMessage: () => def.message(messageCtxFor(name, def.refreshCtx(ctx))) + (opts.extra ? `\n\n${opts.extra}` : "") }
4687
4956
  : {}),
4688
4957
  // #5b: a followup is a warm session resume — tell runStage so a hard-wall timeout breaks after ONE attempt
4689
4958
  // (an already-over-budget resume can't fit the 1.5× extension; this restores ~1× the wall, was ~2.5×).
@@ -5972,7 +6241,7 @@ const UPSTREAM_STALE_REPAIR = {
5972
6241
  // re-run could leave outvoting its prose, and it is re-runnable by contract (no one-shot receipt — the
5973
6242
  // assertReceiptSettled call ahead of its normal dispatch settles the plan receipt, which is already
5974
6243
  // settled by the time the delivery gate asks).
5975
- "placement-inquiry": (ctx) => stage("placement-inquiry", ctx, { force: true, trigger: "stale-repair" }),
6244
+ "placement-inquiry": async (ctx) => recordPlacementPass(ctx, await stage("placement-inquiry", ctx, { force: true, trigger: "stale-repair" }), "stale-repair"),
5976
6245
  // frame-diff is DELIBERATELY ABSENT and must stay absent. It is one-shot by contract (no in-process
5977
6246
  // re-diff), so an entry here would re-dispatch a stage the design says runs once, for a model call
5978
6247
  // that cannot change anything: nothing declares frame-diff's outputs as a stage input, so by the time
@@ -5994,14 +6263,16 @@ export function partitionDeliveryStale(staleStages) { // @internal
5994
6263
  // file is byte-unchanged (or every named finding's object is) and no corrections marker landed, fire
5995
6264
  // ONE warm followup demanding the re-emit; a terminal miss NEVER withholds (never-withhold) — it logs,
5996
6265
  // flags the internal review bar, and closes the CLIENT export via _driver/corrections-state.json.
5997
- function snapshotFindingsForCorrections(P, runDir) {
6266
+ export function snapshotFindingsForCorrections(P, runDir) { // @internal
5998
6267
  let raw = null;
5999
6268
  try { raw = readFileSync(P.findings, "utf8"); } catch { /* legacy / not-yet-populated */ }
6000
6269
  if (raw == null) return null;
6001
6270
  const sha = createHash("sha256").update(raw).digest("hex");
6002
6271
  // THE RECORD IN THE MODEL'S NUMBERING, beside the one on disk. The review's flags name findings in the
6003
6272
  // 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.
6273
+ // record is folded the two differ (record-fold.mjs). The fold is held too, for a rollback to put back,
6274
+ // and so are narrative.md and the transport's merge base (`base`): a rollback that put back findings.json
6275
+ // alone left the next patch to merge onto the failed pass's record.
6005
6276
  const modelAt = modelRecordPath(runDir, P.findings);
6006
6277
  let modelRaw = raw;
6007
6278
  if (modelAt !== P.findings) { try { modelRaw = readFileSync(modelAt, "utf8"); } catch { modelRaw = raw; } }
@@ -6016,7 +6287,7 @@ function snapshotFindingsForCorrections(P, runDir) {
6016
6287
  writeFileSync(tmp, JSON.stringify({ ts: new Date().toISOString(), sha, text: raw }, null, 2));
6017
6288
  renameSync(tmp, driverDir(runDir, "findings-pre-corrective.json"));
6018
6289
  } catch { /* advisory sidecar */ }
6019
- return { raw, sha, model, fold };
6290
+ return { raw, sha, model, fold, base: snapshotSynthesisBase(runDir) };
6020
6291
  }
6021
6292
  /**
6022
6293
  *, T3b — THE CORRECTIVE PASS FAILED VALIDATION. RESTORE THE LAST GOOD STATE.
@@ -6042,6 +6313,11 @@ function snapshotFindingsForCorrections(P, runDir) {
6042
6313
  * is the document every earlier stage's output was checked against. It is not a guess at a good state;
6043
6314
  * it is the last one this run demonstrated.
6044
6315
  *
6316
+ * The narrative beside it and the transport's merge base come back with it, whether or not findings.json
6317
+ * moved: a pass can save a narrative change alone, and a base left at the failed pass's record is what the
6318
+ * next patch would merge onto, which applies the discarded pass again. The snapshot holds both
6319
+ * (`snapshotFindingsForCorrections`); a snapshot taken without them leaves both files as they are.
6320
+ *
6045
6321
  * ── TWO CASES THAT MUST STILL THROW, BECAUSE SWALLOWING THEM LOSES MORE THAN IT SAVES ────────────
6046
6322
  *
6047
6323
  * rate-limited The run is PARKED and auto-resumes after the cap resets, with every stage intact.
@@ -6066,8 +6342,10 @@ export function rollbackCorrectivePass(P, runDir, pre, fail) { // @internal
6066
6342
  atomicWrite(P.findings, pre.raw);
6067
6343
  if (pre.fold) restoreFold(runDir, pre.fold); // the record the model wrote and the map, as they stood
6068
6344
  }
6345
+ // `restored` keeps speaking for findings.json alone; what happened to the narrative and the base is its own field.
6346
+ const base = restoreSynthesisBase(runDir, pre.base);
6069
6347
  const record = { ts: new Date().toISOString(), reason: String(fail?.fail ?? "failed").slice(0, 200),
6070
- restored: changed, preSha: pre.sha, failedSha: nowSha };
6348
+ restored: changed, preSha: pre.sha, failedSha: nowSha, ...(base ? { base } : {}) };
6071
6349
  try { atomicWrite(driverDir(runDir, "corrective-rollback.json"), JSON.stringify(record, null, 2) + "\n"); }
6072
6350
  catch { /* sidecar is best-effort; the runLog event below is the record that decides */ }
6073
6351
  return record;
@@ -6154,26 +6432,35 @@ export function repairUnnamedRemovals(P, runDir, pre, namedOrdinals, namedMarks,
6154
6432
 
6155
6433
  if (!restoredFindings.length && !restoredKeys.length && !restoredRows.length) return null;
6156
6434
 
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
- }
6435
+ // ONE RESTORATION, applied to a record in the model's numbering: the file's here, and the merge base's
6436
+ // below. Whatever a record already holds stays as it holds it; only what came back is added.
6437
+ const byOrdinal = new Map(preList.map((f, i) => [String(f?.ordinal), i]));
6438
+ const restoreInto = (doc) => {
6439
+ const merged = { ...doc };
6440
+ for (const k of restoredKeys) if (!(k in merged)) merged[k] = preDoc[k];
6441
+ for (const [reg, back] of Object.entries(rowsBack)) {
6442
+ // In the snapshot's order, the post-pass version of every row it kept, then any row the pass added.
6443
+ const keyOf = REPORT_LINE_KEY[reg];
6444
+ const now = Array.isArray(doc[reg]) ? doc[reg] : [];
6445
+ const post = new Map(now.map((r) => [keyOf(r), r]));
6446
+ const restored = new Set(back);
6447
+ const inPre = new Set(preDoc[reg].map(keyOf));
6448
+ merged[reg] = [
6449
+ ...preDoc[reg].flatMap((r) => restored.has(r) ? [r] : post.has(keyOf(r)) ? [post.get(keyOf(r))] : []),
6450
+ ...now.filter((r) => !inPre.has(keyOf(r))),
6451
+ ];
6452
+ }
6453
+ if (restoredFindings.length) {
6454
+ // Back in the order the reviewer read them, by ordinal — a restored finding appended to the end
6455
+ // would be a different document from the one that was correct.
6456
+ const list = Array.isArray(doc.findings) ? doc.findings : [];
6457
+ const has = new Set(list.map((f) => String(f?.ordinal)));
6458
+ merged.findings = [...list, ...restoredFindings.filter((f) => !has.has(String(f?.ordinal)))]
6459
+ .sort((a, b) => (byOrdinal.get(String(a?.ordinal)) ?? 0) - (byOrdinal.get(String(b?.ordinal)) ?? 0));
6460
+ }
6461
+ return merged;
6462
+ };
6463
+ const merged = restoreInto(postDoc);
6177
6464
  const brief = (f) => ({ ordinal: f?.ordinal ?? null, mark: f?.mark ?? null });
6178
6465
  const record = {
6179
6466
  ts: new Date().toISOString(),
@@ -6184,11 +6471,33 @@ export function repairUnnamedRemovals(P, runDir, pre, namedOrdinals, namedMarks,
6184
6471
  };
6185
6472
  atomicWrite(P.findings, JSON.stringify(merged, null, 2) + "\n");
6186
6473
  foldAfterSave(runDir, P.findings); // after the seam the repaired record is folded again, as a save's is
6474
+ // THE MERGE BASE TAKES THE SAME RESTORATION. The patch that follows (the corrections gate's re-emit) merges
6475
+ // onto the base and re-renders the file from it, so a restore written to the file alone is dropped again.
6476
+ // The edit is judged as a call is; a refusal leaves the base as it was, and the record says so.
6477
+ record.base = rebaseSynthesis(runDir, restoreInto);
6187
6478
  try { atomicWrite(driverDir(runDir, "corrective-repair.json"), JSON.stringify(record, null, 2) + "\n"); }
6188
6479
  catch { /* sidecar is best-effort; the runLog event is the record that decides */ }
6189
6480
  return record;
6190
6481
  }
6191
6482
 
6483
+ /**
6484
+ * THE REPAIR'S RUN-LOG ROW, built from what `repairUnnamedRemovals` returned. PURE.
6485
+ *
6486
+ * `base` says what the merge base took, in the words the basis derivation's row uses for the same edit:
6487
+ * `restored`, or `unchanged` where the base already held what came back; `refused` where the acceptance boundary
6488
+ * refused the edit and the base was left as it was, so the next accepted patch drops the restore again; and
6489
+ * `no-base` where the run holds no base, so a later patch is refused for having none. The sidecar keeps the reason.
6490
+ */
6491
+ export function removalRepairedRow(repaired) { // @internal
6492
+ const b = repaired?.base;
6493
+ const base = !b ? null : b.ok ? (b.changed ? "restored" : "unchanged") : /^no_base:/.test(String(b.reason)) ? "no-base" : "refused";
6494
+ return { event: "corrective-unnamed-removal-repaired", defect: true,
6495
+ restored: repaired.restoredFindings.length, keys: repaired.restoredKeys.length,
6496
+ rows: repaired.restoredRows.length,
6497
+ leftRemoved: repaired.leftRemoved.length,
6498
+ findings: repaired.restoredFindings.map((f) => `${f.ordinal}:${f.mark}`), ...(base ? { base } : {}) };
6499
+ }
6500
+
6192
6501
  /**
6193
6502
  * The ordinals the reviewer's flags name, from the typed call where the run has one and the rendered
6194
6503
  * parse where it does not — the same two sources, in the same order, that `correctionsExtra` uses to
@@ -6240,7 +6549,9 @@ export function restoredFindingsTable(repair) { // @internal
6240
6549
  function correctionNamedOrdinals(P) {
6241
6550
  const out = [];
6242
6551
  try {
6243
- const typed = readAcceptedFlags(P.runDir);
6552
+ // `P.reviewFlags`: the pass after a delivery-check repair applies a LATER review's flags, which
6553
+ // `readAcceptedFlags` (the first review's call) cannot see. Absent everywhere else.
6554
+ const typed = Array.isArray(P.reviewFlags) ? P.reviewFlags : readAcceptedFlags(P.runDir);
6244
6555
  if (typed) { for (const f of typed) if (Array.isArray(f?.on)) out.push(...f.on); return out; }
6245
6556
  } catch { /* fall through to the rendered parse, exactly as the worklist does */ }
6246
6557
  try {
@@ -6301,6 +6612,426 @@ function correctionNamedSet(P, doc0 = null) {
6301
6612
  }
6302
6613
  return [...named];
6303
6614
  }
6615
+ // ── THE REVIEW AFTER A DELIVERY-CHECK REPAIR, AND ITS ONE CORRECTION ─────────────────────────────────────
6616
+ //
6617
+ // When the delivery check finds a fault in the narrative, the writer repairs its record and the narrative's
6618
+ // bytes move, so the reviewer reads stale. It used to be sent the whole review again, cold, and nothing applied
6619
+ // what it flagged then. Now, where the scope can be established, it reads what the repair changed and every
6620
+ // statement resting on it, worked out by value from the writer's record before and after the repair
6621
+ // (repair-scope.mjs), and its flags go to ONE correction by the writer that nothing reviews again.
6622
+ //
6623
+ // THE FIRST REVIEW STAYS WHOLE, and so does this one whenever the scope is not certain: no record kept from
6624
+ // before the repair, another of the reviewer's inputs moved, a whole re-synthesis ran in this pass, or the
6625
+ // record's version or framework changed. A rewrite with the same values is no change and no review runs.
6626
+
6627
+ /** Which review the reviewer's staleness after a delivery-check repair calls for. Reads the run; never throws. */
6628
+ /**
6629
+ * A SAVE TRIED AGAIN BEFORE IT IS GIVEN UP. Calls `save` up to `attempts` times, waiting `waitMs` between tries, and
6630
+ * says how it went: `{ ok, attempts, error }`. Never throws. `wait` is injectable so a test does not sleep.
6631
+ */
6632
+ 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
6633
+ let error = null;
6634
+ for (let n = 1; n <= attempts; n++) {
6635
+ try { save(); return { ok: true, attempts: n, error }; }
6636
+ catch (e) { error = e; if (n < attempts) wait(waitMs); }
6637
+ }
6638
+ return { ok: false, attempts, error };
6639
+ }
6640
+
6641
+ /**
6642
+ * THE STAGES STALE ON WHAT PLACEMENT WROTE, which join the chain of a changed decision whoever moved it: the delivery
6643
+ * check's own repeat, the decisions written again when the shape is derived again, or a late pass whose readers had
6644
+ * already run. `names` are the base names of placement's files. PURE.
6645
+ */
6646
+ export function stagesStaleOnPlacement(stale = [], names = new Set()) { // @internal — driven by its test
6647
+ return stale.filter((x) => (x?.changed ?? []).some((c) => names.has(String(c?.name ?? "")))).map((x) => x.label);
6648
+ }
6649
+
6650
+ /**
6651
+ * A CLAUSE JUDGED AGAIN REPLACES THE ONE IT REPEATS, BY ITS KIND. Withdraws every entry whose reason opens with
6652
+ * `<token>:` from the index-aligned reasons and clauses, and a withdrawn clause from the guard set once no entry left
6653
+ * carries it. Returns the arrays as they now stand and how many entries went. PURE.
6654
+ */
6655
+ export function withdrawByToken(reasons = [], clauses = [], guard = new Set(), token) { // @internal — driven by its test
6656
+ const keep = reasons.map((r) => !String(r ?? "").startsWith(`${token}:`));
6657
+ const out = { reasons: reasons.filter((_, i) => keep[i]), clauses: clauses.filter((_, i) => keep[i]), withdrawn: keep.filter((k) => !k).length };
6658
+ for (const [i, c] of clauses.entries()) if (!keep[i] && !out.clauses.includes(c)) guard.delete(c);
6659
+ return out;
6660
+ }
6661
+
6662
+ /**
6663
+ * 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
6664
+ * and was not before the repair, or is stale on an input that has moved since; a stage stale before for the same reasons
6665
+ * 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.
6666
+ * Of those, a stage already queued ahead runs where it stands, and one this pass already re-did is re-done once more at
6667
+ * most. PURE.
6668
+ */
6669
+ export function whatTheRepairLeftStale({ before = [], after = [], repaired, ahead = new Set(), processed = new Set(), requeued = new Set() }) { // @internal — driven by its test
6670
+ const keys = (x) => (x?.changed ?? []).map((c) => `${c?.name}@${c?.now}`);
6671
+ const was = new Map(before.map((x) => [x.label, new Set(keys(x))]));
6672
+ const chain = [], queue = [], requeue = [];
6673
+ for (const x of after) {
6674
+ if (x.label === repaired) continue;
6675
+ const prior = was.get(x.label);
6676
+ if (prior && keys(x).every((k) => prior.has(k))) continue; // stale before, for the same reasons: not this repair's
6677
+ chain.push(x.label);
6678
+ if (ahead.has(x.label)) continue;
6679
+ if (processed.has(x.label)) { if (requeued.has(x.label)) continue; requeue.push(x.label); }
6680
+ queue.push(x);
6681
+ }
6682
+ return { chain, queue, requeue };
6683
+ }
6684
+
6685
+ export function postRepairScope(runDir, P, s2, { staleRepaired = [], baseKept = false } = {}) { // @internal
6686
+ if (!baseKept) return { mode: "whole", why: "no-repair-base" };
6687
+ const narrative = basename(String(P?.narrative ?? "narrative.md"));
6688
+ const moved = (s2?.changed ?? []).filter((c) => c?.via !== "repair-record" && basename(String(c?.name ?? "")) !== narrative);
6689
+ if (moved.length) return { mode: "whole", why: "another-input-moved", moved: moved.map((c) => String(c?.name ?? "")) };
6690
+ if (staleRepaired.includes("synthesis")) return { mode: "whole", why: "re-synthesised" };
6691
+ // Any other step this loop re-ran ahead of the reviewer (the digest, placement, the skeptic) moved what the review
6692
+ // stands on beyond the repair's own change, so the scope the queue captured no longer says what changed.
6693
+ const upstream = staleRepaired.filter((l) => l !== "narrative-refutation" && !/^report-(overview|card:)/.test(l));
6694
+ if (upstream.length) return { mode: "whole", why: "upstream-re-run", upstream };
6695
+ let before = null, after = null;
6696
+ try { before = readRepairBase(runDir); after = lastAcceptedCall(runDir); } catch { /* read as no base */ }
6697
+ const changed = changedContent(before, after);
6698
+ if (changed.whole) return { mode: "whole", why: changed.why };
6699
+ if (isEmptyChange(changed)) return { mode: "none", why: "repair-changed-no-content" };
6700
+ return { mode: "narrowed", changed, after, findingOnly: queuedForTheRecordOnly(s2) };
6701
+ }
6702
+
6703
+ /**
6704
+ * Whether the delivery-check repair's change to the findings record is owed a review when the narrative did not move:
6705
+ * any change but the same values saved again. A change the scope cannot narrow (a moved version or framework, or a
6706
+ * record that could not be compared) is owed one too, and the loop gives it the whole review. Never throws.
6707
+ */
6708
+ export function recordChangeOwesReview(runDir) { // @internal
6709
+ let c = null;
6710
+ try { c = changedContent(readRepairBase(runDir), lastAcceptedCall(runDir)); } catch { c = null; }
6711
+ return !isEmptyChange(c);
6712
+ }
6713
+
6714
+ /**
6715
+ * 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
6716
+ * parts, which part it is and the questions that part holds, so two parts of one request never read as one line.
6717
+ */
6718
+ export function openAskForClosure(a) { // @internal — driven by its test
6719
+ return { ask_id: a.ask_id, born: a.born, ask: a.ask,
6720
+ ...(a.part ? { part: a.part, qids: Array.isArray(a.qids) ? a.qids : [] } : {}), ...(a.family ? { family: a.family } : {}) };
6721
+ }
6722
+
6723
+ /** The change a whole review's record lists when its scope could not be compared: none. */
6724
+ const NO_CHANGE = Object.freeze({ findings: [], lines: [], registers: [], sections: [], whole: true, why: "not-compared" });
6725
+
6726
+ /** Whether the reviewer was queued for a change to the findings record only: its narrative did not move. */
6727
+ function queuedForTheRecordOnly(s2) {
6728
+ const changed = s2?.changed ?? [];
6729
+ return changed.length > 0 && changed.every((c) => c?.via === "repair-record");
6730
+ }
6731
+
6732
+ /** What the record of an unread review says its change was: findings only, or more. null with no unread record. */
6733
+ function unreadRecordFindingOnly(runDir) {
6734
+ try {
6735
+ const r = JSON.parse(readFileSync(postRepairRecordPath(runDir), "utf8"));
6736
+ return r?.status === "unread" && typeof r.findingOnly === "boolean" ? r.findingOnly : null;
6737
+ } catch { return null; }
6738
+ }
6739
+
6740
+ /**
6741
+ * A review after a repair that could not be read, ON RECORD. Its change went unreviewed, and the reviewer's own stamp
6742
+ * cannot be trusted to say so: a findings-only change does not move the files the reviewer declares, and a stage
6743
+ * that ends with no accepted call still reads ok whenever the earlier review's file stands, so its stamp is
6744
+ * refreshed over the repaired narrative. This record is never handed to the delivery guard: a run is not held on an
6745
+ * unread review (owner's ruling, 2026-10-19), and such a run delivers as the released engine delivers it. The one
6746
+ * boundary is the released engine's own: a reviewer whose re-run fails outright leaves its stamp stale on a changed
6747
+ * narrative, and the freshness guard decides that, as it always has. The record keeps what went unreviewed, and a
6748
+ * resume for any other cause reviews that change whole, until a review that is read replaces the record.
6749
+ */
6750
+ export function unreadRecordReview(runDir) { // @internal
6751
+ try { return JSON.parse(readFileSync(postRepairRecordPath(runDir), "utf8"))?.status === "unread"; }
6752
+ catch { return false; }
6753
+ }
6754
+
6755
+ const reviewBeforeRepairPath = (runDir) => driverDir(runDir, "review-before-repair.md");
6756
+ const reviewAfterRepairPath = (runDir) => driverDir(runDir, "review-after-repair.md");
6757
+ const postRepairCorrectionsPath = (runDir) => driverDir(runDir, "post-repair-corrections.json");
6758
+ const scopeRecord = (scope, items) => ({
6759
+ findingOnly: Boolean(scope.findingOnly),
6760
+ changed: { findings: scope.changed.findings.map((f) => ({ ordinal: f.ordinal, change: f.change, fields: f.fields })),
6761
+ lines: scope.changed.lines.map((l) => ({ area: l.area, change: l.change })),
6762
+ sections: scope.changed.sections.map((x) => x.title), registers: scope.changed.registers.map((r) => r.label) },
6763
+ items: items.map((i) => ({ kind: i.kind, role: i.role, label: i.label, ...(i.delivered != null ? { delivered: i.delivered } : {}) })),
6764
+ });
6765
+
6766
+ /**
6767
+ * The narrowed review. Keeps the whole review aside first, so whatever this one writes, the run can put the
6768
+ * whole review back: `senior-eye-review.md` holds the review whose verdict the run carries, and the caller
6769
+ * decides that after the late-verdict rule. Returns `{reviewed: false}` when no accepted review came back —
6770
+ * the label then stays stale and the delivery guard decides, as for a whole review that failed.
6771
+ */
6772
+ async function runPostRepairReview(ctx, P, run, scope) {
6773
+ const runDir = run.runDir;
6774
+ const kept = existsSync(P.seniorEyeReview) ? readFileSync(P.seniorEyeReview, "utf8") : null;
6775
+ if (kept != null) atomicWrite(reviewBeforeRepairPath(runDir), kept);
6776
+ const putBack = () => {
6777
+ if (kept == null) return;
6778
+ let now = null;
6779
+ try { now = readFileSync(P.seniorEyeReview, "utf8"); } catch { now = null; }
6780
+ if (now !== kept) atomicWrite(P.seniorEyeReview, kept);
6781
+ };
6782
+ const seq0 = nextRefutationSeq(runDir);
6783
+ const fold = readFold(runDir);
6784
+ const resting = restingOn(scope.changed, scope.after);
6785
+ const items = scopeItems(scope.changed, resting, fold?.map);
6786
+ const list = listForReview(scope.changed, resting, fold?.map);
6787
+ const changedInputs = changedInputsOf(scope.changed, { narrative: P.narrative, findings: P.findings });
6788
+ const message = repairFollowup("narrative-refutation:post-repair-review", {
6789
+ findings: P.findings, ...list, registerOnly: Boolean(ctx.registerOnly), job: ctx.job ?? {},
6790
+ skepticFlags: P.skepticFlags, frameReopenReceipt: P.frameReopenReceipt, floorDecisions: placementFloorDecisionLines(P),
6791
+ });
6792
+ const { text: blocks } = composeDispatchExtra("narrative-refutation", ctx, { skip: ["refute-plain-register"] });
6793
+ const t0 = Date.now();
6794
+ const r = await stage("narrative-refutation", ctx, {
6795
+ force: true, trigger: "post-repair-review", freshMessage: message, ...(blocks ? { extra: blocks } : {}), changedInputs,
6796
+ });
6797
+ const rec = r?.ok ? readAcceptedReviewSince(runDir, seq0) : null;
6798
+ const base = { seq0, ...scopeRecord(scope, items), changedInputs: changedInputs.map((x) => basename(String(x))), ms: Date.now() - t0 };
6799
+ if (!rec) {
6800
+ // NOT A PASS: no review the driver can act on came back. The whole review goes back in place, the record
6801
+ // says the review could not be read, and the label stays stale for the delivery guard.
6802
+ putBack();
6803
+ writePostRepairRecord(runDir, { status: "unread", ...base, fail: r?.ok ? "no-accepted-call" : String(r?.fail ?? "failed") });
6804
+ runLog(runDir, { event: "post-repair-review", read: false, fail: r?.ok ? "no-accepted-call" : String(r?.fail ?? "failed").slice(0, 120) });
6805
+ 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`);
6806
+ return { reviewed: false };
6807
+ }
6808
+ const reviewText = readFileSync(P.seniorEyeReview, "utf8");
6809
+ atomicWrite(reviewAfterRepairPath(runDir), reviewText);
6810
+ const points = classifyPoints(rec.flags, items);
6811
+ const by = points.reduce((a, x) => ((a[x.class] = (a[x.class] ?? 0) + 1), a), {});
6812
+ writePostRepairRecord(runDir, { status: "read", ...base, verdict: rec.verdict, seq: rec.seq,
6813
+ 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 ?? "") })) });
6814
+ runLog(runDir, { event: "post-repair-review", read: true, verdict: rec.verdict, flags: rec.flags.length, classes: by,
6815
+ changed: { findings: scope.changed.findings.length, lines: scope.changed.lines.length, sections: scope.changed.sections.length, registers: scope.changed.registers.length },
6816
+ findingOnly: Boolean(scope.findingOnly), ms: base.ms });
6817
+ return { reviewed: true, rec, reviewText, items, points, putBack, base };
6818
+ }
6819
+
6820
+ /**
6821
+ * THE WHOLE REVIEW OF A REPAIR'S CHANGE: when the narrowed one could not be read, and when the change cannot be
6822
+ * narrowed at all (a change to the record the scope cannot compare, or one an earlier review could not read, as on
6823
+ * a resume). It returns what runPostRepairReview returns, so its points go to the same one correction; adopting its
6824
+ * verdict alone would leave its flags unapplied. When it cannot be read, the record says the change was not reviewed
6825
+ * (`unreadRecordReview`). No unread review is a pass, and the record never holds the run; a re-run that failed outright
6826
+ * leaves the reviewer's stamp as the released engine leaves it, for the freshness guard. `rerun`
6827
+ * is the reviewer's own stale-repair arm.
6828
+ */
6829
+ async function runWholeReviewForRecord(ctx, P, run, scope, rerun) {
6830
+ const runDir = run.runDir;
6831
+ const seq0 = nextRefutationSeq(runDir);
6832
+ const t0 = Date.now();
6833
+ const r = await rerun();
6834
+ const rec = r?.ok ? readAcceptedReviewSince(runDir, seq0) : null;
6835
+ const fold = readFold(runDir);
6836
+ const items = scopeItems(scope.changed, restingOn(scope.changed, scope.after), fold?.map);
6837
+ const base = { seq0, ...scopeRecord(scope, items), whole: true, ms: Date.now() - t0 };
6838
+ if (!rec) {
6839
+ const fail = r?.ok ? "no-accepted-call" : String(r?.fail ?? "failed");
6840
+ writePostRepairRecord(runDir, { status: "unread", ...base, fail });
6841
+ runLog(runDir, { event: "post-repair-review", read: false, whole: true, fail: fail.slice(0, 120) });
6842
+ 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"}`);
6843
+ return { reviewed: false };
6844
+ }
6845
+ const reviewText = readFileSync(P.seniorEyeReview, "utf8");
6846
+ atomicWrite(reviewAfterRepairPath(runDir), reviewText);
6847
+ const points = classifyPoints(rec.flags, items);
6848
+ writePostRepairRecord(runDir, { status: "read", ...base, verdict: rec.verdict, seq: rec.seq,
6849
+ 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 ?? "") })) });
6850
+ runLog(runDir, { event: "post-repair-review", read: true, whole: true, verdict: rec.verdict, flags: rec.flags.length, ms: base.ms });
6851
+ return { reviewed: true, rec, reviewText, items, points, putBack: () => {}, base };
6852
+ }
6853
+
6854
+ /** Where the state the review after a repair reads is kept whole, before the review and its correction run. */
6855
+ const postRepairStatePath = (runDir) => driverDir(runDir, "post-repair-state.json");
6856
+
6857
+ /**
6858
+ * THE STATE THE REVIEW AFTER A REPAIR READS, KEPT WHOLE before the review and its correction can lose any of it: the
6859
+ * findings record, its narrative, the merge base the next patch builds on, and the fold, in one file under `_driver/`,
6860
+ * 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
6861
+ * removed so that it never stands for this review. Returns whether a state was kept. Never throws.
6862
+ */
6863
+ function retainStateBeforeReview(runDir, P) {
6864
+ try {
6865
+ const findings = readFileSync(P.findings, "utf8");
6866
+ const base = snapshotSynthesisBase(runDir);
6867
+ if (base.accepted == null || base.narrative == null) throw new Error("a part of the state could not be read");
6868
+ atomicWrite(postRepairStatePath(runDir), JSON.stringify({ ts: new Date().toISOString(), findings, narrative: base.narrative,
6869
+ accepted: base.accepted, fold: snapshotFold(runDir) }) + "\n");
6870
+ return true;
6871
+ } catch (e) {
6872
+ try { rmSync(postRepairStatePath(runDir), { force: true }); } catch { /* nothing to remove */ }
6873
+ runLog(runDir, { event: "post-repair-state-not-kept", reason: String(e?.message ?? e).slice(0, 160) });
6874
+ return false;
6875
+ }
6876
+ }
6877
+
6878
+ /**
6879
+ * THE TRUSTED STATE BEFORE THE CORRECTION AFTER A POST-REPAIR REVIEW, for its rollback, from the first source that
6880
+ * stands, in this order:
6881
+ * 1. the findings record as it stands, with its fold, narrative and base (the usual snapshot);
6882
+ * 2. the state kept whole before the review (`retainStateBeforeReview`), byte for byte;
6883
+ * 3. the save the review read (`before`, read before the correction starts), rebuilt through the driver's own
6884
+ * acceptance, never from a save the correction may write afterwards;
6885
+ * 4. the save kept from before the delivery-check repair, rebuilt the same way.
6886
+ * A save that the run's CURRENT duty refuses is not lost: it was accepted when it was made, and the records owed since
6887
+ * are evidence added later. It is rebuilt with the duty set aside and marked so (`duty: "relaxed"`); once the correction
6888
+ * settles, the writer's duty is judged again on the record that stands, and names what it leaves unaccounted in the
6889
+ * approved sentence. A save that cannot be read, or that
6890
+ * the acceptance refuses for anything else, is not a source. Null only when none of the four stands. Never throws.
6891
+ */
6892
+ function trustedStateBeforeCorrection(Pv, runDir, before) {
6893
+ const held = snapshotFindingsForCorrections(Pv, runDir);
6894
+ if (held) return held;
6895
+ const sha = (t) => createHash("sha256").update(t).digest("hex");
6896
+ try {
6897
+ const k = JSON.parse(readFileSync(postRepairStatePath(runDir), "utf8"));
6898
+ if (typeof k?.findings === "string" && typeof k.narrative === "string" && typeof k.accepted === "string") {
6899
+ const h = sha(k.findings);
6900
+ return { raw: k.findings, sha: h, model: { raw: k.findings, sha: h }, fold: k.fold ?? null,
6901
+ base: { accepted: k.accepted, narrative: k.narrative }, from: "state-before-the-review" };
6902
+ }
6903
+ } catch { /* no state kept, or it does not read: the saves below */ }
6904
+ const reads = (() => { try { return driverReadsFor(runDir); } catch { return null; } })();
6905
+ const fromSave = (params, accepted, from) => {
6906
+ if (!params || accepted == null || !reads) return null;
6907
+ const accept = (r) => { try { return acceptSynthesis(params, r); } catch { return null; } };
6908
+ let v = accept(reads), duty = "current", refused = null;
6909
+ if (!v?.ok && reads.owed != null) {
6910
+ refused = v?.reason ?? null;
6911
+ const w = accept({ ...reads, owed: null, declined: null });
6912
+ if (w?.ok) { v = w; duty = "relaxed"; }
6913
+ }
6914
+ if (!v?.ok || typeof v.findings !== "string" || typeof v.narrative !== "string") return null;
6915
+ const h = sha(v.findings);
6916
+ return { raw: v.findings, sha: h, model: { raw: v.findings, sha: h }, fold: null, base: { accepted, narrative: v.narrative }, from, duty,
6917
+ ...(duty === "relaxed" ? { refusedUnderCurrentDuty: String(refused ?? "").slice(0, 160) } : {}) };
6918
+ };
6919
+ const bytes = (at) => { try { return readFileSync(at, "utf8"); } catch { return null; } };
6920
+ return fromSave(before, snapshotSynthesisBase(runDir).accepted, "accepted-save")
6921
+ ?? fromSave(readRepairBase(runDir), bytes(repairBasePath(runDir)), "save-before-the-repair");
6922
+ }
6923
+
6924
+ /**
6925
+ * A FAILED CORRECTION AFTER A POST-REPAIR REVIEW NEVER ENDS THE RUN (the owner's ruling of 2026-10-19: a run is never
6926
+ * held). The trusted state comes back whole: the record, its narrative and the merge base, then the fold and the seam's
6927
+ * writes made again over a record rebuilt from a save, as after any save, so the delivered verdict is derived from the
6928
+ * record that stands. The correction's points stay in the run's open-questions record. A rate-limited correction is
6929
+ * not waited for either: it is rolled back as a failed one is, since the report it would correct is already reviewed,
6930
+ * and waiting for the reset would hold the run.
6931
+ *
6932
+ * WITH NO TRUSTED STATE, THIS PASS DOES NOT DELIVER. That is the precise limit: the record, the state kept whole before
6933
+ * the review, the save the review read and the save kept from before the repair are all unreadable or refused, a
6934
+ * storage failure. Nothing the failed pass wrote is adopted: the narrative and the base
6935
+ * 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
6936
+ * report with no findings, labelled with the verdict as it stood (measured on the mock pipeline, 2026-10-19): a clear
6937
+ * result over a record nobody can read. So this case alone keeps the failure it always had. Returns the rollback
6938
+ * record, or null for this case.
6939
+ */
6940
+ async function recoverFromFailedCorrection(ctx, Pv, runDir, pre, pass, { baseBefore, findingsBefore }) {
6941
+ // A rate limit is a failure of this pass like any other here: the shared rollback would leave it to wait.
6942
+ const failure = pass?.fail === "rate_limited" || pass?.resetsAt ? { fail: "rate_limited, not waited for" } : pass;
6943
+ if (pre?.raw) {
6944
+ const rollback = rollbackCorrectivePass(Pv, runDir, pre, failure);
6945
+ if (rollback && pre.from) rollback.from = pre.from;
6946
+ if (rollback && pre.duty) rollback.duty = pre.duty;
6947
+ if (rollback && (pre.from === "accepted-save" || pre.from === "save-before-the-repair")) {
6948
+ // A record rebuilt from a save is folded, and given the seam's writes, as the save itself was.
6949
+ try { foldAfterSave(runDir, Pv.findings); } catch (e) { note(`[post-repair] the restored record could not be folded (${String(e?.message ?? e).slice(0, 100)})`); }
6950
+ 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)})`); }
6951
+ }
6952
+ return rollback;
6953
+ }
6954
+ // NO TRUSTED STATE: nothing the failed pass wrote is adopted, and the pass ends as it always did.
6955
+ const base = restoreSynthesisBase(runDir, baseBefore);
6956
+ let findings = "unchanged";
6957
+ if (!findingsBefore) {
6958
+ let now = null;
6959
+ try { now = readFileSync(Pv.findings, "utf8"); } catch { now = null; }
6960
+ // Set aside, never deleted: the failed pass's record stays beside the run's record for whoever diagnoses the run.
6961
+ if (now != null) {
6962
+ try { renameSync(Pv.findings, driverDir(runDir, "findings-failed-correction.json")); findings = "set-aside"; }
6963
+ catch { try { rmSync(Pv.findings, { force: true }); findings = "removed"; } catch { findings = "failed"; } }
6964
+ }
6965
+ }
6966
+ runLog(runDir, { event: "post-repair-correction-unrecoverable", reason: String(failure?.fail ?? "failed").slice(0, 200), findings, ...(base ? { base } : {}) });
6967
+ 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`);
6968
+ return null;
6969
+ }
6970
+
6971
+ /**
6972
+ * The one correction after that review: every accepted point goes to it, whatever its class, and nothing
6973
+ * reviews it again. It runs the first correction's own backstops — the rollback, the put-back of a removal no
6974
+ * flag named, the follow-up when a correction missed the findings — on a view of the run's paths that points at
6975
+ * the review after the repair, and records what each flag led to beside the first correction's record.
6976
+ */
6977
+ async function runPostRepairCorrection(ctx, P, run, { rec, items, points, grounds = false, whole = false, synthesisRef }) {
6978
+ const runDir = run.runDir;
6979
+ const Pv = { ...P, seniorEyeReview: reviewAfterRepairPath(runDir), correctionsApplied: postRepairCorrectionsPath(runDir), reviewFlags: rec.flags };
6980
+ const named = [...new Set(points.filter((x) => x.target.kind !== "none").map((x) => x.target.label))];
6981
+ const rows = rec.flags.map((f, i) => ({ n: i + 1, kind: f.kind, text: f.text, fix: f.fix ?? null,
6982
+ ordinals: Array.isArray(f.on) && f.on.length ? f.on : null }));
6983
+ const review = readFileSync(Pv.seniorEyeReview, "utf8");
6984
+ const rulingsTail = extractRulingsTail(existsSync(P.placement) ? readFileSync(P.placement, "utf8") : "");
6985
+ const followup = repairFollowup("synthesis:post-repair-corrective", {
6986
+ narrative: P.narrative, findings: P.findings, placement: P.placement, rulingsTail,
6987
+ items: named, renumbered: anyRenumbered(items), worklist: correctionsWorklist(rows, { afterRepair: true }), review, whole,
6988
+ });
6989
+ const before = lastAcceptedCall(runDir);
6990
+ // What stood before the correction, held before it can alter any of it: the files as they are, for a pass that
6991
+ // fails with no trusted state to restore, and the trusted state itself.
6992
+ const baseBefore = snapshotSynthesisBase(runDir);
6993
+ const findingsBefore = (() => { try { readFileSync(Pv.findings); return true; } catch { return false; } })();
6994
+ const pre = trustedStateBeforeCorrection(Pv, runDir, before);
6995
+ prepareDeclinationSpec(ctx, P);
6996
+ const pass = await stage("synthesis", ctx, { force: true, followup: followup + stageCharter("synthesis", ctx.depth, ctx.framework), sessionKey: synthesisRef.key,
6997
+ model: synthesisRef.model, thinking: synthesisRef.thinking, trigger: "post-repair-corrective" });
6998
+ recordSynthesisSeam(ctx, pass, "post-repair-corrective");
6999
+ const rollback = pass.ok ? null : await recoverFromFailedCorrection(ctx, Pv, runDir, pre, pass, { baseBefore, findingsBefore });
7000
+ if (rollback) {
7001
+ runLog(runDir, { event: "corrective-rollback", reason: rollback.reason, restored: rollback.restored, pass: "post-repair", from: rollback.from ?? "record",
7002
+ ...(rollback.duty ? { duty: rollback.duty } : {}) });
7003
+ 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`);
7004
+ } else {
7005
+ must(pass, "synthesis(post-repair)");
7006
+ let preDocForNames = null;
7007
+ try { preDocForNames = pre ? parseFindingsJsonLenient(pre.raw) : null; } catch { /* fall back to the file */ }
7008
+ const repaired = repairUnnamedRemovals(Pv, runDir, pre, correctionNamedOrdinals(Pv), correctionNamedSet(Pv, preDocForNames), correctionNamedLines(Pv, preDocForNames));
7009
+ if (repaired) runLog(runDir, removalRepairedRow(repaired));
7010
+ await enforceCorrectionsReachFindings(ctx, Pv, pre, { synthesisKey: synthesisRef.key, synthesisModel: synthesisRef.model, synthesisThinking: synthesisRef.thinking });
7011
+ }
7012
+ let applied = null;
7013
+ try {
7014
+ const preDoc = pre ? parseFindingsJsonLenient(pre.model?.raw ?? pre.raw) : null;
7015
+ let postDoc = null;
7016
+ try { postDoc = parseFindingsJsonLenient(readFileSync(modelRecordPath(runDir, P.findings), "utf8")); } catch { /* shape defects ride the normal ladder */ }
7017
+ const after = lastAcceptedCall(runDir);
7018
+ const sections = points.filter((x) => x.target.kind === "section").map((x) => {
7019
+ const sec = NARRATIVE_PROSE.find((y) => y.key === x.target.key);
7020
+ return { n: x.n, label: x.target.label, before: sectionText(sec?.read(before?.narrative)), after: sectionText(sec?.read(after?.narrative)) };
7021
+ });
7022
+ applied = buildCorrectionsApplied(rows, preDoc, postDoc, { sections });
7023
+ // `grounds`: the review these rows answer hardened the run's verdict, so its points are that verdict's grounds.
7024
+ atomicWrite(Pv.correctionsApplied, JSON.stringify({ ts: new Date().toISOString(), pass: "post-repair", ...(grounds ? { grounds: true } : {}),
7025
+ rows: applied, classes: points.map((x) => ({ n: x.n, class: x.class, target: x.target.label })) }, null, 2) + "\n");
7026
+ const outcomes = applied.reduce((a, x) => ((a[x.outcome] = (a[x.outcome] ?? 0) + 1), a), {});
7027
+ runLog(runDir, { event: "post-repair-fix-pass", handed: rows.length, ok: Boolean(pass.ok), rolledBack: Boolean(rollback), outcomes });
7028
+ } catch (e) {
7029
+ 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`);
7030
+ runLog(runDir, { event: "post-repair-fix-pass", handed: rows.length, ok: Boolean(pass.ok), rolledBack: Boolean(rollback), observation: false });
7031
+ }
7032
+ return { ran: true, ok: Boolean(pass.ok), rollback, applied };
7033
+ }
7034
+
6304
7035
  async function enforceCorrectionsReachFindings(ctx, P, pre, resume) {
6305
7036
  if (!pre) return; // no findings.json before the pass — nothing to enforce
6306
7037
  const { run } = ctx;
@@ -6318,7 +7049,7 @@ async function enforceCorrectionsReachFindings(ctx, P, pre, resume) {
6318
7049
  await stage("synthesis", ctx, {
6319
7050
  force: true, sessionKey: resume.synthesisKey, model: resume.synthesisModel, thinking: resume.synthesisThinking,
6320
7051
  trigger: "corrective-findings",
6321
- followup: repairFollowup("synthesis:corrective-findings", { findings: P.findings, named }) + stageCharter("synthesis", ctx.depth, ctx.framework),
7052
+ followup: repairFollowup("synthesis:corrective-findings", { findings: P.findings, named, seniorEyeReview: P.seniorEyeReview }) + stageCharter("synthesis", ctx.depth, ctx.framework),
6322
7053
  });
6323
7054
  if (readState().stale) {
6324
7055
  runLog(run.runDir, { event: "corrective-findings-stale", named });
@@ -6534,15 +7265,22 @@ function plainRegisterExtra(ctx) {
6534
7265
  // The surfaces a clearance reader meets before opening anything, named one by one so adding a
6535
7266
  // field to the page is a deliberate addition here too.
6536
7267
  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);
7268
+ const add = (where, v, field = null) => { const t = String(v ?? "").trim(); if (t) visible.push({ where, text: t, field }); };
7269
+ for (const f of doc.findings ?? []) add(`conflict ${f?.ordinal ?? "?"}'s finding sentence`, f?.net, "net");
6539
7270
  // The lines that are not a finding, labelled by `reportLines` — the labels the corrective cycle's
6540
7271
  // observation joins a flag back to, so what the reviewer is handed and what the driver reads agree.
6541
7272
  for (const l of reportLines(doc)) add(l.label, l.text);
6542
7273
 
7274
+ // A FINDING SENTENCE IS AT MOST TWO SENTENCES, so its length hit cannot say "split it": a finding sentence
7275
+ // already at two would be told to grow a third. What does not fit goes to the legal and practical
7276
+ // positions, in the approved paragraph's own words. Every other visible line, and the knockout's, keeps the
7277
+ // shared hit word for word — `plainRegisterFlags` is not changed.
7278
+ 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
7279
  const hits = [];
6544
- for (const { where, text } of visible) {
6545
- for (const flag of plainRegisterFlags(text, about)) hits.push(`- ${where} — ${flag.say}`);
7280
+ for (const { where, text, field } of visible) {
7281
+ for (const flag of plainRegisterFlags(text, about)) {
7282
+ hits.push(`- ${where} — ${field === "net" && flag.kind === "length" ? findingSentenceLength(flag.words) : flag.say}`);
7283
+ }
6546
7284
  }
6547
7285
  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
7286
 
@@ -7483,6 +8221,79 @@ export function injectLaneDepthCoverage(P, runDir, note) { // @internal
7483
8221
  }
7484
8222
  }
7485
8223
 
8224
+ /**
8225
+ * WHAT EACH POLICED GOODS DROP RESTS ON, recorded beside the screen gate (screen-gate.mjs `screenGateEvidence`).
8226
+ * Taken in two halves: `screenGateSnapshot` at the moment the gate judges, before it fetches anything (the findings
8227
+ * it judged, each policed drop's goods evidence, and the reading log), and the record after the gate's fetches,
8228
+ * which reads each drop's goods again only where the gate fetched. Disclosure only: the gate's rule and its
8229
+ * outcome are decided above and nothing here changes them.
8230
+ */
8231
+ function goodsEvidenceOfDrops(P, runPrefix, uris, gateFetched = null) {
8232
+ const out = new Map();
8233
+ if (!uris.length) return out;
8234
+ const calls = readRunCallRows(DEFAULT_LEDGER_PATH, runPrefix);
8235
+ const held = heldRecordProvenance(P.runDir);
8236
+ // Each drop's own band row, so that a goods reader which reads a row's goods reads this one's too.
8237
+ const band = safeReadJson(P.registerNamedBand);
8238
+ const bandRows = Array.isArray(band?.enumerated) ? band.enumerated : [];
8239
+ const rowOf = new Map(bandRows.map((r) => [normalizeRecordUri(r?.record_id), r]));
8240
+ const goods = bandShapeGoods(P, bandRows).goods;
8241
+ const fetches = recordFetchOutcomes(calls.rows, { runPrefix });
8242
+ // The gate's own fetches are this run's too, recorded by the gate whatever the register's ledger kept of them.
8243
+ for (const [u, r] of gateFetched ?? []) {
8244
+ const key = normalizeRecordUri(u) || String(u ?? "").toLowerCase();
8245
+ const o = fetches.get(key) ?? { sent: 0, from_memory: 0, failed: 0, ok: false, last_status: null };
8246
+ if (r?.ok === true) o.ok = true; else o.failed = Math.max(o.failed, 1);
8247
+ fetches.set(key, o);
8248
+ }
8249
+ for (const uri of uris) {
8250
+ const e = recordGoodsEvidence(rowOf.get(normalizeRecordUri(uri)) ?? { record_id: uri }, { goods, held, fetches });
8251
+ out.set(uri, { goods: e.state, source: e.source });
8252
+ }
8253
+ return out;
8254
+ }
8255
+
8256
+ function screenGateSnapshot(ctx, runPrefix) {
8257
+ const P = ctx.paths;
8258
+ try {
8259
+ const findings = existsSync(P.registerFindings) ? readFileSync(P.registerFindings, "utf8") : "";
8260
+ let readingLog = "";
8261
+ try { readingLog = readFileSync(P.readingLog, "utf8"); } catch { readingLog = ""; }
8262
+ return { findings, readingLog, goods: goodsEvidenceOfDrops(P, runPrefix, policedDropUris(findings)) };
8263
+ } catch (e) {
8264
+ return { findings: "", readingLog: "", goods: new Map(), error: String(e?.message ?? e).slice(0, 160) };
8265
+ }
8266
+ }
8267
+
8268
+ function recordScreenGateEvidence(ctx, { runPrefix, before, heldBeforeGate, heldAfterGate, gateFetched }) {
8269
+ const P = ctx.paths;
8270
+ try {
8271
+ const uris = policedDropUris(before.findings);
8272
+ const after = gateFetched.size ? goodsEvidenceOfDrops(P, runPrefix, uris, gateFetched) : before.goods;
8273
+ // The line each drop's record sits on, as placement's fate count joins it, so a part of the shape or a page
8274
+ // of a group that served the line counts as the goods words shown.
8275
+ let lineOf = () => null, lineReads = "read";
8276
+ try {
8277
+ const index = buildSelectionIndex(readPlacementFormInput(P.runDir));
8278
+ lineOf = (uri) => lineKey(index.resolve(uri));
8279
+ } 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)}`; }
8280
+ const ev = screenGateEvidence({ findingsContent: before.findings, heldBefore: heldBeforeGate, heldAfter: heldAfterGate,
8281
+ before: (uri) => before.goods.get(uri), after: (uri) => after.get(uri),
8282
+ reads: recordReads(before.readingLog), lines: linesRead(before.readingLog), lineOf, readerStage: "register-digest", gateFetched });
8283
+ const calls = readRunCallRows(DEFAULT_LEDGER_PATH, runPrefix);
8284
+ const gateCalls = registerCallAccount(calls.rows.filter((r) => String(r?.sessionKey ?? "").includes("screen-gate-refetch")));
8285
+ const doc = { schema_version: 2, ...ev, gate_calls: gateCalls, line_reads: lineReads,
8286
+ 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.",
8287
+ ...(before.error ? { snapshot_error: before.error } : {}), ...(calls.error ? { call_ledger_error: calls.error } : {}) };
8288
+ ensureDriverDir(P.runDir);
8289
+ atomicWrite(driverDir(P.runDir, "screen-gate-evidence.json"), JSON.stringify({ ts: new Date().toISOString(), ...doc }, null, 2) + "\n");
8290
+ runLog(P.runDir, { event: "screen-gate-evidence", ...ev.counts, unnamed: ev.unnamed, fetches: ev.fetches,
8291
+ alternatives: ev.alternatives, gate_calls: gateCalls });
8292
+ } catch (e) {
8293
+ runLog(P.runDir, { event: "screen-gate-evidence-failed", fail: String(e?.message ?? e).slice(0, 160) });
8294
+ }
8295
+ }
8296
+
7486
8297
  // ── screen-gate unexamined-disclosure readers ────────────────────────────────────────────────────────
7487
8298
  // The screen-gate's disclose-and-continue terminal writes _driver/screen-gate-unresolved.json (atomic,
7488
8299
  // the receipt idiom) the moment its post-repair re-check still fails, so the disclosure survives a
@@ -7536,7 +8347,7 @@ export function injectScreenGateCoverage(P, runDir, note, ctx = {}) { // @inte
7536
8347
  // the EARLIEST qualifying window end. Code fills ABSENCE only (an authored deadline always wins);
7537
8348
  // out-of-window dates never inject (an archived 2015 window is history, not urgency). Receipt:
7538
8349
  // _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 } = {}) {
8350
+ export function enrichFindingDeadlines(P, runDir, note, { nowMs = Date.now(), withinDays = 60, graceDays = 14 } = {}) { // @internal
7540
8351
  try {
7541
8352
  if (!existsSync(P.findings)) return;
7542
8353
  const deadlines = collectOppositionDeadlines(runDir);
@@ -7560,7 +8371,8 @@ function enrichFindingDeadlines(P, runDir, note, { nowMs = Date.now(), withinDay
7560
8371
  if (!enriched.length) return;
7561
8372
  parseFindingsJson(JSON.stringify(doc)); // re-validate the shape (throws → catch keeps original)
7562
8373
  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");
8374
+ const base = rebaseEnrichedDeadlines(runDir, enriched); // a later patch keeps what the file now carries
8375
+ 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
8376
  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
8377
  } catch (e) {
7566
8378
  note(`[deadline-enrich] skipped: ${String(e?.message || e).replace(/\s+/g, " ").slice(0, 100)}`);
@@ -7843,6 +8655,8 @@ export function buildOnlyYouSection(actions, findings, { nowMs = Date.now(), wit
7843
8655
  //
7844
8656
  // BLOCKING every ground the reviewer cites — unchanged, the reviewer refused to sign
7845
8657
  // ANY VERDICT plus every objection the run tried and could not show it closed
8658
+ // A HARDENED VERDICT plus every point of the review after a repair that hardened it, even one its correction
8659
+ // changed: nothing reviewed that change, so the label keeps a visible ground (`ground`)
7846
8660
  //
7847
8661
  // The second source is `corrections-applied.json`, the driver's own flag-by-flag observation,
7848
8662
  // which already exists and is already written on both the CONDITIONAL and the BLOCKING path. What
@@ -7867,7 +8681,7 @@ export function buildOnlyYouSection(actions, findings, { nowMs = Date.now(), wit
7867
8681
  export function buildReviewerOpenPointsSection(reviewMd, appliedRows = null) { // @internal
7868
8682
  const blocking = parseVerdict(reviewMd) === "BLOCKING";
7869
8683
  const cited = blocking ? parseCorrections(reviewMd) : [];
7870
- const unfixed = unresolvedFlags(appliedRows);
8684
+ const unfixed = (Array.isArray(appliedRows) ? appliedRows : []).filter((r) => r?.ground === true || unresolvedFlags([r]).length);
7871
8685
  const seen = new Set();
7872
8686
  const rows = [];
7873
8687
  for (const r of [...cited, ...unfixed]) {
@@ -7917,6 +8731,8 @@ export function buildReviewerOpenPointsSection(reviewMd, appliedRows = null) {
7917
8731
  "not-entity-scoped": "the run could not check whether this was addressed",
7918
8732
  "not-checkable": "the run could not check whether this was addressed",
7919
8733
  "line-unchanged": "the line this named was left as written",
8734
+ // A narrative section a flag of the review after a repair named, left as written: the shipped words.
8735
+ "section-unchanged": "the line this named was left as written",
7920
8736
  "line-removed": "the line this named was removed rather than corrected",
7921
8737
  };
7922
8738
  const items = rows.map((r) => {
@@ -7924,7 +8740,9 @@ export function buildReviewerOpenPointsSection(reviewMd, appliedRows = null) {
7924
8740
  if (Array.isArray(r.ordinals) && r.ordinals.length) bits.push(`finding ${r.ordinals.join(", ")}`);
7925
8741
  // An outcome nobody has classified takes the unchecked wording, never the checked one — the same
7926
8742
  // 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");
8743
+ // A ground the correction changed keeps its observed outcome in the record and prints as unchecked: no review
8744
+ // read the change, so the run cannot say it was addressed, and "did not change" would be false.
8745
+ if (r.outcome) bits.push((r.ground === true ? null : QUALIFIER[r.outcome]) ?? "the run could not check whether this was addressed");
7928
8746
  const tail = bits.length ? ` *(${bits.join(" — ")})*` : "";
7929
8747
  return `- ${ADVISORY_TAG["client-fact"]} ${String(r.text).trim()}${tail}`;
7930
8748
  });
@@ -8060,6 +8878,54 @@ export function buildReasonedNegativesSection(findings) { // @internal
8060
8878
  return `${CODE_BUILT_SECTIONS.reasonedNegatives}\n\n${lines.join("\n\n")}`;
8061
8879
  }
8062
8880
 
8881
+ // ── WHETHER EACH REVIEWER CORRECTION REACHED THE RECORD THE REPORT WAS BUILT FROM ───────────────────────
8882
+ //
8883
+ // The corrective pass keeps a basis — what each flag changed in the fields a report prints from the record —
8884
+ // in `_driver/corrections-delivered.json`, and every assembly reads it against the record the report was
8885
+ // built from and writes the outcome beside it (corrections-delivered.mjs). The last assembly's answer stands.
8886
+ // corrections-applied.json, which the open points and the verdict re-check read, is not touched.
8887
+ export const CORRECTIONS_DELIVERED_FILE = "corrections-delivered.json";
8888
+
8889
+ /** 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. */
8890
+ export function recordCorrectionsBasis(runDir, rows, preDoc, postDoc) { // @internal
8891
+ const basis = correctionTargets(rows, preDoc, postDoc);
8892
+ atomicWrite(driverDir(runDir, CORRECTIONS_DELIVERED_FILE), JSON.stringify({
8893
+ _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)",
8894
+ basisAt: new Date().toISOString(), basis,
8895
+ }, null, 2) + "\n");
8896
+ return basis;
8897
+ }
8898
+
8899
+ /**
8900
+ * Read the basis against the record this assembly was built from, and record the outcome: the file, and one
8901
+ * run-log row. A run with no basis records nothing. A delivered record that cannot be read is recorded as a
8902
+ * failure to look, never as an outcome. Never throws.
8903
+ */
8904
+ export function recordCorrectionsDelivered(P) { // @internal
8905
+ if (!P?.runDir) return null;
8906
+ const at = driverDir(P.runDir, CORRECTIONS_DELIVERED_FILE);
8907
+ let held = null;
8908
+ try { held = JSON.parse(readFileSync(at, "utf8")); } catch { return null; } // no corrective basis on this run
8909
+ if (!Array.isArray(held?.basis)) return null;
8910
+ let deliveredDoc = null, reportMd = null;
8911
+ try { deliveredDoc = parseFindingsJsonLenient(readFileSync(P.findings, "utf8")); } catch { deliveredDoc = null; }
8912
+ try { reportMd = readFileSync(P.report, "utf8"); } catch { reportMd = null; }
8913
+ let foldMap = null;
8914
+ try { foldMap = readFold(P.runDir)?.map ?? null; } catch { foldMap = null; }
8915
+ const rows = deliveredOutcomes({ basis: held.basis, deliveredDoc, foldMap, reportMd });
8916
+ const { rows: _rows, error: _error, assembledAt: _assembledAt, ...kept } = held;
8917
+ const out = { ...kept, assembledAt: new Date().toISOString(), rows: rows ?? null,
8918
+ ...(rows ? {} : { error: "the record the report was built from could not be read, so no correction was compared" }) };
8919
+ try { atomicWrite(at, JSON.stringify(out, null, 2) + "\n"); } catch { /* the run-log row below is the record */ }
8920
+ try {
8921
+ const outcomes = (rows ?? []).reduce((a, r) => (a[r.outcome] = (a[r.outcome] ?? 0) + 1, a), {});
8922
+ runLog(P.runDir, rows
8923
+ ? { event: "corrections-delivered", ok: true, flags: rows.length, outcomes, reportMdRead: reportMd != null }
8924
+ : { event: "corrections-delivered", ok: false, error: out.error });
8925
+ } catch { /* a log that cannot be written must not stop the report it describes */ }
8926
+ return out;
8927
+ }
8928
+
8063
8929
  export function assembleReportMd(P, findings, cardOrdinals, { grouped = [], byRight = false } = {}) { // @internal
8064
8930
  const overviewRaw = existsSync(P.reportOverview) ? readFileSync(P.reportOverview, "utf8") : "---\n---\n";
8065
8931
  let overview = overviewRaw.split(/^#\s+Marks\b/m)[0].replace(/\s*$/, ""); // defensive: overview owns down to # Marks
@@ -8164,6 +9030,26 @@ export function assembleReportMd(P, findings, cardOrdinals, { grouped = [], byRi
8164
9030
  let applied = null;
8165
9031
  try { applied = JSON.parse(readFileSync(P.correctionsApplied, "utf8"))?.rows ?? null; }
8166
9032
  catch { /* absent or malformed — the record falls back to the review alone */ }
9033
+ // …and the correction after the review of a delivery-check repair, which records its rows beside them. Its
9034
+ // points print through the same rule: anything not positively resolved. Which review raised a point stays in
9035
+ // the run's own records (_driver/post-repair-review.json); the printed line adds nothing.
9036
+ // Where that review hardened the verdict its points are the verdict's grounds, and print even when changed.
9037
+ try {
9038
+ const doc = JSON.parse(readFileSync(driverDir(dirname(P.report), "post-repair-corrections.json"), "utf8"));
9039
+ const after = doc?.rows;
9040
+ if (Array.isArray(after) && after.length) applied = [...(applied ?? []), ...(doc.grounds === true ? after.map((r) => ({ ...r, ground: true })) : after)];
9041
+ } catch { /* no correction after a repair on this run */ }
9042
+ // …and a review after a repair that was read and never corrected: the run was parked before its one correction,
9043
+ // and nothing on a resume repeats it. Its points are not shown closed, so they print through the same rule.
9044
+ try {
9045
+ const runDir = dirname(P.report);
9046
+ const rec = JSON.parse(readFileSync(driverDir(runDir, "post-repair-review.json"), "utf8"));
9047
+ if (rec?.status === "read" && !existsSync(driverDir(runDir, "post-repair-corrections.json"))) {
9048
+ const rows = (Array.isArray(rec.points) ? rec.points : []).filter((x) => String(x?.text ?? "").trim())
9049
+ .map((x) => ({ text: String(x.text), kind: x.kind ?? null, outcome: "not-checkable" }));
9050
+ if (rows.length) applied = [...(applied ?? []), ...rows];
9051
+ }
9052
+ } catch { /* no review after a repair on this run */ }
8167
9053
  const openPoints = existsSync(P.seniorEyeReview)
8168
9054
  ? buildReviewerOpenPointsSection(readFileSync(P.seniorEyeReview, "utf8"), applied)
8169
9055
  : "";
@@ -8268,6 +9154,9 @@ export function assembleReportMd(P, findings, cardOrdinals, { grouped = [], byRi
8268
9154
  ensureDriverDir(P.runDir);
8269
9155
  writeFileSync(driverDir(P.runDir, "card-folds.json"), `${JSON.stringify({ schema: 1, folds }, null, 2)}\n`);
8270
9156
  } catch { /* observability only */ }
9157
+ // Whether each reviewer correction reached the record this report was built from: rewritten on every
9158
+ // assembly, like the fold record above, so the last assembly's answer stands. Nothing without a basis.
9159
+ try { recordCorrectionsDelivered(P); } catch { /* observability only: the report is assembled either way */ }
8271
9160
  return { cards: cards.length, folds };
8272
9161
  }
8273
9162
 
@@ -10219,9 +11108,10 @@ async function pipelineInner(job, opts = {}) {
10219
11108
  if (unitKey[axis]) try {
10220
11109
  const byQid = new Map(ctx.registerPlan.entries.map((e) => [e.qid, e]));
10221
11110
  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 })) });
11111
+ 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
11112
  const r = await stage("register-unit", { ...ctx, axis }, { force: true, followup, sessionKey: unitKey[axis], trigger: "xcheck-decide" });
10224
11113
  asked = r?.ok === true;
11114
+ remergeIfBandsMoved(ctx, "xcheck-decide");
10225
11115
  } catch (e) { runLog(run.runDir, { event: "register-xcheck-decide-failed", fail: String(e?.message ?? e).slice(0, 160) }); }
10226
11116
  const released = releasedFamilyQids(run.runDir), withheld = readWithheldFamilies(run.runDir);
10227
11117
  const ran = undecidedCrossChecks(ctx.registerPlan, { released, withheld });
@@ -10922,7 +11812,7 @@ async function pipelineInner(job, opts = {}) {
10922
11812
  // (`x:0` vs `x:null` must differ by value, never by presence). The demote paths carry the SAME
10923
11813
  // rows forward unchanged, deliberately — a failed re-digest demotes CLOSURE, not searchedness:
10924
11814
  // a query that ran still ran.
10925
- const writeReopenReceipt = (swept, deferrals, dom, partials = [], accounting = null, answered = []) => {
11815
+ const writeReopenReceipt = (swept, deferrals, dom, partials = [], accounting = null, answered = [], withheld = []) => {
10926
11816
  try {
10927
11817
  writeFileSync(reopenReceiptPath, JSON.stringify({
10928
11818
  ts: new Date().toISOString(), requested: firing.map(reopenKey),
@@ -10930,6 +11820,9 @@ async function pipelineInner(job, opts = {}) {
10930
11820
  ...(partials.length ? { partials } : {}),
10931
11821
  // a closed directive whose slice the fold refused as already-asked, with the plan row that answered it
10932
11822
  ...(answered.length ? { answered_elsewhere: answered } : {}),
11823
+ // a closed directive with a slice settled by the reading turn's judgment: the slice and the family
11824
+ // it repeats, and nothing else. The reason stays on the turn's own record.
11825
+ ...(withheld.length ? { withheld_by_judgment: withheld } : {}),
10933
11826
  dominant_element_gap, domClosed: dom,
10934
11827
  directive_qids: reopenQids, born: reopenBorn,
10935
11828
  remedy_terms: accounting?.computable === true ? accounting.rows : null,
@@ -10976,6 +11869,7 @@ async function pipelineInner(job, opts = {}) {
10976
11869
  // folded into the defer reason otherwise), never dropped into the aggregate count alone.
10977
11870
  const regPartialMint = new Map();
10978
11871
  const regAnsweredBy = new Map(); // reopenKey → the plan rows that answered its slices the fold refused as already-asked
11872
+ const regWithheldBy = new Map(); // reopenKey → its slices refused as families the reading turn withheld, with each family
10979
11873
  // — the per-TERM rows the remedy ledger accounts. One row per remedy term per directive,
10980
11874
  // recorded at the mint seam (the only place the term→qid mapping exists) so the receipt can
10981
11875
  // later say what each term asked and what came back. reopenKey → [{term, predicates, qids,
@@ -11093,18 +11987,28 @@ async function pipelineInner(job, opts = {}) {
11093
11987
  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
11988
  const beforeBand = existsSync(P.registerBand(a)) ? readFileSync(P.registerBand(a), "utf8") : null;
11095
11989
  if (allMinted.length) {
11096
- const { plan, added, refused } = foldSupplementalEntries(ctx.registerPlan, allMinted);
11990
+ // A QUESTION THE READING TURN WITHHELD IS NOT ASKED AGAIN HERE. The fold lets a question through
11991
+ // when it matches a waiting family, because the reading turn's own ask is what the family waits
11992
+ // for; this sweep asks on nobody's judgment, so its exact repeat of a family the turn withheld is
11993
+ // refused before the fold, with the family as its twin and the turn's recorded reason. The record
11994
+ // is read here, at the fold, so a family the turn has since released is asked as before.
11995
+ const withheldTwins = withheldFamilyTwins(ctx.registerPlan, readWithheldFamilies(run.runDir), releasedFamilyQids(run.runDir));
11996
+ const { kept: toFold, refused: withheldRefused } = refuseWithheldFamilies(allMinted, withheldTwins, ctx.registerPlan);
11997
+ const folded = foldSupplementalEntries(ctx.registerPlan, toFold);
11998
+ const { plan, added } = folded;
11999
+ const refused = [...withheldRefused, ...folded.refused];
11097
12000
  if (added.length) {
11098
12001
  writeFileSync(`${P.registerPlan}.tmp`, JSON.stringify(plan, null, 2) + "\n");
11099
12002
  renameSync(`${P.registerPlan}.tmp`, P.registerPlan);
11100
12003
  ctx.registerPlan = plan;
11101
12004
  }
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.
12005
+ // — expected to hold only the two refusals that mean the question is settled already: a
12006
+ // duplicate of the plan's own question, and a family the reading turn withheld. Every other kind
12007
+ // is a term mintSupplementalEntries should have caught (it runs entryTermIssues at the mint, and
12008
+ // its rejects are already in the sidecar above). Logged anyway, because that is a belief about
12009
+ // two screens agreeing, and the day they stop agreeing this is the only place that would say so.
11106
12010
  if (refused.length) {
11107
- for (const r of refused) if (r?.qid) regFoldRefusals.set(r.qid, { kind: r.kind ?? null, twin: r.twin ?? null });
12011
+ 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
12012
  // THE REASON THE FOLD GAVE IS LOGGED, not just the qid it refused. The fold is the only
11109
12013
  // place that knows WHY, and for a duplicate it knows WHICH plan row already holds the
11110
12014
  // answer. Logging the qid alone threw that away: recovering it on one run meant
@@ -11119,15 +12023,25 @@ async function pipelineInner(job, opts = {}) {
11119
12023
  // in the plan and its existing row carries the coverage. An identity collision or a
11120
12024
  // malformed term IS the mint screen and the fold screen disagreeing, because the mint
11121
12025
  // runs entryTermIssues and should have caught it.
12026
+ // A withheld family's refusal is neither: it is the reading turn's decision standing, recorded on
12027
+ // the run log row above with its kind and its family, and settled by that turn's reason.
11122
12028
  const dupes = refused.filter((r) => r.kind === "duplicate-question");
11123
- const faults = refused.filter((r) => r.kind !== "duplicate-question");
12029
+ const faults = refused.filter((r) => r.kind !== "duplicate-question" && r.kind !== "withheld-by-judgment");
11124
12030
  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
12031
  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
12032
  }
11127
12033
  // Dispatch → derive band → VERIFY per directive (qid-landed + non-collapse + class-scope).
11128
12034
  // A byte-changed band with only a wrong-scope/empty/error block closes NOTHING.
11129
12035
  const dispatchAndVerify = async (qids) => {
11130
- const outcome = await dispatchPlanQids(a, qids, "frame-reopen-sweep", 2);
12036
+ // NOTHING ADDED, NOTHING REPEATED. The executor reads an empty question list as every entry on the
12037
+ // axis, so a fold that refused every slice (the plan's own questions, or families the reading
12038
+ // turn withheld) handed the whole primary sweep back to the executor to run again. It now sends
12039
+ // only the axis's required questions that have not landed (pendingRequiredQids), so a required
12040
+ // search still gets its retry and nothing landed is asked twice. The verify below reads the join
12041
+ // either way, so a refused slice is still checked through its twin or its family.
12042
+ let send = qids;
12043
+ if (!send.length) send = pendingRequiredQids(ctx.registerPlan, a, readRegisterBands(P, axes), releasedFamilyQids(ctx.paths.runDir));
12044
+ const outcome = send.length ? await dispatchPlanQids(a, send, "frame-reopen-sweep", 2) : "ok";
11131
12045
  refreshSupplementalExecution(ctx);
11132
12046
  try { deriveNamedBand(ctx); } catch (e) { regMechFail = `named-band:${String(e.message).slice(0, 80)}`; }
11133
12047
  const bands = readRegisterBands(P, axes);
@@ -11143,6 +12057,7 @@ async function pipelineInner(job, opts = {}) {
11143
12057
  executedQids: ev.executedQids, blocksByQid: ev.blocksByQid, foldRefusals: regFoldRefusals,
11144
12058
  });
11145
12059
  if (v.closed && v.answered_by) regAnsweredBy.set(reopenKey(d), v.answered_by);
12060
+ if (v.closed && v.withheld_by_judgment) regWithheldBy.set(reopenKey(d), v.withheld_by_judgment);
11146
12061
  return v;
11147
12062
  };
11148
12063
  const mintedDirectives = regDirectives.filter((d) => (directiveQids.get(reopenKey(d)) ?? []).length);
@@ -11204,6 +12119,7 @@ async function pipelineInner(job, opts = {}) {
11204
12119
  const a = "primary-sweep";
11205
12120
  const regDom = regDirectives.filter((d) => d.severity === "dominant-element");
11206
12121
  const beforeUnit = existsSync(P.registerUnit(a)) ? readFileSync(P.registerUnit(a), "utf8") : null;
12122
+ const beforeBand = bandBytesOf(P, a); // a sweep that adds blocks moves the band, whatever it does to the notes
11207
12123
  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
12124
  // Lane flag read from the SAME key the tool exclusion keys on (see the stage dispatcher) —
11209
12125
  // this arm is reachable on a lane run whenever the code-side dispatch arm above could not
@@ -11212,7 +12128,7 @@ async function pipelineInner(job, opts = {}) {
11212
12128
  supplementalLane: !!ctx.registerPlan?.contract?.supplemental_lane });
11213
12129
  const r = await stage("register-unit", { ...ctx, axis: a }, { force: true, followup, sessionKey: unitKey[a], trigger: "frame-reopen" });
11214
12130
  if (r.ok) {
11215
- if (readFileSync(P.registerUnit(a), "utf8") !== beforeUnit) { changedAxes.push(a); regChanged = true; }
12131
+ if (readFileSync(P.registerUnit(a), "utf8") !== beforeUnit || bandBytesOf(P, a) !== beforeBand) { changedAxes.push(a); regChanged = true; }
11216
12132
  else note(`frame-reopen: ${a} unchanged after the supplemental sweep`);
11217
12133
  } else {
11218
12134
  regMechFail = r.fail; note(`frame-reopen: ${a} supplemental sweep failed mechanically (${r.fail})`);
@@ -11226,7 +12142,7 @@ async function pipelineInner(job, opts = {}) {
11226
12142
  const retryMsg = repairFollowup("register-unit:frame-reopen-retry", { paths: P, axis: a, directives: regDirectives, reopenFetchCap,
11227
12143
  supplementalLane: !!ctx.registerPlan?.contract?.supplemental_lane });
11228
12144
  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) {
12145
+ if (r2.ok && existsSync(P.registerUnit(a)) && (readFileSync(P.registerUnit(a), "utf8") !== beforeUnit || bandBytesOf(P, a) !== beforeBand)) {
11230
12146
  changedAxes.push(a); regChanged = true; regMechFail = null;
11231
12147
  note(`frame-reopen: ${a} fresh scoped retry closed the gap`);
11232
12148
  } else if (!r2.ok) {
@@ -11240,6 +12156,7 @@ async function pipelineInner(job, opts = {}) {
11240
12156
  regNoSession = true;
11241
12157
  note(`frame-reopen: ${regDirectives.length} register omission(s) — no resumable primary-sweep/digest session in this process`);
11242
12158
  }
12159
+ if (regArm === "resume") remergeIfBandsMoved(ctx, "frame-reopen");
11243
12160
 
11244
12161
  // SOURCE channels: a warm RESUME is taken when the common-law session is live; with no
11245
12162
  // resumable session the omission becomes a DISCLOSED DEFERRAL (open coverage row + the
@@ -11431,7 +12348,7 @@ async function pipelineInner(job, opts = {}) {
11431
12348
  return "unchanged-after-resume";
11432
12349
  });
11433
12350
  // — 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
12351
+ // depends on it. Every term a firing directive restated ends in one of six named states, and
11435
12352
  // `searched-empty` carries the executed query and its counted zero — the state that used to
11436
12353
  // leave no trace anywhere. Non-fatal end to end: a ledger that cannot build leaves the
11437
12354
  // receipt's fields NULL (never absent) and the closure read falls back to its prior rule,
@@ -11486,7 +12403,8 @@ async function pipelineInner(job, opts = {}) {
11486
12403
  const partials = swept.filter((k) => regPartialMint.has(k))
11487
12404
  .map((k) => ({ directive: k, note: `verified-closed on the minted subset — ${regPartialMint.get(k)}` }));
11488
12405
  const answered = swept.filter((k) => regAnsweredBy.has(k)).map((k) => ({ directive: k, slices: regAnsweredBy.get(k) }));
11489
- writeReopenReceipt(swept, deferrals, domClosed, partials, remedyAccounting, answered);
12406
+ const withheldBy = swept.filter((k) => regWithheldBy.has(k)).map((k) => ({ directive: k, slices: regWithheldBy.get(k) }));
12407
+ writeReopenReceipt(swept, deferrals, domClosed, partials, remedyAccounting, answered, withheldBy);
11490
12408
  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
12409
  // C2 (review fix, 2026-07-29) — the redigest-fail demotion invariant, LOCKED path. The
11492
12410
  // unlocked path demotes verified-closed directives inline when its pre-synthesis flush
@@ -11583,23 +12501,10 @@ async function pipelineInner(job, opts = {}) {
11583
12501
  // Step 2C placement → Step 2D digest
11584
12502
  await assertReceiptSettled(ctx, "placement-inquiry");
11585
12503
  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);
12504
+ // What THIS pass did, recorded before anything else can overwrite placements.json, and ahead of `must`
12505
+ // deliberately: a pass that failed or skipped on a partial artifact is exactly the pass whose account is
12506
+ // worth having, and `must` throws. The delivery gate's repeat of this stage records through the same helper.
12507
+ recordPlacementPass(ctx, placementPass);
11603
12508
  must(placementPass, "placement-inquiry");
11604
12509
  await checkLateBind("pre-digest"); // B5b checkpoint 2: a bind here rides the digest message as a re-classification block
11605
12510
  const digestKey = must(await runDigest(ctx), "register-digest").sessionKey;
@@ -11766,6 +12671,7 @@ async function pipelineInner(job, opts = {}) {
11766
12671
  // Only axes whose unit file MATERIALLY changed stay in escalated[]; content-compare is conservative —
11767
12672
  // any byte difference re-fires, so a genuine recall fix is never skipped. Floor-neutral.
11768
12673
  const beforeUnit = existsSync(P.registerUnit(a)) ? readFileSync(P.registerUnit(a), "utf8") : null;
12674
+ const beforeBand = bandBytesOf(P, a); // a narrow sub-query adds blocks to the band, whatever it does to the notes
11769
12675
  note(`skeptic escalated ${a} → resuming its session to defend/adjust (warm, same tier)`);
11770
12676
  let followup = repairFollowup("register-unit:escalation", { paths: P, axis: a, flags,
11771
12677
  supplementalLane: !!ctx.registerPlan?.contract?.supplemental_lane });
@@ -11783,7 +12689,7 @@ async function pipelineInner(job, opts = {}) {
11783
12689
  const r = await stage("register-unit", { ...ctx, axis: a }, { force: true, followup, sessionKey: unitKey[a], trigger: "escalation" });
11784
12690
  if (!r.ok) return { axis: a, changed: false, ok: false };
11785
12691
  const afterUnit = readFileSync(P.registerUnit(a), "utf8");
11786
- const changed = afterUnit !== beforeUnit;
12692
+ const changed = afterUnit !== beforeUnit || bandBytesOf(P, a) !== beforeBand;
11787
12693
  if (!changed) {
11788
12694
  note(`escalation ${a}: unit defended in place (unchanged) — Opus re-digest not needed`);
11789
12695
  runLog(run.runDir, { event: "escalation-noop", axis: a });
@@ -11791,6 +12697,7 @@ async function pipelineInner(job, opts = {}) {
11791
12697
  return { axis: a, changed, ok: true };
11792
12698
  });
11793
12699
  const escalated = escResults.filter((e) => e.changed).map((e) => e.axis);
12700
+ remergeIfBandsMoved(ctx, "escalation");
11794
12701
  // W-3 input (D1): persist the escalation outcome so the client-export gate can WITHHOLD when a
11795
12702
  // load-bearing escalation the system ITSELF raised did not complete. requested = axes attempted this
11796
12703
  // session; failed = those whose register-unit stage returned !r.ok (never ran to a result). The
@@ -11882,6 +12789,7 @@ async function pipelineInner(job, opts = {}) {
11882
12789
  if (closeableAxes.length && decision.close && !digestLockedEnv) {
11883
12790
  const closeResults = await runBatched(closeableAxes, config.gatherConcurrency, async (a) => {
11884
12791
  const beforeUnit = existsSync(P.registerUnit(a)) ? readFileSync(P.registerUnit(a), "utf8") : null;
12792
+ const beforeBand = bandBytesOf(P, a); // a close that runs the deferred slices adds blocks to the band
11885
12793
  // quote the axis's own deferred ledger rows + ⭐-floor breach rows verbatim so the unit closes
11886
12794
  // exactly what was deferred (or mislabelled away). WS-A: the quoted rows come from the SAME
11887
12795
  // loadCoverageLedger read the gate decided on — never a separate prose grep that could diverge.
@@ -11903,8 +12811,9 @@ async function pipelineInner(job, opts = {}) {
11903
12811
  const r = await stage("register-unit", { ...ctx, axis: a }, { force: true, followup, sessionKey: unitKey[a], trigger: "envelope" });
11904
12812
  if (!r.ok) return { axis: a, changed: false, fail: r.fail };
11905
12813
  const afterUnit = readFileSync(P.registerUnit(a), "utf8");
11906
- return { axis: a, changed: afterUnit !== beforeUnit, fail: null };
12814
+ return { axis: a, changed: afterUnit !== beforeUnit || bandBytesOf(P, a) !== beforeBand, fail: null };
11907
12815
  });
12816
+ remergeIfBandsMoved(ctx, "envelope");
11908
12817
  const closedChanged = closeResults.filter((e) => e.changed).map((e) => e.axis);
11909
12818
  const closeFails = closeResults.filter((e) => e.fail);
11910
12819
  if (closedChanged.length) {
@@ -12007,14 +12916,17 @@ async function pipelineInner(job, opts = {}) {
12007
12916
  // Re-read the findings + recompute the fetched universe on EACH check (the recovery re-digest rewrites
12008
12917
  // the findings AND may record_fetch the flagged URIs — both must be picked up on the post-recovery
12009
12918
  // 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.
12919
+ // session's ledger rows) — not the session-prefixed ledger alone. A failed fetch is not a retrieval, and a
12920
+ // body that carries no record is not one either (ruled 2026-10-18): neither answers the gate, so such a drop
12921
+ // gets the gate's own recovery fetch and, failing that, its disclosure. A valid copy the run already holds
12922
+ // still answers it, whatever a later attempt did. A call the register answered `ok` is no record on its own:
12923
+ // every register files the body it fetched, and a 200 carrying an empty record files an empty one, so the
12924
+ // body is what is read, never the call's status.
12925
+ const fetchedUniverse = () => new Set(
12926
+ [...assembleRunRecords(run.runDir, runPrefix).records].filter(([, body]) => recordBodyHasContent(body)).map(([uri]) => uri));
12011
12927
  const checkScreenGate = () => {
12012
12928
  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);
12929
+ return findScreenGateViolations(findings, fetchedUniverse());
12018
12930
  };
12019
12931
 
12020
12932
  // PARSE-GAP LEDGER (2026-07-21): records the gate is blind to because URI_RE's `[a-z]{2}` cannot read
@@ -12114,7 +13026,7 @@ async function pipelineInner(job, opts = {}) {
12114
13026
  // Re-stamp the disclosure to the CURRENT leftover set — the flush may have corrected some
12115
13027
  // rows while new ones appeared; the floor and the sidecar must describe what is true now.
12116
13028
  // 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]));
13029
+ const priorCause = new Map((ctx.screenGateUnresolved ?? []).map((u) => [u.uri ?? u.mark, u]));
12118
13030
  // — the PROVIDER's own words first. `runPostFlushGateRepair` now returns what each fetch
12119
13031
  // said instead of discarding it, so a row disclosed on this path names the register's error
12120
13032
  // rather than the driver's generic string. `cause_source` says which of the three it is,
@@ -12125,7 +13037,10 @@ async function pipelineInner(job, opts = {}) {
12125
13037
  const fresh = v.uri ? postFlushFailures?.get(v.uri) : null;
12126
13038
  const prior = priorCause.get(key);
12127
13039
  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" };
13040
+ // An earlier pass's cause keeps the source it was given: a refusal is the provider's on that earlier
13041
+ // pass, and a fetch that reported success, or a row naming no record, stays what it was.
13042
+ if (prior?.cause) return { mark: v.mark, uri: v.uri ?? null, cause: prior.cause,
13043
+ cause_source: !prior.cause_source || prior.cause_source === "provider" ? "provider-earlier-pass" : prior.cause_source };
12129
13044
  return {
12130
13045
  mark: v.mark, uri: v.uri ?? null,
12131
13046
  cause: v.uri
@@ -12150,6 +13065,10 @@ async function pipelineInner(job, opts = {}) {
12150
13065
  }
12151
13066
  };
12152
13067
  let violations = enforcedViolations();
13068
+ // What the gate's answer stood on before it fetched anything, and each fetch it makes: the evidence below.
13069
+ const heldBeforeGate = fetchedUniverse();
13070
+ const gateBefore = screenGateSnapshot(ctx, runPrefix);
13071
+ const gateFetched = new Map();
12153
13072
  if (violations.length) {
12154
13073
  // REPAIR-OR-DISCLOSE, NEVER BLOCK (2026-06-18). The digest intermittently drops an in-scope-live mark on
12155
13074
  // goods WITHOUT record_fetching it, and a generic re-digest re-runs identically (the model skips the same
@@ -12172,7 +13091,8 @@ async function pipelineInner(job, opts = {}) {
12172
13091
  try {
12173
13092
  const r = await fetcher(v.uri, { agentId: agent, sessionKey: `${runPrefix}screen-gate-refetch`, recordLog: runRecordLogPath(run.runDir) });
12174
13093
  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)); }
13094
+ gateFetched.set(v.uri, { ok: r?.ok === true, cause: r?.ok ? null : (r?.cause ?? "record_fetch returned not-ok") });
13095
+ } 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
13096
  }
12177
13097
  // The re-decide text — shared verbatim between the legacy immediate re-digest (funnel OFF) and
12178
13098
  // the queued settlement-flush section (funnel ON).
@@ -12244,6 +13164,7 @@ async function pipelineInner(job, opts = {}) {
12244
13164
  ...screenGateZeroCause({ findingsPresent: fPresent,
12245
13165
  findingsContent: fPresent ? readFileSync(P.registerFindings, "utf8") : "", unnamedArmed }) });
12246
13166
  }
13167
+ recordScreenGateEvidence(ctx, { runPrefix, before: gateBefore, heldBeforeGate, heldAfterGate: fetchedUniverse(), gateFetched });
12247
13168
  }
12248
13169
 
12249
13170
  // ──: STANDALONE SETTLEMENT / LATE FLUSH ───────────────────────────────────────────────────
@@ -12261,6 +13182,32 @@ async function pipelineInner(job, opts = {}) {
12261
13182
  // never re-fire on any later resume.
12262
13183
  if (!ctx.digestSettled) {
12263
13184
  const digestLockedFlush = existsSync(P.narrative) && !forceFromActive(ctx, "synthesis");
13185
+ // ── LATE EVIDENCE IS ASSESSED BEFORE THE READING STEP'S LAST PASS AND THE WRITER (owner's ruling, 2026-10-19) ──
13186
+ // A record the band gained after placement's pass (a unit's re-run, the code-side recheck) is placed or set aside
13187
+ // by placement itself, through its own stage, before the flush below hands the reading step the placements and
13188
+ // before the writer reads them. The form is a union, so every row placement accepted stands. Only ids placement
13189
+ // never read run it again: a band whose counts alone moved costs nothing. Every path that grows the band after
13190
+ // placement queues the reading step's re-run, so the flush below follows; one that did not is recorded.
13191
+ if (!digestLockedFlush) {
13192
+ const late = lateRecordIds(P);
13193
+ if (late.length) {
13194
+ runLog(run.runDir, { event: "late-evidence", records: late.length });
13195
+ 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`);
13196
+ // A PASS THAT FAILS, OR IS REFUSED BEFORE IT STARTS, DOES NOT END THE RUN HERE. A run is never held for a recovery
13197
+ // that could not run (the owner's ruling of 2026-10-19): placement's earlier account stands, and the floor and the
13198
+ // recall gate state what it leaves open. The stage can throw before it dispatches, as it does for a band past the
13199
+ // size ceiling, so the throw is caught and recorded as the failure it is.
13200
+ let pass;
13201
+ try { pass = recordPlacementPass(ctx, await stage("placement-inquiry", ctx, { force: true, trigger: "late-evidence" }), "late-evidence"); }
13202
+ catch (e) { pass = { ok: false, fail: String(e?.message ?? e) }; }
13203
+ if (!pass?.ok) {
13204
+ runLog(run.runDir, { event: "late-evidence-failed", records: late.length, fail: String(pass?.fail ?? "failed").slice(0, 200) });
13205
+ ctx.latePassFailed = { records: late.length, fail: String(pass?.fail ?? "failed").slice(0, 200) }; // the attempt, for the delivery record
13206
+ 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`);
13207
+ }
13208
+ if (!pendingItems(ctx.digestQueue).length) runLog(run.runDir, { event: "late-evidence-no-digest-work", records: late.length });
13209
+ }
13210
+ }
12264
13211
  if (!digestLockedFlush) await flushDigestQueue("pre-synthesis");
12265
13212
  else if (pendingItems(ctx.digestQueue).length) await flushDigestQueue("late");
12266
13213
  ctx.digestSettled = true;
@@ -12410,8 +13357,9 @@ async function pipelineInner(job, opts = {}) {
12410
13357
  const { text: synthExtra } = composeDispatchExtra("synthesis", ctx);
12411
13358
  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
13359
  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.
13360
+ // The corrective re-synthesis is dispatched on the WINNING attempt's key AND model/thinking. A followup's first
13361
+ // attempt starts fresh, and it never resumes the synthesis session (no engine resumes by key); but if synthesis
13362
+ // fell over to a cross-provider model, a dispatch on the base model would correct a record another model wrote.
12415
13363
  const synthesisKey = synth.sessionKey, synthesisModel = synth.model, synthesisThinking = synth.thinking;
12416
13364
 
12417
13365
  // A2/A4 — the v3 evidence gates (verified-needs-source, symmetric use-check, client-tier
@@ -12475,7 +13423,7 @@ async function pipelineInner(job, opts = {}) {
12475
13423
  await stage("synthesis", ctx, {
12476
13424
  force: true, sessionKey: synthesisKey, model: synthesisModel, thinking: synthesisThinking,
12477
13425
  trigger: "actions-missing",
12478
- followup: repairFollowup("synthesis:actions-missing", { findings: P.findings }) + stageCharter("synthesis", ctx.depth, ctx.framework),
13426
+ followup: repairFollowup("synthesis:actions-missing", { findings: P.findings, narrative: P.narrative }) + stageCharter("synthesis", ctx.depth, ctx.framework),
12479
13427
  });
12480
13428
  const after = readActionsPresence();
12481
13429
  if (after && !after.hasActions) runLog(run.runDir, { event: "findings-actions-absent" });
@@ -12658,8 +13606,9 @@ async function pipelineInner(job, opts = {}) {
12658
13606
  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
13607
  }
12660
13608
  }
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.
13609
+ // One corrective re-synthesis on CONDITIONAL/BLOCKING, feeding the reviewer's flags back in, dispatched on the
13610
+ // WINNING synthesis attempt's key, model and thinking, so the model that wrote a fallback-won synthesis corrects
13611
+ // it. Its first attempt is a fresh turn handed the flags and the inputs; it never resumes the synthesis session.
12663
13612
  let correctionsApplied = null; // — the driver's flag-by-flag observation, shared by both legs
12664
13613
  let evidenceViolations = []; // — claims that moved against their own evidence, carried to the recheck
12665
13614
  let correctionsScope = null; // — the declared per-finding scope, and whether the pass held to it
@@ -12719,11 +13668,7 @@ async function pipelineInner(job, opts = {}) {
12719
13668
  // A DEFECT SIGNAL, not a success. After the schema fix a corrective pass sends a targeted edit
12720
13669
  // and cannot remove a finding at all, so e2e asserts this count is ZERO — a firing reports the
12721
13670
  // 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}`) });
13671
+ runLog(run.runDir, removalRepairedRow(repaired)); // with `base`: whether the merge base took the restore
12727
13672
  note(`[corrections] the corrective pass removed ${repaired.restoredFindings.length} finding(s) `
12728
13673
  + `no flag named — restored whole from the pre-corrective snapshot: `
12729
13674
  + `${repaired.restoredFindings.map((f) => `#${f.ordinal} ${f.mark}`).join(", ")}`
@@ -12791,7 +13736,16 @@ async function pipelineInner(job, opts = {}) {
12791
13736
  runLog(run.runDir, { event: "corrections-applied", flags: correctionsApplied.length, outcomes: by,
12792
13737
  scoped: correctionsScope.scoped, named: correctionsScope.named.length,
12793
13738
  movedOutsideScope: correctionsScope.moved.length, unboundOrdinals: correctionsScope.unbound.length });
13739
+ // The basis for the delivery check each assembly makes (recordCorrectionsDelivered): what each flag
13740
+ // changed in the fields a report prints from the record, from the same two records. Its own file.
13741
+ try { recordCorrectionsBasis(run.runDir, correctionsApplied, preDoc, postDoc); }
13742
+ catch (e) {
13743
+ try { rmSync(driverDir(run.runDir, CORRECTIONS_DELIVERED_FILE), { force: true }); } catch { /* best-effort */ }
13744
+ note(`[corrections] the delivery basis could not be kept (${String(e?.message ?? e).slice(0, 100)}) — this pass has no delivery record`);
13745
+ }
12794
13746
  } catch (e) {
13747
+ // A basis from an earlier pass would be read against this pass's report as if it were this pass's.
13748
+ try { rmSync(driverDir(run.runDir, CORRECTIONS_DELIVERED_FILE), { force: true }); } catch { /* best-effort */ }
12795
13749
  correctionsApplied = null;
12796
13750
  note(`[corrections] the applied table could not be built (${String(e?.message ?? e).slice(0, 100)}) — the recheck reads both files, as before`);
12797
13751
  runLog(run.runDir, { event: "corrections-applied-failed", error: String(e?.message ?? e).slice(0, 160) });
@@ -12801,9 +13755,10 @@ async function pipelineInner(job, opts = {}) {
12801
13755
  // read, so a recheck spends a dispatch to re-derive the verdict it already gave. The entry verdict
12802
13756
  // stands, which is also the fail-safe direction this block uses everywhere else.
12803
13757
  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.)
13758
+ // A1: re-check after the corrective pass with a lean follow-up on the refutation's winning key and model —
13759
+ // NOT a full re-run of the stage, and NOT the base model when another model won. The follow-up's first
13760
+ // attempt starts fresh, and it never resumes the refutation's session (no engine resumes by key). (With no
13761
+ // winning key, as on a crash-resume skip, a clean forced re-run on the same model.)
12807
13762
  // A1: the recheck also runs on the CONDITIONAL path (the VENZY shipping path — previously the
12808
13763
  // corrective output was never re-verified there) and reads the updated FINDINGS too, since the client
12809
13764
  // surfaces render from findings.json, not the narrative. Failure falls back to the ENTRY verdict
@@ -12953,31 +13908,12 @@ async function pipelineInner(job, opts = {}) {
12953
13908
  // are settled by now; the corrective ladder above re-emitted findings last).
12954
13909
  enrichFindingDeadlines(P, run.runDir, note);
12955
13910
 
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
- }
13911
+ // ── P2-A pre-verdict floor: the reconciliation's hard discrepancy list ─────────────────────────
13912
+ // screened-live dominant-element candidates ∖ endings is read here as it always was, and it no longer THROWS:
13913
+ // the owner's ruling of 2026-10-19 that a run is never held replaces the Round-2 rule that this one class never
13914
+ // ships. The run first recovers what it can in-run (the digest seam's warm followups; placement assessing late
13915
+ // evidence before the writer); whatever is still unended ships NAMED, through the terminal guards' deliver-and-clamp
13916
+ // (`clampOnTheRecallGate`, beside the floor duty's clamp below), never as a run that delivers nothing.
12981
13917
 
12982
13918
  // ── — THE TERMINAL GUARDS DELIVER AND CLAMP; THEY DO NOT WITHHOLD ───────────
12983
13919
  //
@@ -13002,6 +13938,11 @@ async function pipelineInner(job, opts = {}) {
13002
13938
  // run earlier in this function. The sidecar write sorts guard clauses AFTER the opinion's, stated
13003
13939
  // there; this set is how it tells them apart without a parallel index to keep aligned.
13004
13940
  const guardClauses = new Set();
13941
+ // THE ACCEPTED ASSESSMENT: the result the review reached, before any guard or floor clamped it. It moves with the
13942
+ // review alone (the degenerate re-ask, a late review that is harsher), and a gap a guard judged again finds closed is
13943
+ // withdrawn against it (ruled 2026-10-19).
13944
+ let assessed = verdict;
13945
+ let restoredClear = false; // a closed gap took this run back to CLEAR, so a later writer's judgment meets the floor again
13005
13946
 
13006
13947
  /**
13007
13948
  * Deliver, clamp conservatively, and name the defect in the run record.
@@ -13018,14 +13959,20 @@ async function pipelineInner(job, opts = {}) {
13018
13959
  // could only be pinned by source text, and a source-text pin cannot tell an armed check from one
13019
13960
  // somebody disarmed while leaving its words in place. This closure is the wiring: run record, note,
13020
13961
  // clamp lists, status. The judgement is all in the module.
13021
- const deliverAndClamp = ({ event, defect, reason, clause, detail = {} }) => {
13962
+ const deliverAndClamp = ({ event, defect, reason, clause, detail = {}, again = false, statedElsewhere = false }) => {
13963
+ // A GUARD JUDGED AGAIN in this run says nothing new when its judgment is unchanged, and otherwise replaces its
13964
+ // earlier clause of the same kind, so a record is never counted twice and no count stands from an earlier pass.
13965
+ if (again && clampReasons.includes(String(reason ?? "").trim())) return;
13966
+ if (again) replaceClauses(String(defect ?? "").split(":")[0]);
13022
13967
  // — reason and clause are SEPARATE inputs now: the run record and the ops note keep
13023
13968
  // the token sentence; the clause list (which riskStatement renders into the client's Verdict row)
13024
13969
  // receives only the reader's sentence. terminalClampDecision refuses a fused clause by shape.
13025
13970
  const d = terminalClampDecision({ verdict, defect, reason, clause, detail });
13026
13971
  runLog(run.runDir, { event, ...d.record });
13027
13972
  note(d.reason);
13028
- if (!clampReasons.includes(d.reason)) { clampClauses.push(d.clause); clampReasons.push(d.reason); guardClauses.add(d.clause); }
13973
+ // A gap stated in another entry's sentence keeps its reason for the run record, and its clause is that sentence's.
13974
+ const stored = statedElsewhere ? null : d.clause;
13975
+ if (!clampReasons.includes(d.reason)) { clampClauses.push(stored); clampReasons.push(d.reason); guardClauses.add(stored); }
13029
13976
  if (d.clamped) {
13030
13977
  runLog(run.runDir, { event: "terminal-guard-clamp", defect: d.record.defect, from: verdict, to: d.verdict });
13031
13978
  verdict = d.verdict;
@@ -13033,31 +13980,96 @@ async function pipelineInner(job, opts = {}) {
13033
13980
  }
13034
13981
  };
13035
13982
 
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
- {
13983
+ // A CLAUSE JUDGED AGAIN REPLACES THE ONE IT REPEATS. A guard judged a second time in this run, after a repeat or a
13984
+ // re-run, withdraws its earlier clause of the same kind before it states the current one, so a record is never
13985
+ // counted twice and a count never stands from an earlier judgment. Returns how many entries it withdrew.
13986
+ const replaceClauses = (token, { event = "guard-clause-replaced" } = {}) => {
13987
+ const w = withdrawByToken(clampReasons, clampClauses, guardClauses, token);
13988
+ if (!w.withdrawn) return 0;
13989
+ clampReasons = w.reasons;
13990
+ clampClauses = w.clauses;
13991
+ runLog(run.runDir, { event, token, withdrawn: w.withdrawn });
13992
+ return w.withdrawn;
13993
+ };
13994
+
13995
+ // A GAP JUDGED AGAIN AND POSITIVELY CLOSED takes its own clause with it, and the result is recomputed from the
13996
+ // accepted assessment and the conditions still standing (ruled 2026-10-19). Only a CONDITIONAL that this run's
13997
+ // guards alone made goes back to CLEAR, and that CLEAR then meets the same deliver-conditional floor any CLEAR meets.
13998
+ // A gap whose account is absent, unreadable or not computable is not closed: its clause stands.
13999
+ const withdrawClosedGap = (token) => {
14000
+ if (!replaceClauses(token, { event: "guard-gap-closed" })) return;
14001
+ const now = verdictWithoutTheClosedGap({ verdict, assessed, reasons: clampReasons });
14002
+ if (now === verdict) return;
14003
+ runLog(run.runDir, { event: "verdict-recomputed-gap-closed", token, from: verdict, to: now });
14004
+ 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`);
14005
+ verdict = now;
14006
+ restoredClear = now === "CLEAR";
14007
+ writeRunStatus(ctx, signoffPatch(verdict));
14008
+ applyCoverageFloor(); // the floor any CLEAR meets: the opinion's forward actions, the coverage gaps
14009
+ };
14010
+
14011
+ // THE RECALL GATE, DELIVERED AND CLAMPED (the owner's ruling of 2026-10-19: a run is never held). A retrieved record still
14012
+ // ending nowhere a reader can see after the run's recovery ships named, in the approved sentence for the same state,
14013
+ // counted in records out of the records the gate weighed. A record another clause already names (the writer's duty,
14014
+ // the floor) is left to it, so no record is counted twice. Returns the count it clamped on, or 0.
14015
+ // THE RECALL GATE'S GAP AS IT STANDS, judged and not yet clamped, so the writer's duty can be stated with it.
14016
+ const recallGapNow = (duty = null) => {
14017
+ const rec = safeReadJson(P.recallReconciliation);
14018
+ const computable = rec?.computable === true;
14019
+ // A record the writer's duty or the floor's clamp already names is left to that clause: one record, one sentence.
14020
+ // The floor's rows count only where its clamp fires, so a record is never left unnamed by both.
14021
+ const floorNames = floorDutyBlock(safeReadJson(P.floorDuty), { armed: floorDutyArmed(run.runDir) })
14022
+ ? (safeReadJson(P.floorDuty)?.rows ?? []).filter((r) => r?.disposition !== "accounted").map((r) => String(r?.record_id ?? "").toLowerCase())
14023
+ : [];
14024
+ const alreadyNamed = new Set([...(duty?.computable === true ? duty.unaccounted : []).map((u) => String(u?.uri ?? "").toLowerCase()), ...floorNames]);
14025
+ // A RECORD A REFUSED MERGE LEFT OUT IS UNREAD, and it is counted here, in the same sentence, out of a total that
14026
+ // includes it: the band could not take it without passing the size its readers can be dispatched over.
14027
+ const left = refusedRecordsLeftOut(P);
14028
+ // A RECORD NO PLACEMENT PASS READ, and that the report does not address, is counted in the same sentence: the band
14029
+ // gained it after placement's last pass, and the pass that would have assessed it did not complete.
14030
+ const late = lateRecordsUnaddressed(P, alreadyNamed);
14031
+ const unended = (computable ? rec.unended ?? [] : []).filter((u) => !(u.candidate_records ?? [u.record_id])
14032
+ .some((x) => alreadyNamed.has(String(x).toLowerCase())));
14033
+ const weighedIds = new Set(unended.flatMap((u) => u.candidate_records ?? [u.record_id]).map((x) => String(x).toLowerCase()));
14034
+ const records = [...new Set([...weighedIds, ...left, ...late])];
14035
+ // Out of the records the gate weighed or, where it could weigh none, the records the band holds; and the records
14036
+ // left out besides. A late record outside the records the gate weighed is added to the total; where the gate
14037
+ // weighed none, the band's count already holds it.
14038
+ const base = computable ? Number(rec.totals?.candidates ?? 0) : (safeReadJson(P.registerNamedBand)?.enumerated ?? []).length;
14039
+ const weighed = base + left.length + (computable ? late.filter((id) => !weighedIds.has(id)).length : 0);
14040
+ // The same population by id, where the gate kept it: what a sentence stated with the writer's duty counts once.
14041
+ const population = computable
14042
+ ? (Array.isArray(rec.candidate_ids) ? [...rec.candidate_ids, ...left, ...late] : null)
14043
+ : [...(safeReadJson(P.registerNamedBand)?.enumerated ?? []).map((r) => String(r?.record_id ?? "").toLowerCase()), ...left];
14044
+ return { rec, computable, left, late, unended, records, base, weighed, population };
14045
+ };
14046
+ // THE RECALL GATE, DELIVERED AND CLAMPED (the owner's ruling of 2026-10-19: a run is never held). A retrieved record still
14047
+ // ending nowhere a reader can see after the run's recovery ships named, in the approved sentence for the same state,
14048
+ // counted in records out of the records the gate weighed. A record another clause already names (the writer's duty,
14049
+ // the floor) is left to it, so no record is counted twice. Where the writer's duty names records too, the one sentence
14050
+ // is the duty's, stated with these (`joint`). Returns the count it clamped on, or 0.
14051
+ const clampOnTheRecallGate = (duty = null, { again = false, gap = recallGapNow(duty), joint = null } = {}) => {
14052
+ const { rec, computable, left, late, unended, records, base, weighed } = gap;
14053
+ if (!left.length && !late.length && (!computable || !((rec.totals?.unended ?? 0) > 0))) {
14054
+ if (again && gapPositivelyClosed("recall_reconciliation_unended", { recall: rec, leftOut: [...left, ...late] })) withdrawClosedGap("recall_reconciliation_unended");
14055
+ return 0;
14056
+ }
14057
+ // Every record the gate would name is named by a clause standing now: its own earlier clause goes, so the record
14058
+ // is counted once. The gap is not closed, and the result is not recomputed.
14059
+ if (again && !records.length && unended.length < (rec?.unended ?? []).length) replaceClauses("recall_reconciliation_unended", { event: "guard-clause-named-elsewhere" });
14060
+ if (!records.length || !(weighed > 0)) return 0;
14061
+ const sample = [...unended.slice(0, 4).map((u) => `${u.mark_text} (${u.record_id})`), ...left.slice(0, 2), ...late.slice(0, 2)].join("; ");
14062
+ deliverAndClamp({
14063
+ event: "recall-reconciliation-unended-delivered",
14064
+ defect: `recall_reconciliation_unended:${records.length}`,
14065
+ detail: { positions: unended.length, records: records.length, candidates: base, refused: left.length, late: late.length, ...(joint ? { stated_with_duty: joint } : {}) },
14066
+ 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).` : ""}`,
14067
+ again,
14068
+ clause: clauseForDefect("recall_reconciliation_unended", records.length, weighed),
14069
+ statedElsewhere: Boolean(joint),
14070
+ });
14071
+ return records.length;
14072
+ };
13061
14073
  // ── — THE DECLINATION DUTY, CHECKED AGAINST THE DELIVERED DOCUMENT ────────────────────────
13062
14074
  //
13063
14075
  // A SECOND NET OVER ONE REACHABLE PATH, and it exists because I argued it could not fire and was
@@ -13070,10 +14082,19 @@ async function pipelineInner(job, opts = {}) {
13070
14082
  // boundary ever saw a call about the records added since, because the only call ordered against them
13071
14083
  // was the one that was refused.
13072
14084
  //
14085
+ // JUDGED AGAIN after the delivery check repeats placement or re-runs the writer, with `again`: a gap that remains
14086
+ // replaces its earlier clause with the current count, and a gap positively closed withdraws it (withdrawClosedGap).
14087
+ //
13073
14088
  // NO ERA STAMP, and it needs none: an archived run carries no declination spec, so `owed` is null,
13074
14089
  // the reconcile is `computable:false` and this never blocks. Archive compatibility by construction.
13075
- {
14090
+ const clampOnTheWritersDuty = ({ again = false } = {}) => {
13076
14091
  const duty = synthesisDutyForRun(run.runDir);
14092
+ // THE TWO GAPS STATED ONCE when both name records: the approved sentence, with each record counted once out of the
14093
+ // records either required to be assessed (ruled 2026-10-19). The run record keeps each gap's own counts.
14094
+ const gap = recallGapNow(duty);
14095
+ const joint = duty?.computable === true && duty.unaccounted.length
14096
+ ? jointUnaccounted({ dutyIds: duty.unaccounted.map((u) => u.uri), owedIds: owedUrisFor(run.runDir), recallIds: gap.records, weighedIds: gap.population })
14097
+ : null;
13077
14098
  if (duty?.computable === true && duty.unaccounted.length) {
13078
14099
  const sample = duty.unaccounted.slice(0, 4).map((u) => `${u.mark ? `${u.mark} ` : ""}(${u.uri})`).join("; ");
13079
14100
  // DELIVERS AND CLAMPS. It used to throw. The defect is real and stays
@@ -13083,18 +14104,48 @@ async function pipelineInner(job, opts = {}) {
13083
14104
  event: "synthesis-duty-undischarged",
13084
14105
  defect: `synthesis_unaccounted_delivered:${duty.unaccounted.length}`,
13085
14106
  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.`,
14107
+ 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).` : ""}`,
14108
+ again,
13087
14109
  // — the READER's sentence: what is open, in a lawyer's nouns. Counts survive;
13088
14110
  // the token, the record ids and the engine's nouns stay in `reason` and the run record.
13089
14111
  // COMPOSED BY THE AUTHORITY, NOT SPELLED HERE. The same sentence has to be reachable from a
13090
14112
  // run that stored no clause, where these two objects are long gone and only the reason's own
13091
14113
  // counts survive; `terminal-clamp.mjs` composes it from the counts either way, so the fresh
13092
14114
  // run and the republished archive cannot drift apart.
13093
- clause: clauseForDefect("synthesis_unaccounted_delivered", duty.unaccounted.length, duty.totals.owed),
14115
+ clause: joint ? clauseForDefect("synthesis_unaccounted_delivered", joint.n, joint.m)
14116
+ : clauseForDefect("synthesis_unaccounted_delivered", duty.unaccounted.length, duty.totals.owed),
13094
14117
  });
13095
- }
13096
- }
13097
-
14118
+ } else if (again && gapPositivelyClosed("synthesis_unaccounted_delivered", { duty })) withdrawClosedGap("synthesis_unaccounted_delivered");
14119
+ const recall = clampOnTheRecallGate(duty, { again, gap, joint }); // the recall gate's gap, beside the writer's duty
14120
+ return { duty, recall };
14121
+ };
14122
+ // ── — THE FLOOR DUTY, DISCLOSED AND CLAMPED AT DELIVERY (it BLOCKED until) ───
14123
+ //
14124
+ // The floors are placement-inquiry's one UNCAPPED obligation — every live in-class identical or
14125
+ // near-identical record, listed individually — and the duty has been measured and never enforced.
14126
+ // Measured on two delivered runs before it was even derived: 45 of 207 floors on one and 99 of 225
14127
+ // on the next never reached the placement form, every one a LIVE filing.
14128
+ //
14129
+ // ARMED BY THE STAMP, NOT BY THE ARTIFACT'S PRESENCE. Across the 24 run directories on the test box,
14130
+ // 19 carry no floor-duty artifact at all — and on this box "absent" means a knockout run far more
14131
+ // often than an old one, so absence cannot be read as either "clean" or "old". The stamp is written
14132
+ // only by a placement pass that actually ran under the code carrying the order.
14133
+ //
14134
+ // IT COUNTS THE SEAT'S OWN SHARE. A floor row the band gave no record id is undischarged and
14135
+ // UNANSWERABLE — no seat behaviour could have closed it, and the repair it implies can be performed
14136
+ // by nobody. Blocking on it would fail a run for a band defect under a rule about seat behaviour, so
14137
+ // it is disclosed in the message and excluded from the count that blocks. That is a stated limit,
14138
+ // not an oversight: if the unanswerable count is ever the large one, it wants its own issue against
14139
+ // the band rather than a wider floor here.
14140
+ //
14141
+ // NO REPAIR RUNG, deliberately and on a measurement (ruling relayed 2026-08-27). Every
14142
+ // registered repair composer belongs to another stage; placement has none, and building one is a
14143
+ // larger piece of work than this floor. The blast radius said it could wait: on the day this was
14144
+ // written today's run discharged all 60 of its floors, and the only two runs with undischarged rows
14145
+ // carried 5 and 9 out of 222 and 352. A rung is a follow-up, never a precondition.
14146
+ // THE FLOOR DUTY'S CLAMP, callable: applied where the terminal checks run, and again to a placement pass the
14147
+ // delivery gate repeats, whose account the report is then rebuilt from. Returns the block it clamped on, or null.
14148
+ const clampOnTheFloorDuty = ({ again = false } = {}) => {
13098
14149
  // THE PREDICATE IS `floorDutyBlock`, called not copied — it lives in floor-duty.mjs so the arms can
13099
14150
  // DRIVE it. Inline here it could only be pinned by source text, and a source-text pin cannot tell
13100
14151
  // an armed check from one somebody disarmed while leaving its words in place.
@@ -13113,6 +14164,7 @@ async function pipelineInner(job, opts = {}) {
13113
14164
  + `${block.named_without_ground ? ` (${block.named_without_ground} named with no ground)` : ""}`
13114
14165
  + `${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
14166
  + `. The report ships with these named rather than being withheld.`,
14167
+ again,
13116
14168
  // — the READER's sentence. "Floor row" is an engine noun; what the fact IS for a
13117
14169
  // lawyer: live registrations identical or near-identical to the mark that the report does not
13118
14170
  // individually address. Counts survive; token, ids and engine nouns stay in `reason`.
@@ -13120,7 +14172,14 @@ async function pipelineInner(job, opts = {}) {
13120
14172
  // gives: a republished pre-split run has to reach the same sentence from the counts alone.
13121
14173
  clause: clauseForDefect("floor_duty_undischarged", block.undischarged, block.floors),
13122
14174
  });
13123
- }
14175
+ } else if (again && gapPositivelyClosed("floor_duty_undischarged", { floorClosed: floorDutyClosed(safeReadJson(P.floorDuty), { armed: floorDutyArmed(run.runDir) }) }))
14176
+ withdrawClosedGap("floor_duty_undischarged");
14177
+ return block;
14178
+ };
14179
+ {
14180
+ clampOnTheWritersDuty(); // the writer's duty and, beside it, the recall gate (defined above the floor's block)
14181
+
14182
+ clampOnTheFloorDuty();
13124
14183
  }
13125
14184
 
13126
14185
  // PR-3 (report voice) — the factual open-state clause per reason, INDEX-ALIGNED with clampReasons.
@@ -13350,7 +14409,7 @@ async function pipelineInner(job, opts = {}) {
13350
14409
  // MANDATORY now (was best-effort): the sidecar IS the single authority; a failed write is a run
13351
14410
  // failure, never a silent fallback to divergent labels. Re-invoked after findings consolidation
13352
14411
  // (the delivery block) so the sidecar always reflects the FINAL findings set.
13353
- const writeVerdictSidecar = () => {
14412
+ const composeVerdictSidecar = () => {
13354
14413
  let findingsArr = [], sv = 1, actionsArr = null, actionsDropped = [];
13355
14414
  try {
13356
14415
  const doc = parseFindingsJsonLenient(readFileSync(P.findings, "utf8"));
@@ -13447,10 +14506,16 @@ async function pipelineInner(job, opts = {}) {
13447
14506
  // consumer keys on (never the statement's wording — the magic-string coupling is retired).
13448
14507
  const statement = riskStatement({ tier: derived.tier, verdict, reasons: reasonsOut, clauses: clausesOut,
13449
14508
  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,
14509
+ return { derived, statement, doc: { ts: new Date().toISOString(), verdict, reasons: reasonsOut, clauses: clausesOut, kinds: kindsOut,
13452
14510
  tier: derived.tier, badge: derived.badge, gaugeIndex: derived.gaugeIndex, maxComposite: derived.maxComposite,
13453
- band: derived.band ?? null, statement, stance: verdictStance(verdict) }, null, 2));
14511
+ band: derived.band ?? null, statement, stance: verdictStance(verdict) } };
14512
+ };
14513
+ // The label, composed above and written here. Composing is apart so the delivery check can show the stages it re-runs
14514
+ // the label it could not yet save.
14515
+ const writeVerdictSidecar = () => {
14516
+ const { derived, statement, doc } = composeVerdictSidecar();
14517
+ const tmp = driverDir(run.runDir, "verdict.json.tmp");
14518
+ writeFileSync(tmp, JSON.stringify(doc, null, 2));
13454
14519
  renameSync(tmp, driverDir(run.runDir, "verdict.json"));
13455
14520
  // — THE STATUS RECORD CARRIES THE BAND AND THE SENTENCE, so a reader of the run does not
13456
14521
  // have to reach for `verdict` to say something about the outcome. `verdict` is the gate's decision
@@ -13460,6 +14525,26 @@ async function pipelineInner(job, opts = {}) {
13460
14525
  writeRunStatus(ctx, { tier: derived.tier ?? null, statement: statement ?? null });
13461
14526
  return derived;
13462
14527
  };
14528
+ // THE LABEL, SAVED AGAIN IN THE DELIVERY CHECK, WITHOUT ENDING THE RUN (2026-10-19). After a clamp there the label
14529
+ // has to reach every surface the report is published from, and they all read it from the one file. A save that
14530
+ // fails is tried again. One that still fails is owed: the stages the check re-runs are shown the label as composed,
14531
+ // and the save is made once more before anything is published, so no surface is built from the label settled
14532
+ // before the clamp. Returns whether it is saved.
14533
+ let labelOwed = null;
14534
+ let rankingBeforeRepeat = null; // where each record stood before the delivery check repeated placement
14535
+ const saveTheLabel = (why) => {
14536
+ const saved = saveWithRetries(() => writeVerdictSidecar());
14537
+ if (saved.ok) {
14538
+ if (saved.attempts > 1) runLog(run.runDir, { event: "verdict-write-retried", why, attempts: saved.attempts });
14539
+ try { displayVerdict = JSON.parse(readFileSync(driverDir(run.runDir, "verdict.json"), "utf8")); } catch { /* the label is re-read at reassembly */ }
14540
+ return true;
14541
+ }
14542
+ labelOwed = labelOwed ?? why;
14543
+ runLog(run.runDir, { event: "verdict-write-deferred", why, attempts: saved.attempts, fail: String(saved.error?.message ?? saved.error).slice(0, 160) });
14544
+ 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`);
14545
+ try { displayVerdict = composeVerdictSidecar().doc; } catch { /* composed again when it is saved */ }
14546
+ return false;
14547
+ };
13463
14548
  try { writeVerdictSidecar(); }
13464
14549
  catch (e) { throw new StageFailure("verdict", `verdict sidecar write failed (the single label authority): ${String(e.message).slice(0, 120)}`); }
13465
14550
 
@@ -13524,6 +14609,7 @@ async function pipelineInner(job, opts = {}) {
13524
14609
  reviewLedger.record("reviewer-degenerate-reask", run.codename, rr.ok ? "ok" : `failed: ${rr.fail}`, { max: degenerateReaskMax });
13525
14610
  if (rr.ok) {
13526
14611
  verdict = parseVerdict(readReview()) ?? "BLOCKING";
14612
+ assessed = verdict; // the re-asked review is the assessment now
13527
14613
  runLog(run.runDir, { event: "verdict-3", verdict, trigger: "degenerate-reask" });
13528
14614
  writeRunStatus(ctx, signoffPatch(verdict));
13529
14615
  applyCoverageFloor(); // a re-asked CLEAR passes the same deliver-conditional floor
@@ -13632,12 +14718,19 @@ async function pipelineInner(job, opts = {}) {
13632
14718
  catch (e2) { throw new StageFailure("verdict", `verdict sidecar refresh failed after consolidation: ${String(e2.message).slice(0, 120)}`); }
13633
14719
  }
13634
14720
  };
14721
+ // THE RECORD THE CARDS ARE BUILT ON CARRIES EVERY DEMOTION. The derivation settled the record after the first
14722
+ // synthesis save; a corrective pass saves again before this seam, and a whole re-send brings back every stamp it had
14723
+ // demoted. Built on those, every card whose finding carried one read as stale after the derivation before delivery
14724
+ // demoted them again, and was rebuilt with the overview for a finding nothing had changed. Settled here, once more,
14725
+ // before the overview and the cards; idempotent on a record that already carries every demotion.
14726
+ settleDerivedBases(ctx, "seam");
13635
14727
  await seamWrites();
13636
14728
  // Every write above is idempotent: each injection skips a row the record already carries, the stamp
13637
14729
  // replaces its rows, and the fold leaves a folded record as it is.
13638
14730
  ctx.afterSynthesisSave = async () => {
13639
14731
  note(`[seam] a synthesis save after the seam rewrote findings.json — re-applying the seam's writes`);
13640
14732
  runLog(run.runDir, { event: "seam-reapplied", after: "synthesis-save" });
14733
+ rewriteFromBeforeTheSeam(ctx); // the basis derivation and the deadline enrichment, as the pass made them
13641
14734
  await seamWrites();
13642
14735
  };
13643
14736
  // wp50/wi2 — the overview PROSE must speak the derived tier (VENZY: caption said "High risk" while
@@ -13703,13 +14796,11 @@ async function pipelineInner(job, opts = {}) {
13703
14796
  // plausible zero, and a zero here means every floor row quietly loses the full card the owner made
13704
14797
  // non-negotiable. An absent band-shape yields an empty map and every finding reads as holding no
13705
14798
  // 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");
14799
+ // The plan is made in one place (planWriteUps), the same way here and wherever a later save changes the record:
14800
+ // the full cards in the record's order, then the remainder, in the same order, for the short-entry and grouped-row
14801
+ // render below.
14802
+ const writeUps = planWriteUps(findingsForCards, ctx.depth, safeReadJson(P.bandShape));
14803
+ const { cardOrdinals, entryOrdinals } = writeUps;
13713
14804
  // §9 — WRITTEN ON EVERY GRADED PRODUCT, INCLUDING ONE WHOSE ENTRY COUNT IS 0. The old gate was
13714
14805
  // `entryOrdinals.length`, which conflated two different empties: one country is empty BY CONSTRUCTION
13715
14806
  // (every form is `full`, so the row would report the product's own definition back), while a graded run
@@ -13780,17 +14871,11 @@ async function pipelineInner(job, opts = {}) {
13780
14871
  // The bodies are written to the SAME card files a dispatched card would use, so `assembleReportMd`
13781
14872
  // frames them with the same composer and the ordinal binding is untouched. What they never get is a
13782
14873
  // 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
- }
14874
+ const { entryFindings, byRight, entrySingles } = writeUps;
14875
+ writeShortEntries(P, entrySingles);
13790
14876
  // Assembly sees full cards AND short entries; the DISPATCH set and the freshness stage list stay
13791
14877
  // `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);
14878
+ const { assembledOrdinals, groupedMembers } = writeUps;
13794
14879
  if (entryFindings.length)
13795
14880
  note(`write-up forms: ${cardOrdinals.length} full card(s), ${entrySingles.length} short entr(y/ies), ${groupedMembers.length} grouped line(s)`);
13796
14881
  assembleReportMd(P, findingsForCards, assembledOrdinals, { grouped: groupedMembers, byRight });
@@ -13912,6 +14997,9 @@ async function pipelineInner(job, opts = {}) {
13912
14997
  // A2 — full audit: DETERMINISTIC CODE (parse the spine tables → audit.md, count-guarded). Replaces the LLM
13913
14998
  // audit-emit (output varied 47↔69 on identical input). NON-FATAL: if it fails, the report still ships (publish
13914
14999
  // renders the report with no xlsx when audit.md is absent), and the audit self-recovers on a re-run.
15000
+ // `rebuildTheDeliveredRecord` is set by the block once the audit is written: the delivery check calls it after a repair.
15001
+ let rebuildTheDeliveredRecord = null;
15002
+ let auditWrittenThisPass = false, earlierPairKept = null;
13915
15003
  try {
13916
15004
  // One resolution per claim: hand the builder the run's OWN dispositions so a withdrawn conflict reads
13917
15005
  // as withdrawn on the audit surface too (it already does on report + client summary). Best-effort —
@@ -13932,6 +15020,8 @@ async function pipelineInner(job, opts = {}) {
13932
15020
  // mint below, the predelivery lint's `recordCarry` arms) reads THIS artifact rather than deriving
13933
15021
  // its own: two derivations from different inputs is what let the doubt ledger be roughly right
13934
15022
  // while the artifact and every lint arm were inverted.
15023
+ // A resumed pass writes both again: what the earlier pass wrote is kept under _history first.
15024
+ earlierPairKept = keepDeliveredRecordAsHistory(P, "earlier-pass");
13935
15025
  deriveRecordCarry(ctx, "publish", { findings: auditRunFindings ?? [] });
13936
15026
  // ── — AND NOW THE JOIN BETWEEN THE TWO ARTIFACTS THAT WERE EACH RIGHT ────────
13937
15027
  //
@@ -14062,6 +15152,7 @@ async function pipelineInner(job, opts = {}) {
14062
15152
  supplementalPlans,
14063
15153
  xcheck: readJson(driverDir(run.runDir, "register-xcheck.json")),
14064
15154
  recall: recallReceiptForOwnCompany(readJson(driverDir(run.runDir, "register-recall.json"))), // never another company's recall line
15155
+ withheldFamilies: readWithheldFamilies(run.runDir), // the reading turn's reasons, for a frame ask its judgment settled
14065
15156
  planExecution: ctx.planExecution ?? readJson(P.planExecution),
14066
15157
  }, { ts: askTs });
14067
15158
  } catch (e) {
@@ -14163,12 +15254,13 @@ async function pipelineInner(job, opts = {}) {
14163
15254
  // a record of what the EXECUTOR did, and nothing after the reopen can retroactively make an
14164
15255
  // undispatched term dispatched, so the receipt is the authority and re-deriving would need band
14165
15256
  // 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.
15257
+ // answered — and nor does one the reading turn's recorded judgment settled (SETTLED_CLASSES). A run
15258
+ // with no reopen, or a legacy receipt with no term rows, mints nothing.
14167
15259
  try {
14168
15260
  const fr = JSON.parse(readFileSync(driverDir(run.runDir, "frame-reopen.json"), "utf8"));
14169
15261
  if (Array.isArray(fr?.remedy_terms)) {
14170
15262
  doubts.push(...mintRemedyAccountingDoubts(
14171
- { not_accounted: fr.remedy_terms.filter((r) => !["found", "searched-empty"].includes(r?.class)) },
15263
+ { not_accounted: fr.remedy_terms.filter((r) => !SETTLED_CLASSES.includes(r?.class)) },
14172
15264
  { sourceName: "_driver/frame-reopen.json" },
14173
15265
  ));
14174
15266
  }
@@ -14223,7 +15315,7 @@ async function pipelineInner(job, opts = {}) {
14223
15315
  ? openAfterStitch.filter((d) => doubtSel.ids.includes(d.id))
14224
15316
  : openAfterStitch;
14225
15317
  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 }));
15318
+ ctx.openAsks = openAsksForStage.map(openAskForClosure);
14227
15319
  // THE SPEC SIDECAR, WRITTEN BEFORE THE DISPATCH. The seat supplies none of its own context:
14228
15320
  // the ids it may speak about and the files it may cite are the driver's, so a doubt it was not
14229
15321
  // given is refused by name and a file it was not given cannot be expressed at all. Both
@@ -14326,16 +15418,7 @@ async function pipelineInner(job, opts = {}) {
14326
15418
  // _driver/reading-log.jsonl). Passed on any run that derived a band shape — an EMPTY array renders
14327
15419
  // the honest zero ("no lookups recorded"), because a band-consuming stage that read nothing is a
14328
15420
  // 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
- }
15421
+ const readingLog = readReadingLog(P);
14339
15422
  // item 33 — hand the builder the machine grid so the common-law search log is derived from the
14340
15423
  // tool-written per-term cells rather than from a heading the tagger has to match. Absent (pre-grid
14341
15424
  // archive, register-only run) it falls back to the prose tables, which is honest: those runs have
@@ -14377,13 +15460,25 @@ async function pipelineInner(job, opts = {}) {
14377
15460
  runLog(run.runDir, { event: "register-presence-skipped", reason: "no register-named-band.json" });
14378
15461
  }
14379
15462
  const a = buildAuditMd(registerFindingsText, commonLawText,
14380
- { findings: auditRunFindings, doubts: runDoubts, asks: runAsks, readingLog, commonLawGrid, doubtTruncations, registerPresence });
15463
+ { findings: auditRunFindings, doubts: runDoubts, asks: runAsks, readingLog, commonLawGrid, doubtTruncations, registerPresence,
15464
+ placementRows: placementAuditRows(P) });
14381
15465
  if (!a.counts.findings) throw new Error("0 findings parsed from the spine tables"); // count-guard
14382
15466
  writeFileSync(P.audit, a.md);
15467
+ auditWrittenThisPass = true;
14383
15468
  runLog(run.runDir, { event: "audit", ...a.counts }); // — counts now carry doubtsOmitted
15469
+ // The trace and the Audit Trail, written again over a repair (`rebuildDeliveredRecord`), from the inputs this
15470
+ // pass built them from; the doubts minted from the trace follow the trace written again.
15471
+ rebuildTheDeliveredRecord = (trigger) => rebuildDeliveredRecord(ctx,
15472
+ { commonLawText, doubts: runDoubts, asks: runAsks, commonLawGrid, doubtTruncations, registerPresence }, trigger);
14384
15473
  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
15474
  } catch (e) {
14386
15475
  note(`audit build failed (NON-FATAL — report still ships, audit will regenerate on re-run): ${e.message}`);
15476
+ // A findings record with no finding row trips the count guard, and on a clean run it can: that is recorded apart,
15477
+ // as a note on the run's content, and only a build that broke is recorded as a failure.
15478
+ const noFindings = /^0 findings parsed from the spine tables/.test(String(e?.message ?? ""));
15479
+ runLog(run.runDir, { event: noFindings ? "audit-not-built-no-findings" : "audit-build-failed", reason: String(e?.message ?? e).slice(0, 160) });
15480
+ // An audit an earlier pass wrote describes that pass, not this one: the report ships without it, kept under _history.
15481
+ if (!auditWrittenThisPass) withdrawSupersededAudit(P, "audit-build-failed", { keep: !earlierPairKept });
14387
15482
  }
14388
15483
 
14389
15484
  // ── Spec A1 + A2: pre-delivery verification — pure code, before ANYTHING outward-facing. Per the
@@ -14667,6 +15762,9 @@ async function pipelineInner(job, opts = {}) {
14667
15762
  // (composeEmailHtml opts.verdict, from _driver/verdict.json), the pre-delivery lint below, and
14668
15763
  // the client gate. On the R2 evidence run this ladder was where the cost landed: three
14669
15764
  // byte-identical failures before it exhausted.
15765
+ // The writer's accepted record as it stood just before the delivery-check repair, kept only when that
15766
+ // repair runs: the review after the repair is handed what changed between the two (repair-scope.mjs).
15767
+ let repairBaseKept = false;
14670
15768
  if (lint.failures.length) {
14671
15769
  runLog(run.runDir, { event: "predelivery-lint-failed", failures: lint.failures.map((f) => f.id) });
14672
15770
  const bySurface = (s) => lint.failures.filter((f) => f.surface === s || (s === "report" && f.surface === "all"));
@@ -14684,13 +15782,13 @@ async function pipelineInner(job, opts = {}) {
14684
15782
  // `Write` and `Edit` out of its grant. Same class as the ten anchor sites and the max-tokens
14685
15783
  // corrective before it: a retirement reaches the first-attempt surfaces and misses a correction
14686
15784
  // 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) => {
15785
+ const redo = async (label, failures, key, model, thinking, file, more = {}) => {
14688
15786
  if (!failures.length || !key) return;
14689
15787
  note(`pre-delivery lint: ${failures.length} failed check(s) on ${label} — one warm named-correction redo`);
14690
15788
  const toolWritten = file ? toolWrittenArtifact(file) : null;
14691
15789
  const r = await stage(label, ctx, {
14692
15790
  force: true, sessionKey: key, model, thinking, trigger: "lint-repair",
14693
- followup: repairFollowup("*:lint-repair", { label, file, toolWritten, failures })
15791
+ followup: repairFollowup("*:lint-repair", { label, file, toolWritten, failures, ...more })
14694
15792
  + stageCharter(label, ctx.depth, ctx.framework),
14695
15793
  });
14696
15794
  if (r.ok) repaired.push(label);
@@ -14705,7 +15803,14 @@ async function pipelineInner(job, opts = {}) {
14705
15803
  // finding-provenance bleed) re-renders THAT card alone, FRESH (isolated from its own record, so the bleed
14706
15804
  // cannot recur); a shell failure (no ordinal) re-runs report-overview. Then re-assemble. Bounded: one pass.
14707
15805
  let reassembled = false;
14708
- const badOrdinals = [...new Set(reportFixable.map((f) => f.ordinal).filter((o) => o != null))];
15806
+ // Each fault goes to the part that holds it (repair-targets.mjs): a card's to that card, with the detail its
15807
+ // check gives on that card alone; the overview's to the overview; and a fault only the lines composed from the
15808
+ // record hold to the writer's repair below, since the record is the writer's. One no single part holds keeps the
15809
+ // overview's repair.
15810
+ const targets = locateReportFaults(reportFixable, reportPartsOf(P, findingsForCards, writeUps));
15811
+ if (reportFixable.length) runLog(run.runDir, { event: "report-repair-targets", cards: [...targets.cards.keys()],
15812
+ overview: targets.overview.map((f) => f.id), stamped: targets.stamped.map((f) => f.id), unlocated: targets.unlocated.map((f) => f.id) });
15813
+ const badOrdinals = [...targets.cards.keys()];
14709
15814
  for (const ord of badOrdinals) {
14710
15815
  const f = findingsForCards.find((x) => x.ordinal === ord);
14711
15816
  if (!f) continue;
@@ -14718,7 +15823,7 @@ async function pipelineInner(job, opts = {}) {
14718
15823
  // isolation from the contaminated rendering is the entire point — patching the bad card would
14719
15824
  // preserve exactly what the re-render exists to discard. The redo() ladder below is the opposite
14720
15825
  // 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);
15826
+ const why = targets.cards.get(ord).map((x) => x.detail).filter(Boolean);
14722
15827
  const rc = await stage("report-card", { ...ctx, axis: String(ord), finding: f }, {
14723
15828
  force: true, trigger: "lint-repair",
14724
15829
  ...(why.length ? { extra: lines(
@@ -14729,14 +15834,26 @@ async function pipelineInner(job, opts = {}) {
14729
15834
  });
14730
15835
  if (rc.ok) { repaired.push(`report-card:${ord}`); reassembled = true; }
14731
15836
  }
14732
- const shellFixable = reportFixable.filter((f) => f.ordinal == null);
15837
+ const shellFixable = targets.overview;
14733
15838
  if (shellFixable.length) { await redo("report-overview", shellFixable, rov.sessionKey, rov.model, rov.thinking, P.reportOverview); if (repaired.includes("report-overview")) reassembled = true; }
14734
15839
  // — THE NARRATIVE REDO ROUTE. The same mechanism report-overview already has, re-aimed at
14735
15840
  // the seat that wrote narrative.md. Without it a depth check could only FLAG, and a flag on a
14736
15841
  // narrative that broke the rules is the run shipping the defect with a note attached. ONE warm
14737
15842
  // 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);
15843
+ //
15844
+ // A FAULT IN A LINE COMPOSED FROM THE RECORD (a finding's own sentence, a short entry, a grouped line) is the
15845
+ // writer's too: no card or overview repair can change what the renderer stamps from the record. It joins the
15846
+ // narrative's faults in the writer's one repair, each named by the finding that owns it, in the numbering the
15847
+ // writer's save takes. Its consumers then rebuild, and its change is reviewed, as for any repair of the record.
15848
+ const fold = readFold(run.runDir);
15849
+ const sentenceFixable = targets.stamped.flatMap((f) => (f.parts ?? []).map((x) => ({ ...f, finding: keptModelOrdinal(fold, x.ordinal), detail: x.detail })));
15850
+ const narrativeFixable = [...modelFixable(bySurface("narrative")), ...sentenceFixable];
15851
+ if (narrativeFixable.length && synthesisKey) repairBaseKept = snapshotRepairBase(run.runDir);
15852
+ // A writer fault with no writer session to repair it is not repaired, and the run says so.
15853
+ if (narrativeFixable.length && !synthesisKey)
15854
+ runLog(run.runDir, { event: "lint-redo-skipped", surface: "writer", why: "no-writer-session", failures: narrativeFixable.map((f) => f.id) });
15855
+ if (narrativeFixable.length) await redo("synthesis", narrativeFixable, synthesisKey, synthesisModel, synthesisThinking, P.narrative,
15856
+ { findings: modelRecordPath(run.runDir, P.findings), narrative: P.narrative });
14740
15857
  if (reassembled) assembleReportMd(P, findingsForCards, assembledOrdinals, { grouped: groupedMembers, byRight });
14741
15858
  // The client-summary redo arm is retired with the stage (2026-08-01). Its `email`-surface
14742
15859
  // failures have not gone unowned: the `:email` reference check now runs over the report-derived
@@ -14763,16 +15880,35 @@ async function pipelineInner(job, opts = {}) {
14763
15880
  // lint-repair-tax metric: recording the verdict here means no consumer can read the failure list of
14764
15881
  // a stale-blocked run and mistake it for a delivery-quality signal. The receipt itself stays
14765
15882
  // ALWAYS-WRITTEN (spec A2 auditor evidence) — blocking a run must not cost us the evidence of why.
14766
- const deliveryPathStages = [...new Set([
15883
+ // The cards on the path are the ones the record owes as it stands, read again each time the path is judged: a
15884
+ // synthesis save after the cards can add a finding that is owed one, or withdraw one whose card is owed no longer.
15885
+ const deliveryPathStages = () => [...new Set([
14767
15886
  ...(ctx.skippedStages ?? []), "register-digest", "synthesis", "narrative-refutation",
14768
- "report-overview", ...cardOrdinals.map((o) => `report-card:${o}`),
15887
+ "report-overview", ...owedCardOrdinals(P, ctx.depth).map((o) => `report-card:${o}`),
14769
15888
  ])];
14770
15889
  const deliveryInputsFor = (label) => {
14771
15890
  const bare = label.replace(/:.*$/, "");
14772
15891
  const axis = label.includes(":") ? label.slice(label.indexOf(":") + 1) : null;
14773
15892
  try { return stageInputs(bare, P, { axes: ctx.axes, axis, registerOnly: ctx.registerOnly }); } catch { return []; }
14774
15893
  };
14775
- staleStages = staleOnPath(run.runDir, deliveryPathStages, deliveryInputsFor, { project: projectStageInput });
15894
+ staleStages = [...staleOnPath(run.runDir, deliveryPathStages(), deliveryInputsFor, { project: projectStageInput }),
15895
+ ...cardsNeverBuilt(P, ctx.depth, cardOrdinals)]; // a finding a later save added is owed its one card call
15896
+ // A DELIVERY-CHECK REPAIR THAT CHANGED ONLY THE FINDINGS RECORD leaves the narrative's bytes, and so the
15897
+ // reviewer, fresh — the reviewer does not declare findings.json. The change is still content no review has
15898
+ // read, so the reviewer is queued for the review after a repair, marked as the record's change.
15899
+ if (repairBaseKept && !staleStages.some((s2) => s2.label === "narrative-refutation")) {
15900
+ if (recordChangeOwesReview(run.runDir)) {
15901
+ // At the FRONT, so the dependency order below puts it after the writer and ahead of the overview and the
15902
+ // cards, which then rebuild from whatever its correction changes.
15903
+ staleStages.unshift({ label: "narrative-refutation", changed: [{ name: basename(P.findings), via: "repair-record" }] });
15904
+ runLog(run.runDir, { event: "post-repair-review-queued", why: "the repair changed the findings record only" });
15905
+ }
15906
+ }
15907
+ // A RESUME AFTER SUCH A CHANGE WENT UNREVIEWED: its record queues the reviewer again, so this pass reviews it.
15908
+ if (unreadRecordReview(run.runDir) && !staleStages.some((s2) => s2.label === "narrative-refutation")) {
15909
+ staleStages.unshift({ label: "narrative-refutation", changed: [{ name: basename(P.findings), via: "repair-record" }] });
15910
+ runLog(run.runDir, { event: "post-repair-review-queued", why: "a change to the findings record went unreviewed" });
15911
+ }
14776
15912
  // ── AD-2 A1 (E2E-R2, ordering) — repair-owned staleness is re-done IN-PASS, never parked. The
14777
15913
  // pre-delivery repair can move an input under the tail this pass already built (the observed
14778
15914
  // case: the report-overview lint redo rewrote findings.json, staling every report card + the
@@ -14816,20 +15952,232 @@ async function pipelineInner(job, opts = {}) {
14816
15952
  if (existsSync(P.caseLaw)) caseLawNow = joinCaseLawProfiles(parseCaseLawProfiles(readFileSync(P.caseLaw, "utf8")), findingsNow);
14817
15953
  } catch { /* no case-law layer */ }
14818
15954
  let reassemble = false;
15955
+ // THE LATE VERDICT, ONE RULE FOR BOTH REVIEWS THIS LOOP CAN RUN — the whole review and the review after
15956
+ // a delivery-check repair. A late review may only harden the run's verdict, and a softer one is logged,
15957
+ // never adopted. Returns what it found, so the review after a repair can keep the review whose verdict
15958
+ // the run carries in senior-eye-review.md.
15959
+ const adoptLateReview = (reviewNow, stageLabel) => {
15960
+ const { hardened, softened: reviewSays } = lateReviewAgainst(verdict, reviewNow);
15961
+ // A late review harsher than the assessment is a ground of its own: a gap closed later never lifts it.
15962
+ const says = parseVerdict(reviewNow);
15963
+ if ((VERDICT_RANK[says] ?? -1) > (VERDICT_RANK[assessed] ?? -1)) assessed = says;
15964
+ // A REVIEW THAT SOFTENED IS NOT ADOPTED, AND THE RUN SAYS SO. The ratchet below only
15965
+ // tightens, so a re-review that comes back softer leaves the settled verdict in force while
15966
+ // senior-eye-review.md, rewritten by this repair, opens with the softer word. That is the
15967
+ // design; recording nothing was the defect: a reader of the run found two answers and no
15968
+ // line saying which one governs (measured in testing, 2026-09-23: BLOCKING recorded, CONDITIONAL on disk).
15969
+ if (reviewSays) {
15970
+ runLog(run.runDir, { event: "verdict-softening-not-adopted", was: verdict, reviewSays, stage: stageLabel });
15971
+ 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`);
15972
+ }
15973
+ if (hardened) {
15974
+ // ── T3a — THE LATE VERDICT IS ADOPTED, NOT THROWN, AND ADOPTION IS THE WHOLE JOB ──
15975
+ //
15976
+ // built the throw here because a late hardening was an input the gate had been
15977
+ // DENIED: the verdict settles thousands of lines earlier, nothing re-reads the review,
15978
+ // so a reviewer refusing to sign DURING this repair "is written to disk and never
15979
+ // heard". Measured on `bf21580e` — the narrative printed a registration date of
15980
+ // 1 March 2011 against a record reading 25 February 2011, the reviewer said so, and
15981
+ // the run delivered anyway.
15982
+ //
15983
+ // DELETING THE THROW WITHOUT A DESTINATION RE-OPENS EXACTLY THAT HOLE, and that is
15984
+ // why this branch is not the same edit as the gate's. The owner's ruling removes the
15985
+ // REFUSAL; it does not say the reviewer stops being heard. So the late verdict is
15986
+ // adopted as the run's own and everything derived from it is rebuilt:
15987
+ //
15988
+ // · `verdict` and status.json — the run's own record
15989
+ // · verdict.json — "the single label authority" (its words)
15990
+ // · `reassemble` — assembleReportMd re-runs, so the open-points
15991
+ // section is built from THIS review, and
15992
+ // applyVerdictFrontMatter re-reads the sidecar
15993
+ // and re-stamps overall_label / overall_badge
15994
+ //
15995
+ // Without the sidecar write the report would ship BADGED WITH THE VERDICT SETTLED
15996
+ // BEFORE THE REVIEWER HARDENED — refused by the reviewer, labelled CONDITIONAL. That
15997
+ // is worse than either behaviour this replaces, and it is `bf21580e`'s shape one
15998
+ // layer up: the reviewer's word on disk and the client's document disagreeing.
15999
+ //
16000
+ // `verdictHardenedTo` still ratchets ONE WAY, so a review that SOFTENED during the
16001
+ // repair still cannot lift a clamp. This branch only ever tightens.
16002
+ runLog(run.runDir, { event: "verdict-hardened-by-repair", was: verdict, now: hardened, stage: stageLabel, adopted: true });
16003
+ 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`);
16004
+ verdict = hardened;
16005
+ writeRunStatus(ctx, signoffPatch(verdict));
16006
+ try { writeVerdictSidecar(); }
16007
+ catch (e) { throw new StageFailure("verdict", `verdict sidecar write failed (the single label authority): ${String(e.message).slice(0, 120)}`); }
16008
+ reassemble = true;
16009
+ }
16010
+ return { hardened, softened: reviewSays };
16011
+ };
16012
+ const processedLabels = new Set(); // the labels this loop has re-done, for the re-queue after a correction
16013
+ const requeuedLabels = new Set();
16014
+ const repairFailed = new Map(); // label → why its re-run in this loop did not complete
16015
+ // A CHANGED DECISION IS READ AGAIN IN THIS PASS. A repeated placement pass whose decisions moved leaves the reading
16016
+ // step, the writer and the review stale, and each of them leaves what reads it stale in turn. They are queued here,
16017
+ // in dependency order after the stage that moved them, each re-done at most once more; one already queued runs where
16018
+ // it stands, and a stage stale before the repair for the same reasons is not the repair's. The reviewer gets the whole
16019
+ // review and its one correction, and the writer is judged again against its duty and the recall gate. The pass
16020
+ // then ends with all of them rebuilt from the decision that stands. The guard below used to park the run on them,
16021
+ // 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
16022
+ // after its re-run still falls to the guard: no approved sentence states that case yet.
16023
+ const chain = new Set();
16024
+ const staleNow = () => staleOnPath(run.runDir, deliveryPathStages(), deliveryInputsFor, { project: projectStageInput });
16025
+ const queueWhatItLeftStale = (s2, before) => {
16026
+ const at = staleStages.indexOf(s2) + 1;
16027
+ const left = whatTheRepairLeftStale({ before, after: staleNow(), repaired: s2.label,
16028
+ ahead: new Set(staleStages.slice(at).map((x) => x.label)), processed: processedLabels, requeued: requeuedLabels });
16029
+ for (const l of left.chain) chain.add(l);
16030
+ for (const l of left.requeue) requeuedLabels.add(l);
16031
+ if (!left.queue.length) return;
16032
+ const rest = [...staleStages.slice(at), ...left.queue];
16033
+ const order = [...new Set(dependencyOrder(rest.map((x) => x.label), P, { axes: ctx.axes }))]; // each label once
16034
+ staleStages.splice(at, staleStages.length - at, ...order.map((l) => rest.find((x) => x.label === l)).filter(Boolean));
16035
+ runLog(run.runDir, { event: "delivery-stale-requeued", after: s2.label, stages: left.queue.map((x) => x.label) });
16036
+ };
16037
+ // A STAGE STALE ON WHAT PLACEMENT WROTE IS THE CHAIN'S FROM THE START, whoever moved it: the decisions written
16038
+ // again when the shape is derived again, or a late pass whose readers had already run, as much as this check's
16039
+ // own repeat. Its re-run is followed, and a re-run that does not complete delivers.
16040
+ const placementWrote = new Set([...stageOutputs("placement-inquiry", P, { axes: ctx.axes }).map((f) => basename(f)), basename(P.placementDecisions)]);
16041
+ for (const l of stagesStaleOnPlacement(staleStages, placementWrote)) chain.add(l);
14819
16042
  for (const s2 of staleStages) {
16043
+ processedLabels.add(s2.label);
14820
16044
  if (s2.label === "report-overview") {
14821
16045
  const r = await stage("report-overview", { ...ctx, displayVerdict }, { force: true, trigger: "stale-repair" });
14822
16046
  if (r.ok) { staleRepaired.push(s2.label); reassemble = true; }
14823
16047
  else note(`stale-repair: report-overview re-emit failed (${r.fail}) — stays stale, the guard below decides`);
16048
+ } else if (s2.label === "narrative-refutation"
16049
+ && (chain.has(s2.label) || postRepairScope(run.runDir, P, s2, { staleRepaired, baseKept: repairBaseKept }).mode !== "whole"
16050
+ || queuedForTheRecordOnly(s2) || unreadRecordReview(run.runDir))) {
16051
+ // A REVIEWER A CHANGED DECISION QUEUED is given the whole review and its one correction here, as the pass that
16052
+ // re-ran it on a resume gave it a review and a correction.
16053
+ // THE REVIEW AFTER A DELIVERY-CHECK REPAIR (see runPostRepairReview). Reached where the scope is certain, and
16054
+ // where a change to the record, or one an earlier review could not read, is reviewed whole: that review's
16055
+ // flags go to the same one correction. Every other staleness of the reviewer takes the arm below, as before.
16056
+ // A CHANGE NO REVIEW READ IS REVIEWED WHOLE, whatever this pass's own repair changed: the base this pass
16057
+ // kept follows the earlier change, so a narrowed review would read only the later one, and saving the same
16058
+ // values again would settle a stamp over a change nobody read.
16059
+ const unreadOnEntry = unreadRecordReview(run.runDir);
16060
+ const narrowedScope = postRepairScope(run.runDir, P, s2, { staleRepaired, baseKept: repairBaseKept });
16061
+ const scope = unreadOnEntry && narrowedScope.mode !== "whole" ? { mode: "whole", why: "an earlier change went unreviewed" } : narrowedScope;
16062
+ if (scope.mode === "none") {
16063
+ // A FILE WRITTEN AGAIN WITH THE SAME VALUES IS NOT A CHANGE: no review runs, and the reviewer's stamp
16064
+ // takes the rewrite as accounted for, so the delivery guard does not block on it.
16065
+ settleOneShotStamp(run.runDir, "narrative-refutation", [P.narrative], "repair-changed-no-content");
16066
+ runLog(run.runDir, { event: "post-repair-review-not-needed", why: scope.why });
16067
+ note(`[post-repair] the repair saved the same values again — no review needed`);
16068
+ staleRepaired.push(s2.label);
16069
+ continue;
16070
+ }
16071
+ const rerun = () => UPSTREAM_STALE_REPAIR[s2.label](ctx, { displayVerdict });
16072
+ // THE STATE THIS REVIEW READS IS KEPT WHOLE before it runs, so a correction that fails after it has a trusted
16073
+ // state to come back to even where the record itself is lost meanwhile.
16074
+ retainStateBeforeReview(run.runDir, P);
16075
+ // A scope that cannot be narrowed is reviewed whole at once, and its record lists no change it could compare.
16076
+ let review = scope.mode === "whole"
16077
+ // A resume reviews the change its earlier record names, so that record says what the change was.
16078
+ ? await runWholeReviewForRecord(ctx, P, run, { ...scope, changed: scope.changed ?? NO_CHANGE,
16079
+ findingOnly: unreadRecordFindingOnly(run.runDir) ?? queuedForTheRecordOnly(s2) }, rerun)
16080
+ : await runPostRepairReview(ctx, P, run, scope);
16081
+ // A narrowed review that could not be read falls back to the whole review, whose points go to the same one
16082
+ // correction below: neither the stamp of a findings-only change nor that of a stage that ends ok on the
16083
+ // earlier review's file can show the change unread. When the whole review cannot be read either, its
16084
+ // record keeps the change no review read; the record holds nothing (see `unreadRecordReview`).
16085
+ if (!review.reviewed && scope.mode !== "whole") review = await runWholeReviewForRecord(ctx, P, run, scope, rerun);
16086
+ if (!review.reviewed) continue;
16087
+ staleRepaired.push(s2.label);
16088
+ reassemble = true;
16089
+ // senior-eye-review.md HOLDS THE REVIEW WHOSE VERDICT THE RUN CARRIES. A narrowed review that hardened
16090
+ // the verdict stays in place, so the open questions print its grounds; otherwise the whole review goes
16091
+ // back, and the narrowed one is kept under _driver/.
16092
+ const { hardened } = adoptLateReview(review.reviewText, s2.label);
16093
+ if (!hardened) review.putBack();
16094
+ if (review.rec.flags.length) {
16095
+ const pass = await runPostRepairCorrection(ctx, P, run, { rec: review.rec, items: review.items, points: review.points,
16096
+ grounds: Boolean(hardened), whole: review.base?.whole === true, synthesisRef: { key: synthesisKey, model: synthesisModel, thinking: synthesisThinking } });
16097
+ // NOTHING REVIEWS THE CORRECTION: the reviewer's stamp takes what it changed as accounted for, and so
16098
+ // does the first cycle's receipt, so a resume re-fires neither.
16099
+ settleOneShotStamp(run.runDir, "narrative-refutation", [P.narrative], "post-repair-fix");
16100
+ try {
16101
+ const at = driverDir(run.runDir, "corrective-cycle.json");
16102
+ let receipt = null;
16103
+ try { receipt = JSON.parse(readFileSync(at, "utf8")); } catch { receipt = null; }
16104
+ atomicWrite(at, JSON.stringify({ ...(receipt ?? { entryVerdict: verdict }), ts: new Date().toISOString(), verdict,
16105
+ shas: { review: shaOf(P.seniorEyeReview), narrative: shaOf(P.narrative) }, settledBy: "post-repair-cycle" }, null, 2) + "\n");
16106
+ } 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`); }
16107
+ // ITS CONSUMERS REBUILD FROM WHAT IT WROTE: re-read the findings, and queue the overview and any card it
16108
+ // left stale that this loop has not queued already.
16109
+ readNow = readFindingsForReport(P);
16110
+ findingsNow = readNow.findings;
16111
+ ordinalsNow = fullProseOrdinals(findingsNow);
16112
+ try {
16113
+ if (existsSync(P.caseLaw)) caseLawNow = joinCaseLawProfiles(parseCaseLawProfiles(readFileSync(P.caseLaw, "utf8")), findingsNow);
16114
+ } catch { /* no case-law layer */ }
16115
+ // After a correction, and after a rollback that rebuilt the record from a save, what reads the record is
16116
+ // rebuilt from it; a rollback that put the same bytes back leaves nothing stale, and queues nothing.
16117
+ if (pass.ok || pass.rollback) {
16118
+ // A label not queued yet is appended; one this loop already re-done is re-queued ONCE, so the
16119
+ // pass stays bounded and still ends with every surface rebuilt from the corrected record.
16120
+ const queued = new Set(staleStages.map((x) => x.label));
16121
+ // A finding the correction's save added is owed its one card call, as after any later save.
16122
+ for (const x of [...staleOnPath(run.runDir, deliveryPathStages(), deliveryInputsFor, { project: projectStageInput }),
16123
+ ...cardsNeverBuilt(P, ctx.depth, cardOrdinals)]) {
16124
+ if (!(x.label === "report-overview" || x.label.startsWith("report-card:"))) continue;
16125
+ if (!queued.has(x.label)) { staleStages.push(x); queued.add(x.label); }
16126
+ else if (processedLabels.has(x.label) && !requeuedLabels.has(x.label)) { requeuedLabels.add(x.label); staleStages.push(x); }
16127
+ }
16128
+ }
16129
+ // THE WRITER'S DUTY, JUDGED AGAIN ONCE THE CORRECTION HAS SETTLED, rollback included. The terminal guards judged
16130
+ // it on the record before this correction. A correction that rolled back restores a record that the duty as it
16131
+ // now stands may not be met by: a save restored as accepted work, or records owed since the save was made. The
16132
+ // approved clause names what it leaves unaccounted, and a gap it closed withdraws its clause, as after the
16133
+ // writer's re-run in this loop; the label is saved again before anything is published.
16134
+ {
16135
+ const was = verdict, stood = JSON.stringify(clampReasons);
16136
+ const judged = clampOnTheWritersDuty({ again: true });
16137
+ if (restoredClear && verdict === "CLEAR") applyCoverageFloor();
16138
+ if (JSON.stringify(clampReasons) !== stood || verdict !== was) saveTheLabel("after the correction after a post-repair review");
16139
+ runLog(run.runDir, { event: "writer-duty-after-correction", was, now: verdict, rolledBack: Boolean(pass.rollback),
16140
+ owed: judged.duty?.computable === true ? judged.duty.totals?.owed ?? null : null,
16141
+ unaccounted: judged.duty?.computable === true ? judged.duty.unaccounted.length : null, recall: judged.recall });
16142
+ }
16143
+ }
16144
+ continue;
14824
16145
  } else if (UPSTREAM_STALE_REPAIR[s2.label]) {
14825
16146
  // The upstream arm (item 15a). Each of these re-runs the SAME stage the pipeline would have
14826
16147
  // run, forced, with a `stale-repair` trigger so the telemetry says why. Dependency order put
14827
16148
  // it ahead of anything that reads what it writes, so the tail arms below rebuild from the
14828
16149
  // repaired artifact rather than from the one that moved.
16150
+ const reviewSeq0 = s2.label === "narrative-refutation" ? nextRefutationSeq(run.runDir) : null;
16151
+ const staleBefore = s2.label === "placement-inquiry" || chain.has(s2.label) ? staleNow() : null; // what was stale before this one
16152
+ if (s2.label === "placement-inquiry" && !rankingBeforeRepeat) rankingBeforeRepeat = rankingNow(P); // as the writer last read it
14829
16153
  const r = await UPSTREAM_STALE_REPAIR[s2.label](ctx, { displayVerdict });
14830
- if (r?.ok) {
16154
+ // NOT A PASS: a reviewer that ends with no accepted call reads ok whenever the earlier review's file
16155
+ // stands, and its stamp is refreshed over the change it never read. The record says so, the run delivers,
16156
+ // and a resume for any other cause reviews the change whole.
16157
+ if (r?.ok && s2.label === "narrative-refutation" && !readAcceptedReviewSince(run.runDir, reviewSeq0)) {
16158
+ writePostRepairRecord(run.runDir, { status: "unread", whole: true, findingOnly: queuedForTheRecordOnly(s2), by: "stale-repair", fail: "no-accepted-call" });
16159
+ runLog(run.runDir, { event: "post-repair-review", read: false, whole: true, fail: "no-accepted-call" });
16160
+ 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`);
16161
+ } else if (r?.ok) {
14831
16162
  staleRepaired.push(s2.label);
14832
16163
  reassemble = true;
16164
+ // THE FLOOR, APPLIED AGAIN TO THE PLACEMENT THE REPORT IS NOW BUILT FROM. The delivery floor was judged on
16165
+ // the placement account before this repair; the repeated pass has recorded its own floor duty, and the
16166
+ // stages after it rebuild from it in this loop. The same rule is applied to that account, through the
16167
+ // same clamp, so the delivered verdict, its floor and its record describe one pass. It only tightens.
16168
+ if (s2.label === "placement-inquiry") {
16169
+ const was = verdict, stood = JSON.stringify(clampReasons);
16170
+ clampOnTheFloorDuty({ again: true });
16171
+ clampOnTheWritersDuty({ again: true }); // the recall gate leaves out the floor that now stands
16172
+ // A save that fails here is tried again, then owed, never the end of the run: see saveTheLabel.
16173
+ if (JSON.stringify(clampReasons) !== stood || verdict !== was) saveTheLabel("after the repeated placement pass");
16174
+ runLog(run.runDir, { event: "floor-duty-after-repair", was, now: verdict });
16175
+ }
16176
+ // A whole review that is read reviews the change an earlier unread one left: its record says so, and
16177
+ // its points stand under the late-review rule below, as any whole review's do. (The arm above has
16178
+ // already set aside a stage that read ok without an accepted review.)
16179
+ if (s2.label === "narrative-refutation" && unreadRecordReview(run.runDir))
16180
+ writePostRepairRecord(run.runDir, { status: "read", whole: true, by: "stale-repair" });
14833
16181
  // its consumers rebuild from what it JUST wrote — re-read, do not carry the pre-repair copy
14834
16182
  readNow = readFindingsForReport(P);
14835
16183
  findingsNow = readNow.findings;
@@ -14837,6 +16185,7 @@ async function pipelineInner(job, opts = {}) {
14837
16185
  try {
14838
16186
  if (existsSync(P.caseLaw)) caseLawNow = joinCaseLawProfiles(parseCaseLawProfiles(readFileSync(P.caseLaw, "utf8")), findingsNow);
14839
16187
  } catch { /* no case-law layer */ }
16188
+ for (const owed of cardsNeverBuilt(P, ctx.depth, cardOrdinals)) if (!staleStages.some((x) => x.label === owed.label)) staleStages.push(owed);
14840
16189
  // — AND THE REVIEW IS ALSO SOMETHING THIS PASS JUST WROTE.
14841
16190
  //
14842
16191
  // The re-reads above are the artifacts a repaired stage's CONSUMERS need. narrative-refutation
@@ -14854,56 +16203,28 @@ async function pipelineInner(job, opts = {}) {
14854
16203
  // stays where it is; this restores the input it was denied. `verdictHardenedTo` ratchets one
14855
16204
  // way, so a review that softened during the repair cannot lift a clamp.
14856
16205
  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
- }
16206
+ adoptLateReview(existsSync(P.seniorEyeReview) ? readFileSync(P.seniorEyeReview, "utf8") : "", s2.label);
16207
+ }
16208
+ // THE WRITER, JUDGED AGAIN WHEN A CHANGED DECISION RE-RAN IT. Its duty and the recall gate were judged on the
16209
+ // writer before it; a gap this one opens is named through the same clamps, and one already named stays named.
16210
+ // The label is saved again as the floor's is above, through saveTheLabel.
16211
+ if (s2.label === "synthesis") keepRankingReadByWriter(P); // where each record stood as this writer read it
16212
+ if (s2.label === "synthesis" && chain.has(s2.label)) {
16213
+ const was = verdict, stood = JSON.stringify(clampReasons);
16214
+ const judged = clampOnTheWritersDuty({ again: true });
16215
+ // A CLEAR a closed gap gave back meets the floor again on what this writer now says: its forward actions
16216
+ // are a ground of their own.
16217
+ if (restoredClear && verdict === "CLEAR") applyCoverageFloor();
16218
+ if (JSON.stringify(clampReasons) !== stood || verdict !== was) saveTheLabel("after the writer's re-run");
16219
+ runLog(run.runDir, { event: "writer-duty-after-repair", was, now: verdict,
16220
+ owed: judged.duty?.computable === true ? judged.duty.totals?.owed ?? null : null,
16221
+ unaccounted: judged.duty?.computable === true ? judged.duty.unaccounted.length : null, recall: judged.recall });
14905
16222
  }
14906
- } else note(`stale-repair: ${s2.label} re-run failed (${r?.fail}) — stays stale, the guard below decides`);
16223
+ if (staleBefore) queueWhatItLeftStale(s2, staleBefore);
16224
+ } else {
16225
+ repairFailed.set(s2.label, String(r?.fail ?? "failed").slice(0, 200));
16226
+ note(`stale-repair: ${s2.label} re-run failed (${r?.fail}) — stays stale, the guard below decides`);
16227
+ }
14907
16228
  } else if (s2.label.startsWith("report-card:")) {
14908
16229
  const ord = Number(s2.label.slice("report-card:".length));
14909
16230
  const f = findingsNow.find((x) => x.ordinal === ord);
@@ -14923,12 +16244,19 @@ async function pipelineInner(job, opts = {}) {
14923
16244
  }
14924
16245
  }
14925
16246
  if (reassemble) {
14926
- assembleReportMd(P, findingsNow, ordinalsNow);
16247
+ reassembleFromTheRecord(P, findingsNow, ctx.depth); // the plan the first assembly used, made from the record as it stands
14927
16248
  applyVerdictFrontMatter(); // the reassembly rewrote report.md — re-apply every driver-owned
14928
16249
  // field, exactly as the lint-repair branch above does
14929
16250
  writeScopeFacts();
14930
16251
  applyScopeFrontMatter();
14931
16252
  applyRatedUnderFrontMatter();
16253
+ // A repair that re-ran a stage the trace and the Audit Trail report has both written again, before the lint
16254
+ // below reads the trace. A rebuild that fails withdraws the audit written before the repair, as a failed first
16255
+ // build ships none.
16256
+ if (rebuildTheDeliveredRecord && staleRepaired.some((l) => REBUILDS_THE_DELIVERED_RECORD.has(l))) {
16257
+ const rebuilt = rebuildTheDeliveredRecord("stale-repair");
16258
+ 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`);
16259
+ }
14932
16260
  }
14933
16261
  // The AD-2 in-pass stale-repair arm for client-summary is retired with the stage
14934
16262
  // (2026-08-01): nothing generates that label, so it can never turn up stale here.
@@ -14936,7 +16264,65 @@ async function pipelineInner(job, opts = {}) {
14936
16264
  // freshness question — one bounded pass; anything still stale blocks below, exactly as before.
14937
16265
  lint = lintNow();
14938
16266
  autoCorrectRegistry();
14939
- staleStages = staleOnPath(run.runDir, deliveryPathStages, deliveryInputsFor, { project: projectStageInput });
16267
+ staleStages = staleOnPath(run.runDir, deliveryPathStages(), deliveryInputsFor, { project: projectStageInput });
16268
+ // A REPAIR WHOSE REVIEW COULD NOT BE READ IS NOT REVIEWED, and its record says so (`unreadRecordReview`). The guard
16269
+ // is never handed that record: a run is not held on an unread review (owner's ruling, 2026-10-19). What the guard
16270
+ // reads here is staleness alone, as in the released engine.
16271
+ // A CHANGED DECISION STILL UNREAD AFTER ITS RE-RUN IS DELIVERED, NOT HELD (the owner's ruling of 2026-10-19 that a
16272
+ // run is never held). When every stage still stale is one the changed decision left stale and whose re-run in
16273
+ // this pass did not complete, the run delivers what it has. The writer's duty and the recall gate are judged
16274
+ // again on the writer delivered, and name in their approved sentences what they find. Staleness for any other
16275
+ // reason still falls to the guard.
16276
+ // LATE RECORDS NO PLACEMENT PASS COULD ASSESS ARE DELIVERED, NOT HELD (ruled 2026-10-19). The band gained records
16277
+ // after placement's last pass, and neither the pass that would have assessed them nor the delivery check's repeat
16278
+ // of placement completed. The report stands as the last accepted passes built it. An account from before the late
16279
+ // records cannot show they were assessed, so the floor and the recall gate are judged again on the band as it
16280
+ // stands, and the late records the report does not address are counted in the approved sentence for records
16281
+ // neither addressed nor set aside. The attempts and the records are kept in the run record.
16282
+ const unassessed = repairFailed.has("placement-inquiry") ? lateRecordIds(P) : [];
16283
+ if (unassessed.length && staleStages.some((x) => x.label === "placement-inquiry")
16284
+ && staleStages.every((x) => x.label === "placement-inquiry" || chain.has(x.label))) {
16285
+ const was = verdict, stood = JSON.stringify(clampReasons);
16286
+ clampOnTheFloorDuty({ again: true });
16287
+ const judged = clampOnTheWritersDuty({ again: true });
16288
+ if ((JSON.stringify(clampReasons) !== stood || verdict !== was) && saveTheLabel("after late records went unassessed")) applyVerdictFrontMatter();
16289
+ runLog(run.runDir, { event: "late-evidence-unassessed-delivered", was, now: verdict, records: unassessed.length, ids: unassessed.slice(0, 20),
16290
+ stated: judged.recall, latePass: ctx.latePassFailed ?? null, repeat: repairFailed.get("placement-inquiry") });
16291
+ 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`);
16292
+ staleStages = staleStages.filter((x) => x.label !== "placement-inquiry");
16293
+ }
16294
+ if (staleStages.length && staleStages.every((x) => chain.has(x.label))) {
16295
+ const was = verdict, named = clampReasons.length, stood = JSON.stringify(clampReasons);
16296
+ clampOnTheWritersDuty({ again: true });
16297
+ // THE RECORDS RE-RANKED SINCE THE WRITER LAST WROTE, in the owner's words of 2026-10-19. The report states the
16298
+ // ranking the writer last read; the audit trail, written again after the repeat, states the one that stands.
16299
+ const readByWriter = rankingFromJson(safeReadJson(rankingReadByWriterPath(P))) ?? rankingBeforeRepeat;
16300
+ if (readByWriter && staleStages.some((x) => x.label === "synthesis")) {
16301
+ const now = rankingNow(P), n = recordsReRanked(readByWriter, now);
16302
+ const m = Math.max(rankedRecords(readByWriter), rankedRecords(now), n);
16303
+ if (n > 0) deliverAndClamp({
16304
+ event: "ranking-changed-unread-delivered",
16305
+ defect: `ranking_changed_unread:${n}`,
16306
+ detail: { records: n, of: m },
16307
+ 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.`,
16308
+ clause: clauseForDefect("ranking_changed_unread", n, m),
16309
+ });
16310
+ }
16311
+ if ((JSON.stringify(clampReasons) !== stood || verdict !== was) && saveTheLabel("after a changed decision went unread")) applyVerdictFrontMatter();
16312
+ runLog(run.runDir, { event: "changed-decision-unread-delivered", was, now: verdict, named: clampReasons.length - named,
16313
+ stages: staleStages.map((x) => ({ label: x.label, changed: (x.changed ?? []).map((c) => c.name) })) });
16314
+ 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`);
16315
+ staleStages = [];
16316
+ }
16317
+ // THE LABEL OWED IS SAVED NOW, before anything is published from it, and the report's own label follows it. Only
16318
+ // a run directory that refuses every save of it ends the run here, and then nothing the run writes can be published.
16319
+ if (labelOwed) {
16320
+ const owed = labelOwed;
16321
+ labelOwed = null;
16322
+ if (!saveTheLabel(`${owed}, saved again before delivery`))
16323
+ throw new StageFailure("verdict", `the run directory refused every save of the result's label (${owed}) — no surface can be published with it`);
16324
+ applyVerdictFrontMatter();
16325
+ }
14940
16326
  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
16327
  }
14942
16328
  // P2 — record the delivery-BLOCKING stale labels the moment they are known (not only at the throw
@@ -16421,12 +17807,12 @@ async function runExperimentInner(job, opts) {
16421
17807
 
16422
17808
  // — THE CORRECTIVE PASS, on a COLD turn, and it says which pass it reproduced.
16423
17809
  //
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.
17810
+ // Production's corrective pass is a followup dispatch on the run's own synthesis key, and its message is
17811
+ // these blocks: its first attempt starts fresh, and it never resumes the synthesis session (no engine resumes
17812
+ // by key). An arm builds a fresh key in a sandbox, and dispatching on the real run's key was rejected because
17813
+ // "canonical run untouched" is the arm's whole contract. So the arm carries the same INPUTS on a cold turn,
17814
+ // composed by the same function production uses — reaching the seam, not reproducing production's dispatch.
17815
+ // The receipt says `dispatched-cold`, never `dispatched-warm`, so the two cannot be read as one another.
16430
17816
  const P = shadowCtx.paths;
16431
17817
  const correctiveReady = correctiveReadiness({
16432
17818
  preCorrective: existsSync(driverDir(shadowDir, "findings-pre-corrective.json")),
@@ -17060,6 +18446,52 @@ async function afterLateSynthesisSave(name, ctx, r) {
17060
18446
  if (r?.ok && !r.skipped && name === "synthesis" && typeof ctx?.afterSynthesisSave === "function") await ctx.afterSynthesisSave();
17061
18447
  }
17062
18448
 
18449
+ /**
18450
+ * THE DRIVER'S WRITES FROM BEFORE THE SEAM, MADE AGAIN AFTER A SYNTHESIS SAVE THAT FOLLOWS IT.
18451
+ *
18452
+ * Two writes into the findings record run before the seam rather than at it: the basis derivation, which demotes a
18453
+ * `verified-from-record` stamp the reading log cannot back, and the deadline enrichment, which gives a finding with
18454
+ * no deadline the register's own opposition window. A save after the seam renders the record from the model's call.
18455
+ * A patch keeps both, because each also lands in the merge base the patch is merged onto; a whole re-send replaces
18456
+ * that base, and they went with it. The seam's own writes were made again after such a save, and these two were not,
18457
+ * so the delivered record claimed reads no step had made, and every card whose finding had carried a demoted stamp
18458
+ * read as stale and was rebuilt for a finding the save had not changed. They run again here, in the order the pass
18459
+ * ran them, before the seam's writes. Each leaves a record that already carries it as it is, and neither throws.
18460
+ */
18461
+ export function rewriteFromBeforeTheSeam(ctx) { // @internal
18462
+ settleDerivedBases(ctx, "late-save");
18463
+ enrichFindingDeadlines(ctx.paths, ctx.paths.runDir, note, { nowMs: deadlineClockOf(ctx.paths.runDir) });
18464
+ }
18465
+
18466
+ /**
18467
+ * The clock the run's deadline enrichment judged its windows by, so a window is judged again exactly as it was
18468
+ * judged the first time, never moved across its edge by the minutes a repair took. Now, on a run whose enrichment
18469
+ * recorded none: it judged nothing, so there is nothing to keep it consistent with. Never throws.
18470
+ */
18471
+ function deadlineClockOf(runDir) {
18472
+ try {
18473
+ const at = Date.parse(JSON.parse(readFileSync(driverDir(runDir, "deadline-enrich.json"), "utf8"))?.judged_at);
18474
+ return Number.isFinite(at) ? at : Date.now();
18475
+ } catch { return Date.now(); }
18476
+ }
18477
+
18478
+ /**
18479
+ * THE MERGE BASE TAKES THE ENRICHED DEADLINES. A patch after the enrichment is merged onto
18480
+ * `_driver/synthesis-calls/accepted.json` and re-renders every finding from there, so a deadline written to the
18481
+ * findings file alone was dropped by the next patch, and a card built with it was rebuilt without it. In the model's
18482
+ * numbering: under a fold, a deadline lands on the member the fold kept, whose object the folded finding is, and the
18483
+ * fold carries it again. Only a finding the base holds with no deadline of its own takes one, as in the file. The
18484
+ * edit is judged as a call is; a refusal leaves the base as it was, and the receipt says what happened. Never throws.
18485
+ */
18486
+ function rebaseEnrichedDeadlines(runDir, enriched) {
18487
+ const fold = readFold(runDir);
18488
+ const byOrdinal = new Map(enriched.map((e) => [keptModelOrdinal(fold, e.ordinal), e.date]).filter(([o]) => o != null));
18489
+ if (!byOrdinal.size) return "unmapped";
18490
+ const r = rebaseSynthesis(runDir, (held) => ({ ...held, findings: (Array.isArray(held?.findings) ? held.findings : [])
18491
+ .map((f) => (byOrdinal.has(f?.ordinal) && !f?.deadline?.date ? { ...f, deadline: { kind: "opposition", date: byOrdinal.get(f.ordinal) } } : f)) }));
18492
+ return r.ok ? (r.changed ? "enriched" : "unchanged") : /^no_base:/.test(String(r.reason)) ? "no-base" : "refused";
18493
+ }
18494
+
17063
18495
  /**
17064
18496
  * The reader's name for a planned search that never ran and that no open ledger row on its axis claims —
17065
18497
  * the orphan row of `coverageJudgmentRows`. Its area is the plan's qid, which a lawyer cannot read; the
@@ -17112,3 +18544,202 @@ export function foreignRecordLinksBlanked(r, ctx, name) { // exported for its
17112
18544
  runLog(ctx.paths.runDir, { event: "foreign-record-links-recheck", stage: name, ok: false, fail: String(v?.reason ?? "no validator").slice(0, 200) });
17113
18545
  return false;
17114
18546
  }
18547
+
18548
+ /**
18549
+ * THE WRITE-UPS A RECORD OWES, PLANNED IN ONE PLACE. PURE.
18550
+ *
18551
+ * §9: a finding owed prose earns a full card where its form is `full`, and the rest render code-side, as a short
18552
+ * entry or as a line in the grouped section (§8). The first assembly made this plan inline, and the delivery check's
18553
+ * repair, which reassembles the report after a synthesis save has moved the record, made its own: every finding owed
18554
+ * prose and no grouped section. So a report reassembled on a graded product lost its "Also on the register" section,
18555
+ * where its grouped findings are read by territory or by right, each short entry kept the text composed before the
18556
+ * save, and the cards a run owed stayed the ones the first plan named whatever the record became. One plan, made here
18557
+ * from the record as it stands, now serves the first build, the delivery path and every reassembly.
18558
+ *
18559
+ * The floor tier joins on `mark_text` (floorTierByMark); an absent band shape yields an empty map, and every finding
18560
+ * then reads as holding no floor. On one country every form is `full`, so the plan is `fullProseOrdinals` itself.
18561
+ */
18562
+ export function planWriteUps(findings, depth, bandShape) { // @internal
18563
+ const floorTiers = floorTierByMark(bandShape);
18564
+ const formOf = (f) => writeUpForm(depth, f, { floorTier: floorTiers.get(floorMarkKey(f?.mark)) ?? null });
18565
+ const byOrdinal = new Map((findings ?? []).filter((f) => f?.ordinal != null).map((f) => [f.ordinal, f]));
18566
+ const proseOrdinals = fullProseOrdinals(findings);
18567
+ const cardOrdinals = proseOrdinals.filter((ord) => formOf(byOrdinal.get(ord)) === "full");
18568
+ const entryOrdinals = proseOrdinals.filter((ord) => formOf(byOrdinal.get(ord)) !== "full");
18569
+ const entryFindings = entryOrdinals.map((ord) => byOrdinal.get(ord)).filter(Boolean);
18570
+ // Worldwide groups by the right, multi-country by territory: the depth row's prose rung says which.
18571
+ const byRight = String(depth?.narrativeProse ?? "") === "adversarial";
18572
+ const { groups, singles: entrySingles } = groupEntries(entryFindings, { byRight });
18573
+ return {
18574
+ cardOrdinals, entryOrdinals, entryFindings, byRight, entrySingles,
18575
+ // Assembly sees full cards AND short entries; a grouped finding is a line in its section, never a card.
18576
+ assembledOrdinals: [...cardOrdinals, ...entrySingles.map((f) => f.ordinal)],
18577
+ groupedMembers: groups.flatMap((g) => g.findings),
18578
+ };
18579
+ }
18580
+
18581
+ /** Each short entry's card file, composed by code from its finding as it stands. No model turn. */
18582
+ function writeShortEntries(P, entrySingles) {
18583
+ for (const f of entrySingles) {
18584
+ try { writeFileSync(P.reportCard(String(f.ordinal)), `${shortEntryBody(f)}\n`); }
18585
+ 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`); }
18586
+ }
18587
+ }
18588
+
18589
+ /** The full cards the record owes as it stands now, in its order. */
18590
+ function owedCardOrdinals(P, depth) {
18591
+ return planWriteUps(readFindingsForReport(P).findings, depth, safeReadJson(P.bandShape)).cardOrdinals;
18592
+ }
18593
+
18594
+ /**
18595
+ * THE CARD A FINDING ADDED AFTER THE CARDS IS OWED. A card with no stamp is never stale, so a finding that a
18596
+ * synthesis save after the cards made owed a full card, at an ordinal the first plan never dispatched one for (a
18597
+ * finding it added, or one it moved from a short entry to a full card), got no card call, and the report rendered it
18598
+ * structured-only or as the short entry. Each such card joins the delivery check's repair once, as owed and never
18599
+ * built; a card the first plan dispatched and that failed keeps that outcome, as a failed card always has. Returns
18600
+ * entries in the shape `staleOnPath` gives.
18601
+ */
18602
+ function cardsNeverBuilt(P, depth, firstPlan) {
18603
+ const dispatched = new Set(firstPlan ?? []);
18604
+ return owedCardOrdinals(P, depth)
18605
+ .filter((o) => !dispatched.has(o) && !readStamp(P.runDir, `report-card:${o}`))
18606
+ .map((o) => ({ label: `report-card:${o}`, changed: [{ name: `${o}.md`, was: null, now: null, via: "never-built" }] }));
18607
+ }
18608
+
18609
+ /**
18610
+ * The repairs that fix only what they name: a delivery lint fault, or an object of the writer's record that failed the
18611
+ * strict parse. They are not told placement's set-asides with the order to check each ground (ruled 2026-10-19); every
18612
+ * other follow-up of the reading step and the writer is.
18613
+ */
18614
+ export const NARROW_REPAIR_TRIGGERS = new Set(["lint-repair", "finding-reemit", "action-reemit", "ask-answer-reemit"]); // @internal — driven by its test
18615
+
18616
+ /** The repaired stages whose output the record-carry trace or the Audit Trail reports. */
18617
+ const REBUILDS_THE_DELIVERED_RECORD = new Set(["placement-inquiry", "register-digest", "synthesis", "narrative-refutation"]);
18618
+
18619
+ /**
18620
+ * Keep the Audit Trail, the record-carry trace and the doubt ledger as they stand, under _history/<ts>-audit-and-trace-
18621
+ * <reason>/, before any of them is written again, so a pass that writes them over a repair or a resume never loses what
18622
+ * the earlier state said. The pass that wrote them may have delivered nothing: a pass the delivery check held writes
18623
+ * them too. Returns the directory, or null when there was nothing to keep. Best-effort, like every snapshot.
18624
+ */
18625
+ export function keepDeliveredRecordAsHistory(P, reason) { // @internal — driven by its test with a run's own files
18626
+ const files = [P.audit, P.recordCarry, driverDir(P.runDir, "doubts.json")].filter((f) => existsSync(f));
18627
+ if (!files.length) return null;
18628
+ try {
18629
+ const ts = new Date().toISOString().replace(/[:.]/g, "-");
18630
+ const dest = join(P.runDir, "_history", `${ts}-audit-and-trace-${reason}`);
18631
+ mkdirSync(dest, { recursive: true });
18632
+ for (const f of files) copyFileSync(f, join(dest, basename(f)));
18633
+ return dest;
18634
+ } catch { return null; }
18635
+ }
18636
+
18637
+ /**
18638
+ * The reading audit's lookups (band-server appends every lookup to _driver/reading-log.jsonl). Read on any run that
18639
+ * derived a band shape, an empty list included, because a band-consuming stage that read nothing is a fact the audit
18640
+ * must show; null on a run with no shape, which renders no section. A torn tail line never kills the audit.
18641
+ */
18642
+ function readReadingLog(P) {
18643
+ if (!existsSync(P.bandShape)) return null;
18644
+ const log = [];
18645
+ try {
18646
+ for (const line of readFileSync(P.readingLog, "utf8").split("\n")) {
18647
+ if (!line.trim()) continue;
18648
+ try { log.push(JSON.parse(line)); } catch { /* a torn tail line never kills the audit */ }
18649
+ }
18650
+ } catch { /* no lookups recorded — the empty array IS the disclosure */ }
18651
+ return log;
18652
+ }
18653
+
18654
+ /**
18655
+ * Take an audit that no longer describes the run out of the place publishing reads it from, keeping it under _history
18656
+ * (unless the caller has just kept it). The report then ships without that Audit Trail, as it does when a first build
18657
+ * fails, never with an earlier one offered as current; the publisher still builds the workbook from the current
18658
+ * findings. Returns whether an audit was withdrawn.
18659
+ */
18660
+ export function withdrawSupersededAudit(P, reason, { keep = true } = {}) { // @internal — driven by its test with a run's own files
18661
+ if (!existsSync(P.audit)) return false;
18662
+ if (keep) keepDeliveredRecordAsHistory(P, reason);
18663
+ try { rmSync(P.audit, { force: true }); } catch { return false; }
18664
+ runLog(P.runDir, { event: "audit-withdrawn", reason });
18665
+ return true;
18666
+ }
18667
+
18668
+ /** The doubt ledger on disk, written again with the doubts the rebuilt audit carries; what it said of the closure stays. */
18669
+ function writeDoubtLedgerAgain(P, doubts, truncated) {
18670
+ const path = driverDir(P.runDir, "doubts.json");
18671
+ const earlier = safeReadJson(path) ?? {};
18672
+ const settled = doubts.filter((d) => d?.status === "checked-and-settled").length;
18673
+ const { doubts: _d, minted: _m, settled: _s, open: _o, truncated: _t, ...closure } = earlier;
18674
+ writeFileSync(path, JSON.stringify({ minted: doubts.length, settled, open: doubts.length - settled,
18675
+ ...(truncated?.length ? { truncated } : {}), ...closure, doubts }, null, 2));
18676
+ }
18677
+
18678
+ /**
18679
+ * THE TRACE AND THE AUDIT TRAIL, WRITTEN AGAIN OVER A REPAIR. Both describe a pass before the delivery check repairs
18680
+ * anything, and a stage that check re-runs can change what they report: a repeated placement pass can reverse a
18681
+ * set-aside or change its ground. This keeps the pair as it stands under _history, then writes the trace and the audit
18682
+ * again from the run's files, so what is delivered describes the decision that stands at delivery. `inputs` are what the
18683
+ * pass built its audit from. The doubts minted from the trace follow the trace written again (recordCarryDoubtsAgain);
18684
+ * every other doubt and ask stands as the pass raised and closed it, and nothing is dispatched again.
18685
+ * A rebuild that fails, the trace written and the audit not among the ways, withdraws the audit on disk: it describes
18686
+ * the state before the repair. Returns {ok, counts} or {ok:false, reason}. IMPURE (writes the run).
18687
+ */
18688
+ export function rebuildDeliveredRecord(ctx, inputs = {}, trigger = "stale-repair") { // @internal — driven by its test with a run's own files
18689
+ const P = ctx.paths;
18690
+ keepDeliveredRecordAsHistory(P, trigger);
18691
+ try {
18692
+ let findingsNow = [], actionsNow = [];
18693
+ try {
18694
+ if (existsSync(P.findings)) {
18695
+ const parsed = parseFindingsJsonLenient(readFileSync(P.findings, "utf8"));
18696
+ findingsNow = parsed.findings ?? [];
18697
+ actionsNow = parsed.actions ?? [];
18698
+ }
18699
+ } catch { /* the audit still builds without them */ }
18700
+ deriveRecordCarry(ctx, trigger, { findings: findingsNow });
18701
+ const registerFindingsText = readFileSync(P.registerFindings, "utf8");
18702
+ // THE DOUBTS MINTED FROM THE TRACE FOLLOW THE TRACE. The record-carry family was minted from the trace before the
18703
+ // repair; read against the trace written just above, a record the repair answered loses its doubt and one it left
18704
+ // unreasoned gains one, keyed by record so no closure the closure step recorded moves onto another record. Every
18705
+ // other family stands as the pass raised and closed it.
18706
+ let doubts = inputs.doubts ?? null, doubtTruncations = inputs.doubtTruncations ?? null;
18707
+ if (Array.isArray(doubts)) {
18708
+ const coverageRows = (() => { try { const rows = JSON.parse(readFileSync(P.registerCoverageLedger, "utf8")); return Array.isArray(rows) ? rows : []; } catch { return []; } })();
18709
+ const re = recordCarryDoubtsAgain(doubts, safeReadJson(P.recordCarry),
18710
+ { stitch: (ds) => stitchDoubts(ds, { findings: { findings: findingsNow, actions: actionsNow }, coverageRows, registerFindingsText }) });
18711
+ doubts = re.doubts;
18712
+ // A trace that could not be read says nothing about any record: the doubts stand as raised, and so does the count
18713
+ // of those the cap left out.
18714
+ if (!re.unavailable) doubtTruncations = [...(doubtTruncations ?? []).filter((t) => t?.source !== "record-carry"),
18715
+ ...(re.omitted ? [{ source: "record-carry", minted: re.minted, omitted: re.omitted }] : [])];
18716
+ writeDoubtLedgerAgain(P, doubts, doubtTruncations);
18717
+ if (re.fresh.length || re.dropped.length || re.unavailable)
18718
+ runLog(P.runDir, { event: "record-carry-doubts-again", trigger, fresh: re.fresh.length, dropped: re.dropped.length, ...(re.unavailable ? { unavailable: true } : {}) });
18719
+ }
18720
+ const again = buildAuditMd(registerFindingsText, inputs.commonLawText ?? "",
18721
+ { findings: findingsNow, doubts, asks: inputs.asks ?? null, readingLog: readReadingLog(P),
18722
+ commonLawGrid: inputs.commonLawGrid ?? null, doubtTruncations,
18723
+ registerPresence: inputs.registerPresence ?? null, placementRows: placementAuditRows(P) });
18724
+ if (!again.counts.findings) throw new Error("0 findings parsed from the spine tables");
18725
+ writeFileSync(P.audit, again.md);
18726
+ runLog(P.runDir, { event: "audit-rebuilt", trigger, ...again.counts });
18727
+ return { ok: true, counts: again.counts };
18728
+ } catch (e) {
18729
+ const reason = String(e?.message ?? e).slice(0, 160);
18730
+ runLog(P.runDir, { event: "audit-rebuild-failed", trigger, reason });
18731
+ withdrawSupersededAudit(P, `${trigger}-failed`, { keep: false }); // the pair as it stood was kept above
18732
+ return { ok: false, reason };
18733
+ }
18734
+ }
18735
+
18736
+ /**
18737
+ * Reassemble report.md from the record as it stands, as the first assembly did: the plan made again, each short entry
18738
+ * composed again from its finding, and the grouped section built. Returns the plan.
18739
+ */
18740
+ export function reassembleFromTheRecord(P, findings, depth) { // @internal
18741
+ const plan = planWriteUps(findings, depth, safeReadJson(P.bandShape));
18742
+ writeShortEntries(P, plan.entrySingles);
18743
+ assembleReportMd(P, findings, plan.assembledOrdinals, { grouped: plan.groupedMembers, byRight: plan.byRight });
18744
+ return plan;
18745
+ }