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
@@ -1,6 +1,6 @@
1
1
  // SPDX-License-Identifier: AGPL-3.0-only
2
2
  // Copyright 2026 Cordillera Sàrl. Additional terms under section 7 of the AGPL-3.0 apply — see ADDITIONAL-TERMS.md
3
- // pipeline.mjs — the deterministic driver: sequences every prelim-search stage as a blocking engine
3
+ // pipeline.mjs — the deterministic driver: sequences every clearance-search stage as a blocking engine
4
4
  // turn, joins the fan-out in code (the fan-in barrier), gates on the refutation
5
5
  // verdict, and never parks (there is no LLM continuation decision anywhere). CLI: `node pipeline.mjs --job <file.json>`.
6
6
 
@@ -10,7 +10,7 @@ import { readFileSync, existsSync, mkdirSync, writeFileSync, renameSync, copyFil
10
10
  import { createHash } from "node:crypto";
11
11
  import { join, dirname, basename, resolve } from "node:path"; // resolve: the resume line must work from any cwd
12
12
  import { driverDir, driverRel, ensureDriverDir } from "../shared/driver-dir.mjs"; // — one definition of where `_driver/` is
13
- import { terminalClampDecision, orderClausesForLede, clientConditions } from "./terminal-clamp.mjs"; // — deliver and clamp, never withhold
13
+ import { terminalClampDecision, orderClausesForLede, clientConditions, clauseForDefect } from "./terminal-clamp.mjs"; // — deliver and clamp, never withhold
14
14
  import { recordSpan } from "./attributed-span.mjs"; // — driver work the decomposition can attribute
15
15
  import { fileURLToPath } from "node:url";
16
16
  import { runStage, correctionHint, gridLedgerNameFor, draftCarryEligible, toolWrittenArtifact, selectEngine } from "./gateway.mjs";
@@ -31,7 +31,7 @@ import { buildRunContext, deriveSlug, kebab } from "./phase0.mjs";
31
31
  import { paths, STAGES, axisTier, decideAxes, assertTierSanity, assertEffectiveTier, lines, AGENT_WHATSAPP, whatsappRouting,
32
32
  chainEntries, stageOrdinal, stageInputs, stageOutputs, dependencyOrder, REGISTER_AXES, REGISTER_ENUMERATE_TOOL,
33
33
  buildEscalationFollowup, buildEnvelopeCloseFollowup, buildFrameReopenFollowup,
34
- buildFrameReopenRetryMessage, thinkingFor, composeFollowup, stampDispatchBlocks, recordEmptyReturn, nothingFound, nothingToRead, PROVIDER_META, proseRungDirective, inquiryRungDirective } from "./stages.mjs"; import { bandSizeForStage } from "./band-size.mjs";
34
+ buildFrameReopenRetryMessage, thinkingFor, composeFollowup, stampDispatchBlocks, recordEmptyReturn, nothingFound, nothingToRead, PROVIDER_META, proseRungDirective, inquiryRungDirective } from "./stages.mjs"; import { bandSizeForStage, derivedLimitSec, limitExceedsCeiling, ceilingRefusal } from "./band-size.mjs";
35
35
  import { IDENTITY_FILE as REPORT_IDENTITY_FILE } from "./report-overview-record.mjs";
36
36
  import { dispatchRows, clearedSignatures } from "./seat-attempts.mjs";
37
37
  import { CONTEXT_DERIVATIONS, DISPATCH_EXTRAS, INLINE_CONTEXT, sandboxManifest, sandboxGaps, derivationsFor } from "./stage-context.mjs"; // — what a stage is actually handed
@@ -45,7 +45,8 @@ import { readRegisterTaint, readActiveTaintAxes } from "./register-taint.mjs";
45
45
  import { parseNamedBand, mergeNamedBands, findCollapsedBands, quarantineUnknownStates, taintQuarantineCleanBlocks, bandRecords } from "./named-band.mjs";
46
46
  import { recordOriginsFor } from "./record-origins.mjs";
47
47
  import { REGISTER_PROVIDER } from "./driver.config.mjs";
48
- import { FACTS_FILE as DIGEST_FACTS_FILE, ACCOUNTING_STAMP as DIGEST_ACCOUNTING_STAMP, recordedFindingUris } from "./register-digest-record.mjs"; // conversion 11 — the render's facts sidecar and the accounting era stamp
48
+ import { FACTS_FILE as DIGEST_FACTS_FILE, ACCOUNTING_STAMP as DIGEST_ACCOUNTING_STAMP, recordedFindingUris,
49
+ digestAccountingGap, digestBatchBrief, batchesOf } from "./register-digest-record.mjs"; // conversion 11 — the render's facts sidecar and the accounting era stamp
49
50
  import { buildBandShape, dominantElementComposites, deriveRegisterPositions, floorTierByMark, floorMarkKey } from "./band-shape.mjs"; // PR-8 — the deterministic reading layer; P2-A — candidates + positions
50
51
  import { deriveOwnerScreen, ownerScreenNegative } from "./owner-screen.mjs"; // P2-B — the owner×element screen's own receipt
51
52
  import { reconcileRecall, parseFindingsEndings, parseCrowdRulings, readOkRecordUris,
@@ -79,7 +80,7 @@ import { resolveSearchPolicy, gateResolvedPolicy, loadRecipes, policyFor, isRegi
79
80
  import { profileOrdinals } from "./profile-selection.mjs"; // lever 3 — driver selection
80
81
  // THE OFFERING'S own sentence about where the native-language investigation can be bought. It reaches a
81
82
  // CLIENT REPORT through the script-scope coverage row, which is why it is imported rather than typed:
82
- // the row used to name "Depth 5 (prelim-jx)" — a retired key on a retired ladder — as its remedy.
83
+ // the row used to name "Depth 5 (clearance-jx)" — a retired key on a retired ladder — as its remedy.
83
84
  import { NATIVE_LANGUAGE_REMEDY } from "./products.mjs";
84
85
  import { resolveTerritories, defaultTerritoryState } from "./effective-scope.mjs"; // the ONE territory ladder (the geography stamp included) + the stored-defaults reading
85
86
  import { acquireSlot, releaseSlot } from "./slot-lock.mjs";
@@ -96,8 +97,10 @@ import { publishReport, composeEmailHtml, deliverySubject } from "./publish/inde
96
97
  import { parseCaseLawProfiles, joinCaseLawProfiles } from "./publish/parse.mjs";
97
98
  import { buildAuditMd, parseSpineFindingBlocks } from "./publish/audit-from-spine.mjs";
98
99
  import { deriveRegisterPresence } from "./publish/register-presence.mjs"; // — the audit stores every live in-scope record
99
- import { lastAcceptedMatterFrame, frameIdentifiedClasses } from "./matter-frame-record.mjs"; // — the frame's inferred scope, when nothing was instructed; and the classes it judged necessary beyond the instructed ones, which the plan compile unions in
100
- import { romanizedTermsFromPlan, mintSupplementalQid } from "./register-plan.mjs"; // — the stamp the late lanes never met
100
+ import { lastAcceptedMatterFrame, frameIdentifiedClasses, frameHouseElementCandidate } from "./matter-frame-record.mjs"; // — the frame's inferred scope, when nothing was instructed; and the classes it judged necessary beyond the instructed ones, which the plan compile unions in
101
+ import { romanizedTermsFromPlan, mintSupplementalQid } from "./register-plan.mjs";
102
+ import { excludeHouseElement, verifyHouseElementOwnership, resolveRegions as resolvePlanRegions, HOUSE_ELEMENT_RECEIPT } from "./register-plan.mjs"; // 647 — the client's own element leaves the conflict analysis only on a verified receipt
103
+ import { resolveRecordExecutor } from "./register-records.mjs"; // — the stamp the late lanes never met
101
104
  import { slimLine, crowdLine } from "./hit-list.mjs"; // — the list the run works from; crowds ride it as a sibling array
102
105
  import { mintCrossCheckDoubts, mintContradictionDoubts, stitchDoubts, applyClosure } from "./doubt-ledger.mjs"; // doubt-stitch + doubt-closure (2026-07-22)
103
106
  // Conversion 6: the two line-form parsers are no longer on the live path — the seat sends typed
@@ -167,10 +170,10 @@ import { JURISDICTION_ADAPTERS, LANGUAGE_LANES, scopeJurisdictions as jxScopeJur
167
170
  // into boundDominantElementFrom, so the driver's bind and the record tool's bind are ONE body. Neither
168
171
  // parseBlindFrameModel nor dominantElementFromManifest is called from this file any more.
169
172
  import { boundDominantElementFrom } from "./frame-diff-record.mjs";
170
- import { recordedScopeLedgerRows } from "./prelim-variants-record.mjs";
173
+ import { recordedScopeLedgerRows } from "./clearance-variants-record.mjs";
171
174
  import { parseFrameDiff, applyDominantBackstop, firingDirectives, reopenKey, alreadyAttemptedReopen, partitionFiring, frameResidualGaps, jurisdictionScopeFlags, deriveDirectiveRemedy, firingDirectivesLenient } from "./frame-diff-model.mjs";
172
175
  import { verifyRegisterDirectiveClose } from "./close-verify.mjs";
173
- import { renderFormNeighbourhoodJson, parseFormNeighbourhoodJson, dispatchedQueriesFromBand, formGapDirectives, markText } from "./form-neighbourhood.mjs";
176
+ import { renderFormNeighbourhoodJson, parseFormNeighbourhoodJson, dispatchedQueriesFromBand, formGapDirectives, markText } from "./form-neighbourhood.mjs"; import { loadOrdinaryWords } from "./ordinary-words.mjs";
174
177
  import { findRecallFloorViolations, findReviewFreshnessViolation, findSeedNeutralityViolations, findProbativeGradingViolations, findStatusHonestyViolation, findMatrixCeilingViolations, findDeadlineUrgencyMiss, findUnresolvedDisagreements, findOrphanVerificationFlags, findUncrossCheckedDemotions, findRecallRegressionViolations, findDeadlineCarryViolations, formatRecallRegression } from "./reasoning-tripwires.mjs";
175
178
  import { findRuleShapeFlags } from "./rule-shape.mjs";
176
179
  import { failureSignature, classifyFailureReason, decideRecovery, createRepairLedger, countTrailingStageStrikes, countRecoveryLanes, weatherCeilingFor, TRANSIENT_RE, REFUSAL_TERMINAL_KIND, fanInMissingEvidence, retryCannotHelpWith, unnamedStructuredFailure, classificationSource, isCapPark, capParkSchedule, capWaitFrom, humanWait } from "./repairs.mjs";
@@ -936,7 +939,7 @@ function deriveGridSpec(ctx) {
936
939
  // 2026-08-11 codex R2 run (no grid spec authored at all), and it is the branch that emits NO event
937
940
  // today, which is why absence was the only signal and absence is not a record.
938
941
  // The three inputs are recorded, not just their verdict. Working hit exactly this wall: the
939
- // 2026-08-11 run authored no spec, `prelim-variants` had succeeded, and WHICH of the three conjuncts
942
+ // 2026-08-11 run authored no spec, `clearance-variants` had succeeded, and WHICH of the three conjuncts
940
943
  // was false could not be recovered from any artifact — so the round could name the path but not the
941
944
  // cause. A record that says "no spec" and not "no spec BECAUSE the profile carried no platforms" just
942
945
  // moves the same unanswerable question one step along.
@@ -1717,7 +1720,7 @@ export function attachSearchPolicy(ctx, job, { write = true } = {}) { // @inte
1717
1720
  // a clearance (review 2026-07-17 — the crash-reclaim/reconstruct window).
1718
1721
  if (!noSelector)
1719
1722
  throw new Error(`run dir has no frozen search policy but the job names a selector (product=${JSON.stringify(job?.product ?? null)}, recipeKey=${JSON.stringify(job?.recipeKey ?? null)}) — refusing the legacy-implicit clearotron assumption; re-dispatch cold so the policy is minted`);
1720
- ctx.searchPolicy = { schema: 1, level: "prelim", pipeline: "clearance", stageLabel: "Depth 4", components: {}, recipe: null, origins: { level: "legacy-implicit" } };
1723
+ ctx.searchPolicy = { schema: 1, level: "clearance", pipeline: "clearance", stageLabel: "Depth 4", components: {}, recipe: null, origins: { level: "legacy-implicit" } };
1721
1724
  } else {
1722
1725
  let resolved;
1723
1726
  // force:true — the recipe store is written by a LIVE service; a long-lived driver process must see a
@@ -1902,7 +1905,7 @@ function renderDocumentCoverageFromRecords(ctx, trigger) {
1902
1905
  // The set the GATES consume, not `_records/` alone: a run whose bodies live only in the ledger would
1903
1906
  // otherwise be described as one that fetched nothing — the defect this renders against, re-created by
1904
1907
  // reading a narrower artifact than the one that holds the answer.
1905
- const { records } = assembleRunRecords(P.runDir, `prelim-${ctx.run.slug}-${ctx.run.codename}-`);
1908
+ const { records } = assembleRunRecords(P.runDir, `clearance-${ctx.run.slug}-${ctx.run.codename}-`);
1906
1909
  const summary = documentCoverage({ records, findings: safeReadJson(P.findings) });
1907
1910
  const section = renderDocumentCoverageSection(summary);
1908
1911
  if (!section) return;
@@ -2100,7 +2103,7 @@ function deriveCoverageLedgerJson(ctx, trigger) {
2100
2103
  }
2101
2104
  }
2102
2105
 
2103
- // Frame-omission design — CODE-DERIVE scope-ledger.json from the validated prelim-variants prose
2106
+ // Frame-omission design — CODE-DERIVE scope-ledger.json from the validated clearance-variants prose
2104
2107
  // `### Scope ledger` table (so the JSON is authored by the driver, not the model, and matches the prose
2105
2108
  // by construction — exactly the deriveCoverageLedgerJson pattern). NEVER-KILL: a manifest with no/unparseable
2106
2109
  // Scope ledger (legacy artifacts, a terse run) logs a note and skips the write; the frame-diff then reads the
@@ -2129,7 +2132,7 @@ function deriveScopeLedgerJson(ctx) {
2129
2132
  // are different facts about a run, and a reader of an archived ledger must be able to tell which.
2130
2133
  runLog(P.runDir, { event: "scope-ledger-derived", source: recordedRows ? "typed-call" : "prose-parse" });
2131
2134
  } catch (e) {
2132
- note(`prelim-variants: scope-ledger derivation skipped (${String(e.message).slice(0, 100)}) — frame-diff reads the manifest prose`);
2135
+ note(`clearance-variants: scope-ledger derivation skipped (${String(e.message).slice(0, 100)}) — frame-diff reads the manifest prose`);
2133
2136
  runLog(P.runDir, { event: "scope-ledger-skipped", reason: `derive_failed:${String(e.message).slice(0, 80)}` });
2134
2137
  }
2135
2138
  }
@@ -2176,14 +2179,14 @@ function deriveFormNeighbourhood(ctx) {
2176
2179
  // The MARK is the floor's fallback seed and the only input to it that is not model output —
2177
2180
  // resolved exactly as every other job-mark consumer resolves it (stages.mjs blind-frame).
2178
2181
  const mark = ctx.job.marks ?? ctx.job.markName ?? ctx.job.name ?? "";
2179
- const json = renderFormNeighbourhoodJson(manifestMd, { markets, droppedAxes, model, mark });
2182
+ const ow = loadOrdinaryWords("en"), json = renderFormNeighbourhoodJson(manifestMd, { markets, droppedAxes, model, mark, ordinaryWords: ow.words }); // an unloadable word list removes nothing
2180
2183
  const tmp = P.formNeighbourhood + ".tmp";
2181
2184
  writeFileSync(tmp, json);
2182
2185
  renameSync(tmp, P.formNeighbourhood);
2183
- let exact = 0, floor = 0, added = 0, seededFrom = "", seeds = [];
2186
+ let exact = 0, floor = 0, added = 0, seededFrom = "", seeds = [], notSearched = 0;
2184
2187
  try {
2185
2188
  const o = JSON.parse(json);
2186
- exact = o.elements.reduce((a, e) => a + (e.band?.exactQueries?.length ?? 0), 0);
2189
+ exact = o.elements.reduce((a, e) => a + (e.band?.exactQueries?.length ?? 0), 0); notSearched = o.elements.reduce((a, e) => a + (e.band?.ordinaryWordDifferentSound?.length ?? 0), 0);
2187
2190
  floor = o.variant_floor?.counts?.floor ?? 0;
2188
2191
  added = o.variant_floor?.counts?.model_added ?? 0;
2189
2192
  seededFrom = String(o.seeded_from ?? "");
@@ -2191,11 +2194,11 @@ function deriveFormNeighbourhood(ctx) {
2191
2194
  } catch { /* counts are telemetry only */ }
2192
2195
  // The SEED SET is logged by name, because it is the input that decides whether two runs of the same
2193
2196
  // matter can produce the same floor — a floor count alone cannot tell a stable run from a re-anchored one.
2194
- runLog(P.runDir, { event: "form-neighbourhood-derived", exact, floor, modelAdded: added, droppedAxes, seeds, seededFrom });
2197
+ runLog(P.runDir, { event: "form-neighbourhood-derived", exact, floor, modelAdded: added, droppedAxes, seeds, seededFrom, ordinaryWordNotSearched: notSearched, ...(ow.error ? { ordinaryWordListError: ow.error } : {}) });
2195
2198
  // The fallback is not a quiet degrade: it means this run's variant floor rests on the mark alone
2196
2199
  // because the stage named no usable element. Loud, so the absence is a finding and not a shrug.
2197
2200
  if (seededFrom.startsWith("job mark"))
2198
- note(`prelim-variants: variant floor seeded from the JOB MARK — the manifest named no usable distinctive element (${floor} floor terms generated; the floor is complete, its seed is not the model's)`);
2201
+ note(`clearance-variants: variant floor seeded from the JOB MARK — the manifest named no usable distinctive element (${floor} floor terms generated; the floor is complete, its seed is not the model's)`);
2199
2202
  } catch (e) {
2200
2203
  // The reason is carried WHOLE. It used to be truncated to 80 characters into the runLog and
2201
2204
  // 100 into the note, which cut the part that says WHICH cause fired — and the empty floor this path
@@ -2203,7 +2206,7 @@ function deriveFormNeighbourhood(ctx) {
2203
2206
  // compiles and freezes with no floor entries and no error, looking healthy. This line is the only
2204
2207
  // record that the mechanical floor is missing from the run, so it says the whole of why.
2205
2208
  const why = String(e.message);
2206
- note(`prelim-variants: form-neighbourhood derivation skipped (${why}) — register falls back to the manifest variants, so this run has NO mechanical form floor`);
2209
+ note(`clearance-variants: form-neighbourhood derivation skipped (${why}) — register falls back to the manifest variants, so this run has NO mechanical form floor`);
2207
2210
  runLog(P.runDir, { event: "form-neighbourhood-skipped", reason: `derive_failed:${why}`, mark: markText(ctx.job.marks ?? ctx.job.markName ?? ctx.job.name ?? "") });
2208
2211
  }
2209
2212
  }
@@ -2258,6 +2261,69 @@ function logFullyDeferredAxes(plan, P, source) {
2258
2261
  // `frozenOnly` (item): read the FROZEN plan or nothing. reconstructCtx is an introspection path —
2259
2262
  // it must never compile and freeze a plan into an existing run as a side effect of being asked what the
2260
2263
  // run already holds. (attachProfile / attachFramework take the same posture via `write:false`.)
2264
+ /**
2265
+ * Ask the register whether the client owns the element the frame proposed, and record the answer.
2266
+ *
2267
+ * ASYNC AND SEPARATE FROM THE COMPILE, which is why it is its own step rather than a branch inside
2268
+ * `attachRegisterPlan`: that function is synchronous and is called on the resume path too. The shape is
2269
+ * the digest's — an async driver step writes a receipt, a synchronous consumer reads it — and it buys
2270
+ * the property that matters here: the compile cannot accidentally exclude anything by reaching for a
2271
+ * promise it does not await.
2272
+ *
2273
+ * NOT A REGISTERED CONTEXT DERIVATION, and the reason is worth stating because the obvious reading is
2274
+ * that it should be. `stage-context.mjs` declares the derivations a sandbox can REPLAY, and its runners
2275
+ * are synchronous. This one asks a live register a question; there is no offline re-derivation of that,
2276
+ * and declaring it would promise the `--experiment` rig something no runner can deliver — which the
2277
+ * guard catches by name, loudly, as it should. A sandbox instead inherits the receipt with the rest of
2278
+ * `_driver/`, and a sandbox that has none excludes nothing, which is the same answer every other
2279
+ * unverified run gets.
2280
+ *
2281
+ * NEVER-KILL. Every failure writes an unverified receipt and the run plans exactly as it does today.
2282
+ * The element being searched in full is the safe direction and the status quo; the only thing that can
2283
+ * narrow a client's search is a register answer naming their own live registration.
2284
+ */
2285
+ async function verifyAndRecordHouseElement(ctx, opts = {}) {
2286
+ const P = ctx.paths;
2287
+ try {
2288
+ const proposed = frameHouseElementCandidate(P.runDir);
2289
+ if (!proposed) return; // the ordinary case: the frame proposed nothing and there is no question
2290
+
2291
+ const classes = [...new Set([...inScopeClassList(ctx.job, ctx.profile).map(String), ...frameIdentifiedClasses(P.runDir)])];
2292
+ // THE CLIENT'S OWN NAMES. The profile's trading names are the curated list; the matter's customer is
2293
+ // the free-text one. Both, because a profile may be absent on a one-off matter and a customer string
2294
+ // may be a person where the filings are held by a company.
2295
+ const owners = [...new Set([...(ctx.profile?.selfExclusionOwners ?? []), ctx.job?.customer].map((o) => String(o ?? "").trim()).filter(Boolean))];
2296
+ const caps = registerCapabilities();
2297
+ const { regions } = resolvePlanRegions(registerJurisdictions(ctx.job, ctx.profile), caps);
2298
+ const rec = resolveRecordExecutor({
2299
+ lister: opts?.recordLister ?? null, adapter: activeProvider(),
2300
+ agentId: ctx.agentId ?? null, sessionKey: `clearance-${ctx.run.slug}-${ctx.run.codename}`,
2301
+ recordLog: runRecordLogPath(P.runDir),
2302
+ fixtureDir: ctx.job?.registerFixtures?.records ?? null,
2303
+ });
2304
+ // AN EXACT-NAME LISTING OF THE ELEMENT, filtered to the client's own live in-class registrations by
2305
+ // the verifier. The owner join is done on REAL RECORDS the register returned, which is what
2306
+ // "verified by owner, not asserted" means — there is no owner-predicate listing on this interface
2307
+ // and inventing one would be a second way to ask the same question.
2308
+ const lookup = typeof rec.list !== "function" ? null : async ({ element, classes: cls }) => {
2309
+ const r = await rec.list(element, { classes: cls, regions, limit: 50 });
2310
+ if (!r || r.ok === false) return { ok: false, reason: String(r?.reason ?? "the register listing did not answer") };
2311
+ return { ok: true, records: Array.isArray(r.records) ? r.records : [] };
2312
+ };
2313
+
2314
+ const receipt = await verifyHouseElementOwnership({ element: proposed.element, classes, owners, lookup });
2315
+ receipt.remainder = proposed.remainder; // carried so the compile needs only this one file
2316
+ ensureDriverDir(P.runDir);
2317
+ atomicWrite(driverDir(P.runDir, HOUSE_ELEMENT_RECEIPT), JSON.stringify(receipt, null, 2) + "\n");
2318
+ runLog(P.runDir, { event: "house-element-ownership", verified: receipt.verified, reason: receipt.reason,
2319
+ records: receipt.records.length, owners: owners.length, executor: rec.source });
2320
+ if (!receipt.verified) note(`house element: ${proposed.element} stays in the search — ${receipt.reason}`);
2321
+ } catch (e) {
2322
+ // A THROW HERE MUST NOT COST A REPORT. No receipt means no exclusion, which is today's plan.
2323
+ runLog(P.runDir, { event: "house-element-ownership-failed", reason: String(e?.message ?? e).slice(0, 160) });
2324
+ }
2325
+ }
2326
+
2261
2327
  function attachRegisterPlan(ctx, { frozenOnly = false } = {}) {
2262
2328
  const P = ctx.paths;
2263
2329
  if (frozenOnly) {
@@ -2281,7 +2347,30 @@ function attachRegisterPlan(ctx, { frozenOnly = false } = {}) {
2281
2347
  ctx.registerPlan = null;
2282
2348
  let compiled;
2283
2349
  try {
2284
- const manifest = parseVariantManifestModel(readFileSync(P.variantManifestModel, "utf8"));
2350
+ let manifest = parseVariantManifestModel(readFileSync(P.variantManifestModel, "utf8"));
2351
+ // ── 647 — THE CLIENT'S OWN ELEMENT LEAVES THE CONFLICT ANALYSIS, ON EVIDENCE OR NOT AT ALL ─────
2352
+ //
2353
+ // The receipt is the ONLY thing that arms this. An absent, unreadable or unverified receipt plans
2354
+ // exactly as this compile did before the rule existed, which is the element searched in full — the
2355
+ // safe direction, and the one a client is never harmed by.
2356
+ let houseConfirmation = null;
2357
+ let houseReceipt = null;
2358
+ try { houseReceipt = JSON.parse(readFileSync(driverDir(P.runDir, HOUSE_ELEMENT_RECEIPT), "utf8")); }
2359
+ catch { houseReceipt = null; }
2360
+ if (houseReceipt?.verified === true) {
2361
+ const r = excludeHouseElement(manifest, { element: houseReceipt.element, remainder: houseReceipt.remainder });
2362
+ if (r.refused) {
2363
+ // VERIFIED OWNERSHIP AND STILL NOT APPLIED. The receipt answers who owns the element; the
2364
+ // transform answers whether this manifest's mark can survive the cut. Both must hold.
2365
+ runLog(P.runDir, { event: "house-element-not-applied", reason: r.refused, element: houseReceipt.element });
2366
+ } else {
2367
+ manifest = r.manifest;
2368
+ houseConfirmation = r.confirmation;
2369
+ runLog(P.runDir, { event: "house-element-excluded", element: houseReceipt.element,
2370
+ remainder: houseReceipt.remainder, dominant_element: manifest.dominant_element,
2371
+ evidence: (houseReceipt.records ?? []).length });
2372
+ }
2373
+ }
2285
2374
  let form = null;
2286
2375
  try { form = JSON.parse(readFileSync(P.formNeighbourhood, "utf8")); } catch { /* form band optional */ }
2287
2376
  compiled = compileRegisterPlan({
@@ -2296,7 +2385,12 @@ function attachRegisterPlan(ctx, { frozenOnly = false } = {}) {
2296
2385
  // sweep); fatal on a provider whose regions[] is mandatory, where every entry then errored on its
2297
2386
  // count probe and the whole plan joined MISSING at fan-in (review finding 11).
2298
2387
  job: { jobKey: ctx.run.slug, classes: [...new Set([...inScopeClassList(ctx.job, ctx.profile).map(String), ...frameIdentifiedClasses(P.runDir)])], jurisdictions: registerJurisdictions(ctx.job, ctx.profile) },
2299
- form, skillVersion: "prelim-register@spec48",
2388
+ form, skillVersion: "clearance-register@spec48",
2389
+ // — WHICH ELEMENT THE EXCLUSION TOOK OUT, so the compile can make its form band unreachable
2390
+ // rather than merely unasked-for. Null unless the ownership receipt verified, which is the same
2391
+ // condition the exclusion itself runs under: an unverified receipt excludes nothing, and it must
2392
+ // not remove a band either.
2393
+ houseElement: houseConfirmation ? houseReceipt?.element : null,
2300
2394
  // phase 3 — the plan is compiled AGAINST THE ACTIVE PROVIDER's declared capabilities, so the
2301
2395
  // frozen artifact is executable by construction: the OR-stack split uses that provider's width,
2302
2396
  // jurisdictions are translated into its office vocabulary (EU→EM on Compumark), and a predicate
@@ -2308,6 +2402,19 @@ function attachRegisterPlan(ctx, { frozenOnly = false } = {}) {
2308
2402
  // rides the plan as a disclosed deferral, never as a silently narrower search.
2309
2403
  unavailableOffices: registerUnavailableOffices(),
2310
2404
  });
2405
+ // REQUIREMENT 3 — the element is still checked ONCE, as a confirmation of the client's own live
2406
+ // registrations rather than as a conflict sweep, so the exclusion is evidenced on the report by a
2407
+ // query that ran. Appended after the compile because it is not derived from the manifest: it is the
2408
+ // one question about the element the plan still owes.
2409
+ if (houseConfirmation && compiled?.entries) {
2410
+ const used = new Set(compiled.entries.map((e) => e.qid));
2411
+ compiled.entries.push({
2412
+ ...houseConfirmation,
2413
+ qid: mintSupplementalQid({ prefix: "house", term: houseConfirmation.term, used }),
2414
+ nice_classes: compiled.nice_classes ?? [],
2415
+ regions: compiled.regions ?? [],
2416
+ });
2417
+ }
2311
2418
  } catch (e) {
2312
2419
  // NEVER-KILL at mint time: no/malformed sibling or a class-less matter degrades to the legacy
2313
2420
  // path with a logged reason — the plan improves the run, it never turns delivery off.
@@ -3648,6 +3755,27 @@ export function digestDispatchExtra(ctx, { trigger = "fresh", willRun = true, ex
3648
3755
  }
3649
3756
  }
3650
3757
  } catch (e) { note(`coverage-form brief skipped (non-fatal — the form is in _driver/ and the validator refuses an unsettled row): ${String(e.message).slice(0, 80)}`); }
3758
+ // — THE BATCH BLOCK: how the driver split this run's band, and which batches are still outstanding.
3759
+ //
3760
+ // The seat is told the split HERE rather than in the stage's dictation because the numbers are the
3761
+ // run's, not the contract's: a band of 40 records is one batch and a band of 1,161 is twelve, and on a
3762
+ // resume the outstanding set is what the killed attempt did not reach. The dictation says the rule;
3763
+ // this says the arithmetic.
3764
+ //
3765
+ // BEST-EFFORT, AND SAFE BY CONSTRUCTION rather than by argument — the coverage brief's precedent above,
3766
+ // for its reason. Nothing here enforces anything: a call naming a batch is judged against the driver's
3767
+ // own split whether or not this block composed, the tool's refusal re-states what is outstanding, and
3768
+ // the stage's exit gate refuses a document that ends fewer records than the run carried in. A brief
3769
+ // that fails to compose costs a corrective round; it cannot cost a record.
3770
+ try {
3771
+ const gap = digestAccountingGap(P.runDir);
3772
+ const block = digestBatchBrief(gap);
3773
+ if (block) {
3774
+ out = out ? `${out}\n\n${block}` : block;
3775
+ runLog(P.runDir, { event: "digest-batch-brief", trigger, owed: gap.owed.length,
3776
+ batches: batchesOf(gap.owed).length, unaccounted: gap.unaccounted.length });
3777
+ }
3778
+ } catch (e) { note(`digest batch brief skipped (non-fatal — the tool judges every batch against the driver's own split regardless): ${String(e.message).slice(0, 80)}`); }
3651
3779
  // AD-2 A9 (E2E-R2) + the P5 review (2026-07-31): a corrective/repair pass is told NOT to re-read the
3652
3780
  // whole placement file — the per-candidate tiers are in placements.json — but the RULINGS TAIL (band
3653
3781
  // reconciliation, disagreements, coverage rulings, open questions) lives ONLY in the md. Leaving it to
@@ -3710,7 +3838,7 @@ export function digestDispatchExtra(ctx, { trigger = "fresh", willRun = true, ex
3710
3838
  // The old `findFloorBreaches` read ⭐-marked lines out of the manifest TEXT, tokenised them, dropped
3711
3839
  // generic words and matched the remainder against coverage-row text. That join is why it needed free text
3712
3840
  // at all, and it is what conversion 3 made unreachable. This one reads `search_floor` — axis names, closed
3713
- // against REGISTER_AXES, designated by prelim-variants in an earlier turn — and joins on `axis`, which
3841
+ // against REGISTER_AXES, designated by clearance-variants in an earlier turn — and joins on `axis`, which
3714
3842
  // both sides type. No tokens, no matching, nothing to go quietly wrong.
3715
3843
  //
3716
3844
  // A BREACH IS: an axis this mark's floor obliges, carrying a row labelled `coverage-limited`. That label
@@ -3736,7 +3864,7 @@ export function findFloorBreaches(ledger, floorAxes) { // @internal
3736
3864
  * a pass, because the whole mechanism is opt-in — a run with no designation owes no floor, and a run whose
3737
3865
  * manifest cannot be read has no designation to honour. The refusal for an absent or unparseable manifest
3738
3866
  * belongs to the stage that writes it and already exists there — verify.mjs:1335 runs the same parser
3739
- * through `checkSiblingJson` and fails prelim-variants with `variantmodel_missing`. Checked, because
3867
+ * through `checkSiblingJson` and fails clearance-variants with `variantmodel_missing`. Checked, because
3740
3868
  * "something else refuses it" is exactly the assumption that turns a swallowed error into a silent pass.
3741
3869
  */
3742
3870
  export function readFloorAxes(paths) { // @internal
@@ -3745,7 +3873,7 @@ export function readFloorAxes(paths) { // @internal
3745
3873
  }
3746
3874
 
3747
3875
  // A never-active axis self-writes a harmless "not applicable" digest — the skill MANDATES the exact
3748
- // scope wording (skills/prelim-register/unit.md:89-90: `query:"<axis> not applicable"`,
3876
+ // scope wording (skills/clearance-register/unit.md: `query:"<axis> not applicable"`,
3749
3877
  // `reason:"not applicable — <why>"`), so the coverage-ledger row lands as `<axis> / axis not applicable`.
3750
3878
  // It is `deferred` only because a non-search must not be dressed as a clean — it is NOT floor work left
3751
3879
  // open. Keyed on the self-digest scope (deterministic, skill-dictated), never a genuine floor's
@@ -3936,7 +4064,7 @@ export async function runSaturationProbeCodeSide(ctx, planExec) { // @internal
3936
4064
  const label = `register-unit:${a}`;
3937
4065
  const out = P.registerUnit(a);
3938
4066
  const bandPath = P.registerBand(a);
3939
- const sessionKey = `prelim-${ctx.run.slug}-${ctx.run.codename}-register-unit-${a}`;
4067
+ const sessionKey = `clearance-${ctx.run.slug}-${ctx.run.codename}-register-unit-${a}`;
3940
4068
  // crash-resume idempotency — mirrors stageOnce's skip: output present + valid ⇒ never re-execute.
3941
4069
  const forced = forceFromActive(ctx, "register-unit");
3942
4070
  // — hoisted above the skip return. Below it, this member's dispatch row carries boundaries and its
@@ -4056,7 +4184,7 @@ function writeSatProbeAuditNote({ ctx, out, bandPath, entries }) {
4056
4184
 
4057
4185
  // ── stage contracts (2026-07-30 review round) ──────────────────────────────────────────────────────
4058
4186
  // _driver/stage-contracts.json records, PER STAGE, the output contract the prompt THIS driver
4059
- // dispatched holds the artifact to (declared on the stage def, e.g. prelim-variants
4187
+ // dispatched holds the artifact to (declared on the stage def, e.g. clearance-variants
4060
4188
  // `contract:{romanization:1}`). Written at DISPATCH time only — a skipped stage never gains one — so
4061
4189
  // it is evidence of PROMPT VINTAGE, which is the only honest gate for a new validation rule:
4062
4190
  // - crash-resume: the skip check re-validates the completed output BEFORE any dispatch, marker
@@ -4220,7 +4348,7 @@ async function stageOnce(name, ctx, opts = {}) {
4220
4348
  // skips — i.e. on the babysit surface, in the middle of the shift where someone is watching it.
4221
4349
  runLog(P.runDir, { event: "skip", stage: label, trigger: "skip", model: won?.model ?? resolveModel(model), output: outputMeta(out), ...stageWallFields(tDispatch) });
4222
4350
  (ctx.skippedStages ??= []).push(label); // V4-1: the lint receipt states which artifacts this delivery inherited
4223
- recordTransition(ctx, name); // advance the displayed step on resume too (monotonic, no-op for unmapped)
4351
+ recordTransition(ctx, name); // advance the displayed step on resume too (monotonic; an unmapped stage records what it is, and moves no step)
4224
4352
  // `won ? won.key : …` (NOT `won?.key ?? …`): a recovered CODE-SIDE winner carries key:null on purpose —
4225
4353
  // falling back to opts.sessionKey there would re-mint the phantom session the null exists to suppress.
4226
4354
  return { ok: true, skipped: true, sessionKey: won ? won.key : opts.sessionKey, model: won?.model ?? model };
@@ -4231,6 +4359,25 @@ async function stageOnce(name, ctx, opts = {}) {
4231
4359
  // output under the rules it is being minted under. See recordStageContract's doc block.
4232
4360
  if (def.contract) recordStageContract(P.runDir, name, def.contract);
4233
4361
 
4362
+ // ── AND RECORD THAT THE RUN IS IN IT, AT THE SAME MOMENT ───────────────────────────────────────────
4363
+ //
4364
+ // The displayed step advanced only where a stage SUCCEEDED, so a run inside a stage that takes minutes
4365
+ // read as being in the previous one for the whole of it — a resumed run entered placement at 10:05Z and
4366
+ // still showed "Register sweeps" at 10:10Z, which reads as the run having gone backwards. Nothing
4367
+ // durable said a stage had STARTED: every stage record is written at completion, and the one start-time
4368
+ // signal goes to stderr and never reaches the trace. So no reader could derive it either.
4369
+ //
4370
+ // Here rather than at the dispatch below, because this is the line that already declares the stage is
4371
+ // dispatching, and every mutating path — corrective, followup, escalation, frame-reopen, stale-repair,
4372
+ // verdict-recheck — funnels through it. One declaration of entry, for the contract record and the
4373
+ // status alike.
4374
+ //
4375
+ // Safe against the two things that look like it should not be. `writeRunStatus` keeps the furthest step
4376
+ // ever reached, so an escalation re-run mapping to an earlier stage still cannot pull the stepper back;
4377
+ // and the bare `name` is passed exactly as the success path passes it, so a fan-out's axes go on
4378
+ // collapsing to one display step.
4379
+ recordTransition(ctx, name);
4380
+
4234
4381
  // — THE PLACEMENT FORM IS ON DISK BEFORE THE SEAT IS ASKED ANYTHING, and it is written at the SAME
4235
4382
  // choke point every dispatch funnels through: the main path, the stale-repair path and the frame-reopen
4236
4383
  // path all reach the seat through here, and three separate pre-dispatch writes would be three chances
@@ -4342,6 +4489,25 @@ async function stageOnce(name, ctx, opts = {}) {
4342
4489
  // Evaluated on `effThinking`, not `thinking`: the anthropic gate above nulls the tier for a
4343
4490
  // cross-provider model, and a guard that reads the nulled value would go blind on the same overrides.
4344
4491
  assertEffectiveTier(label, { model, thinking: effThinking });
4492
+ // ── THE TIME LIMIT IS DERIVED FROM THE BAND THIS DISPATCH IS HANDED ─────────────────────────────
4493
+ //
4494
+ // `def.timeoutSec` was a constant chosen once against a band nobody recorded beside it, and on a
4495
+ // dense matter the two band-reading stages died AT their walls having written nothing. The base is
4496
+ // still the stage's own number; what the band decides is how far above it this dispatch may go. A
4497
+ // band at or under the reference returns the base untouched, so nothing already verified moves.
4498
+ const dispatchBand = bandSizeForStage(name, P);
4499
+ const derivedLimit = derivedLimitSec(def.timeoutSec, dispatchBand);
4500
+ // REFUSED BEFORE A SEAT IS DISPATCHED, not after the wall. A limit past the ceiling is a prediction
4501
+ // that this stage will be killed; starting it spends the whole prediction to arrive where the refusal
4502
+ // already is, and the refusal NAMES the band size, which is the finding a killed attempt never gives.
4503
+ if (limitExceedsCeiling(derivedLimit.sec)) {
4504
+ const reason = ceilingRefusal(name, derivedLimit);
4505
+ runLog(P.runDir, { event: "stage-input-over-ceiling", stage: name,
4506
+ inputBytes: derivedLimit.inputBytes, derivedLimitSec: derivedLimit.sec });
4507
+ throw new StageFailure(name, reason, undefined, { failClass: "deterministic" });
4508
+ }
4509
+ runLog(P.runDir, { event: "stage-limit-derived", stage: name, base: def.timeoutSec,
4510
+ inputBytes: derivedLimit.inputBytes, derivedLimitSec: derivedLimit.sec, basis: derivedLimit.basis });
4345
4511
  const r = await runStage(label, {
4346
4512
  agent: execAgent,
4347
4513
  message,
@@ -4349,12 +4515,13 @@ async function stageOnce(name, ctx, opts = {}) {
4349
4515
  thinking,
4350
4516
  // opts.sessionKey lets a followup RESUME the exact key a prior run won on (winning-key hardening); else
4351
4517
  // the canonical base key. (A followup with a stale base key would resume a failed attempt — see runStage.)
4352
- sessionKey: opts.sessionKey ?? `prelim-${ctx.run.slug}-${ctx.run.codename}-${name}${keyAxis}`,
4353
- timeoutSec: def.timeoutSec,
4518
+ sessionKey: opts.sessionKey ?? `clearance-${ctx.run.slug}-${ctx.run.codename}-${name}${keyAxis}`,
4519
+ timeoutSec: derivedLimit.sec ?? def.timeoutSec,
4520
+ derivedLimit, // recorded on every attempt row: the size it was derived from, and the number
4354
4521
  stallSec: def.stallSec, // per-stage stall override (heavy stages); undefined → global CLEAROTRON_STALL_MS
4355
4522
  expectFile: out,
4356
4523
  validate: def.validate,
4357
- runDir: P.runDir, bandSize: bandSizeForStage(name, P),
4524
+ runDir: P.runDir, bandSize: dispatchBand,
4358
4525
  // ── — THE PRESENTING SIDE, RECOMPOSED PER ATTEMPT ────────────────────────────────────────
4359
4526
  //
4360
4527
  // A stage that declares `refreshCtx` is saying its dispatch text depends on state the run CHANGES
@@ -4437,6 +4604,8 @@ async function stageOnce(name, ctx, opts = {}) {
4437
4604
  });
4438
4605
  // P2 — record WHAT this output was produced from, so a later resume can tell whether it still holds.
4439
4606
  if (r.ok && out) writeStamp(P.runDir, label, stageInputs(name, P, { axes: ctx.axes, axis: ctx.axis, registerOnly: ctx.registerOnly }), { project: projectStageInput });
4607
+ // Re-asserted on success, and no longer the only advance: entry records the same transition above, so
4608
+ // this is what re-states the stage after a fan-out's concurrent axes have each written their own.
4440
4609
  if (r.ok) recordTransition(ctx, name); // bare name → fan-out axes collapse to one display step (monotonic)
4441
4610
  // PR-4 — document-growth tripwire, keyed on TRIGGER so it covers BOTH growth loops (the lint-repair
4442
4611
  // "re-emit the COMPLETE updated file" cycle AND the skeptic/refutation corrective loop — the 2026-07-28 postmortem run's
@@ -4627,7 +4796,7 @@ function must(r, name) {
4627
4796
  * absence reads as an empty cause — which is the whole defect, one level along. PURE.
4628
4797
  *
4629
4798
  * — VISIBLE IN THE STRING, not only in a sibling key. `reason` was a bare slice, so a cut sentence
4630
- * read as a finished one. Three failed runs of 2026-08-19 ended theirs mid-path — ".../studio/prelim-se"
4799
+ * read as a finished one. Three failed runs of 2026-08-19 ended theirs mid-path — ".../studio/clearance-se"
4631
4800
  * — and the reader who stat'd that directory got ENOENT and diagnosed a working mechanism as a broken
4632
4801
  * one. Nothing had been lost: `reasonFull` carried the whole path on all three and `reasonTruncated` was
4633
4802
  * true on all three. The field that gets read FIRST simply never said it was short. `abbrev` is this
@@ -5247,7 +5416,7 @@ export function recordConnotationAudit(run, P) { // @internal
5247
5416
  // ── — DOES A CHARGED RATING CARRY ITS GROUNDS? RECORDED, NOT ENFORCED ───────────────────────
5248
5417
  //
5249
5418
  // Doctrine already asks for this in as many words: "rule `loaded` and use the note to state plainly
5250
- // what you could not establish and what a human should look at" (prelim-common-law/SKILL.md). The
5419
+ // what you could not establish and what a human should look at" (clearance-common-law/SKILL.md). The
5251
5420
  // seat does not do it — e2e READ all 24 `loaded` notes in the corpus (not keyword-probed them) and
5252
5421
  // every one describes the material instead. `grounds-grammar.mjs` was built to measure exactly that,
5253
5422
  // proven against those real notes and against a planted violation, AND CALLED BY NOTHING. A check
@@ -6111,7 +6280,7 @@ function refuteRegistryCheckExtra(ctx) {
6111
6280
  const P = ctx.paths;
6112
6281
  const narrative = existsSync(P.narrative) ? readFileSync(P.narrative, "utf8") : "";
6113
6282
  if (!narrative) return "";
6114
- const recordsByUri = assembleRunRecords(P.runDir, `prelim-${ctx.run.slug}-${ctx.run.codename}-`).records;
6283
+ const recordsByUri = assembleRunRecords(P.runDir, `clearance-${ctx.run.slug}-${ctx.run.codename}-`).records;
6115
6284
  const probs = [
6116
6285
  ...findRegistryArithmeticIssues(narrative).map((a) => `${a.block}: ${a.detail}`),
6117
6286
  ...findRegistryViolations(narrative, recordsByUri).filter((v) => v.kind === "mismatch")
@@ -6984,7 +7153,7 @@ function injectDeferralCoverage(P, runDir, note) {
6984
7153
  // ── THE REMEDY CLAUSE NAMES A PRODUCT, AND FOR A WHILE IT NAMED A DELETED ONE ───────────────────────
6985
7154
  //
6986
7155
  // This row reaches a CLIENT REPORT. It read "…not searched at this level — available at Depth 5
6987
- // (prelim-jx)": an internal product key and a rung on the depth ladder, both retired by, offered as
7156
+ // (clearance-jx)": an internal product key and a rung on the depth ladder, both retired by, offered as
6988
7157
  // the remedy for a coverage limit the same sentence had just disclosed. `injectScriptScopeCoverage`
6989
7158
  // calls the decision on EVERY run, and it was driven — it fires for a worldwide scope, a CN-named scope
6990
7159
  // and the ja lane — so this was delivered prose telling a reader to buy something that does not exist.
@@ -6998,7 +7167,7 @@ const WORLDWIDE_SCOPE_RE = /^(worldwide|global|all|all[- ]jurisdictions)$/i;
6998
7167
  /** What a synthesis-authored Stage-1.5 coverage row looks like when it has ALREADY made this
6999
7168
  * disclosure — see the suppression in injectScriptScopeCoverage. Both the current vocabulary and the
7000
7169
  * retired one, because a resumed run carries whichever was current when its synthesis ran. */
7001
- const SCRIPT_SCOPE_RECOMMENDATION_TOKENS = Object.freeze(["native-language investigation", "prelim-jx"]);
7170
+ const SCRIPT_SCOPE_RECOMMENDATION_TOKENS = Object.freeze(["native-language investigation", "clearance-jx"]);
7002
7171
 
7003
7172
  /** The disclosure vocabulary for ONE candidate lane, derived (never tabulated). Returns
7004
7173
  * `{area, note, territories, marker}` — `marker` is the lowercased script prefix the suppression
@@ -7122,14 +7291,14 @@ export function injectScriptScopeCoverage(P, runDir, note, { searchPolicy = null
7122
7291
  const rowText = (c) => `${c?.area ?? ""} ${c?.note ?? ""}`.toLowerCase();
7123
7292
  const added = [];
7124
7293
  for (const p of pending) {
7125
- // Suppression is PER LANE. The old check treated any coverage row mentioning "prelim-jx" as
7294
+ // Suppression is PER LANE. The old check treated any coverage row mentioning "clearance-jx" as
7126
7295
  // covering the disclosure, which was harmless while zh was the only lane and is a silent row-
7127
7296
  // eater the moment there are two: on a CN+JP scope the zh row lands first, its own note ended in
7128
- // "(prelim-jx)", and the ja row was then swallowed by a match on the sibling's text. So the
7297
+ // "(clearance-jx)", and the ja row was then swallowed by a match on the sibling's text. So the
7129
7298
  // deference to a synthesis-authored Stage-1.5 row requires the row to be about THIS lane.
7130
7299
  //
7131
7300
  // TWO VOCABULARIES ON PURPOSE. `native-language investigation` is what the offering calls it now
7132
- // and what synthesis reads in its own prompt; `prelim-jx` is the retired key, kept because a run
7301
+ // and what synthesis reads in its own prompt; `clearance-jx` is the retired key, kept because a run
7133
7302
  // RESUMED from before carries a synthesis row written in it — dropping the old token would
7134
7303
  // put a second, duplicate disclosure into a report that already makes the same one.
7135
7304
  const already = doc.coverage.some((c) => {
@@ -7329,7 +7498,7 @@ export function fullProseOrdinals(findings) {
7329
7498
  .filter((o) => o != null);
7330
7499
  }
7331
7500
  // ASSEMBLE report.md = the overview shell (front-matter + Actions/Coverage/Methodology) + `# Marks` + the
7332
- // per-card files in render order (composite desc, ordinal asc — mirrors render.mjs:502), `open: true` on the
7501
+ // per-card files in render order (composite desc, ordinal asc — mirrors render.mjs `sortedAll`), `open: true` on the
7333
7502
  // single top card. Findings with no card file (a failed report-card, or a secondary finding) are NOT emitted —
7334
7503
  // render.mjs synthesizes them structured-only from findings.json, so nothing is silently dropped. Pure file IO.
7335
7504
  // spec 64 — "### Only you can close these" is CODE-BUILT from the typed actions register (the report-
@@ -7540,7 +7709,7 @@ export function buildOnlyYouSection(actions, findings, { nowMs = Date.now(), wit
7540
7709
  // blocking review goes." That REVERSES T3, which retired "delivered-with-open-questions" and
7541
7710
  // is itself recorded as an owner-approved decision — both are his, and this is the standing one.
7542
7711
  //
7543
- // The section the reviewer's concerns land in is not new. `driver/skills/prelim-search/SKILL.md:241`
7712
+ // The section the reviewer's concerns land in is not new. `driver/skills/clearance-search/SKILL.md`
7544
7713
  // has described it all along — "delivered … as a prominent Reviewer's open questions section at the
7545
7714
  // top of the body (the driver passes them in)" — and `:295` lists it as a required section with
7546
7715
  // "Never omit it to look more finished." deleted the driver's half and left that text
@@ -7870,7 +8039,7 @@ export function assembleReportMd(P, findings, cardOrdinals, { grouped = [], byRi
7870
8039
  } catch { /* never-kill: a malformed findings.json leaves the overview untouched (its own gates own that) */ }
7871
8040
  // ── T3a — THE REVIEWER'S OPEN POINTS, AT THE TOP OF THE BODY ──────────────────────────────────────
7872
8041
  //
7873
- // `driver/skills/prelim-search/SKILL.md:241` says where: "a prominent Reviewer's open questions section at the TOP of the body
8042
+ // `driver/skills/clearance-search/SKILL.md` says where: "a prominent Reviewer's open questions section at the TOP of the body
7874
8043
  // (the driver passes them in)". That sentence has been true of the contract and false of the code
7875
8044
  // since deleted the driver's half; this is the half coming back.
7876
8045
  //
@@ -8846,9 +9015,10 @@ async function pipelineInner(job, opts = {}) {
8846
9015
  // Phase 0 done in code (slug/codename/run-dir/customer). Phase 1+2 stages:
8847
9016
  must(await stage("matter-frame", ctx), "matter-frame");
8848
9017
  await deriveIntakeAsks(ctx); // A6: freeze the intake-ask register (one save-only followup if the section is missing)
8849
- must(await stage("prelim-variants", ctx), "prelim-variants");
9018
+ must(await stage("clearance-variants", ctx), "clearance-variants");
8850
9019
  deriveScopeLedgerJson(ctx); // frame-omission design: code-derive scope-ledger.json from the validated prose (never-kill)
8851
9020
  deriveFormNeighbourhood(ctx); // mechanical FORM band: code-derive form-neighbourhood.json from the manifest's distinctive element(s) — the model-free form floor the register funnel searches (never-kill)
9021
+ await verifyAndRecordHouseElement(ctx, opts); // ask the register who owns the frame's proposed house element, BEFORE the plan is compiled from it
8852
9022
  attachRegisterPlan(ctx); // WS2 (B3): compile/freeze/reuse the deterministic register plan (flag-gated; frozen plan wins on resume; never-kill on mint)
8853
9023
 
8854
9024
  // spec 64 (B2) — proactive recall probes: prior-confirmed conflicts for THIS mark (the workspace
@@ -9659,7 +9829,7 @@ async function pipelineInner(job, opts = {}) {
9659
9829
  const dispatchStartedMs = Date.now();
9660
9830
  try {
9661
9831
  const r = await planExec({ planPath: P.registerPlan, axis: a, outputPath: P.registerBand(a), ...(qids?.length ? { qids } : {}) },
9662
- { agentId: agent, sessionKey: `prelim-${run.slug}-${run.codename}-register-unit-${a}`, recordLog: runRecordLogPath(run.runDir) });
9832
+ { agentId: agent, sessionKey: `clearance-${run.slug}-${run.codename}-register-unit-${a}`, recordLog: runRecordLogPath(run.runDir) });
9663
9833
  outcome = r?.ok ? "ok" : `failed: ${String(r?.cause ?? "unknown").slice(0, 140)}`;
9664
9834
  } catch (err) { outcome = `threw: ${String(err?.message ?? err).slice(0, 140)}`; }
9665
9835
  // — MEASURE WHETHER THE HOLE CLOSED, not whether the phone was answered. `outcome` is the
@@ -9746,7 +9916,7 @@ async function pipelineInner(job, opts = {}) {
9746
9916
  // production default is ON.
9747
9917
  if (process.env.CLEAROTRON_BAND_TRUTH_GATE !== "0"
9748
9918
  && unitKey[a] && existsSync(P.registerBand(a)) && (ctx.registerPlan?.entries ?? []).some((e) => e?.axis === a)) {
9749
- const laneKey = `prelim-${run.slug}-${run.codename}-register-unit-${a}`;
9919
+ const laneKey = `clearance-${run.slug}-${run.codename}-register-unit-${a}`;
9750
9920
  const bandHasQids = (() => {
9751
9921
  try { const b = JSON.parse(readFileSync(P.registerBand(a), "utf8")); return Array.isArray(b) && b.some((x) => x && typeof x === "object" && x.qid); }
9752
9922
  catch { return false; /* parse defects are the stage validator's problem, not this gate's */ }
@@ -9819,7 +9989,7 @@ async function pipelineInner(job, opts = {}) {
9819
9989
  if (!digestLocked && repairLedger.canAttempt("taint-rerun", a, { max: taintRerunMax, epoch: repairEpoch })) {
9820
9990
  note(`register-unit ${a}: taint-rerun — one fresh re-run (fresh session; a killed session is never warm-resumed)`);
9821
9991
  const r = await stage("register-unit", { ...ctx, axis: a },
9822
- { force: true, trigger: "taint-rerun", sessionKey: `prelim-${run.slug}-${run.codename}-register-unit-${a}-taint-rerun-${recoveryAttempts}` });
9992
+ { force: true, trigger: "taint-rerun", sessionKey: `clearance-${run.slug}-${run.codename}-register-unit-${a}-taint-rerun-${recoveryAttempts}` });
9823
9993
  repairLedger.record("taint-rerun", a, r.ok ? "ok" : `failed: ${String(r.fail ?? "unknown").slice(0, 100)}`, { epoch: repairEpoch, max: taintRerunMax });
9824
9994
  repairs.push(`taint-rerun:${r.ok ? "ok" : `failed: ${String(r.fail ?? "unknown").slice(0, 60)}`}`);
9825
9995
  if (r.ok && r.sessionKey) unitKey[a] = r.sessionKey;
@@ -10144,7 +10314,7 @@ async function pipelineInner(job, opts = {}) {
10144
10314
  runLog(run.runDir, { event: "plan-qids-missing", axis: a, qids: entries.map((e) => e.qid), action: "fresh-execute-plan" });
10145
10315
  note(`register-unit ${a}: ${entries.length} dictated plan entr${entries.length === 1 ? "y" : "ies"} unexecuted on a resumed-past axis — one fresh execute_plan-only call`);
10146
10316
  const freshMsg = repairFollowup("register-unit:plan-join-fresh", { axis: a, registerPlan: P.registerPlan, bandPath: P.registerBand(a) });
10147
- const rf = await stage("register-unit", { ...ctx, axis: a }, { force: true, freshMessage: freshMsg, sessionKey: `prelim-${ctx.run.slug}-${ctx.run.codename}-register-unit-${a}-plan-join-fresh`, trigger: "plan-join-fresh" });
10317
+ const rf = await stage("register-unit", { ...ctx, axis: a }, { force: true, freshMessage: freshMsg, sessionKey: `clearance-${ctx.run.slug}-${ctx.run.codename}-register-unit-${a}-plan-join-fresh`, trigger: "plan-join-fresh" });
10148
10318
  if (!rf.ok) note(`register-unit ${a}: fresh execute_plan call failed (${rf.fail}) — the plan-unexecuted StageFailure below holds the line`);
10149
10319
  continue;
10150
10320
  }
@@ -10969,7 +11139,7 @@ async function pipelineInner(job, opts = {}) {
10969
11139
  note(`frame-reopen: ${a} timed out — ONE fresh SCOPED retry through the fallback chain (backup provider + retries)`);
10970
11140
  const retryMsg = repairFollowup("register-unit:frame-reopen-retry", { paths: P, axis: a, directives: regDirectives, reopenFetchCap,
10971
11141
  supplementalLane: !!ctx.registerPlan?.contract?.supplemental_lane });
10972
- const r2 = await stage("register-unit", { ...ctx, axis: a }, { force: true, freshMessage: retryMsg, sessionKey: `prelim-${ctx.run.slug}-${ctx.run.codename}-register-unit-${a}-reopen-retry`, trigger: "frame-reopen-retry" });
11142
+ const r2 = await stage("register-unit", { ...ctx, axis: a }, { force: true, freshMessage: retryMsg, sessionKey: `clearance-${ctx.run.slug}-${ctx.run.codename}-register-unit-${a}-reopen-retry`, trigger: "frame-reopen-retry" });
10973
11143
  if (r2.ok && existsSync(P.registerUnit(a)) && readFileSync(P.registerUnit(a), "utf8") !== beforeUnit) {
10974
11144
  changedAxes.push(a); regChanged = true; regMechFail = null;
10975
11145
  note(`frame-reopen: ${a} fresh scoped retry closed the gap`);
@@ -11740,7 +11910,7 @@ async function pipelineInner(job, opts = {}) {
11740
11910
  // violating after that one recovery pass is disclosed as unexamined + clamps the verdict (owner
11741
11911
  // decision 2026-07-22 — the terminal branch below); the gate never silently passes a drop.
11742
11912
  {
11743
- const runPrefix = `prelim-${run.slug}-${run.codename}-`;
11913
+ const runPrefix = `clearance-${run.slug}-${run.codename}-`;
11744
11914
  // Re-read the findings + recompute the fetched universe on EACH check (the recovery re-digest rewrites
11745
11915
  // the findings AND may record_fetch the flagged URIs — both must be picked up on the post-recovery
11746
11916
  // check). V4-1: the fetched universe is the DELIVERED ARTIFACT SET's records (run-dir _records/ ∪ this
@@ -12062,7 +12232,7 @@ async function pipelineInner(job, opts = {}) {
12062
12232
  regions: ctx.registerPlan?.regions ?? [], entries,
12063
12233
  }, null, 2) + "\n");
12064
12234
  const r = await planExec({ planPath: ccPlanPath, axis: CROWD_CONTEXT_AXIS, outputPath: ccBandPath },
12065
- { agentId: ctx.agent, sessionKey: `prelim-${run.slug}-${run.codename}-crowd-context`, recordLog: runRecordLogPath(run.runDir) });
12235
+ { agentId: ctx.agent, sessionKey: `clearance-${run.slug}-${run.codename}-crowd-context`, recordLog: runRecordLogPath(run.runDir) });
12066
12236
  if (!r?.ok) throw new Error(String(r?.cause ?? "crowd-context plan execution failed").slice(0, 200));
12067
12237
  const blocks = JSON.parse(readFileSync(ccBandPath, "utf8"));
12068
12238
  return Array.isArray(blocks) ? blocks : [];
@@ -12102,7 +12272,7 @@ async function pipelineInner(job, opts = {}) {
12102
12272
  // on fetched records) as a structured aim-attention artifact for synthesis. Best-effort; NEVER a
12103
12273
  // rating rule (explicitly not the rejected escalation filter) — absence writes nothing.
12104
12274
  try {
12105
- const { records } = assembleRunRecords(run.runDir, `prelim-${run.slug}-${run.codename}-`);
12275
+ const { records } = assembleRunRecords(run.runDir, `clearance-${run.slug}-${run.codename}-`);
12106
12276
  const signals = extractEnforcerSignals(records);
12107
12277
  if (signals.length) {
12108
12278
  writeFileSync(`${P.enforcerSignals}.tmp`, JSON.stringify({ signals }, null, 2) + "\n");
@@ -12644,7 +12814,7 @@ async function pipelineInner(job, opts = {}) {
12644
12814
  status: r?.screen?.status ?? r?.status, live_status: r?.screen?.live_status });
12645
12815
  }
12646
12816
  } catch { /* no merged band (legacy run) — fetched-record facts alone rank */ }
12647
- const srPrefix = `prelim-${run.slug}-${run.codename}-`;
12817
+ const srPrefix = `clearance-${run.slug}-${run.codename}-`;
12648
12818
  let srRecords = assembleRunRecords(run.runDir, srPrefix).records;
12649
12819
  const srFetcher = opts.recordFetcher ?? defaultRecordFetcher;
12650
12820
  const srLedger = ctx.repairLedger ?? (ctx.repairLedger = createRepairLedger(run.runDir, { log: (o) => runLog(run.runDir, o) }));
@@ -12718,7 +12888,7 @@ async function pipelineInner(job, opts = {}) {
12718
12888
  const carriedUris = (pf?.findings ?? []).flatMap((f) =>
12719
12889
  Array.isArray(f?.owner?.registrations) ? f.owner.registrations : []
12720
12890
  ).map((r) => r?.uri && (normalizeRecordUri(r.uri) || String(r.uri).trim())).filter(Boolean);
12721
- const fetchedUris = [...assembleRunRecords(run.runDir, `prelim-${run.slug}-${run.codename}-`).records.keys()];
12891
+ const fetchedUris = [...assembleRunRecords(run.runDir, `clearance-${run.slug}-${run.codename}-`).records.keys()];
12722
12892
  const rfMd = existsSync(P.registerFindings) ? readFileSync(P.registerFindings, "utf8") : "";
12723
12893
  const violations = findRecallRegressionViolations({
12724
12894
  knownConflicts, searchedNames: namesForRecall, carriedUris, fetchedUris,
@@ -12872,7 +13042,11 @@ async function pipelineInner(job, opts = {}) {
12872
13042
  reason: `synthesis_unaccounted_delivered:${duty.unaccounted.length} of ${duty.totals.owed} record(s) reached the findings surface and the delivered document accounts for none of them — neither a finding that names them nor a declination with a ground: ${sample}${duty.unaccounted.length > 4 ? " …" : ""}. The report ships with this named rather than being withheld; these records are open points a reader must weigh.`,
12873
13043
  // — the READER's sentence: what is open, in a lawyer's nouns. Counts survive;
12874
13044
  // the token, the record ids and the engine's nouns stay in `reason` and the run record.
12875
- clause: `${duty.unaccounted.length} of the ${duty.totals.owed} register records this search surfaced are neither addressed as findings nor expressly set aside in this report — they remain open points a reader must weigh`,
13045
+ // COMPOSED BY THE AUTHORITY, NOT SPELLED HERE. The same sentence has to be reachable from a
13046
+ // run that stored no clause, where these two objects are long gone and only the reason's own
13047
+ // counts survive; `terminal-clamp.mjs` composes it from the counts either way, so the fresh
13048
+ // run and the republished archive cannot drift apart.
13049
+ clause: clauseForDefect("synthesis_unaccounted_delivered", duty.unaccounted.length, duty.totals.owed),
12876
13050
  });
12877
13051
  }
12878
13052
  }
@@ -12898,7 +13072,9 @@ async function pipelineInner(job, opts = {}) {
12898
13072
  // — the READER's sentence. "Floor row" is an engine noun; what the fact IS for a
12899
13073
  // lawyer: live registrations identical or near-identical to the mark that the report does not
12900
13074
  // individually address. Counts survive; token, ids and engine nouns stay in `reason`.
12901
- clause: `${block.undischarged} of the ${block.floors} live registrations identical or near-identical to the mark are not individually addressed in this report — each remains an open point a reader must weigh`,
13075
+ // Composed by the authority in `terminal-clamp.mjs` for the reason the sibling site above
13076
+ // gives: a republished pre-split run has to reach the same sentence from the counts alone.
13077
+ clause: clauseForDefect("floor_duty_undischarged", block.undischarged, block.floors),
12902
13078
  });
12903
13079
  }
12904
13080
  }
@@ -12940,7 +13116,7 @@ async function pipelineInner(job, opts = {}) {
12940
13116
  for (const c of fresh) clampClauses.push(conditionClauses[conditions.indexOf(c)] ?? c);
12941
13117
  clampReasons.push(...fresh);
12942
13118
  if (verdict === "CLEAR") {
12943
- runLog(run.runDir, { event: "coverage-floor-clamp", from: "CLEAR", to: "CONDITIONAL", legalActions: conditions.length });
13119
+ runLog(run.runDir, { event: "coverage-floor-clamp", cause: "forward-actions", from: "CLEAR", to: "CONDITIONAL", legalActions: conditions.length });
12944
13120
  note(`deliver-conditional floor: the opinion names ${conditions.length} forward legal action(s) a human must take (${conditions[0].slice(0, 140)}${conditions.length > 1 ? `; +${conditions.length - 1} more` : ""}) — clamping CLEAR→CONDITIONAL (spec 64: the disposition is derived from the findings' named actions).`);
12945
13121
  verdict = "CONDITIONAL";
12946
13122
  writeRunStatus(ctx, { verdict });
@@ -12979,7 +13155,7 @@ async function pipelineInner(job, opts = {}) {
12979
13155
  // The CLAMP is outside that guard on purpose: a re-ask can flip a clamped verdict back to CLEAR,
12980
13156
  // and it must meet this floor again with the reason already recorded.
12981
13157
  if (droppedConditions.length && verdict === "CLEAR") {
12982
- runLog(run.runDir, { event: "coverage-floor-clamp", from: "CLEAR", to: "CONDITIONAL", actionsDropped: droppedConditions.length });
13158
+ runLog(run.runDir, { event: "coverage-floor-clamp", cause: "dropped-actions", from: "CLEAR", to: "CONDITIONAL", actionsDropped: droppedConditions.length });
12983
13159
  verdict = "CONDITIONAL";
12984
13160
  writeRunStatus(ctx, { verdict });
12985
13161
  }
@@ -13070,28 +13246,55 @@ async function pipelineInner(job, opts = {}) {
13070
13246
  // could-not-examine record, and an unfinished register slice — CONDITIONAL carries
13071
13247
  // lawyer-judged/disclosed residue only.
13072
13248
  if (coverageInsufficient || frameResidual || screenGateGap || seniorGap || registerGap || deadlineGap) {
13073
- const reasons = [];
13074
- if (coverageInsufficient) reasons.push(`the lawyer judged a material slice not fully cleared: ${coverageJudgment.reason || "register coverage gap"}`);
13075
- if (frameGap) reasons.push("the blind frame-diff flagged a dominant-element omission the reopen pass did not close");
13076
- else if (frameDeferrals.length) reasons.push(`follow-ups left open this run: ${frameDeferrals.map((d) => plainDirective(d.directive)).slice(0, 3).join(", ")}`);
13077
- if (screenGateGap) reasons.push(`${sgUnresolved.length} in-scope mark(s) dropped on goods could not be record_fetched (unverified): ${sgUnresolved.map((g) => g.mark).join(", ")}`);
13078
- if (seniorGap) reasons.push(`the oldest registration in a verdict-driving family could not be retrieved (policy: clamp): ${(ctx.seniorRights?.rows ?? []).filter((r) => r.applicable && !r.verified).map((r) => r.mark).join(", ")}`);
13249
+ // THESE SENTENCES REACH A CLIENT AND THEY ARE NOT OURS TO WRITE (owner, 2026-09-17). One of
13250
+ // them — the screen-gate line, which says a mark "could not be record_fetched" — carried an
13251
+ // engine identifier into the list a client reads as the conditions on their result, by a route
13252
+ // `terminalClampDecision` guards and this one did not. A reader's sentence for it was written
13253
+ // here and refused: the objection was the class, not the wording. RULED 2026-09-18, design item
13254
+ // 25 — cut the clause: it stays in the run record and leaves the client's result entirely, with
13255
+ // NOTHING written in its place. That is what the `null` clause below means.
13256
+ const reasons = [], clauseOf = new Map(); // run-record reason → the client's clause: the same words, with any engine token taken out
13257
+ const machinery = (reason, clause = reason) => { reasons.push(reason); clauseOf.set(reason, clause); };
13258
+ if (coverageInsufficient) machinery(`the lawyer judged a material slice not fully cleared: ${coverageJudgment.reason || "register coverage gap"}`);
13259
+ if (frameGap) machinery("the blind frame-diff flagged a dominant-element omission the reopen pass did not close");
13260
+ else if (frameDeferrals.length) machinery(`follow-ups left open this run: ${frameDeferrals.map((d) => plainDirective(d.directive)).slice(0, 3).join(", ")}`);
13261
+ if (screenGateGap) machinery(`${sgUnresolved.length} in-scope mark(s) dropped on goods could not be record_fetched (unverified): ${sgUnresolved.map((g) => g.mark).join(", ")}`, null); // run record ONLY: no client clause (ruled 2026-09-18)
13262
+ if (seniorGap) machinery(`the oldest registration in a verdict-driving family could not be retrieved (policy: clamp): ${(ctx.seniorRights?.rows ?? []).filter((r) => r.applicable && !r.verified).map((r) => r.mark).join(", ")}`);
13079
13263
  if (registerGap) {
13080
- if (regGap.deferred.length) reasons.push(`register coverage deferred on ${[...new Set(regGap.deferred.map((g) => g.axis))].join(", ")} — the search did not finish and must be re-run before this can be relied on`);
13081
- if (regGap.taintAxes.length) reasons.push(`the ${regGap.taintAxes.join(", ")} register pass was cut down at the timeout wall and its self-reported coverage is unverified`);
13264
+ for (const { reason, clause } of registerGapConditions(regGap)) machinery(reason, clause);
13265
+ // (the deferred and cut-down register lines: registerGapConditions, at the end of this file)
13082
13266
  // Named regressions (2026-07-22): `<MARK> (<owner> — <canonical uri>)` — a bare mark name
13083
13267
  // shipped "ION, ION, ION" (three indistinguishable strings); the identity is front-loaded
13084
13268
  // because the delivered statement truncates from the tail.
13085
- if (regGap.recallRegressions.length) reasons.push(`a prior-confirmed live conflict was neither carried nor justified this run: ${regGap.recallRegressions.slice(0, 3).map(formatRecallRegression).join(", ")}`);
13269
+ if (regGap.recallRegressions.length) machinery(`a prior-confirmed live conflict was neither carried nor justified this run: ${regGap.recallRegressions.slice(0, 3).map(formatRecallRegression).join(", ")}`);
13086
13270
  }
13087
- if (deadlineGap) reasons.push(`a recorded opposition deadline was delivered without its date: ${ctx.deadlineCarryMaterial.map((v) => `${v.mark_text ?? v.uri} (window closes ${v.opposition_end})`).slice(0, 3).join(", ")}`);
13088
- runLog(run.runDir, { event: "coverage-floor-clamp", from: "CLEAR", to: "CONDITIONAL", coverageInsufficient: coverageInsufficient || undefined, frameGap: frameGap || undefined, frameDeferred: frameDeferrals.length || undefined, screenGate: screenGateGap ? sgUnresolved.length : undefined, seniorRight: seniorGap || undefined, registerGap: registerGap ? { deferred: regGap.deferred.length, taint: regGap.taintAxes.length, recall: regGap.recallRegressions.length } : undefined, deadlineCarry: deadlineGap ? ctx.deadlineCarryMaterial.length : undefined });
13271
+ if (deadlineGap) machinery(`a recorded opposition deadline was delivered without its date: ${ctx.deadlineCarryMaterial.map((v) => `${v.mark_text ?? v.uri} (window closes ${v.opposition_end})`).slice(0, 3).join(", ")}`);
13272
+ // ── THE THIRD CLAMP SITE NAMES ITSELF, AND NAMES WHICH OF ITS SEVEN INPUTS FIRED ────────────────
13273
+ //
13274
+ // All three clamp sites emitted this event under one name with the same from/to, distinguishable
13275
+ // only by which optional payload key happened to be present. Two of them fired three milliseconds
13276
+ // apart on a production run — the first without `frameDeferred`, the second with it — which reads
13277
+ // as one decision logged twice. It was two different decisions wearing one name, and the test lane
13278
+ // reasonably discounted one of them. `cause` makes the event self-describing.
13279
+ //
13280
+ // THE EVENT NAME IS DELIBERATELY NOT SPLIT. It is a true statement about the effect — the verdict
13281
+ // was clamped — and something downstream may already count clamps in aggregate. A discriminator is
13282
+ // additive; three names would not be.
13283
+ //
13284
+ // This site is itself seven causes under one name, so it also lists WHICH fired rather than
13285
+ // leaving a reader to key on field presence and guess.
13286
+ const clampInputs = Object.entries({
13287
+ coverageInsufficient, frameGap, frameDeferred: frameDeferrals.length,
13288
+ screenGate: screenGateGap ? sgUnresolved.length : 0, seniorRight: seniorGap,
13289
+ registerGap, deadlineCarry: deadlineGap ? ctx.deadlineCarryMaterial.length : 0,
13290
+ }).filter(([, v]) => Boolean(v)).map(([k]) => k);
13291
+ runLog(run.runDir, { event: "coverage-floor-clamp", cause: "coverage", causes: clampInputs, from: "CLEAR", to: "CONDITIONAL", coverageInsufficient: coverageInsufficient || undefined, frameGap: frameGap || undefined, frameDeferred: frameDeferrals.length || undefined, screenGate: screenGateGap ? sgUnresolved.length : undefined, seniorRight: seniorGap || undefined, registerGap: registerGap ? { deferred: regGap.deferred.length, taint: regGap.taintAxes.length, recall: regGap.recallRegressions.length } : undefined, deadlineCarry: deadlineGap ? ctx.deadlineCarryMaterial.length : undefined });
13089
13292
  note(`deliver-conditional floor: ${reasons.join("; ")} — clamping CLEAR→CONDITIONAL so the delivered status carries the gap (never withheld, never halted).`);
13090
13293
  verdict = "CONDITIONAL";
13091
13294
  // APPEND (dedup by exact text) — the legalActions arm may already have recorded conditions,
13092
13295
  // and this callable runs more than once (degenerate re-ask, lint repair, post-consolidation).
13093
13296
  const freshMachinery = reasons.filter((r) => !clampReasons.includes(r));
13094
- clampClauses.push(...freshMachinery); // machinery reasons ARE factual open-states — clause == reason
13297
+ clampClauses.push(...freshMachinery.map((r) => (clauseOf.has(r) ? clauseOf.get(r) : r))); // the clause is the reason less any engine token; null = the run record's alone
13095
13298
  clampReasons.push(...freshMachinery);
13096
13299
  // The reason KINDS distinguish coverage/frame/screen-gate/senior-right/register residue for the
13097
13300
  // report bound line and the client conditions row (merged — legalActions survives).
@@ -13209,7 +13412,7 @@ async function pipelineInner(job, opts = {}) {
13209
13412
  // review goes." This REVERSES T3 (H3/H5), whose flip to fail-on-BLOCKING is itself recorded
13210
13413
  // itself an owner-approved decision. Both are his; this is the standing one, and the
13211
13414
  // history is kept here rather than deleted because a reader who finds only one of them will conclude
13212
- // the code drifted from its contract. (It did not; driver/skills/prelim-search/SKILL.md:241/295 was the stale half, and this
13415
+ // the code drifted from its contract. (It did not; driver/skills/clearance-search/SKILL.md was the stale half, and this
13213
13416
  // change makes it true again.)
13214
13417
  //
13215
13418
  // COPPER-SPIRE IS STILL ANSWERED, AND NOT BY REFUSING. That failure was a BLOCKING verdict reaching
@@ -14104,7 +14307,7 @@ async function pipelineInner(job, opts = {}) {
14104
14307
  // the spec's "blocks delivery", approved 2026-06-10: delivering nothing is worse than delivering good
14105
14308
  // work with one clearly-labelled defect — and code-produced defects would deadlock a block.)
14106
14309
  {
14107
- const lintPrefix = `prelim-${run.slug}-${run.codename}-`;
14310
+ const lintPrefix = `clearance-${run.slug}-${run.codename}-`;
14108
14311
  // V4-1: the gates evaluate the artifact set assembled for THIS delivery — run-dir _records/
14109
14312
  // (inherited or prior-session) ∪ this-session ledger rows — never the session-prefixed ledger
14110
14313
  // alone (empty on a fork ⇒ the vacuous registry pass that shipped a false citation).
@@ -14709,7 +14912,7 @@ async function pipelineInner(job, opts = {}) {
14709
14912
  Array.isArray(f?.owner?.registrations) ? f.owner.registrations : []
14710
14913
  ).map((r) => r?.uri && (normalizeRecordUri(r.uri) || String(r.uri).trim())).filter(Boolean);
14711
14914
  const executedQids = (ctx.planExecution?.executed ?? []).map((x) => x.qid);
14712
- const fetchedUrisForRecall = [...assembleRunRecords(run.runDir, `prelim-${run.slug}-${run.codename}-`).records.keys()];
14915
+ const fetchedUrisForRecall = [...assembleRunRecords(run.runDir, `clearance-${run.slug}-${run.codename}-`).records.keys()];
14713
14916
  const regLedgerRows = loadCoverageLedger(run.runDir).rows;
14714
14917
  // judgment-relocation (2026-06-23): the search-shape gate (findFloorShapeGaps) was DELETED here too —
14715
14918
  // sufficiency is judgment's call (coverage_judgment), not a re-parse of the ledger at the surface. This
@@ -14998,7 +15201,7 @@ async function pipelineInner(job, opts = {}) {
14998
15201
  let providerTally = null;
14999
15202
  try {
15000
15203
  const provider = activeProvider().id;
15001
- const usage = tallyRegisterCalls(DEFAULT_LEDGER_PATH, `prelim-${run.slug}-${run.codename}-`);
15204
+ const usage = tallyRegisterCalls(DEFAULT_LEDGER_PATH, `clearance-${run.slug}-${run.codename}-`);
15002
15205
  runLog(run.runDir, { event: "provider-usage", provider, ...usage });
15003
15206
  writeRunStatus(ctx, { providerUsage: { [provider]: usage } });
15004
15207
  providerTally = { [provider]: usage };
@@ -15637,6 +15840,46 @@ function resolveRun(job, opts) {
15637
15840
 
15638
15841
  // Rebuild the ctx a single stage needs WITHOUT re-running upstream: axes from the persisted manifest, verdict
15639
15842
  // from status.json, publishedUrl from .published. (Mirrors what pipeline() accumulates mid-run.)
15843
+ /**
15844
+ * THE JOB A RUN RAN, READ BACK OUT OF THE RUN'S OWN STATUS. PURE.
15845
+ *
15846
+ * `--resume` needs a job file, and the job file is gone by the time a resume is needed: the queue entry
15847
+ * that carried it is consumed when the run starts, and nothing copies it into the run directory. So the
15848
+ * one command that repairs a failed run names a path the box does not have, and the person who finds out
15849
+ * is the person trying to repair it.
15850
+ *
15851
+ * This is not a guess at the job. `seedRunStatus` writes these fields FROM the job the engine ran, so
15852
+ * reading them back is the engine's own record, not a reconstruction of intent. What it cannot carry is
15853
+ * scope, and it does not have to: `reconstructCtx` reads profile and framework from the run's frozen
15854
+ * sidecars with `write: false`, so the run's own configuration governs and the job supplies identity.
15855
+ *
15856
+ * THE CALLER MUST CHECK THE SLUG, and `resumeJobRefusal` below is that check. A rebuilt job that derives
15857
+ * the run's own slug has reproduced the run's identity — `deriveSlug` reads `ref` and `markName` and
15858
+ * nothing else — and one that derives a different slug is a job for a different matter.
15859
+ */
15860
+ export function jobFromStatus(status) { // @internal
15861
+ const s = status ?? {};
15862
+ if (!s.id && !s.ref && !s.markName) return null; // not a status this can rebuild from
15863
+ return {
15864
+ id: s.id ?? null, ref: s.ref ?? null, markName: s.markName ?? null,
15865
+ classes: Array.isArray(s.classes) ? s.classes : null,
15866
+ forwarder: s.forwarder ?? null,
15867
+ };
15868
+ }
15869
+
15870
+ /** Why a rebuilt job may not stand in for the run's own, or null when it may. PURE. */
15871
+ export function resumeJobRefusal(job, slug) { // @internal
15872
+ if (!job) return "this run's status.json carries no id, reference or mark name, so the job it ran cannot be read back from it";
15873
+ // EVERY IDENTIFYING FIELD, OR NO REBUILD: a job missing one is a guess at that field, and a resume must not guess.
15874
+ const missing = [["id", job.id], ["markName", job.markName], ["forwarder", job.forwarder]].filter(([, v]) => !v).map(([k]) => k).concat(Array.isArray(job.classes) ? [] : ["classes"]);
15875
+ if (missing.length) return `this run's status.json carries no ${missing.join(", ")}, so the job it ran cannot be read back whole — pass --job <file.json> rather than resume on a guess`;
15876
+ const derived = deriveSlug(job);
15877
+ if (derived !== slug)
15878
+ return `the job rebuilt from this run's status.json derives ${derived}, and the run directory is ${slug} — `
15879
+ + "they are different matters, so the rebuild is refused rather than resumed into the wrong run";
15880
+ return null;
15881
+ }
15882
+
15640
15883
  export function reconstructCtx(job, opts) { // @internal
15641
15884
  const run = resolveRun(job, opts);
15642
15885
  const P = paths(run.runDir);
@@ -15819,7 +16062,7 @@ export async function repairStale(job, opts = {}) { // @internal
15819
16062
 
15820
16063
  // --experiment <stage>: re-run ONE stage SANDBOXED — no canonical ARTIFACT is read back or overwritten. The
15821
16064
  // stage reads COPIES of its canonical inputs from a shadow dir and writes its output there; a distinct
15822
- // `prelim-exp-…` session key never collides with the canonical session AND sorts outside the run's
16065
+ // `clearance-exp-…` session key never collides with the canonical session AND sorts outside the run's
15823
16066
  // provider-usage prefix (experiment Corsearch calls are not billed to the canonical run). Single-model by
15824
16067
  // design (the canonical primary unless --model overrides) so the comparison is clean.
15825
16068
  //
@@ -16016,7 +16259,7 @@ async function runExperimentInner(job, opts) {
16016
16259
  // so a run with no case-law layer must arrive as null here too, never as an absent key.
16017
16260
  for (const f of inlineDecl.fields) shadowCtx[f] = resolved[f] ?? null;
16018
16261
  }
16019
- const sessionKey = `prelim-exp-${ctx.run.slug}-${ctx.run.codename}-${name}${axis ? `-${axis}` : ""}-${ts}`;
16262
+ const sessionKey = `clearance-exp-${ctx.run.slug}-${ctx.run.codename}-${name}${axis ? `-${axis}` : ""}-${ts}`;
16020
16263
  let { text: extra, ids: extraIds } = experimentExtra(shadowCtx, name, opts);
16021
16264
 
16022
16265
  // — THE CORRECTIVE PASS, on a COLD turn, and it says which pass it reproduced.
@@ -16278,8 +16521,13 @@ export function retiredEnvWarnings(env = process.env) {
16278
16521
  // ABSOLUTE PATHS on purpose: this line is read hours later, possibly from a different directory, and a
16279
16522
  // relative path that silently resolves somewhere else would be a worse answer than no line at all.
16280
16523
  export function resumeCommand({ script, jobPath, codename, agent = null }) { // @internal
16281
- if (!script || !jobPath || !codename) return null;
16282
- return `node ${script} --job ${jobPath}${agent ? ` --agent ${agent}` : ""} --resume ${codename}`;
16524
+ // THE IDENTITY IS THE CODENAME, NOT THE JOB PATH, and requiring both is what made this print nothing
16525
+ // on the failures that most needed it. A resume rebuilds the job from the run's own status when no
16526
+ // `--job` is given, so a command without one is valid — and it is the only valid one by the time a
16527
+ // failure has made a resume necessary, because the queue entry carrying the job is long consumed.
16528
+ // Requiring a path here meant every such failure was told "nothing to resume", which was false.
16529
+ if (!script || !codename) return null;
16530
+ return `node ${script}${jobPath ? ` --job ${jobPath}` : ""}${agent ? ` --agent ${agent}` : ""} --resume ${codename}`;
16283
16531
  }
16284
16532
 
16285
16533
  // The stderr lines a non-clean exit owes its reader, as an array (empty = say nothing further).
@@ -16378,7 +16626,7 @@ if (isEntrypoint(import.meta.url)) void (async () => {
16378
16626
  let a;
16379
16627
  try { a = parseArgv(process.argv.slice(2)); }
16380
16628
  catch (e) { console.error(`error: ${e.message}`); console.error(USAGE); process.exit(2); }
16381
- if (!a.job) { console.error(USAGE); process.exit(2); }
16629
+ if (!a.job && !a.codename) { console.error(USAGE); process.exit(2); } // --resume alone rebuilds the job below
16382
16630
  // composition guards
16383
16631
  if (a.fromStage && !a.codename) { console.error("error: --from requires --resume <codename>"); process.exit(2); }
16384
16632
  if (a.experiment && !a.codename) { console.error("error: --experiment requires --resume <codename>"); process.exit(2); }
@@ -16400,8 +16648,47 @@ if (isEntrypoint(import.meta.url)) void (async () => {
16400
16648
  // mistake a first-time reader is likeliest to make. Kept guarded here through the rebase — the
16401
16649
  // pool check above is a separate refusal and must not swallow this one.
16402
16650
  let job;
16403
- try { job = JSON.parse(readFileSync(a.job, "utf8")); }
16404
- catch (e) { console.error(`error: cannot read job file ${a.job} — ${e.message}`); process.exit(2); }
16651
+ if (!a.job && a.codename) {
16652
+ // ── RESUMING A RUN WHOSE JOB FILE THE BOX NO LONGER HAS ───────────────────────────────────────
16653
+ //
16654
+ // The queue entry carrying the job is consumed when the run starts, so by the time a failure makes
16655
+ // a resume necessary the input needed to perform it is already gone — and the exit advice printed
16656
+ // on that failure composes a command naming a path that is not there. Rebuilt from the run's own
16657
+ // status.json, which the engine wrote FROM the job it ran, and refused unless the rebuild derives
16658
+ // this run's own slug.
16659
+ //
16660
+ // The scan is what makes it possible at all: finding a run by codename needs its slug, and the
16661
+ // slug comes from the job, which is the thing being rebuilt. So the studio root's matters are
16662
+ // scanned for the one holding a run dir with this codename. `findRunDirFor` decides what counts as
16663
+ // that leaf, imported rather than re-spelled here — a second private copy of the leaf rule is how
16664
+ // a reclaim and a resume come to disagree about which directory is the run. The import is lazy
16665
+ // because runner.mjs imports THIS module; at this point both are loaded and nothing cycles.
16666
+ const { findRunDirFor } = await import("./runner.mjs");
16667
+ const studioRoot = a.agent ? config.studioRootForAgent(a.agent) : config.studioRoot;
16668
+ const archiveRoot = a.agent ? config.archiveRootForAgent(a.agent) : config.archiveRoot;
16669
+ let found = null;
16670
+ try {
16671
+ for (const slug of readdirSync(studioRoot)) {
16672
+ const hit = findRunDirFor({ codename: a.codename, slug, studioRoot, archiveRoot });
16673
+ if (hit) { found = { ...hit, slug }; break; }
16674
+ }
16675
+ } catch (e) { console.error(`error: cannot read the studio root ${studioRoot} — ${e.message}`); process.exit(2); }
16676
+ if (!found) {
16677
+ console.error(`error: no run directory for --resume ${a.codename} under ${studioRoot}`);
16678
+ console.error(" pass --job <file.json> if the run lives somewhere this cannot see.");
16679
+ process.exit(2);
16680
+ }
16681
+ let status = null;
16682
+ try { status = JSON.parse(readFileSync(join(found.dir, "status.json"), "utf8")); }
16683
+ catch (e) { console.error(`error: cannot read ${join(found.dir, "status.json")} — ${e.message}`); process.exit(2); }
16684
+ job = jobFromStatus(status);
16685
+ const refusal = resumeJobRefusal(job, found.slug);
16686
+ if (refusal) { console.error(`error: ${refusal}`); process.exit(2); }
16687
+ console.error(`[pipeline] --job not given; rebuilt the job for ${a.codename} from ${join(found.dir, "status.json")} (slug ${found.slug} confirmed)`);
16688
+ } else {
16689
+ try { job = JSON.parse(readFileSync(a.job, "utf8")); }
16690
+ catch (e) { console.error(`error: cannot read job file ${a.job} — ${e.message}`); process.exit(2); }
16691
+ }
16405
16692
  // Manual runs: if --agent is given, root the run-dir in that agent's workspace too (mirrors the runner).
16406
16693
  const base = a.agent
16407
16694
  ? { agent: a.agent, studioRoot: config.studioRootForAgent(a.agent), archiveRoot: config.archiveRootForAgent(a.agent) }
@@ -16413,7 +16700,10 @@ if (isEntrypoint(import.meta.url)) void (async () => {
16413
16700
  ? { ...base, codename: a.codename, experiment: a.experiment, model: a.model, instructions: a.instructions, axis: a.axis, label: a.label, dispatchTrigger: a.dispatchTrigger }
16414
16701
  : { ...base, codename: a.codename, fromStage: a.fromStage };
16415
16702
  const script = resolve(fileURLToPath(import.meta.url));
16416
- const advise = (o) => { for (const line of resumeAdvice({ script, jobPath: resolve(a.job), agent: a.agent ?? null, experiment: Boolean(a.experiment), ...o })) console.error(line); };
16703
+ // `jobPath` is null on a rebuilt resume, so the advice composes a command that does not name a file
16704
+ // the box does not have — which is the whole defect this path exists to close, and printing it again
16705
+ // in the exit advice would reintroduce it one layer along.
16706
+ const advise = (o) => { for (const line of resumeAdvice({ script, jobPath: a.job ? resolve(a.job) : null, agent: a.agent ?? null, experiment: Boolean(a.experiment), ...o })) console.error(line); };
16417
16707
 
16418
16708
  // ── the laptop case: a closed lid, a shutdown, a ^C ────────────────────────────────────────────────
16419
16709
  // Until now this CLI had NO signal handler: node's default die-now killed the process mid-stage, the run
@@ -16516,3 +16806,28 @@ if (isEntrypoint(import.meta.url)) void (async () => {
16516
16806
  //
16517
16807
  // Move it to the top the day those citations name symbols instead of numbers — which is what
16518
16808
  // CONTRIBUTING.md asks for, and what makes them checkable at all.
16809
+
16810
+ /**
16811
+ * The verdict conditions for an unfinished register search: the run record's sentence, and the client's.
16812
+ *
16813
+ * THE CLIENT'S CLAUSE IS THE SAME SENTENCE WITH THE ENGINE TOKEN TAKEN OUT (ruled 2026-09-18). The axis
16814
+ * names — `primary-sweep`, `incumbent-class` — are how the engine files a register slice, and they reached
16815
+ * the verdict a client reads as "register coverage deferred on primary-sweep". Nothing is written in their
16816
+ * place: the client reads the remaining words, and the run record keeps the axes for whoever repairs the run.
16817
+ * PURE.
16818
+ *
16819
+ * @returns {{ reason: string, clause: string }[]}
16820
+ */
16821
+ export function registerGapConditions(regGap) { // @internal
16822
+ const out = [];
16823
+ const deferredAxes = [...new Set((regGap?.deferred ?? []).map((g) => g?.axis).filter(Boolean))];
16824
+ if ((regGap?.deferred ?? []).length) out.push({
16825
+ reason: `register coverage deferred on ${deferredAxes.join(", ")} — the search did not finish and must be re-run before this can be relied on`,
16826
+ clause: "register coverage deferred — the search did not finish and must be re-run before this can be relied on",
16827
+ });
16828
+ if ((regGap?.taintAxes ?? []).length) out.push({
16829
+ reason: `the ${regGap.taintAxes.join(", ")} register pass was cut down at the timeout wall and its self-reported coverage is unverified`,
16830
+ clause: "the register pass was cut down at the timeout wall and its self-reported coverage is unverified",
16831
+ });
16832
+ return out;
16833
+ }