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
@@ -53,6 +53,7 @@ import { basename } from "node:path";
53
53
  // this file would trade locality for nothing: what was missing was never their location, it was that
54
54
  // nothing enumerated them, so the guard could not walk them. They are imported and registered.
55
55
  import { buildFrameReopenFollowup, buildEscalationFollowup, buildEnvelopeCloseFollowup, buildFrameReopenRetryMessage, DECIDE_WAITING_FAMILIES } from "./stages.mjs";
56
+ import { refutationStandingLines, refutationOpenItemsLine, REFUTATION_RECORDING_LINES } from "./stages.mjs";
56
57
  import { buildReconcileFollowup } from "./recall-reconciliation.mjs";
57
58
  import { buildFlushFollowup } from "./digest-queue.mjs";
58
59
 
@@ -88,18 +89,123 @@ const SAMPLE_PATHS = Object.freeze({
88
89
  commonLawGridHalf: (h) => `grid-ledger-${h}.json`, registerUnit: (a) => `units/${a}.md`,
89
90
  });
90
91
 
92
+ // ── THE CORRECTION'S RULES AND STEPS, ONE TEXT FOR BOTH CORRECTIONS ──────────────────────────────────
93
+ //
94
+ // The first correction after the full review and the one correction after the review of a delivery-check
95
+ // repair carry the same rules and steps, from the narrowing rule through the placement rulings. They live
96
+ // here once, so the two corrections cannot drift apart; the first correction's text is byte-identical to
97
+ // what it was before they moved.
98
+ const KNOCK_ON = `If applying a flag genuinely REQUIRES changing a finding outside that list — a knock-on the reviewer did not see — make the change and say which one and why in the "corrections" note. A silent edit outside the scope is the one thing this instruction forbids; a reasoned one is not.`;
99
+ // ── THE REVIEW AFTER A REPAIR: HOW ITS LIST IS RENDERED FROM VALUES ─────────────────────────────────────
100
+ //
101
+ // `repair-scope.mjs listForReview` computes the changes and the statements resting on them; these render each
102
+ // one in the list's own words. A value is shown as written: text as text, anything else as JSON, an absent
103
+ // value as null.
104
+ const shown = (v) => (v == null ? "null" : typeof v === "string" ? v.replace(/\s+/g, " ").trim() : JSON.stringify(v));
105
+ const lineState = (row) => (row ? `${row.state ?? ""}${row.note ? `: ${String(row.note).replace(/\s+/g, " ").trim()}` : ""}` : "null");
106
+ const findingHead = (c) => (c.delivered != null ? `- Finding ${c.ordinal} (finding ${c.delivered} in findings.json):` : `- Finding ${c.ordinal}:`);
107
+ const changeLine = (c) => {
108
+ if (c.kind === "finding") {
109
+ if (c.change === "added") return `${findingHead(c)} added by the repair: ${shown(c.after)}. It cites: ${shown(c.records)}.`;
110
+ if (c.change === "removed") return `${findingHead(c)} removed by the repair. It read: ${shown(c.before)}.`;
111
+ return `${findingHead(c)} ${c.fields.join(", ")} changed. Before: ${shown(c.before)}. After: ${shown(c.after)}. It cites: ${shown(c.records)}.`;
112
+ }
113
+ if (c.kind === "coverage") {
114
+ if (c.change === "added") return `- ${c.label}: added by the repair: ${lineState(c.after)}.`;
115
+ if (c.change === "removed") return `- ${c.label}: removed by the repair. It read: ${lineState(c.before)}.`;
116
+ return `- ${c.label}: before: ${lineState(c.before)}. After: ${lineState(c.after)}.`;
117
+ }
118
+ if (c.kind === "section") return `- the narrative's "${c.title}" section. Before: ${c.before} After: ${c.after}`;
119
+ return `- ${c.label}: before: ${shown(c.before)}. After: ${shown(c.after)}.`;
120
+ };
121
+ const restingLine = (r) => {
122
+ if (r.kind === "section") return `- the narrative's "${r.title}" section: ${r.text}`;
123
+ if (r.kind === "coverage-judgment") return `- the coverage judgment: ${shown(r.value)}`;
124
+ return `- ${r.label}, which names finding ${(r.ordinals ?? []).join(", ")}: ${r.text}`;
125
+ };
126
+
127
+ const correctiveRules = ({ narrative, findings, placement, rulingsTail }) => [
128
+ // ── — THE PASS THAT WRITES THE DEFECT ──────────────────────────────────
129
+ //
130
+ // Five verdict refusals in seven days, all one shape: a coverage or search-completion claim
131
+ // contradicted by the run's own records. FOUR of the five were introduced or worsened BY THIS
132
+ // PASS. one reviewer names it — "the REPLACEMENT for the frame-reopen sentence is now an
133
+ // overconfident global negative". another names it — "the corrective pass deleted the
134
+ // evidence and left the claim standing in a stronger and now factually wrong form".
135
+ // a third run's offending sentence entered at the corrective rewrite, measured at the
136
+ // sentence level: that pass removed one coverage assertion and wrote two, one of which refused
137
+ // the run.
138
+ //
139
+ // NOTHING ABOVE FORBADE ANY OF IT. The instruction constrains WHICH passages may move — "every
140
+ // passage the flags do not name stays byte-identical", plus the scope block below when the
141
+ // reviewer declared ordinals. It says nothing about what the REPLACEMENT may assert, and every
142
+ // one of these defects was an in-scope rewrite that broadened. The lead move — "correct the
143
+ // statement to what the evidence supports" — is itself a licence to rewrite, which is where the
144
+ // broadening happens.
145
+ //
146
+ // AND THE RESOLUTION WITH THE LINE ABOVE IS STATED, BECAUSE THE TWO LOOK LIKE THEY FIGHT.
147
+ // "Never softened" forbids adding hedges; "may never broaden" requires claiming less. A model
148
+ // handed both without the distinction will pick one. Narrowing is not hedging: stating a
149
+ // smaller claim, or removing the claim, is a correction; keeping the same claim and wrapping it
150
+ // in "may" or "appears to" is the hedge. Said in the text, not just here.
151
+ //
152
+ // NO DETECTOR, AND THAT IS DELIBERATE. The lexical gate this issue also proposed was measured
153
+ // over 32 delivered narratives and withdrawn: 31 of 32 fired, 141 sentences, and nine of twelve
154
+ // before/after run-pairs read identically, because a clearance narrative's job IS to state what
155
+ // was searched and what came back — the legitimate sentence and the defective one have the same
156
+ // shape and differ only in truth. The typed half of this class IS enforced structurally, on the
157
+ // corrective pair, by `evidenceClaimViolations` (pipeline.mjs ~10973) — but it reads
158
+ // `findings.json` meters, and a narrative sentence with no findings-side counterpart is invisible
159
+ // to it. That prose gap is conversion-era work, not an instruction. This is the instruction.
160
+ `A REPAIR MAY NARROW A CLAIM AND MAY NEVER BROADEN ONE. If a flagged statement is wrong because it claims too much, the correction states LESS: strike the claim, or replace it with the smaller claim the evidence actually carries. Never restate the same claim in different words, and never replace a specific sentence with a general one. NARROWING IS NOT HEDGING and the rule above still holds — claiming less is a correction; keeping the claim and wrapping it in "may", "appears to" or "broadly" is the hedge that is forbidden.`,
161
+ `THE COVERAGE ACCOUNT IS NOT YOURS TO REOPEN HERE. Do not introduce any statement about what was searched, counted, screened, finished, or came back empty that is not already in ${narrative}. That account was settled when you wrote it against the evidence in front of you; this pass has the reviewer's flags and not that evidence, so it is not in a position to widen it. Deleting a false coverage sentence is a correction. Replacing it with a different coverage sentence is not — strike it and say nothing in its place.`,
162
+ `IF YOU REMOVE THE SUPPORT FOR A CLAIM, THE CLAIM GOES WITH IT. Deleting the evidence and leaving the sentence standing does not make it safer, it makes it unsupported and therefore stronger than the record allows. A run was refused for exactly this.`,
163
+ `1. Correct the flagged narrative sections — send back ONLY the sections a flag names, and every section the flags do not name is left out and comes back byte-identical;`,
164
+ `2. mirror every correction into the finding objects the flags name (owner names, levels/composites, meters). A finding the review KILLS is not deleted: keep its object, set "disposition": "withdrawn" and add "withdrawn_reason": "<the review flag that killed it>". Add a "corrections": {"applied": true, "note": "<one line per flagged entity: corrected / withdrawn / no-change-because-…>"} marker inside the call's \`findings\` object.`,
165
+ // THE LINES THAT ARE NOT A FINDING. A coverage note, an action and the mark assessment carry no
166
+ // ordinal, so step 2 cannot reach them, and nothing said where a rewrite of one goes: seven such
167
+ // flags survived one clearance on the test box, each recorded as naming nothing (2026-09-11). The
168
+ // channel already existed — a PATCH copies a top-level register of the findings document through
169
+ // — and this names it. The coverage-account rule above forbids NEW claims about what was searched;
170
+ // rewording a note keeps its claims, and the text says so, or the seat reads the two as a conflict
171
+ // and does nothing, which is what it did.
172
+ `3. A flag about a line the reader sees that is NOT a finding — a coverage row's "note", an action's "text", or the mark assessment's "distinctiveness" or "connotation" — is applied in the same PATCH, inside the call's \`findings\` object beside the "corrections" marker: send the complete \`coverage\` array (every row, the unflagged ones exactly as you last sent them), the complete \`actions\` array, or the complete \`mark_assessment\` object, with the flagged line rewritten. A row left out of the array is a row deleted, so send every row. Rewording a coverage note is NOT reopening the coverage account: keep its area, its state and every fact it states, and change only how it says them. A flagged line you leave as written is named in the "corrections" note with the reason, as the flag names it — for example: the coverage line for "<area>": no-change-because …`,
173
+ // ── CONVERTED. `editRepairTail` stood here and ordered targeted Edits of
174
+ // both files. The seat holds no Edit for either any more — but the ECONOMICS that argued for a
175
+ // targeted edit are preserved rather than lost, which is why the call has a patch shape at all:
176
+ // measured full re-emission on four delivered clearances at 707.7 / 406.2 / 746.3 / 293.3 s
177
+ // on the serial critical path, and a conversion that answered it with "re-send everything
178
+ // through a tool" would have paid that cost back in a different currency. The driver holds the
179
+ // accepted values; the seat sends what changed.
180
+ `Send it all in ONE \`record_synthesis\` PATCH call, sending only the change: \`narrative\` carrying only the sections you corrected; \`findings_patch\` carrying the complete corrected finding object(s), each with the \`ordinal\` it replaces; and \`findings\` carrying the "corrections" marker and any register from step 3, complete, and never a \`findings[]\` list. The driver merges this patch into the last accepted record, keeps the values the patch does not replace, and renders both files. There is no file to write or edit and nothing you write by hand is read.`,
181
+ // 2026-07-04 (the VENZY corrective thrash): three attempts died inventing three different keys
182
+ // while trying to EXPRESS a hold. The contract is closed and every correction is expressible
183
+ // inside it — say so.
184
+ `THE FINDINGS CONTRACT IS CLOSED — MINIMAL CHANGE ONLY: start from the finding objects as you last sent them and change the smallest set of existing fields the flags require; NEVER invent a key, a state, or an enum value (any unknown key fails the file and, repeated, fails the WHOLE RUN). Every correction the reviewer can ask for is expressible with existing fields: an unsourced/confabulated attribution ⇒ that finding gets "disposition":"withdrawn" + "withdrawn_reason", OR its owner/prose is re-attributed to what the sources actually support — an identity that needs the applicant's confirmation is stated in the finding's prose/impact text, NEVER as a new field or note-type; use_check.quality is EXACTLY one of owner-site | independent | register-mirror or omitted; context_notes entries are EXACTLY {"type","mark","owner","context"}. RE-TYPING A DISPOSITION CARRIES ITS FIELDS WITH IT — this is the one case where a minimal edit MUST add a key, and these are the only keys it may add: re-typing a finding TO "off-field" also sets "off_field_ground" (EXACTLY "different-field" — only where that finding's own goods_proximity meter reads "low" — or "no-material-risk"), and re-typing AWAY from "off-field" REMOVES it; any finding that is not "withdrawn" keeps a non-empty "legal_position" and "practical_position", so a re-type that lands on a finding missing either must write both. Nothing else may be added.`,
185
+ rulingsTail ? lines(
186
+ `PLACEMENT RULINGS TAIL (verbatim from ${placement}, provided AS DATA — do NOT re-read the placement file; where a flagged correction touches a coverage disposition, a coverage[] row, or a placement call, adjudicate it against these rulings — adopt each ruling or counter-reason it, never silently drop one):`,
187
+ "```markdown",
188
+ rulingsTail,
189
+ "```",
190
+ ) : "",
191
+ ];
192
+
91
193
  export const REPAIR_COMPOSERS = [
92
194
  {
93
195
  trigger: "corrective-findings",
94
196
  stage: "synthesis",
95
197
  key: "synthesis:corrective-findings",
96
- compose: ({ findings, named }) => lines(
97
- `You are RESUMING your own synthesis session. Your corrective pass updated the narrative but did NOT re-emit ${findings} — the structured findings the client report renders from. The review named: ${named.join(", ")}.`,
98
- `Apply each flag to the finding objects it names: correct the fields, or set "disposition": "withdrawn" + "withdrawn_reason" for a killed finding, and add the top-level "corrections": {"applied": true, "note": "…"} marker. Change nothing else.`,
99
- `Send the correction with \`record_synthesis\`: a PATCH call carrying \`findings_patch\` — the complete corrected finding object(s), each with the \`ordinal\` it replaces — and nothing else. The driver is holding every value you already sent and re-renders both files from them, so what you do not name comes back byte-identical. There is no file for you to write or edit and nothing you write by hand is read.`,
198
+ // A FRESH SESSION IS HANDED WHERE THE FLAGS ARE. The message names the findings the review flagged and
199
+ // nothing else of the review, so a session that starts with nothing would have no flag to apply; the
200
+ // review's own file is named for it to open. `seniorEyeReview` is that file: the run's review on the first
201
+ // cycle, and the review after the delivery-check repair on the pass that applies it.
202
+ compose: ({ findings, named, seniorEyeReview }) => lines(
203
+ `This is a fresh session: your earlier synthesis is not in your context, and the driver holds the record it last accepted from you. Your corrective pass updated the narrative but did NOT re-emit ${findings} — the structured findings the client report renders from. The review named: ${named.join(", ")}. Its flags are in ${seniorEyeReview}; open it and ${findings} for the findings it names.`,
204
+ `Apply each flag to the finding objects it names: correct the fields, or set "disposition": "withdrawn" + "withdrawn_reason" for a killed finding, and add the "corrections": {"applied": true, "note": "…"} marker inside the call's \`findings\` object. Change nothing else.`,
205
+ `Send the correction with \`record_synthesis\`, sending only the change: a PATCH call carrying \`findings_patch\` — the complete corrected finding object(s), each with the \`ordinal\` it replaces — and \`findings\` carrying only the "corrections" marker, never a \`findings[]\` list; nothing else. The driver merges this patch into the last accepted record, keeps the values the patch does not replace, and renders both files. There is no file for you to write or edit and nothing you write by hand is read.`,
100
206
  ),
101
207
  samples: [{ name: "the standard re-emit demand", tail: "tool",
102
- args: { findings: "findings.json", named: ["#1", "#4"] } }],
208
+ args: { findings: "findings.json", named: ["#1", "#4"], seniorEyeReview: "senior-eye-review.md" } }],
103
209
  },
104
210
  {
105
211
  trigger: "intake-asks-followup",
@@ -126,7 +232,7 @@ export const REPAIR_COMPOSERS = [
126
232
  stage: "synthesis",
127
233
  key: "synthesis:schema-downlevel",
128
234
  compose: ({ findings, declaredSv, frameworkKey, bandLabels }) => lines(
129
- `You are RESUMING your own synthesis session. The findings file you emitted (${findings}) declares "schema_version": ${declaredSv}, but the dictation requires "schema_version": 4 — rated in the framework's band WORDS with a top-level "rated_under_framework": "${frameworkKey}", and NO composite/level/dispute_type keys (that scale is retired).`,
235
+ `This is a fresh session: your earlier synthesis is not in your context. The findings file you emitted (${findings}) declares "schema_version": ${declaredSv}, but the dictation requires "schema_version": 4 — rated in the framework's band WORDS with a top-level "rated_under_framework": "${frameworkKey}", and NO composite/level/dispute_type keys (that scale is retired).`,
130
236
  `Re-emit the COMPLETE record through \`record_synthesis\` at schema_version 4 — same findings, same judgments, each rated finding carrying "band" (EXACTLY one of: ${bandLabels.join(" / ")}) per the original dictation. Change nothing else. This one is a whole re-send rather than a patch, because the key set itself changes shape: there is no set of named findings to correct.`,
131
237
  ),
132
238
  // A WHOLE-FILE EMISSION WITH NO REPAIR TAIL, AND THE SITE SAYS WHY: the key set changes shape, so
@@ -139,12 +245,14 @@ export const REPAIR_COMPOSERS = [
139
245
  trigger: "actions-missing",
140
246
  stage: "synthesis",
141
247
  key: "synthesis:actions-missing",
142
- compose: ({ findings }) => lines(
143
- `You are RESUMING your own synthesis session. The findings file you emitted (${findings}) has no top-level "actions" array, but the dictation requires the ACTIONS REGISTER: ONE object per forward step your opinion names that a HUMAN must still take, each EXACTLY {"id","kind","text","ordinals"} (+ optional "deadline":{"kind","date"}) — kinds: conditions consent / coexistence-agreement / territorial-delimitation / goods-amendment / mark-modification / senior-clearance / proceeding-response / counsel-opinion-required; advisory client-fact / commercial-decision / monitoring / filing-routine.`,
144
- `Add ONE top-level "actions": [...] key that registers every forward step your own narrative names ("actions": [] ONLY if the opinion genuinely demands nothing beyond ordinary filing). Same findings, same judgments — the findings themselves do not change.`,
145
- `Send it with \`record_synthesis\` as a PATCH call carrying only "actions". The driver is holding every finding you already sent and re-renders from them, so nothing else moves. There is no file for you to write or edit.`,
248
+ // `narrative` is named because a fresh session has not read it, and the register it is asked for is the
249
+ // forward steps that narrative names.
250
+ compose: ({ findings, narrative }) => lines(
251
+ `This is a fresh session: your earlier synthesis is not in your context. The findings file you emitted (${findings}) has no top-level "actions" array, but the dictation requires the ACTIONS REGISTER: ONE object per forward step your opinion names that a HUMAN must still take, each EXACTLY {"id","kind","text","ordinals"} (+ optional "deadline":{"kind","date"}) — kinds: conditions consent / coexistence-agreement / territorial-delimitation / goods-amendment / mark-modification / senior-clearance / proceeding-response / counsel-opinion-required; advisory client-fact / commercial-decision / monitoring / filing-routine.`,
252
+ `Add ONE top-level "actions": [...] key that registers every forward step your own narrative (${narrative}) names ("actions": [] ONLY if the opinion genuinely demands nothing beyond ordinary filing). Same findings, same judgments — the findings themselves do not change.`,
253
+ `Send it with \`record_synthesis\` as a PATCH call carrying only "actions". The driver merges this patch into the last accepted record, keeps the values the patch does not replace, and renders both files. There is no file for you to write or edit.`,
146
254
  ),
147
- samples: [{ name: "the actions register demand", tail: "tool", args: { findings: "findings.json" } }],
255
+ samples: [{ name: "the actions register demand", tail: "tool", args: { findings: "findings.json", narrative: "narrative.md" } }],
148
256
  },
149
257
  {
150
258
  trigger: "corrective",
@@ -162,77 +270,15 @@ export const REPAIR_COMPOSERS = [
162
270
  `SCOPE — THE REVIEWER DECLARED WHICH FINDINGS ITS FLAGS ARE ABOUT, AND THIS IS THE COMPLETE LIST: ${
163
271
  scope.ordinals.length ? scope.ordinals.map((o) => `#${o}`).join(", ") : "(none — every flag is about the document, not a finding)"}.`,
164
272
  `Change ONLY those finding objects and ONLY the narrative passages that discuss them. EVERY OTHER FINDING OBJECT IN ${findings} MUST COME BACK BYTE-IDENTICAL — do not re-derive it, do not re-word it, do not re-order its keys. The driver compares them and records anything that moved.`,
165
- `If applying a flag genuinely REQUIRES changing a finding outside that list — a knock-on the reviewer did not see — make the change and say which one and why in the "corrections" note. A silent edit outside the scope is the one thing this instruction forbids; a reasoned one is not.`,
273
+ KNOCK_ON,
166
274
  ) : "";
167
275
  return lines(
168
276
  // — RULING 2026-08-09: "add caveat" is DELETED, not softened. A repair has exactly
169
277
  // four moves and there is no fifth. The standing outward-language rule governs the REPAIR path
170
278
  // exactly as it governs a first draft, and this instruction was the one place the engine invited
171
279
  // a hedge into a deliverable. It fired on 4 of 4 delivered clearances in the 2026-08-09 round.
172
- `You are RESUMING your own synthesis session — your narrative and all inputs are already in your context; do NOT redo it from scratch. The independent refutation flagged the corrections below. Apply them (minimum-change per flag — correct the statement to what the evidence supports, demote the tier, remove the sentence, or verify-flag for re-check) to BOTH deliverables. A caveat, hedge, qualifier or disclaimer is NEVER a repair: an unsupported statement is corrected or it is removed, never softened.`,
173
- // ── — THE PASS THAT WRITES THE DEFECT ──────────────────────────────────
174
- //
175
- // Five verdict refusals in seven days, all one shape: a coverage or search-completion claim
176
- // contradicted by the run's own records. FOUR of the five were introduced or worsened BY THIS
177
- // PASS. one reviewer names it — "the REPLACEMENT for the frame-reopen sentence is now an
178
- // overconfident global negative". another names it — "the corrective pass deleted the
179
- // evidence and left the claim standing in a stronger and now factually wrong form".
180
- // a third run's offending sentence entered at the corrective rewrite, measured at the
181
- // sentence level: that pass removed one coverage assertion and wrote two, one of which refused
182
- // the run.
183
- //
184
- // NOTHING ABOVE FORBADE ANY OF IT. The instruction constrains WHICH passages may move — "every
185
- // passage the flags do not name stays byte-identical", plus the scope block below when the
186
- // reviewer declared ordinals. It says nothing about what the REPLACEMENT may assert, and every
187
- // one of these defects was an in-scope rewrite that broadened. The lead move — "correct the
188
- // statement to what the evidence supports" — is itself a licence to rewrite, which is where the
189
- // broadening happens.
190
- //
191
- // AND THE RESOLUTION WITH THE LINE ABOVE IS STATED, BECAUSE THE TWO LOOK LIKE THEY FIGHT.
192
- // "Never softened" forbids adding hedges; "may never broaden" requires claiming less. A model
193
- // handed both without the distinction will pick one. Narrowing is not hedging: stating a
194
- // smaller claim, or removing the claim, is a correction; keeping the same claim and wrapping it
195
- // in "may" or "appears to" is the hedge. Said in the text, not just here.
196
- //
197
- // NO DETECTOR, AND THAT IS DELIBERATE. The lexical gate this issue also proposed was measured
198
- // over 32 delivered narratives and withdrawn: 31 of 32 fired, 141 sentences, and nine of twelve
199
- // before/after run-pairs read identically, because a clearance narrative's job IS to state what
200
- // was searched and what came back — the legitimate sentence and the defective one have the same
201
- // shape and differ only in truth. The typed half of this class IS enforced structurally, on the
202
- // corrective pair, by `evidenceClaimViolations` (pipeline.mjs ~10973) — but it reads
203
- // `findings.json` meters, and a narrative sentence with no findings-side counterpart is invisible
204
- // to it. That prose gap is conversion-era work, not an instruction. This is the instruction.
205
- `A REPAIR MAY NARROW A CLAIM AND MAY NEVER BROADEN ONE. If a flagged statement is wrong because it claims too much, the correction states LESS: strike the claim, or replace it with the smaller claim the evidence actually carries. Never restate the same claim in different words, and never replace a specific sentence with a general one. NARROWING IS NOT HEDGING and the rule above still holds — claiming less is a correction; keeping the claim and wrapping it in "may", "appears to" or "broadly" is the hedge that is forbidden.`,
206
- `THE COVERAGE ACCOUNT IS NOT YOURS TO REOPEN HERE. Do not introduce any statement about what was searched, counted, screened, finished, or came back empty that is not already in ${narrative}. That account was settled when you wrote it against the evidence in front of you; this pass has the reviewer's flags and not that evidence, so it is not in a position to widen it. Deleting a false coverage sentence is a correction. Replacing it with a different coverage sentence is not — strike it and say nothing in its place.`,
207
- `IF YOU REMOVE THE SUPPORT FOR A CLAIM, THE CLAIM GOES WITH IT. Deleting the evidence and leaving the sentence standing does not make it safer, it makes it unsupported and therefore stronger than the record allows. A run was refused for exactly this.`,
208
- `1. Correct the flagged narrative sections — send back ONLY the sections a flag names, and every section the flags do not name is left out and comes back byte-identical;`,
209
- `2. mirror every correction into the finding objects the flags name (owner names, levels/composites, meters). A finding the review KILLS is not deleted: keep its object, set "disposition": "withdrawn" and add "withdrawn_reason": "<the review flag that killed it>". Add a top-level "corrections": {"applied": true, "note": "<one line per flagged entity: corrected / withdrawn / no-change-because-…>"}.`,
210
- // THE LINES THAT ARE NOT A FINDING. A coverage note, an action and the mark assessment carry no
211
- // ordinal, so step 2 cannot reach them, and nothing said where a rewrite of one goes: seven such
212
- // flags survived one clearance on the test box, each recorded as naming nothing (2026-09-11). The
213
- // channel already existed — a PATCH copies a top-level register of the findings document through
214
- // — and this names it. The coverage-account rule above forbids NEW claims about what was searched;
215
- // rewording a note keeps its claims, and the text says so, or the seat reads the two as a conflict
216
- // and does nothing, which is what it did.
217
- `3. A flag about a line the reader sees that is NOT a finding — a coverage row's "note", an action's "text", or the mark assessment's "distinctiveness" or "connotation" — is applied in the same PATCH: send the complete top-level \`coverage\` array (every row, the unflagged ones exactly as you last sent them), the complete \`actions\` array, or the complete \`mark_assessment\`, with the flagged line rewritten. A row left out of the array is a row deleted, so send every row. Rewording a coverage note is NOT reopening the coverage account: keep its area, its state and every fact it states, and change only how it says them. A flagged line you leave as written is named in the "corrections" note with the reason, as the flag names it — for example: the coverage line for "<area>": no-change-because …`,
218
- // ── CONVERTED. `editRepairTail` stood here and ordered targeted Edits of
219
- // both files. The seat holds no Edit for either any more — but the ECONOMICS that argued for a
220
- // targeted edit are preserved rather than lost, which is why the call has a patch shape at all:
221
- // measured full re-emission on four delivered clearances at 707.7 / 406.2 / 746.3 / 293.3 s
222
- // on the serial critical path, and a conversion that answered it with "re-send everything
223
- // through a tool" would have paid that cost back in a different currency. The driver holds the
224
- // accepted values; the seat sends what changed.
225
- `Send it all in ONE \`record_synthesis\` PATCH call: \`narrative\` carrying only the sections you corrected, and \`findings_patch\` carrying the complete corrected finding object(s), each with the \`ordinal\` it replaces. The driver is holding everything else you sent and re-renders both files, so what you do not name is byte-identical by construction rather than by your care. There is no file to write or edit and nothing you write by hand is read.`,
226
- // 2026-07-04 (the VENZY corrective thrash): three attempts died inventing three different keys
227
- // while trying to EXPRESS a hold. The contract is closed and every correction is expressible
228
- // inside it — say so.
229
- `THE FINDINGS CONTRACT IS CLOSED — MINIMAL CHANGE ONLY: start from the finding objects as you last sent them and change the smallest set of existing fields the flags require; NEVER invent a key, a state, or an enum value (any unknown key fails the file and, repeated, fails the WHOLE RUN). Every correction the reviewer can ask for is expressible with existing fields: an unsourced/confabulated attribution ⇒ that finding gets "disposition":"withdrawn" + "withdrawn_reason", OR its owner/prose is re-attributed to what the sources actually support — an identity that needs the applicant's confirmation is stated in the finding's prose/impact text, NEVER as a new field or note-type; use_check.quality is EXACTLY one of owner-site | independent | register-mirror or omitted; context_notes entries are EXACTLY {"type","mark","owner","context"}. RE-TYPING A DISPOSITION CARRIES ITS FIELDS WITH IT — this is the one case where a minimal edit MUST add a key, and these are the only keys it may add: re-typing a finding TO "off-field" also sets "off_field_ground" (EXACTLY "different-field" — only where that finding's own goods_proximity meter reads "low" — or "no-material-risk"), and re-typing AWAY from "off-field" REMOVES it; any finding that is not "withdrawn" keeps a non-empty "legal_position" and "practical_position", so a re-type that lands on a finding missing either must write both. Nothing else may be added.`,
230
- rulingsTail ? lines(
231
- `PLACEMENT RULINGS TAIL (verbatim from ${placement}, provided AS DATA — do NOT re-read the placement file; where a flagged correction touches a coverage disposition, a coverage[] row, or a placement call, adjudicate it against these rulings — adopt each ruling or counter-reason it, never silently drop one):`,
232
- "```markdown",
233
- rulingsTail,
234
- "```",
235
- ) : "",
280
+ `This is a fresh session: your earlier synthesis is not in your context, and you do NOT redo it from scratch. The driver holds the record it last accepted from you; your earlier work is in ${narrative} and ${findings} — open them for the parts each flag names — and your inputs are listed below. The independent refutation flagged the corrections below. Apply them (minimum-change per flag — correct the statement to what the evidence supports, demote the tier, remove the sentence, or verify-flag for re-check) to BOTH deliverables. A caveat, hedge, qualifier or disclaimer is NEVER a repair: an unsupported statement is corrected or it is removed, never softened.`,
281
+ ...correctiveRules({ narrative, findings, placement, rulingsTail }),
236
282
  scopeBlock ? lines(scopeBlock, ``) : "",
237
283
  worklist ? lines(worklist, ``) : "",
238
284
  `The reviewer's flags, verbatim:`,
@@ -254,12 +300,52 @@ export const REPAIR_COMPOSERS = [
254
300
  worklist: "- #1 correct the owner", review: "the flags, verbatim" } },
255
301
  ],
256
302
  },
303
+ {
304
+ trigger: "post-repair-corrective",
305
+ stage: "synthesis",
306
+ key: "synthesis:post-repair-corrective",
307
+ // THE ONE CORRECTION AFTER THE REVIEW OF A DELIVERY-CHECK REPAIR. Its opening says where the flags come from
308
+ // and that nothing reviews this pass; it offers no "verify-flag for re-check", because no re-check follows.
309
+ // Its items line replaces the first correction's scope block, a changed coverage line may be narrowed, and a
310
+ // flag that names no item is applied to the one it is about. The rules and steps are the first correction's,
311
+ // from one shared text, and the flag list closes on the line for a pass nothing reviews.
312
+ // A WHOLE REVIEW SAYS SO (design's wording, 2026-10-18): its flags reach this correction too, when the narrowed
313
+ // review could not be read, when the change could not be narrowed, and on a resume after an unread review.
314
+ compose: ({ narrative, findings, placement, rulingsTail, items, renumbered, worklist, review, whole = false }) => lines(
315
+ `This is a fresh session: your earlier synthesis is not in your context, and you do NOT redo it from scratch. The driver holds the record it last accepted from you; your earlier work is in ${narrative} and ${findings} — open them for the parts each flag names — and your inputs are listed below. ${whole ? "After the delivery check, that record was repaired, and the full independent review ran again." : "After the delivery check, that record was repaired, and an independent review then read only what the repair changed and what rests on it."} It flagged the corrections below, and nothing reviews this pass again. Apply them (minimum-change per flag — correct the statement to what the evidence supports, demote the tier, or remove the sentence) to BOTH deliverables. A caveat, hedge, qualifier or disclaimer is NEVER a repair: an unsupported statement is corrected or it is removed, never softened.`,
316
+ // An empty list does not reuse the first correction's "every flag is about the document": here an unplaced flag
317
+ // may be about a finding, and the line below says how to apply one.
318
+ `THE FLAGS NAME THESE ITEMS: ${items?.length ? items.join(", ") : "(none named; apply each unplaced flag as described below)"}. Change only the items the flags are about and the narrative passages that discuss them. Everything else in the record stays as the driver last accepted it from you.`,
319
+ `For a coverage line the repair changed, a flag may ask for its state or its facts to change, not only its words. Make that change only where the line then claims less than it does now, as the narrowing rule below requires. This is the one exception to the rule below that the coverage account is not yours to reopen; every other coverage line keeps its area, its state and every fact it states.`,
320
+ `A flag that names no item is applied to the item it is about; name that item in the "corrections" note. If you cannot tell what the flag is about, change nothing for it and say so in the note.`,
321
+ renumbered ? `Where a flag's own words give a finding a different number from its [on: …], use the [on: …] number. It is the number in your record.` : "",
322
+ KNOCK_ON,
323
+ ...correctiveRules({ narrative, findings, placement, rulingsTail }),
324
+ worklist ? lines(worklist, ``) : "",
325
+ `The reviewer's flags, verbatim:`,
326
+ ``,
327
+ ``,
328
+ review,
329
+ ),
330
+ samples: [
331
+ { name: "flags on a coverage line and a finding, no placement tail", tail: "tool",
332
+ args: { narrative: "narrative.md", findings: "findings.json", placement: "placements.md", rulingsTail: "",
333
+ items: ["finding 2", "the coverage line for \"register / EU\""], renumbered: false,
334
+ worklist: "THE FLAGS, TYPED (2):", review: "the flags, verbatim" } },
335
+ { name: "a renumbered finding, a placement tail, and no item named", tail: "tool",
336
+ args: { narrative: "narrative.md", findings: "findings.json", placement: "placements.md", rulingsTail: "| axis | ruling |",
337
+ items: [], renumbered: true, worklist: "", review: "the flags, verbatim" } },
338
+ { name: "the flags of a whole review", tail: "tool",
339
+ args: { narrative: "narrative.md", findings: "findings.json", placement: "placements.md", rulingsTail: "",
340
+ items: [], renumbered: false, worklist: "", review: "the flags, verbatim", whole: true } },
341
+ ],
342
+ },
257
343
  {
258
344
  trigger: "verdict-recheck",
259
345
  stage: "narrative-refutation",
260
346
  key: "narrative-refutation:verdict-recheck",
261
347
  compose: ({ narrative, findings, seniorEyeReview, correctionsScope, appliedTable, restoredTable, evidenceTable, planAuditCarry }) => lines(
262
- `You are RESUMING your own narrative-refutation session — your prior verdict, the narrative, and the source files you refuted against are already in your context; do NOT re-read them from scratch.`,
348
+ `This is a fresh session: your earlier refutation is not in your context, and you do NOT redo it from scratch. Your prior verdict and its flags are in ${seniorEyeReview} — open it first — and the source files you refuted against are listed below.`,
263
349
  // — WHEN THE SCOPE WAS DECLARED, THE RE-READ IS NARROWED TO IT. The recheck cost 270-510 s on
264
350
  // every measured run, and its whole job is to judge whether the corrections are RIGHT — not to
265
351
  // re-derive which parts of two long documents changed. The driver already knows: it holds the
@@ -304,9 +390,62 @@ export const REPAIR_COMPOSERS = [
304
390
  correctionsScope: { scoped: true, named: [1], moved: [4] },
305
391
  appliedTable: "| flag | outcome |", restoredTable: "", evidenceTable: "| finding | meter | what moved |", planAuditCarry: "plan-audit carry" } },
306
392
  ],
307
- singleMember: "narrative-refutation has exactly one bespoke composer. Recorded so the census reds if "
308
- + "the stage gains a second without this note being read — a guard proven at n=1 has proven "
309
- + "nothing about ordering or interaction between two composers on one stage.",
393
+ },
394
+ {
395
+ trigger: "post-repair-review",
396
+ stage: "narrative-refutation",
397
+ key: "narrative-refutation:post-repair-review",
398
+ // THE REVIEW AFTER A DELIVERY-CHECK REPAIR. The first review stays whole; this one reads what the repair
399
+ // changed and every statement that rests on it, with the evidence to check both, and its flags go to one
400
+ // correction. VALUES IN: the list `repair-scope.mjs listForReview` computed from the writer's record before
401
+ // and after the repair. The standing tools and notes, the open-items check and the recording lines are the
402
+ // full review's own, read from one shared text in stages.mjs.
403
+ compose: ({ findings, changes, resting, renumbered, coverageChanged, evidence, registerOnly = false, job = {}, skepticFlags, frameReopenReceipt, floorDecisions = null }) => lines(
404
+ `This is a review of a repair, not a full review. Earlier in this run the narrative was reviewed in full, and the corrections that review raised went to the writer. The delivery check then found a fault, and the writer repaired the record.`,
405
+ `Review only what that repair changed, listed below under WHAT THE REPAIR CHANGED, and any statement elsewhere in the narrative or in the findings record, ${findings}, that rests on a changed item. Such a statement no longer holds if the change leaves it untrue, without its support, or incomplete.`,
406
+ `Everything else stands as this run's earlier reviews left it. Do not flag a statement that rests on no changed item.`,
407
+ `From your own manual, skills/narrative-refutation/SKILL.md, use only the checks that bear on these items. Its whole-report checks, its length target for a review and its expectation that a clean review is rare belong to the full review, not to this one. A clean result is a normal outcome here.`,
408
+ `Check each changed item against the evidence named for it, and each statement that rests on a changed item against the new values.`,
409
+ `Say what each flag is about. For a finding, put its number from the list below in \`on\`. For anything else, begin the flag's text with the item's name exactly as the list gives it, for example: the coverage line for "<area>".`,
410
+ renumbered ? `Where a finding below shows two numbers, put the first in \`on\`. The second is its number in findings.json.` : "",
411
+ `Your verdict is about the repair. CLEAR: what changed is sound, and every statement that rests on a changed item still holds. CONDITIONAL: a flag needs a correction. BLOCKING: the change makes the report unsafe to sign, under your manual's verdict logic.`,
412
+ `Your flags go to one correction pass by the writer, and nothing reviews that pass again. Make each fix exact.`,
413
+ ...refutationStandingLines({ registerOnly, job, floorDecisions }),
414
+ // A repair that rewrites coverage lines can drop an open item; when coverage is untouched, the full review
415
+ // already checked this.
416
+ coverageChanged ? refutationOpenItemsLine({ skepticFlags, frameReopenReceipt }) : "",
417
+ ...REFUTATION_RECORDING_LINES,
418
+ `WHAT THE REPAIR CHANGED. The driver worked this out from the writer's saved record before and after the repair. A value saved again unchanged is not listed.`,
419
+ ...(changes ?? []).map(changeLine),
420
+ resting?.length ? `WHAT RESTS ON THESE CHANGES, as it reads now. Check that each still holds:` : "",
421
+ ...(resting ?? []).map(restingLine),
422
+ `WHERE THE EVIDENCE IS:`,
423
+ evidence?.coverage ? `- For a coverage line: the run's coverage ledger, register-coverage-ledger.json, and the plan-execution check below.` : "",
424
+ evidence?.finding ? `- For a finding: the records it cites, through band_record, and its row in placements.json.` : "",
425
+ `- For a narrative section: the source files in your declared inputs, below.`,
426
+ ),
427
+ // NO REPAIR TAIL, DECLARED: like the re-check, this orders the reviewer's own review, recorded through its
428
+ // own tool, which the recording lines name.
429
+ samples: [
430
+ { name: "a repair that changed coverage lines only", tail: "none",
431
+ args: { findings: "findings.json", skepticFlags: "skeptic-flags.json", frameReopenReceipt: "frame-reopen.json",
432
+ changes: [{ kind: "coverage", change: "changed", label: "the coverage line for \"register / EU\"",
433
+ before: { state: "confirmed-clean", note: "searched in full" }, after: { state: "coverage-limited", note: "one class was not read" } },
434
+ { kind: "coverage", change: "added", label: "the coverage line for \"register / UK\"", after: { state: "not-searched", note: "outside the plan" } }],
435
+ resting: [{ kind: "section", title: "Verdict", text: "One conflict needs a decision." }, { kind: "coverage-judgment", value: { sufficient: true, reason: "the plan ran" } }],
436
+ renumbered: false, coverageChanged: true, evidence: { coverage: true, finding: false } } },
437
+ { name: "a prose section and a finding the fold renumbered", tail: "none",
438
+ args: { findings: "findings.json", skepticFlags: "skeptic-flags.json", frameReopenReceipt: "frame-reopen.json",
439
+ changes: [{ kind: "finding", change: "changed", ordinal: 7, delivered: 6, fields: ["band"], before: { band: "Moderate" }, after: { band: "Serious" },
440
+ records: { source_type: "register-vendor", resolved_link: "https://example.invalid/mark/1" } },
441
+ { kind: "section", change: "changed", title: "The spine", before: "The spine was the coined element.", after: "The spine is the coined element." }],
442
+ resting: [{ kind: "action", label: "the action \"a1\"", ordinals: [7], text: "Ask counsel about finding 7." }],
443
+ renumbered: true, coverageChanged: false, evidence: { coverage: false, finding: true } } },
444
+ { name: "a register-only run whose repair removed a finding", tail: "none",
445
+ args: { findings: "findings.json", registerOnly: true, skepticFlags: "skeptic-flags.json", frameReopenReceipt: "frame-reopen.json",
446
+ changes: [{ kind: "finding", change: "removed", ordinal: 3, delivered: null, before: { ordinal: 3, mark: "NOVA" } }],
447
+ resting: [], renumbered: false, coverageChanged: false, evidence: { coverage: false, finding: true } } },
448
+ ],
310
449
  },
311
450
  {
312
451
  trigger: "lint-repair",
@@ -315,17 +454,29 @@ export const REPAIR_COMPOSERS = [
315
454
  // delivery-tail stage failed its pre-delivery lint.
316
455
  stage: "*",
317
456
  key: "*:lint-repair",
318
- compose: ({ label, file, toolWritten, failures }) => lines(
319
- toolWritten
320
- ? `You are RESUMING your own ${label} session — the driver wrote ${file} from your \`${toolWritten.tool}\` call, and the deterministic pre-delivery lint failed these checks on it:`
321
- : `You are RESUMING your own ${label} session — ${file} is already written and the deterministic pre-delivery lint failed these checks on it:`,
322
- ...failures.map((f) => `- ${f.detail || f.id}`),
457
+ // THE WRITER'S FORM names both files its call wrote: its faults can sit in the narrative or in a line the report
458
+ // composes from its findings record, and a fault in such a line is named by the finding that owns it, in the
459
+ // numbering its save takes.
460
+ compose: ({ label, file, toolWritten, failures, findings, narrative }) => lines(
461
+ toolWritten?.tool === "record_synthesis"
462
+ ? `This is a fresh session: your earlier synthesis work is not in your context. The driver wrote ${findings} and ${narrative} from your \`record_synthesis\` call. The deterministic pre-delivery checks found these faults in content rendered from that record:`
463
+ : toolWritten
464
+ ? `This is a fresh session: your earlier ${label} work is not in your context — the driver wrote ${file} from your \`${toolWritten.tool}\` call, and the deterministic pre-delivery lint failed these checks on it:`
465
+ : `This is a fresh session: your earlier ${label} work is not in your context — ${file} is already written and the deterministic pre-delivery lint failed these checks on it:`,
466
+ ...failures.map((f) => `- ${f.finding != null ? `finding ${f.finding}: ` : ""}${f.detail || f.id}`),
467
+ toolWritten?.tool === "record_synthesis" ? `Read ${findings} and ${narrative}, then correct the finding or narrative section that owns each fault.` : "",
323
468
  `Where a check quotes the fetched record's true value, correct the identifier to the record's value EXACTLY (the record is the only permitted source for registry identifiers).`,
324
469
  `Risk tiers and Level/Composite labels are COPIED from the findings values quoted in the check — never re-derive or re-word them.`,
325
470
  `Fix ONLY what the checks name; change nothing else.`,
326
- toolWritten
327
- ? `Apply the fix by calling \`${toolWritten.tool}\` AGAIN with the corrected values — the COMPLETE payload, not a patch, because the driver re-renders the whole artifact from what you send. You hold no Write or Edit tool for it and nothing you write by hand is read.`
328
- : editRepairTail(file),
471
+ // THE WRITER'S SAVE MERGES A PATCH; EVERY OTHER TOOL HERE TAKES ITS WHOLE PAYLOAD. `record_synthesis`
472
+ // holds the record it last accepted and merges a patch into it, so the writer sends only the change, in
473
+ // the shape its correction message uses. The other tools a delivery repair reaches render their artifact
474
+ // from the call they are sent, so they keep the complete-payload line.
475
+ toolWritten?.tool === "record_synthesis"
476
+ ? `Apply the fix in ONE \`record_synthesis\` PATCH call, sending only the change: \`narrative\` carrying only the sections you corrected; \`findings_patch\` carrying the complete corrected finding object(s), each with the \`ordinal\` it replaces; and \`findings\` carrying any register you corrected, complete, and never a \`findings[]\` list. The driver merges this patch into the last accepted record, keeps the values the patch does not replace, and renders both files. You hold no Write or Edit tool for it and nothing you write by hand is read.`
477
+ : toolWritten
478
+ ? `Apply the fix by calling \`${toolWritten.tool}\` AGAIN with the corrected values — the COMPLETE payload, not a patch, because the driver re-renders the whole artifact from what you send. You hold no Write or Edit tool for it and nothing you write by hand is read.`
479
+ : editRepairTail(file),
329
480
  ),
330
481
  // BOTH BRANCHES, AND THE CENSUS REQUIRES BOTH. This composer is the one that can hand a seat either
331
482
  // a tool order or a file-edit order depending on how its artifact is written, so a sample set
@@ -347,12 +498,19 @@ export const REPAIR_COMPOSERS = [
347
498
  // reported a defect in text no seat can be handed." The edit branch IS exercised — by the fixed
348
499
  // `samples` below, which is where the census checks branch coverage.
349
500
  samplesForStage: ({ stage, tool, file }) => [
350
- { name: `${stage}'s own tool-written artifact`, tail: "tool", args: { label: stage, file, toolWritten: { tool }, failures: [{ id: "lint_1", detail: "a check the lint named" }] } },
501
+ { name: `${stage}'s own tool-written artifact`, tail: "tool", args: { label: stage, file, toolWritten: { tool }, findings: "findings.json", narrative: file,
502
+ failures: [{ id: "lint_1", detail: "a check the lint named" }] } },
351
503
  ],
352
504
  samples: [
353
505
  { name: "a tool-written artifact", tail: "tool",
354
506
  args: { label: "report-card", file: "report-cards/0.md", toolWritten: { tool: "record_report_card" },
355
507
  failures: [{ id: "rc_band_mismatch", detail: "band says CLEAR, findings say BORDERLINE" }] } },
508
+ // The writer's branch: its save merges a patch, so it is the one tool-written artifact told to send only
509
+ // the change.
510
+ { name: "the writer's record, which takes a patch", tail: "tool",
511
+ args: { label: "synthesis", file: "narrative.md", toolWritten: { tool: "record_synthesis" }, findings: "findings.json", narrative: "narrative.md",
512
+ failures: [{ id: "narrative_coverage_claim", detail: "a coverage line claims a slice the plan did not run" },
513
+ { id: "prescription-prose", finding: 2, detail: "the sentence composed from finding 2 recommends a filing" }] } },
356
514
  // THE SAMPLE MOVED STAGES RATHER THAN FLIPPING ITS SHAPE. It read
357
515
  // `{label:"synthesis", file:"narrative.md", toolWritten:null}`, and when the writer converted, the
358
516
  // sample-honesty arm fails it by name — narrative.md now has a row. Flipping this entry to
@@ -383,11 +541,11 @@ export const REPAIR_COMPOSERS = [
383
541
  // The direction is scoped to the two files by name BEFORE the Edit tool is offered, per the hazard
384
542
  // GRID_SCOPED records: the rulings tail below names the placement file precisely to forbid re-reading
385
543
  // it, and an unscoped Edit affordance beside a named path is an invitation to edit it.
386
- `You are RESUMING your own synthesis session — your narrative and inputs are already in your context; do NOT redo the analysis.`,
544
+ `This is a fresh session: your earlier synthesis is not in your context, and you do NOT redo the analysis.`,
387
545
  `Exactly ${quarantined.length} finding object(s) in ${findings} failed the strict parse. Fix ONLY these objects and change NOTHING else:`,
388
546
  ...quarantined.map((q) => `- "${q.mark}" (index ${q.index}): ${String(q.error ?? "invalid shape").slice(0, 160)}`),
389
547
  `THE FINDINGS CONTRACT IS CLOSED — MINIMAL EDIT ONLY: change the smallest set of existing fields these errors require; NEVER invent a key, a state, or an enum value. A correction that cannot be expressed in existing fields goes in the finding's prose/impact text. ONE exception: a finding re-typed TO "off-field" also gets "off_field_ground" ("different-field" only where its goods_proximity reads "low", else "no-material-risk"), re-typing away from off-field removes it, and every finding that is not "withdrawn" carries a non-empty "legal_position" and "practical_position".`,
390
- `Send the correction with \`record_synthesis\`: a PATCH call carrying \`findings_patch\` — the complete corrected finding object(s), each with the \`ordinal\` it replaces — and nothing else. The driver is holding every value you already sent and re-renders both files from them, so what you do not name comes back byte-identical. There is no file for you to write or edit and nothing you write by hand is read.`,
548
+ `Send the correction with \`record_synthesis\`: a PATCH call carrying \`findings_patch\` — the complete corrected finding object(s), each with the \`ordinal\` it replaces — and nothing else. The driver merges this patch into the last accepted record, keeps the values the patch does not replace, and renders both files. There is no file for you to write or edit and nothing you write by hand is read.`,
391
549
  ),
392
550
  samples: [{ name: "one quarantined finding object", tail: "tool", args: { findings: "findings.json", quarantined: [{ mark: "NOVA", index: 2, error: "band missing" }] } }],
393
551
  },
@@ -396,7 +554,7 @@ export const REPAIR_COMPOSERS = [
396
554
  stage: "synthesis",
397
555
  key: "synthesis:action-reemit",
398
556
  compose: ({ findings, bad }) => lines(
399
- `You are RESUMING your own synthesis session — your narrative and inputs are already in your context; do NOT redo the analysis.`,
557
+ `This is a fresh session: your earlier synthesis is not in your context, and you do NOT redo the analysis.`,
400
558
  `Exactly ${bad.length} object(s) in the "actions" register of ${findings} failed the strict parse. Fix ONLY these objects and change NOTHING else — no finding changes, no new actions, none removed:`,
401
559
  // The id is what the seat can find its own row by; `kind` is echoed because a kind defect is
402
560
  // the one case where the id alone does not locate the fault in the seat's own reading.
@@ -411,7 +569,7 @@ export const REPAIR_COMPOSERS = [
411
569
  stage: "synthesis",
412
570
  key: "synthesis:ask-answer-reemit",
413
571
  compose: ({ findings, bad }) => lines(
414
- `You are RESUMING your own synthesis session — your narrative and inputs are already in your context; do NOT redo the analysis.`,
572
+ `This is a fresh session: your earlier synthesis is not in your context, and you do NOT redo the analysis.`,
415
573
  `Exactly ${bad.length} object(s) in the "ask_answers" array of ${findings} failed the strict parse. Fix ONLY these objects and change NOTHING else — no finding changes, no action changes:`,
416
574
  ...bad.map((q) => `- ask_answers index ${q.index}${q.ask ? ` (ask "${q.ask}")` : ""}: ${String(q.error ?? "invalid shape").slice(0, 160)}`),
417
575
  `THE SHAPE IS CLOSED — MINIMAL EDIT ONLY. Each entry is EXACTLY {"ask","answer"} plus an OPTIONAL "ordinals" array of finding ordinals. \`ask\` is the requester's instruction VERBATIM (it is the join key to the frozen intake asks). \`answer\` is the ANSWER ALONE — never prefixed with "You asked: … →", which the driver adds when it builds the section. Do not resolve a defect by DELETING the entry: the ask was committed at intake and would then ship unanswered.`,
@@ -424,7 +582,7 @@ export const REPAIR_COMPOSERS = [
424
582
  stage: "register-digest",
425
583
  key: "register-digest:late-bind",
426
584
  compose: ({ registerFindings, bind }) => lines(
427
- `You are RESUMING your earlier register-digest session — your prior digest and all unit files are already in your context. Do NOT redo it from scratch and do NOT run any new searches.`,
585
+ `This is a fresh session: your earlier register-digest work is not in your context, and the unit files are among the inputs listed below. Your prior digest is ${registerFindings}. Do NOT redo it from scratch and do NOT run any new searches.`,
428
586
  `The applicant has been named mid-run: ${bind.customer}.${bind.exclusions.length ? ` Affiliate/exclusion set: ${bind.exclusions.join(", ")}.` : ""}`,
429
587
  `Re-classify your existing findings against this: marks owned by the applicant/exclusion set are the client's OWN rights (not conflicts — move them out of the adverse tiers and note them as own-rights context); any finding previously treated as candidate-self resolves normally. Everything the re-classification does not touch stays as it is.`,
430
588
  // ── CONVERTED (conversion 11). `editRepairTail(registerFindings)` stood here and ordered
@@ -446,7 +604,7 @@ export const REPAIR_COMPOSERS = [
446
604
  stage: "common-law-half",
447
605
  key: "common-law-half:connotation-remedy",
448
606
  compose: ({ findingsFile, gridSpecPath, tok }) => lines(
449
- `You are RESUMING your own common-law session — your findings file is ${findingsFile} and your prior work is in context. Do NOT redo the sweep and do NOT rewrite your findings file. You own this matter's MEANING SWEEP (your spec: ${gridSpecPath}).`,
607
+ `This is a fresh session: your earlier common-law work is not in your context, and your findings file is ${findingsFile}. Do NOT redo the sweep and do NOT rewrite your findings file. You own this matter's MEANING SWEEP (your spec: ${gridSpecPath}).`,
450
608
  `The MERGED findings failed the meaning-receipts gate (${abbrev(tok, 160)}). Fix exactly this: ensure ${correctionHint(tok, { gridLedgerName: gridLedgerNameFor(findingsFile) })}.`,
451
609
  `Everything else in your half stays exactly as it is — the driver re-merges the halves and re-runs the canonical gate in code.`,
452
610
  `Record the outstanding rulings ONLY by calling the \`record_dispositions\` tool with grid_spec_path = ${gridSpecPath} — never by writing or editing any file. Everything already recorded is kept; stop when the tool's answer reports nothing outstanding.`,
@@ -469,7 +627,7 @@ export const REPAIR_COMPOSERS = [
469
627
  stage: "register-unit",
470
628
  key: "register-unit:plan-join",
471
629
  compose: ({ axis, registerPlan, bandPath, entries }) => lines(
472
- `You are RESUMING your own register-unit session (axis "${axis}"). Your unit digest stands — do NOT redo it.`,
630
+ `This is a fresh session: your earlier register-unit work (axis "${axis}") is not in your context. Your unit digest stands — do NOT redo it.`,
473
631
  `These DICTATED plan entries have no band block yet:`,
474
632
  ...entries.map((e) => `- qid "${e.qid}": ${e.predicate} ${e.terms ? `names ${JSON.stringify(e.terms)}` : `"${e.term}"`} · nice_classes ${JSON.stringify(e.nice_classes)}${e.regions?.length ? ` · regions ${JSON.stringify(e.regions)}` : ""} · expected: ${e.expected_kind}`),
475
633
  `Close them by calling register_execute_plan ONCE with {"plan_path": ${JSON.stringify(registerPlan)}, "axis": "${axis}", "output_path": ${JSON.stringify(bandPath)}, "qids": ${JSON.stringify(entries.map((e) => e.qid))}} — the tool runs ONLY those entries and MERGES the band itself (your judgment blocks and every other block stay as they are; the missing dictated blocks land, qids stamped). Do NOT run the entries manually or edit the band yourself, and do not call it without qids: that re-runs every entry on the axis.`,
@@ -484,21 +642,23 @@ export const REPAIR_COMPOSERS = [
484
642
  trigger: "xcheck-decide",
485
643
  stage: "register-unit",
486
644
  key: "register-unit:xcheck-decide",
487
- compose: ({ axis, entries }) => lines(
488
- `You are RESUMING your own register-unit session (axis "${axis}"). Your unit digest stands — do NOT redo it.`,
645
+ // A fresh session cannot infer the tool's arguments, so the caller supplies all three.
646
+ compose: ({ axis, registerPlan, bandPath, entries }) => lines(
647
+ `This is a fresh session: your earlier register-unit work (axis "${axis}") is not in your context. Your unit digest stands — do NOT redo it.`,
489
648
  `The web search's findings named these register questions after your reading. They wait for you like the families in your plan:`,
490
649
  ...entries.map((e) => `- qid "${e.qid}": ${e.predicate} "${e.term}" · nice_classes ${JSON.stringify(e.nice_classes)}${e.from ? ` · from ${e.from}` : ""}`),
650
+ `For \`register_execute_plan\`, use \`plan_path: ${JSON.stringify(registerPlan)}\`, \`axis: ${JSON.stringify(axis)}\`, and \`output_path: ${JSON.stringify(bandPath)}\`; pass only the qids you released.`,
491
651
  DECIDE_WAITING_FAMILIES,
492
652
  `Return ONLY: the band path + the tool's summary line.`,
493
653
  ),
494
- samples: [{ name: "a cross-check the web search named", tail: "tool", args: { axis: "primary-sweep", entries: [{ qid: "xcheck-owner-lanternwick-studio", predicate: "owner", term: "Lanternwick Studio", nice_classes: ["9"], from: "https://shop.example.com/lanternwick" }] } }],
654
+ samples: [{ name: "a cross-check the web search named", tail: "tool", args: { axis: "primary-sweep", registerPlan: "register-plan.json", bandPath: "bands/primary-sweep.md", entries: [{ qid: "xcheck-owner-lanternwick-studio", predicate: "owner", term: "Lanternwick Studio", nice_classes: ["9"], from: "https://shop.example.com/lanternwick" }] } }],
495
655
  },
496
656
  {
497
657
  trigger: "grid-ledger",
498
658
  stage: "common-law",
499
659
  key: "common-law:grid-ledger",
500
660
  compose: ({ gridLedger, gridSpecPath }) => lines(
501
- `You are RESUMING your own common-law session. Your findings file is already written and stands.`,
661
+ `This is a fresh session: your earlier common-law work is not in your context. Your findings file is already written and stands.`,
502
662
  `The grid ledger ${gridLedger} is missing — the deterministic grid call did not complete. Call perplexity_research again with enable_sandbox:true and grid_spec_path: ${gridSpecPath}. The tool writes the ledger itself from the API response; do NOT write it yourself and do NOT re-emit the grid JSON.`,
503
663
  `Then return ONLY: the grid ledger path + one line confirming the tool reported it written.`,
504
664
  ),
@@ -509,7 +669,7 @@ export const REPAIR_COMPOSERS = [
509
669
  stage: "common-law",
510
670
  key: "common-law:coverage-closure",
511
671
  compose: ({ findingsFile, suppSpec, suppSpecPath, closable, cellKey, sourceChannels }) => lines(
512
- `You are RESUMING your own common-law session — your findings file is ${findingsFile} and your prior work is in context. Do NOT redo the full sweep.`,
672
+ `This is a fresh session: your earlier common-law work is not in your context, and your findings file is ${findingsFile}. Do NOT redo the full sweep.`,
513
673
  suppSpec ? `These ${closable.length} grid cells are recorded as not-executed/coverage-limited with NO mechanical failure behind them — they are closable now. Run them via the DETERMINISTIC supplementary search-as-code grid: call perplexity_research with enable_sandbox:true and grid_spec_path: ${suppSpecPath}. The tool runs EXACTLY the dictated cells and WRITES the supplementary ledger to its output_path itself — do NOT save it yourself and do NOT re-emit the grid JSON. The cells:` : "",
514
674
  ...(suppSpec ? closable.map((c) => `- ${cellKey(c)}`) : []),
515
675
  sourceChannels.length ? `IN-SCOPE CHANNELS not searched this run (the blind frame-diff flagged them as applied-but-unsearched distribution/discovery surfaces — CLOSE them, never recommend them): ${sourceChannels.join("; ")}. For EVERY manifest variant, run the appropriate search of each channel — its real domain(s) (e.g. github.com / nuget.org / npmjs.com, or the relevant store/registry) or a general-web search scoped to it — and ADD one Negative-results matrix row per (variant × channel) with its receipt, then reconcile the matching Scope-ledger / Coverage rows from not-searched to searched.` : "",
@@ -527,7 +687,7 @@ export const REPAIR_COMPOSERS = [
527
687
  stage: "common-law-half",
528
688
  key: "common-law-half:coverage-closure",
529
689
  compose: ({ findingsFile, gridSpecPath, suppSpec, suppSpecPath, cells, cellKey, chans, scopes }) => lines(
530
- `You are RESUMING your own common-law session — your findings file is ${findingsFile} and your prior work is in context. Do NOT redo the full sweep. You own ONE HALF of this matter's grid (your half-grid spec: ${gridSpecPath}).`,
690
+ `This is a fresh session: your earlier common-law work is not in your context, and your findings file is ${findingsFile}. Do NOT redo the full sweep. You own ONE HALF of this matter's grid (your half-grid spec: ${gridSpecPath}).`,
531
691
  suppSpec ? `These ${cells.length} grid cells are recorded as not-executed/coverage-limited with NO mechanical failure behind them — they are closable now. Run them via the DETERMINISTIC supplementary search-as-code grid: call perplexity_research with enable_sandbox:true and grid_spec_path: ${suppSpecPath}. The tool runs EXACTLY the dictated cells and WRITES the supplementary ledger to its output_path itself — do NOT save it yourself and do NOT re-emit the grid JSON. The cells:` : "",
532
692
  ...(suppSpec ? cells.map((c) => `- ${cellKey(c)}`) : []),
533
693
  chans.length ? `IN-SCOPE CHANNELS not searched this run (the blind frame-diff flagged them as applied-but-unsearched distribution/discovery surfaces — CLOSE them, never recommend them): ${chans.join("; ")}. For EACH of these variants — ${scopes.join("; ")} — (the driver dictates this list; it may include the sibling half's terms re-routed to you because its session is unavailable), run the appropriate search of each channel — its real domain(s) (e.g. github.com / nuget.org / npmjs.com, or the relevant store/registry) or a general-web search scoped to it — and ADD one Negative-results matrix row per (variant × channel) with its receipt, then reconcile the matching Scope-ledger / Coverage rows from not-searched to searched.` : "",
@@ -541,7 +701,7 @@ export const REPAIR_COMPOSERS = [
541
701
  stage: "common-law",
542
702
  key: "common-law:frame-reopen",
543
703
  compose: ({ findingsFile, gridLedger, directives }) => lines(
544
- `You are RESUMING your own common-law session — your findings file is ${findingsFile} and your prior work is in context. Do NOT redo the full sweep.`,
704
+ `This is a fresh session: your earlier common-law work is not in your context, and your findings file is ${findingsFile}. Do NOT redo the full sweep.`,
545
705
  `A blind, frame-INDEPENDENT re-derivation found these SOURCE CHANNELS the run did NOT search. Run a supplementary search over them (enable_sandbox: true, depth: "pro-search") — resolve each channel to its real domain(s) or a general-web search scoped to it:`,
546
706
  ...directives.map((d) => `- ${d.item} — ${d.observation}`),
547
707
  `Then update the Negative-results matrix + Coverage ledger for what you searched, preserving everything else. Do NOT hand-edit ${gridLedger} — the driver owns the machine ledger. The edit direction below covers ${findingsFile} ONLY.`,
@@ -554,7 +714,7 @@ export const REPAIR_COMPOSERS = [
554
714
  stage: "common-law-half",
555
715
  key: "common-law-half:frame-reopen",
556
716
  compose: ({ findingsFile, gridSpecPath, gridLedger, directives, scopes }) => lines(
557
- `You are RESUMING your own common-law session — your findings file is ${findingsFile} and your prior work is in context. Do NOT redo the full sweep. You own ONE HALF of this matter's grid (your half-grid spec: ${gridSpecPath}).`,
717
+ `This is a fresh session: your earlier common-law work is not in your context, and your findings file is ${findingsFile}. Do NOT redo the full sweep. You own ONE HALF of this matter's grid (your half-grid spec: ${gridSpecPath}).`,
558
718
  `A blind, frame-INDEPENDENT re-derivation found these SOURCE CHANNELS the run did NOT search. For EACH of these variants — ${scopes.join("; ")} — (the driver dictates this list; it may include the sibling half's terms re-routed to you because its session is unavailable), run a supplementary search over them (enable_sandbox: true, depth: "pro-search") — resolve each channel to its real domain(s) or a general-web search scoped to it:`,
559
719
  ...directives.map((d) => `- ${d.item} — ${d.observation}`),
560
720
  `Then update the Negative-results matrix + Coverage ledger for what you searched, preserving everything else. Do NOT hand-edit ${gridLedger} — the driver owns the machine ledger. The edit direction below covers ${findingsFile} ONLY.`,
@@ -702,7 +862,7 @@ export const REPAIR_COMPOSERS = [
702
862
  // Its key differs from the spec'd arm because both dispatch under `trigger: "grid-ledger"` on the
703
863
  // same stage — a trigger-only key would have resolved both to whichever came first.
704
864
  compose: ({ gridLedger }) => lines(
705
- `You are RESUMING your own common-law session. Your findings file is already written and stands.`,
865
+ `This is a fresh session: your earlier common-law work is not in your context. Your findings file is already written and stands.`,
706
866
  `One artifact is missing: save the grid call's stdout JSON VERBATIM to ${gridLedger} — the single stdout object, or a JSON ARRAY of the per-batch stdout objects in batch order. Copy it exactly as the tool returned it; do not reformat, re-type, or judge.`,
707
867
  `Then return ONLY: that absolute path + one line confirming the save.`,
708
868
  ),