clearotron 0.3.2-beta.7 → 0.3.2-beta.9

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 (272) hide show
  1. package/.env.example +58 -26
  2. package/CONTRIBUTING.md +8 -8
  3. package/INSTALL.md +148 -81
  4. package/README.md +3 -3
  5. package/SECURITY.md +3 -3
  6. package/bin/brandowner.mjs +3 -3
  7. package/bin/framework-preflight.mjs +1 -1
  8. package/bin/onboard.mjs +637 -216
  9. package/bin/start.mjs +151 -27
  10. package/bin/update.mjs +58 -11
  11. package/build-info.json +2 -2
  12. package/docs/DELIVERY.md +2 -1
  13. package/docs/INTAKE.md +1 -1
  14. package/docs/ONBOARDING.md +1 -1
  15. package/docs/architecture/03-run-lifecycle.md +6 -6
  16. package/docs/architecture/04-configuration-reference.md +32 -14
  17. package/docs/architecture/05-config-governance.md +23 -8
  18. package/docs/architecture/05-customer-profiles.md +2 -2
  19. package/docs/architecture/06-operations-runbook.md +3 -3
  20. package/docs/architecture/08-development-guide.md +6 -6
  21. package/docs/configuration.md +5 -5
  22. package/docs/decisions/0003-credential-model.md +1 -1
  23. package/docs/writing-standard.md +4 -0
  24. package/driver/CHANGELOG.md +124 -0
  25. package/driver/README.md +3 -3
  26. package/driver/band-size.mjs +59 -0
  27. package/driver/binding-layers.mjs +1 -1
  28. package/driver/citation-census.json +3 -3
  29. package/driver/{prelim-variants-record.mjs → clearance-variants-record.mjs} +24 -24
  30. package/driver/common-law-receipts.mjs +2 -2
  31. package/driver/company-bundle.mjs +3 -3
  32. package/driver/compose-read.mjs +8 -14
  33. package/driver/config-inventory.mjs +112 -9
  34. package/driver/consumption-ledger.mjs +2 -2
  35. package/driver/contract-arm2-baseline.json +2 -5
  36. package/driver/contract-dictation-registry.mjs +19 -19
  37. package/driver/contract-e3-backlog.mjs +43 -43
  38. package/driver/contract-e3-baseline.json +14 -14
  39. package/driver/contract-vocabulary.mjs +68 -27
  40. package/driver/deliver-trigger.sh +16 -16
  41. package/driver/demo-container.mjs +3 -3
  42. package/driver/dev-portal.mjs +3 -3
  43. package/driver/disposition-call.mjs +1 -1
  44. package/driver/door-gates.mjs +41 -7
  45. package/driver/doubt-ledger.mjs +2 -2
  46. package/driver/drainer-identity.mjs +34 -8
  47. package/driver/driver.config.mjs +367 -104
  48. package/driver/engine/CONTRACT.md +10 -3
  49. package/driver/engine/README.md +2 -2
  50. package/driver/engine/anthropic-agent.mjs +77 -21
  51. package/driver/engine/auth.mjs +129 -10
  52. package/driver/engine/jx-turn.mjs +7 -6
  53. package/driver/engine/mcp/README.md +1 -1
  54. package/driver/engine/mcp/dispositions-server.mjs +3 -3
  55. package/driver/engine/mcp/gather-config.mjs +9 -9
  56. package/driver/engine/mcp/perplexity-server.mjs +2 -2
  57. package/driver/engine/mcp/recording-server.mjs +18 -5
  58. package/driver/engine/openai-agent.mjs +4 -2
  59. package/driver/engine/probe.mjs +110 -23
  60. package/driver/enqueue-schema.mjs +6 -2
  61. package/driver/findings-model.mjs +6 -3
  62. package/driver/flag-snapshot.mjs +34 -8
  63. package/driver/form-neighbourhood.mjs +54 -7
  64. package/driver/framework.mjs +4 -4
  65. package/driver/gateway.mjs +36 -24
  66. package/driver/jx-lanes.mjs +23 -4
  67. package/driver/jx-units.mjs +7 -4
  68. package/driver/jx.mjs +34 -4
  69. package/driver/knockout-review-record.mjs +56 -4
  70. package/driver/known-conflicts.mjs +1 -1
  71. package/driver/matter-frame-record.mjs +90 -1
  72. package/driver/named-band.mjs +1 -1
  73. package/driver/ordinary-words.mjs +51 -0
  74. package/driver/outbox-backoff.mjs +31 -16
  75. package/driver/package.json +1 -1
  76. package/driver/partial-payload-baseline.json +2 -2
  77. package/driver/phase0.mjs +3 -3
  78. package/driver/pipeline-knockout.mjs +5 -5
  79. package/driver/pipeline.mjs +396 -81
  80. package/driver/placement-form.mjs +77 -1
  81. package/driver/placement-model.mjs +1 -1
  82. package/driver/portal-config-view.mjs +30 -1
  83. package/driver/portal-report.mjs +107 -6
  84. package/driver/portal-service.mjs +80 -14
  85. package/driver/portal-upstream.mjs +1 -1
  86. package/driver/predelivery-lint.mjs +12 -2
  87. package/driver/preserve-merge.mjs +3 -3
  88. package/driver/product-rows.mjs +2 -2
  89. package/driver/products.mjs +1 -1
  90. package/driver/profiles/README.md +3 -3
  91. package/driver/profiles/demo-brand-owner.json +2 -2
  92. package/driver/profiles.mjs +55 -17
  93. package/driver/progress.mjs +18 -8
  94. package/driver/provider-usage.mjs +8 -8
  95. package/driver/publish/index.mjs +154 -8
  96. package/driver/publish/knockout.mjs +39 -5
  97. package/driver/publish/pool-admin.mjs +1 -1
  98. package/driver/publish/publish-inputs.mjs +18 -2
  99. package/driver/publish/render-knockout.mjs +184 -31
  100. package/driver/publish/render.mjs +323 -93
  101. package/driver/publish/report-data.mjs +4 -1
  102. package/driver/publish/report-topbar.mjs +58 -0
  103. package/driver/publish/search-depth.mjs +133 -4
  104. package/driver/publish/templates/report.css +78 -4
  105. package/driver/publish/xlsx.mjs +20 -1
  106. package/driver/queue-order.mjs +2 -2
  107. package/driver/recording-agreement.mjs +1 -1
  108. package/driver/reference-score.mjs +1 -1
  109. package/driver/register-availability.mjs +2 -2
  110. package/driver/register-count.mjs +50 -5
  111. package/driver/register-coverage.mjs +161 -1
  112. package/driver/register-digest-record.mjs +236 -11
  113. package/driver/register-grant-vocabulary.mjs +1 -1
  114. package/driver/register-plan.mjs +189 -2
  115. package/driver/registry-fidelity.mjs +3 -3
  116. package/driver/repair-composers.mjs +1 -1
  117. package/driver/repair-contract.mjs +1 -1
  118. package/driver/replay-archive.mjs +6 -6
  119. package/driver/report-overview-record.mjs +2 -2
  120. package/driver/result-noun-fields.mjs +2 -2
  121. package/driver/run-economics.mjs +41 -10
  122. package/driver/run-requirements.mjs +173 -9
  123. package/driver/runner.mjs +5 -5
  124. package/driver/scope-facts.mjs +20 -5
  125. package/driver/scope-ledger.mjs +5 -5
  126. package/driver/search-policy.mjs +22 -12
  127. package/driver/skills/README.md +15 -15
  128. package/driver/skills/blind-frame/SKILL.md +2 -2
  129. package/driver/skills/case-law-citation/SKILL.md +4 -4
  130. package/driver/skills/case-law-citation/sources/eurlex.md +1 -1
  131. package/driver/skills/{prelim-common-law → clearance-common-law}/SKILL.md +22 -22
  132. package/driver/skills/{prelim-common-law → clearance-common-law}/perplexity-prompts.md +1 -1
  133. package/driver/skills/{prelim-register → clearance-register}/SKILL.md +10 -10
  134. package/driver/skills/{prelim-register → clearance-register}/digest.md +2 -2
  135. package/driver/skills/{prelim-register → clearance-register}/providers/README.md +1 -1
  136. package/driver/skills/{prelim-register → clearance-register}/providers/clarivate.md +37 -35
  137. package/driver/skills/{prelim-register → clearance-register}/providers/corsearch.md +20 -11
  138. package/driver/skills/{prelim-register → clearance-register}/providers/signa.md +5 -5
  139. package/driver/skills/{prelim-register → clearance-register}/register-recipes.md +3 -3
  140. package/driver/skills/{prelim-register → clearance-register}/status-rules.md +2 -2
  141. package/driver/skills/{prelim-register → clearance-register}/stealth-filer-indicators.md +1 -1
  142. package/driver/skills/{prelim-register → clearance-register}/unit.md +2 -2
  143. package/driver/skills/{prelim-search → clearance-search}/SKILL.md +31 -31
  144. package/driver/skills/{prelim-search → clearance-search}/delivery-contract.md +1 -1
  145. package/driver/skills/{prelim-search → clearance-search}/phase2-execution.md +18 -18
  146. package/driver/skills/{prelim-search → clearance-search}/synthesis-rules.md +7 -7
  147. package/driver/skills/{prelim-variants → clearance-variants}/SKILL.md +18 -18
  148. package/driver/skills/{prelim-variants → clearance-variants}/transliteration-scripts.md +5 -5
  149. package/driver/skills/frame-diff/SKILL.md +1 -1
  150. package/driver/skills/knockout-assess/SKILL.md +10 -7
  151. package/driver/skills/matter-frame/SKILL.md +3 -3
  152. package/driver/skills/narrative-refutation/SKILL.md +9 -9
  153. package/driver/skills/placement-inquiry/SKILL.md +5 -5
  154. package/driver/stage-context.mjs +1 -1
  155. package/driver/stages-knockout.mjs +4 -4
  156. package/driver/stages.mjs +65 -61
  157. package/driver/status-snapshot.mjs +2 -2
  158. package/driver/suite-census.json +340 -136
  159. package/driver/surface-exit-verdict.mjs +58 -0
  160. package/driver/systemd/README.md +9 -6
  161. package/driver/systemd/clearotron-worker.service +1 -1
  162. package/driver/terminal-clamp.mjs +109 -1
  163. package/driver/tokens.mjs +169 -3
  164. package/driver/unit-environment.mjs +42 -15
  165. package/driver/unit-inventory.mjs +19 -2
  166. package/driver/usage-ledger.mjs +1 -1
  167. package/driver/variant-manifest-model.mjs +4 -4
  168. package/driver/verify-knockout.mjs +27 -0
  169. package/driver/verify.mjs +94 -6
  170. package/driver/whatif-queue.mjs +1 -1
  171. package/driver/wordlists/en.txt +63906 -0
  172. package/mcp-server/CHANGELOG.md +8 -0
  173. package/mcp-server/README.md +1 -1
  174. package/mcp-server/lib/README.md +1 -1
  175. package/mcp-server/lib/options.mjs +8 -7
  176. package/mcp-server/lib/plan.mjs +18 -2
  177. package/mcp-server/lib/runs.mjs +1 -1
  178. package/mcp-server/lib/usage.mjs +3 -3
  179. package/mcp-server/lib/whatif.mjs +1 -1
  180. package/mcp-server/package.json +1 -1
  181. package/mcp-server/server.mjs +18 -1
  182. package/package.json +12 -11
  183. package/portal-ui/dist/assets/{index-CVOIvdhc.css → index-CtvwLCti.css} +207 -3
  184. package/portal-ui/dist/assets/{index-5UyqAyNM.js → index-EVaSo5-g.js} +1580 -527
  185. package/portal-ui/dist/index.html +2 -2
  186. package/portal-ui/package.json +1 -1
  187. package/providers/README.md +1 -1
  188. package/providers/_shared/enumerate.mjs +6 -6
  189. package/providers/_shared/execute-plan.mjs +3 -3
  190. package/providers/_shared/ledger.mjs +119 -5
  191. package/providers/_shared/provider-text.mjs +2 -2
  192. package/providers/_shared/screen.mjs +2 -2
  193. package/providers/_shared/script-form.mjs +3 -3
  194. package/providers/_shared/territory-codes.mjs +23 -3
  195. package/providers/clarivate/README.md +1 -1
  196. package/providers/clarivate/src/capabilities.js +12 -12
  197. package/providers/clarivate/src/core.js +37 -43
  198. package/providers/corsearch/README.md +1 -1
  199. package/providers/corsearch/src/capabilities.js +5 -5
  200. package/providers/corsearch/src/core.js +3 -3
  201. package/providers/jx/README.md +2 -1
  202. package/providers/jx/src/turn-envelope.mjs +8 -3
  203. package/providers/oauth-mcp-bridge/CHANGELOG.md +8 -0
  204. package/providers/oauth-mcp-bridge/package.json +1 -1
  205. package/providers/perplexity/src/core.js +1 -1
  206. package/providers/signa/README.md +1 -1
  207. package/providers/signa/src/capabilities.js +42 -49
  208. package/providers/signa/src/core.js +106 -29
  209. package/providers/uspto-local/README.md +1 -1
  210. package/providers/uspto-local/src/sync.js +1 -1
  211. package/scripts/README.md +1 -0
  212. package/scripts/ask-ai-render-check.mjs +127 -1
  213. package/scripts/authority-boundary-probe.mjs +8 -6
  214. package/scripts/backfill-started-at.mjs +2 -2
  215. package/scripts/census-merge-driver.mjs +33 -2
  216. package/scripts/citation-anchor-report.mjs +181 -0
  217. package/scripts/dead-names.mjs +1 -1
  218. package/scripts/deprecate-below.mjs +66 -8
  219. package/scripts/drain-preflight.mjs +1 -1
  220. package/scripts/e2e.mjs +174 -0
  221. package/scripts/env-audit.mjs +39 -6
  222. package/scripts/env-classify.mjs +20 -2
  223. package/scripts/freeze-example-run.mjs +61 -18
  224. package/scripts/generated-files-are-current.mjs +69 -4
  225. package/scripts/live-surface-check.mjs +124 -41
  226. package/scripts/markdown-link-check.mjs +1 -1
  227. package/scripts/merge-shape-check.mjs +242 -0
  228. package/scripts/mint-names-in-force.mjs +5 -5
  229. package/scripts/mint-offered-territories.mjs +72 -0
  230. package/scripts/mint-public-residue.mjs +2 -2
  231. package/scripts/mint-reference-strip-backlog.mjs +2 -2
  232. package/scripts/mint-suite-census.mjs +75 -2
  233. package/scripts/mint-writing-standard-backlog.mjs +2 -2
  234. package/scripts/purge-runs.mjs +7 -7
  235. package/scripts/reconcile-runs.mjs +2 -2
  236. package/scripts/release-approve-parked.mjs +20 -2
  237. package/scripts/release-await-cut.mjs +120 -1
  238. package/scripts/release-note-required.mjs +76 -8
  239. package/scripts/report-header-render-check.mjs +164 -0
  240. package/scripts/settings-render-check.mjs +75 -2
  241. package/scripts/test-full.mjs +96 -3
  242. package/scripts/test-run.mjs +10 -0
  243. package/shared/brand.mjs +27 -0
  244. package/shared/connect-clients.mjs +39 -11
  245. package/shared/deployment-box.mjs +7 -2
  246. package/shared/driver-dir.mjs +1 -1
  247. package/shared/env-aliases.mjs +1 -1
  248. package/shared/identifier-scan.mjs +65 -9
  249. package/shared/identifier-sentinels.mjs +22 -0
  250. package/shared/names-in-force.mjs +4 -2
  251. package/shared/offered-territories.json +738 -0
  252. package/shared/pre-rename-spellings.mjs +53 -0
  253. package/shared/reference-guard-classes.mjs +40 -2
  254. package/shared/stdio-connect.mjs +39 -4
  255. package/shared/tree-commit.mjs +48 -0
  256. /package/driver/skills/{prelim-register → clearance-register}/providers/euipo.md +0 -0
  257. /package/driver/skills/{prelim-register → clearance-register}/providers/free-tier.md +0 -0
  258. /package/driver/skills/{prelim-register → clearance-register}/providers/uspto-local.md +0 -0
  259. /package/driver/skills/{prelim-search → clearance-search}/field-doctrine-pharma.md +0 -0
  260. /package/driver/skills/{prelim-search → clearance-search}/firm-wide-reasoning.md +0 -0
  261. /package/driver/skills/{prelim-search → clearance-search}/report-prose.md +0 -0
  262. /package/driver/skills/{prelim-search → clearance-search}/risk-framework-demo.manifest.json +0 -0
  263. /package/driver/skills/{prelim-search → clearance-search}/risk-framework-demo.md +0 -0
  264. /package/driver/skills/{prelim-search → clearance-search}/risk-framework-triage.manifest.json +0 -0
  265. /package/driver/skills/{prelim-search → clearance-search}/risk-framework-triage.md +0 -0
  266. /package/driver/skills/{prelim-search → clearance-search}/risk-framework.manifest.json +0 -0
  267. /package/driver/skills/{prelim-search → clearance-search}/risk-framework.md +0 -0
  268. /package/driver/skills/{prelim-search → clearance-search}/template-formatting.md +0 -0
  269. /package/driver/skills/{prelim-search → clearance-search}/templates/email/generic.md +0 -0
  270. /package/driver/skills/{prelim-search → clearance-search}/templates/search-request-form.html +0 -0
  271. /package/driver/skills/{prelim-search → clearance-search}/worked-examples-demo.md +0 -0
  272. /package/driver/skills/{prelim-search → clearance-search}/worked-examples.md +0 -0
@@ -29,6 +29,25 @@ const nameKey = (s) => norm(s).toLowerCase().replace(/\s+/g, " ");
29
29
 
30
30
  // ── Tone guards (the skill's rules, mechanical subset) ───────────────────────────────────────────────
31
31
  export const BANNED_TONE_RE = /\b(extremely difficult|most dangerous|massive|enormous)\b/i;
32
+
33
+ /**
34
+ * A RECOMMENDATION IS A SHAPE, AND THIS SCREEN MAY NOT WRITE ONE (owner, 2026-09-18).
35
+ *
36
+ * The screen states findings and a rating; what to do with the name is the reading lawyer's. Two
37
+ * instructions that asked for a conclusion were cut first, and a re-run then wrote one anyway, under a
38
+ * heading of its own: "Practical next step — <NAME> is not knocked out at this screening depth. Advance
39
+ * it to clearance…". Cutting an instruction stops the engine asking; only a refusal stops the offer.
40
+ *
41
+ * TWO SHAPES, because both reached a reader: a block the writer heads itself, and a closing line that
42
+ * moves the name on. WHAT THIS CANNOT SEE: the same conclusion as ordinary prose — "the remaining work
43
+ * here is a full search" — and no pattern can. It is a floor under the doctrine, never a substitute.
44
+ *
45
+ * The false positives were measured rather than assumed: the degraded note this lane REQUIRES reads
46
+ * "Manual verification recommended" and matches neither, and a register record "proceeding to
47
+ * registration" matches neither — only proceeding *to clearance* does.
48
+ */
49
+ export const RECOMMENDATION_RE = /^[ \t]*(?:[*_]{0,2}|#{1,6}\s*)(?:practical\s+|immediate\s+|suggested\s+)?(?:next steps?|what to do\b|what happens next|recommendations?|our recommendation)/im;
50
+ export const ADVANCE_RE = /\b(?:advance|move|take|send)\s+(?:it|this|the name|[A-Z][A-Z0-9'’-]{2,})\s+(?:on\s+)?(?:to|into|through)\s+(?:a\s+)?(?:full\s+)?clearance\b|\bproceeds?\s+to\s+(?:a\s+)?(?:full\s+)?clearance\b|\bshould\s+(?:proceed|advance|be advanced|move)\b|\bwe\s+recommend\b/i;
32
51
  export const QUANT_CLAIM_RE = /(\b\d[\d,.]*\s*(?:M|million|billion|k|thousand)?\s*(?:streams|downloads|copies sold|units sold)\b)|(\$\s?\d[\d,.]*\s*(?:M|million|billion|k)?\s*(?:in\s+)?(?:annual\s+)?(?:revenue|sales))/i;
33
52
 
34
53
  // ── URL normalization for the receipts gate ──────────────────────────────────────────────────────────
@@ -486,6 +505,14 @@ export const validators = {
486
505
  const claim = [prose, c.chunkSummary].filter(Boolean).join("\n").match(REGISTER_CLAIM_RE);
487
506
  if (claim)
488
507
  return { ok: false, reason: `knockout_register_claim:${m.name}: "${claim[0].trim()}" — this turn cannot see the register lane and must not describe it. The report states register coverage in code, from the run's own count sidecar; a summary that says the registers were not run, above a table of counts that were, is the contradiction this rule closes. Delete the clause` };
508
+ // THE PER-NAME READ IS SWEPT TOO, and until now it was not: the checks below read the finding
509
+ // fields, while `assessment` was validated for length and structure alone — which is exactly the
510
+ // field the conclusion arrived in. Refused HERE so the stage re-asks with the line quoted, rather
511
+ // than a reader meeting it.
512
+ const read = [m.assessment, prose].filter(Boolean).join("\n");
513
+ const recommended = read.match(RECOMMENDATION_RE) ?? read.match(ADVANCE_RE);
514
+ if (recommended)
515
+ return { ok: false, reason: `mark "${m.name}": "${recommended[0].trim()}" — this screen states findings and a rating; what to do with the name, and whether it advances, is the reading lawyer's. Delete the sentence: the report's own closing caveat already says what the screen is` };
489
516
  const banned = prose.match(BANNED_TONE_RE);
490
517
  if (banned) return { ok: false, reason: `mark "${m.name}": banned tone "${banned[0]}" — measured tone only (the band colour carries urgency)` };
491
518
  const quant = prose.match(QUANT_CLAIM_RE);
package/driver/verify.mjs CHANGED
@@ -35,13 +35,14 @@ import { coverageFormStamp, readCoverageForm } from "./coverage-form-io.mjs";
35
35
  import { readUnacknowledgedTaintAxes } from "./register-taint.mjs";
36
36
  import { registerPlanCallKilled } from "./tool-calls.mjs"; // — did the dictated call return?
37
37
  import { parseFindingsJson, parseFindingsJsonLenient, CLIENT_TIER_BY_COMPOSITE, isUnconditionalProceed, joinFindingToBlock, parseBlockOrd } from "./findings-model.mjs";
38
- import { parsePlacementsJson } from "./placement-model.mjs";
38
+ import { parsePlacementsJson } from "./placement-model.mjs"; import { placementFormSidecarName, parsePlacementForm, placementRenderAccount } from "./placement-form.mjs";
39
39
  import { parseCaseLawLedger, findCaseLawLedgerViolations, caseLawLedgerFail } from "./case-law-ledger.mjs";
40
40
  import { parseFrameworkManifest, aboveLowestBand, normalizeBand } from "./framework.mjs";
41
41
  import { parseNamedBand, findCollapsedBands } from "./named-band.mjs";
42
42
  import { parseBlindFrameModel } from "./blind-frame-model.mjs";
43
43
  import { parseFrameDiff } from "./frame-diff-model.mjs";
44
44
  import { parseVariantManifestModel, variantRomanizationGaps, variantCompletenessGaps, variantTermShapeGaps } from "./variant-manifest-model.mjs";
45
+ import { digestAccountingGap } from "./register-digest-record.mjs";
45
46
 
46
47
  // ── WS-B: the run-scoped profile sidecar ────────────────────────────────────────────────────────────
47
48
  // _driver/profile.json carries the run's frozen customer values (floor, platform list) for these
@@ -1758,7 +1759,7 @@ export const validators = {
1758
1759
  // HERE, where a miss re-prompts the stage that authors it.
1759
1760
  //
1760
1761
  // The floor's gate is the driver-written stage-contract marker (_driver/stage-contracts.json,
1761
- // "prelim-variants".romanization — written by pipeline.mjs recordStageContract at DISPATCH), i.e.
1762
+ // "clearance-variants".romanization — written by pipeline.mjs recordStageContract at DISPATCH), i.e.
1762
1763
  // evidence the artifact was MINTED under the romanisation-carriage prompt. It must never be the
1763
1764
  // instructed-scope sentinel: that sentinel is written at INTAKE and present on EVERY current-era
1764
1765
  // run — archived and parked ones included — so gating the new requirement on it (the first cut of
@@ -1787,7 +1788,7 @@ export const validators = {
1787
1788
  let contract = null;
1788
1789
  const marker = driverDir(dir, "stage-contracts.json");
1789
1790
  if (existsSync(marker)) {
1790
- try { contract = JSON.parse(readFileSync(marker, "utf8"))?.["prelim-variants"] ?? null; }
1791
+ try { contract = variantsStageContract(JSON.parse(readFileSync(marker, "utf8"))); } // either name: see variantsStageContract
1791
1792
  catch { return fail("stagecontracts_invalid"); }
1792
1793
  }
1793
1794
  if (!contract?.romanization && !contract?.completeness && !contract?.term_shape) return sib;
@@ -2004,8 +2005,8 @@ export const validators = {
2004
2005
  const base = all(nonEmpty(c), needsSection(c, "placement-tiers", [/tier|placement|sheet|level/i], "placement tiers"));
2005
2006
  if (!base.ok) return base;
2006
2007
  const dir = dirname(String(p ?? ""));
2007
- if (existsSync(join(dir, "placements.json")))
2008
- return checkSiblingJson(p, "placements.json", parsePlacementsJson, "placement-model", "placementmodel_missing");
2008
+ const sib = existsSync(join(dir, "placements.json")) ? checkSiblingJson(p, "placements.json", parsePlacementsJson, "placement-model", "placementmodel_missing") : null;
2009
+ if (sib) return sib.ok && placementAccountArmed(dir) ? placementAccountVerdict(dir) : sib; // placementAccount: see the block at the end of this file
2009
2010
  let structured = false;
2010
2011
  let formEra = false;
2011
2012
  const marker = driverDir(dir, "stage-contracts.json");
@@ -2031,7 +2032,7 @@ export const validators = {
2031
2032
  // What replaces the floor is stronger, not weaker: the tiers live in a driver-held accumulator that a
2032
2033
  // kill cannot reach, and the rendered file is parse-then-land through this same strict parser before
2033
2034
  // it is allowed on disk. Archived runs carry no `placementForm` key and keep the old floor exactly.
2034
- if (formEra) return base;
2035
+ if (formEra) return placementAccountArmed(dir) ? placementAccountVerdict(dir) : base;
2035
2036
  return structured ? fail("placementmodel_missing") : base;
2036
2037
  },
2037
2038
  // The opus digest legitimately splits the section ("## On-field findings" / "## Off-field findings"), so the
@@ -2087,6 +2088,32 @@ export const validators = {
2087
2088
  ], "findings+ledger"),
2088
2089
  hasCoverageLedgerRow(c) ? ok() : fail("no_coverage_status_row"));
2089
2090
  if (!structural.ok) return structural;
2091
+ // ── EVERY RECORD THE RUN CARRIED IN ENDS SOMEWHERE — CHECKED AT THE EXIT, NOT ONLY AT THE CALL ──
2092
+ //
2093
+ // The call-time refusal is scoped to the batch it judges, which is what lets a dense band be
2094
+ // recorded at all: a 1,161-record band does not fit in one turn, and the stage failed on one for 35
2095
+ // minutes without writing a document. It buys that at a price, and this is where the price is paid.
2096
+ // Once batch 1 is accepted the findings document EXISTS, so a seat that stopped after batch 6 no
2097
+ // longer fails as a missing artifact — it ships a document holding half the band, with every call it
2098
+ // made reading as accepted. Nothing else would notice: this stage's other arms read the document's
2099
+ // shape, and half a band is the same shape as a whole one.
2100
+ //
2101
+ // ARMED BY THE SAME ERA STAMP as the call-time rule, so an archived run carries no stamp and replays
2102
+ // to the verdict it always had. A STAMPED run whose transport stored no model is a driver fault and
2103
+ // is named as one, on `coverage_form_missing`'s precedent below and for its reason: an absent
2104
+ // artifact must never read as a satisfied one. A throw fails closed for the same reason — this gate
2105
+ // going quiet is indistinguishable from a complete digest, which is the state it exists to refuse.
2106
+ {
2107
+ let gap;
2108
+ try { gap = digestAccountingGap(dirname(p)); }
2109
+ catch (e) { return fail(`registerdigest_accounting_unreadable:${short(String(e?.message ?? e))} (driver-written — this is a bug, not a model defect)`.slice(0, 200)); }
2110
+ if (gap.armed && gap.unaccounted === null)
2111
+ return fail("registerdigest_accounting_unreadable: stamped for per-record accounting with no owed list in the driver's facts (driver-written — this is a bug, not a model defect)");
2112
+ if (gap.armed && gap.no_model)
2113
+ return fail("registerdigest_model_missing: stamped for per-record accounting and the typed transport stored no model, while the findings document exists (driver-written — this is a bug, not a model defect)");
2114
+ if (gap.armed && gap.unaccounted.length)
2115
+ return fail(`registerdigest_unaccounted_records:${gap.unaccounted.length} of ${gap.owed.length} — ${gap.unaccounted.slice(0, 6).join(",")}${gap.unaccounted.length > 6 ? ` (+${gap.unaccounted.length - 6} more)` : ""}`.slice(0, 200));
2116
+ }
2090
2117
  // ── THE COVERAGE FORM, AND THE FOUR STATES THAT ARE NOT THE SAME FACT ──────────────────────────
2091
2118
  // not required — no era stamp: EVERY ARCHIVED RUN, and nothing else since M6. A run
2092
2119
  // whose plan apparatus is out of reach used to land here too; it now gets a
@@ -2591,3 +2618,64 @@ function checkClientSummaryJoin(p, c) {
2591
2618
  return ok("client-summary-join");
2592
2619
  }
2593
2620
  const short = (v) => String(v ?? "").replace(/\s+/g, " ").trim().slice(0, 40);
2621
+
2622
+ // ── A PLACEMENT PASS THAT DID NOT RECORD ITS ANSWERS HAS NOT SUCCEEDED ──────────────────────────────────
2623
+ //
2624
+ // THE DEFECT, measured 2026-09-18. A pass tiered 141 in-class identical and near-identical register records;
2625
+ // every tier and reason sat in the driver's form; not one reached placements.json, because every register
2626
+ // row was refused on an owner the fold never carried. The render dropped them as it is written to — an
2627
+ // unsettled row is OMITTED rather than emitted half-formed — and this validator, which only parsed the file
2628
+ // that was left, passed six common-law rows as a completed pass. The client received a report silent on
2629
+ // the register over eighty-one identical live registrations.
2630
+ //
2631
+ // TWO REFUSALS, and the line between them was measured rather than chosen. Replayed over every archived
2632
+ // form-era run (25): three carry the discard-all and would refuse; twenty-two healthy runs would not. A
2633
+ // stricter "any outstanding row refuses" was measured too and it refuses five of those twenty-two — each
2634
+ // over a handful of single records at offices that publish no owner, a real and ordinary register fact
2635
+ // that no corrective turn can supply. Those are counted in the run record, not refused.
2636
+ //
2637
+ // placement_unjudged=N a row the SEAT owes is not finished — the corrective turn can finish it.
2638
+ // placement_register_unrendered register candidates were selected and NONE renders — a lost register.
2639
+ //
2640
+ // ARMED ON ITS OWN STAMP KEY (`placementAccount: 1`), written at dispatch like every other key in the
2641
+ // marker, so an archived run replays to exactly the verdict it was minted under. A marker that will not
2642
+ // parse leaves this arm OFF here rather than failing: both call sites already had an answer for that case
2643
+ // before this arm existed, and this must not change it.
2644
+ function placementAccountArmed(dir) {
2645
+ try {
2646
+ return JSON.parse(readFileSync(driverDir(dir, "stage-contracts.json"), "utf8"))?.["placement-inquiry"]?.placementAccount === 1;
2647
+ } catch { return false; }
2648
+ }
2649
+ function placementAccountVerdict(dir) {
2650
+ const sidecar = driverDir(dir, placementFormSidecarName());
2651
+ let raw = null;
2652
+ try { raw = readFileSync(sidecar, "utf8"); } catch { raw = null; }
2653
+ // The form is written BEFORE the seat is dispatched and re-unioned before every judgement, so under this
2654
+ // stamp its absence is the recording apparatus failing — the class this arm exists for — never "no rows".
2655
+ if (raw === null) return fail("placement_form_unreadable (the driver's placement form is absent — nothing this pass placed can be accounted for)");
2656
+ const { rows, error } = parsePlacementForm(raw);
2657
+ if (rows === null) return fail(`placement_form_unreadable (the driver's placement form ${error})`);
2658
+ const a = placementRenderAccount(rows);
2659
+ if (a.unjudged.length) {
2660
+ const named = a.unjudged.slice(0, 8).map((u) => `${u.row_id ?? u.select ?? "?"} (${u.cause})`).join(", ");
2661
+ return fail(`placement_unjudged=${a.unjudged.length}: ${named}${a.unjudged.length > 8 ? ", …" : ""} — each of these rows is `
2662
+ + "in placement-form.json and its own judgement will not render: a selected row owes a `tier` from the closed set and a `reason` "
2663
+ + "stating the ground; a row you wrote in full owes mark, owner and jurisdiction too. Finish these rows in the form; "
2664
+ + "every other row stands.");
2665
+ }
2666
+ if (a.register_selected > 0 && a.register_rendered === 0) {
2667
+ const causes = Object.entries(a.register_facts).map(([t, n]) => `${t} ×${n}`).join(", ") || "no cause recorded";
2668
+ return fail(`placement_register_unrendered=${a.register_selected}: every register candidate this pass selected and tiered `
2669
+ + `is refused on a fact the driver copies from the register's own records (${causes}) — not one reaches placements.json. `
2670
+ + "This is not a judgement the seat can repair: those fields are machine-copied and anything typed into them is ignored.");
2671
+ }
2672
+ return ok();
2673
+ }
2674
+
2675
+ // THE VARIANTS STAGE WAS RENAMED, AND A RUN'S MARKER IS KEYED BY STAGE NAME. A run dispatched on an earlier
2676
+ // build stamped its manifest floors under `prelim-variants`; read under the new name alone, a resumed run
2677
+ // finds no stamp and skips the romanisation, completeness and term-shape floors it was minted under. The new
2678
+ // key wins where both exist, because the stage writes it on every fresh dispatch. PURE.
2679
+ export function variantsStageContract(marker) {
2680
+ return marker?.["clearance-variants"] ?? marker?.["prelim-variants"] ?? null;
2681
+ }
@@ -10,7 +10,7 @@
10
10
  //
11
11
  // ── WHY IT IS NOT IN THE CLEARANCE QUEUE ─────────────────────────────────────────────────────────────
12
12
  //
13
- // studio/prelim-search/queue holds CLEARANCE jobs, and the runner treats every file in it as one: it
13
+ // studio/clearance-search/queue holds CLEARANCE jobs, and the runner treats every file in it as one: it
14
14
  // runs validateJob's whole field allowlist, resolves a product and a scope through resolveRequest,
15
15
  // checks the matter ledger for duplicates, counts the daily allowance, and takes a slot from the global
16
16
  // run-lock pool. A what-if is none of those things — it re-runs ONE stage of a run that already exists,