clearotron 0.3.2-beta.8 → 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 (238) hide show
  1. package/.env.example +34 -3
  2. package/CONTRIBUTING.md +8 -8
  3. package/INSTALL.md +6 -6
  4. package/SECURITY.md +3 -3
  5. package/bin/brandowner.mjs +3 -3
  6. package/bin/framework-preflight.mjs +1 -1
  7. package/bin/start.mjs +18 -4
  8. package/build-info.json +2 -2
  9. package/docs/DELIVERY.md +2 -1
  10. package/docs/INTAKE.md +1 -1
  11. package/docs/ONBOARDING.md +1 -1
  12. package/docs/architecture/03-run-lifecycle.md +6 -6
  13. package/docs/architecture/04-configuration-reference.md +6 -3
  14. package/docs/architecture/05-config-governance.md +6 -1
  15. package/docs/architecture/05-customer-profiles.md +2 -2
  16. package/docs/architecture/06-operations-runbook.md +3 -3
  17. package/docs/architecture/08-development-guide.md +6 -6
  18. package/docs/configuration.md +5 -5
  19. package/docs/decisions/0003-credential-model.md +1 -1
  20. package/docs/writing-standard.md +4 -0
  21. package/driver/CHANGELOG.md +48 -0
  22. package/driver/README.md +3 -3
  23. package/driver/binding-layers.mjs +1 -1
  24. package/driver/citation-census.json +3 -3
  25. package/driver/{prelim-variants-record.mjs → clearance-variants-record.mjs} +24 -24
  26. package/driver/common-law-receipts.mjs +2 -2
  27. package/driver/company-bundle.mjs +3 -3
  28. package/driver/compose-read.mjs +8 -14
  29. package/driver/consumption-ledger.mjs +2 -2
  30. package/driver/contract-arm2-baseline.json +2 -3
  31. package/driver/contract-dictation-registry.mjs +19 -19
  32. package/driver/contract-e3-backlog.mjs +19 -19
  33. package/driver/contract-e3-baseline.json +14 -14
  34. package/driver/contract-vocabulary.mjs +24 -17
  35. package/driver/deliver-trigger.sh +16 -16
  36. package/driver/demo-container.mjs +3 -3
  37. package/driver/dev-portal.mjs +3 -3
  38. package/driver/disposition-call.mjs +1 -1
  39. package/driver/doubt-ledger.mjs +2 -2
  40. package/driver/drainer-identity.mjs +34 -8
  41. package/driver/driver.config.mjs +95 -45
  42. package/driver/engine/mcp/README.md +1 -1
  43. package/driver/engine/mcp/dispositions-server.mjs +3 -3
  44. package/driver/engine/mcp/gather-config.mjs +9 -9
  45. package/driver/engine/mcp/perplexity-server.mjs +2 -2
  46. package/driver/engine/mcp/recording-server.mjs +5 -5
  47. package/driver/enqueue-schema.mjs +6 -2
  48. package/driver/findings-model.mjs +5 -2
  49. package/driver/flag-snapshot.mjs +6 -3
  50. package/driver/form-neighbourhood.mjs +54 -7
  51. package/driver/framework.mjs +4 -4
  52. package/driver/gateway.mjs +12 -6
  53. package/driver/jx-lanes.mjs +2 -2
  54. package/driver/jx-units.mjs +1 -1
  55. package/driver/jx.mjs +30 -2
  56. package/driver/knockout-review-record.mjs +56 -4
  57. package/driver/known-conflicts.mjs +1 -1
  58. package/driver/named-band.mjs +1 -33
  59. package/driver/ordinary-words.mjs +51 -0
  60. package/driver/outbox-backoff.mjs +31 -16
  61. package/driver/package.json +1 -1
  62. package/driver/partial-payload-baseline.json +2 -2
  63. package/driver/phase0.mjs +3 -3
  64. package/driver/pipeline-knockout.mjs +5 -5
  65. package/driver/pipeline.mjs +206 -68
  66. package/driver/placement-form.mjs +77 -1
  67. package/driver/placement-model.mjs +1 -1
  68. package/driver/portal-report.mjs +92 -5
  69. package/driver/portal-service.mjs +34 -8
  70. package/driver/portal-upstream.mjs +1 -1
  71. package/driver/preserve-merge.mjs +3 -3
  72. package/driver/product-rows.mjs +2 -2
  73. package/driver/products.mjs +1 -1
  74. package/driver/profiles/README.md +3 -3
  75. package/driver/profiles/demo-brand-owner.json +2 -2
  76. package/driver/profiles.mjs +55 -17
  77. package/driver/progress.mjs +18 -8
  78. package/driver/provider-usage.mjs +8 -8
  79. package/driver/publish/index.mjs +110 -5
  80. package/driver/publish/knockout.mjs +29 -4
  81. package/driver/publish/pool-admin.mjs +1 -1
  82. package/driver/publish/publish-inputs.mjs +18 -2
  83. package/driver/publish/render-knockout.mjs +115 -24
  84. package/driver/publish/render.mjs +153 -34
  85. package/driver/publish/search-depth.mjs +133 -4
  86. package/driver/publish/templates/report.css +60 -3
  87. package/driver/publish/xlsx.mjs +8 -1
  88. package/driver/queue-order.mjs +2 -2
  89. package/driver/recording-agreement.mjs +1 -1
  90. package/driver/reference-score.mjs +1 -1
  91. package/driver/register-count.mjs +50 -5
  92. package/driver/register-coverage.mjs +67 -0
  93. package/driver/register-grant-vocabulary.mjs +1 -1
  94. package/driver/register-plan.mjs +19 -2
  95. package/driver/registry-fidelity.mjs +3 -3
  96. package/driver/repair-composers.mjs +1 -1
  97. package/driver/repair-contract.mjs +1 -1
  98. package/driver/replay-archive.mjs +6 -6
  99. package/driver/report-overview-record.mjs +2 -2
  100. package/driver/run-requirements.mjs +3 -3
  101. package/driver/runner.mjs +2 -2
  102. package/driver/scope-facts.mjs +20 -5
  103. package/driver/scope-ledger.mjs +5 -5
  104. package/driver/search-policy.mjs +22 -12
  105. package/driver/skills/README.md +15 -15
  106. package/driver/skills/blind-frame/SKILL.md +2 -2
  107. package/driver/skills/case-law-citation/SKILL.md +4 -4
  108. package/driver/skills/case-law-citation/sources/eurlex.md +1 -1
  109. package/driver/skills/{prelim-common-law → clearance-common-law}/SKILL.md +22 -22
  110. package/driver/skills/{prelim-common-law → clearance-common-law}/perplexity-prompts.md +1 -1
  111. package/driver/skills/{prelim-register → clearance-register}/SKILL.md +10 -10
  112. package/driver/skills/{prelim-register → clearance-register}/digest.md +2 -2
  113. package/driver/skills/{prelim-register → clearance-register}/providers/README.md +1 -1
  114. package/driver/skills/{prelim-register → clearance-register}/providers/clarivate.md +37 -35
  115. package/driver/skills/{prelim-register → clearance-register}/providers/corsearch.md +20 -11
  116. package/driver/skills/{prelim-register → clearance-register}/providers/signa.md +5 -5
  117. package/driver/skills/{prelim-register → clearance-register}/register-recipes.md +3 -3
  118. package/driver/skills/{prelim-register → clearance-register}/status-rules.md +2 -2
  119. package/driver/skills/{prelim-register → clearance-register}/stealth-filer-indicators.md +1 -1
  120. package/driver/skills/{prelim-register → clearance-register}/unit.md +2 -2
  121. package/driver/skills/{prelim-search → clearance-search}/SKILL.md +31 -31
  122. package/driver/skills/{prelim-search → clearance-search}/delivery-contract.md +1 -1
  123. package/driver/skills/{prelim-search → clearance-search}/phase2-execution.md +18 -18
  124. package/driver/skills/{prelim-search → clearance-search}/synthesis-rules.md +7 -7
  125. package/driver/skills/{prelim-variants → clearance-variants}/SKILL.md +18 -18
  126. package/driver/skills/{prelim-variants → clearance-variants}/transliteration-scripts.md +5 -5
  127. package/driver/skills/frame-diff/SKILL.md +1 -1
  128. package/driver/skills/knockout-assess/SKILL.md +10 -7
  129. package/driver/skills/matter-frame/SKILL.md +3 -3
  130. package/driver/skills/narrative-refutation/SKILL.md +9 -9
  131. package/driver/skills/placement-inquiry/SKILL.md +5 -5
  132. package/driver/stage-context.mjs +1 -1
  133. package/driver/stages-knockout.mjs +4 -4
  134. package/driver/stages.mjs +53 -53
  135. package/driver/status-snapshot.mjs +2 -2
  136. package/driver/suite-census.json +218 -92
  137. package/driver/surface-exit-verdict.mjs +58 -0
  138. package/driver/systemd/README.md +2 -2
  139. package/driver/systemd/clearotron-worker.service +1 -1
  140. package/driver/terminal-clamp.mjs +2 -0
  141. package/driver/usage-ledger.mjs +1 -1
  142. package/driver/variant-manifest-model.mjs +4 -4
  143. package/driver/verify-knockout.mjs +27 -0
  144. package/driver/verify.mjs +67 -6
  145. package/driver/whatif-queue.mjs +1 -1
  146. package/driver/wordlists/en.txt +63906 -0
  147. package/mcp-server/CHANGELOG.md +4 -0
  148. package/mcp-server/README.md +1 -1
  149. package/mcp-server/lib/README.md +1 -1
  150. package/mcp-server/lib/options.mjs +8 -7
  151. package/mcp-server/lib/plan.mjs +18 -2
  152. package/mcp-server/lib/runs.mjs +1 -1
  153. package/mcp-server/lib/usage.mjs +3 -3
  154. package/mcp-server/lib/whatif.mjs +1 -1
  155. package/mcp-server/package.json +1 -1
  156. package/mcp-server/server.mjs +3 -0
  157. package/package.json +12 -11
  158. package/portal-ui/dist/assets/{index-CVOIvdhc.css → index-CtvwLCti.css} +207 -3
  159. package/portal-ui/dist/assets/{index-6jzO9HiX.js → index-EVaSo5-g.js} +1459 -482
  160. package/portal-ui/dist/index.html +2 -2
  161. package/portal-ui/package.json +1 -1
  162. package/providers/README.md +1 -1
  163. package/providers/_shared/enumerate.mjs +6 -6
  164. package/providers/_shared/execute-plan.mjs +3 -3
  165. package/providers/_shared/ledger.mjs +119 -5
  166. package/providers/_shared/provider-text.mjs +2 -2
  167. package/providers/_shared/screen.mjs +2 -2
  168. package/providers/_shared/script-form.mjs +3 -3
  169. package/providers/_shared/territory-codes.mjs +23 -3
  170. package/providers/clarivate/README.md +1 -1
  171. package/providers/clarivate/src/capabilities.js +12 -12
  172. package/providers/clarivate/src/core.js +37 -43
  173. package/providers/corsearch/README.md +1 -1
  174. package/providers/corsearch/src/capabilities.js +5 -5
  175. package/providers/corsearch/src/core.js +3 -3
  176. package/providers/oauth-mcp-bridge/CHANGELOG.md +4 -0
  177. package/providers/oauth-mcp-bridge/package.json +1 -1
  178. package/providers/perplexity/src/core.js +1 -1
  179. package/providers/signa/README.md +1 -1
  180. package/providers/signa/src/capabilities.js +42 -49
  181. package/providers/signa/src/core.js +106 -29
  182. package/providers/uspto-local/src/sync.js +1 -1
  183. package/scripts/README.md +1 -0
  184. package/scripts/ask-ai-render-check.mjs +127 -1
  185. package/scripts/authority-boundary-probe.mjs +4 -4
  186. package/scripts/backfill-started-at.mjs +2 -2
  187. package/scripts/census-merge-driver.mjs +33 -2
  188. package/scripts/citation-anchor-report.mjs +181 -0
  189. package/scripts/dead-names.mjs +1 -1
  190. package/scripts/deprecate-below.mjs +66 -8
  191. package/scripts/drain-preflight.mjs +1 -1
  192. package/scripts/e2e.mjs +174 -0
  193. package/scripts/env-audit.mjs +27 -0
  194. package/scripts/env-classify.mjs +20 -2
  195. package/scripts/freeze-example-run.mjs +20 -10
  196. package/scripts/live-surface-check.mjs +124 -41
  197. package/scripts/markdown-link-check.mjs +1 -1
  198. package/scripts/merge-shape-check.mjs +242 -0
  199. package/scripts/mint-names-in-force.mjs +5 -5
  200. package/scripts/mint-offered-territories.mjs +72 -0
  201. package/scripts/mint-public-residue.mjs +2 -2
  202. package/scripts/mint-reference-strip-backlog.mjs +2 -2
  203. package/scripts/mint-suite-census.mjs +75 -2
  204. package/scripts/mint-writing-standard-backlog.mjs +2 -2
  205. package/scripts/purge-runs.mjs +7 -7
  206. package/scripts/reconcile-runs.mjs +2 -2
  207. package/scripts/release-approve-parked.mjs +20 -2
  208. package/scripts/release-await-cut.mjs +120 -1
  209. package/scripts/release-note-required.mjs +76 -8
  210. package/scripts/report-header-render-check.mjs +164 -0
  211. package/shared/brand.mjs +27 -0
  212. package/shared/connect-clients.mjs +39 -11
  213. package/shared/env-aliases.mjs +1 -1
  214. package/shared/identifier-scan.mjs +65 -9
  215. package/shared/identifier-sentinels.mjs +22 -0
  216. package/shared/names-in-force.mjs +3 -1
  217. package/shared/offered-territories.json +738 -0
  218. package/shared/pre-rename-spellings.mjs +53 -0
  219. package/shared/reference-guard-classes.mjs +40 -2
  220. package/shared/stdio-connect.mjs +39 -4
  221. package/shared/tree-commit.mjs +48 -0
  222. /package/driver/skills/{prelim-register → clearance-register}/providers/euipo.md +0 -0
  223. /package/driver/skills/{prelim-register → clearance-register}/providers/free-tier.md +0 -0
  224. /package/driver/skills/{prelim-register → clearance-register}/providers/uspto-local.md +0 -0
  225. /package/driver/skills/{prelim-search → clearance-search}/field-doctrine-pharma.md +0 -0
  226. /package/driver/skills/{prelim-search → clearance-search}/firm-wide-reasoning.md +0 -0
  227. /package/driver/skills/{prelim-search → clearance-search}/report-prose.md +0 -0
  228. /package/driver/skills/{prelim-search → clearance-search}/risk-framework-demo.manifest.json +0 -0
  229. /package/driver/skills/{prelim-search → clearance-search}/risk-framework-demo.md +0 -0
  230. /package/driver/skills/{prelim-search → clearance-search}/risk-framework-triage.manifest.json +0 -0
  231. /package/driver/skills/{prelim-search → clearance-search}/risk-framework-triage.md +0 -0
  232. /package/driver/skills/{prelim-search → clearance-search}/risk-framework.manifest.json +0 -0
  233. /package/driver/skills/{prelim-search → clearance-search}/risk-framework.md +0 -0
  234. /package/driver/skills/{prelim-search → clearance-search}/template-formatting.md +0 -0
  235. /package/driver/skills/{prelim-search → clearance-search}/templates/email/generic.md +0 -0
  236. /package/driver/skills/{prelim-search → clearance-search}/templates/search-request-form.html +0 -0
  237. /package/driver/skills/{prelim-search → clearance-search}/worked-examples-demo.md +0 -0
  238. /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
 
@@ -80,7 +80,7 @@ import { resolveSearchPolicy, gateResolvedPolicy, loadRecipes, policyFor, isRegi
80
80
  import { profileOrdinals } from "./profile-selection.mjs"; // lever 3 — driver selection
81
81
  // THE OFFERING'S own sentence about where the native-language investigation can be bought. It reaches a
82
82
  // CLIENT REPORT through the script-scope coverage row, which is why it is imported rather than typed:
83
- // 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.
84
84
  import { NATIVE_LANGUAGE_REMEDY } from "./products.mjs";
85
85
  import { resolveTerritories, defaultTerritoryState } from "./effective-scope.mjs"; // the ONE territory ladder (the geography stamp included) + the stored-defaults reading
86
86
  import { acquireSlot, releaseSlot } from "./slot-lock.mjs";
@@ -170,10 +170,10 @@ import { JURISDICTION_ADAPTERS, LANGUAGE_LANES, scopeJurisdictions as jxScopeJur
170
170
  // into boundDominantElementFrom, so the driver's bind and the record tool's bind are ONE body. Neither
171
171
  // parseBlindFrameModel nor dominantElementFromManifest is called from this file any more.
172
172
  import { boundDominantElementFrom } from "./frame-diff-record.mjs";
173
- import { recordedScopeLedgerRows } from "./prelim-variants-record.mjs";
173
+ import { recordedScopeLedgerRows } from "./clearance-variants-record.mjs";
174
174
  import { parseFrameDiff, applyDominantBackstop, firingDirectives, reopenKey, alreadyAttemptedReopen, partitionFiring, frameResidualGaps, jurisdictionScopeFlags, deriveDirectiveRemedy, firingDirectivesLenient } from "./frame-diff-model.mjs";
175
175
  import { verifyRegisterDirectiveClose } from "./close-verify.mjs";
176
- 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";
177
177
  import { findRecallFloorViolations, findReviewFreshnessViolation, findSeedNeutralityViolations, findProbativeGradingViolations, findStatusHonestyViolation, findMatrixCeilingViolations, findDeadlineUrgencyMiss, findUnresolvedDisagreements, findOrphanVerificationFlags, findUncrossCheckedDemotions, findRecallRegressionViolations, findDeadlineCarryViolations, formatRecallRegression } from "./reasoning-tripwires.mjs";
178
178
  import { findRuleShapeFlags } from "./rule-shape.mjs";
179
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";
@@ -939,7 +939,7 @@ function deriveGridSpec(ctx) {
939
939
  // 2026-08-11 codex R2 run (no grid spec authored at all), and it is the branch that emits NO event
940
940
  // today, which is why absence was the only signal and absence is not a record.
941
941
  // The three inputs are recorded, not just their verdict. Working hit exactly this wall: the
942
- // 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
943
943
  // was false could not be recovered from any artifact — so the round could name the path but not the
944
944
  // cause. A record that says "no spec" and not "no spec BECAUSE the profile carried no platforms" just
945
945
  // moves the same unanswerable question one step along.
@@ -1720,7 +1720,7 @@ export function attachSearchPolicy(ctx, job, { write = true } = {}) { // @inte
1720
1720
  // a clearance (review 2026-07-17 — the crash-reclaim/reconstruct window).
1721
1721
  if (!noSelector)
1722
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`);
1723
- 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" } };
1724
1724
  } else {
1725
1725
  let resolved;
1726
1726
  // force:true — the recipe store is written by a LIVE service; a long-lived driver process must see a
@@ -1905,7 +1905,7 @@ function renderDocumentCoverageFromRecords(ctx, trigger) {
1905
1905
  // The set the GATES consume, not `_records/` alone: a run whose bodies live only in the ledger would
1906
1906
  // otherwise be described as one that fetched nothing — the defect this renders against, re-created by
1907
1907
  // reading a narrower artifact than the one that holds the answer.
1908
- 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}-`);
1909
1909
  const summary = documentCoverage({ records, findings: safeReadJson(P.findings) });
1910
1910
  const section = renderDocumentCoverageSection(summary);
1911
1911
  if (!section) return;
@@ -2103,7 +2103,7 @@ function deriveCoverageLedgerJson(ctx, trigger) {
2103
2103
  }
2104
2104
  }
2105
2105
 
2106
- // 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
2107
2107
  // `### Scope ledger` table (so the JSON is authored by the driver, not the model, and matches the prose
2108
2108
  // by construction — exactly the deriveCoverageLedgerJson pattern). NEVER-KILL: a manifest with no/unparseable
2109
2109
  // Scope ledger (legacy artifacts, a terse run) logs a note and skips the write; the frame-diff then reads the
@@ -2132,7 +2132,7 @@ function deriveScopeLedgerJson(ctx) {
2132
2132
  // are different facts about a run, and a reader of an archived ledger must be able to tell which.
2133
2133
  runLog(P.runDir, { event: "scope-ledger-derived", source: recordedRows ? "typed-call" : "prose-parse" });
2134
2134
  } catch (e) {
2135
- 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`);
2136
2136
  runLog(P.runDir, { event: "scope-ledger-skipped", reason: `derive_failed:${String(e.message).slice(0, 80)}` });
2137
2137
  }
2138
2138
  }
@@ -2179,14 +2179,14 @@ function deriveFormNeighbourhood(ctx) {
2179
2179
  // The MARK is the floor's fallback seed and the only input to it that is not model output —
2180
2180
  // resolved exactly as every other job-mark consumer resolves it (stages.mjs blind-frame).
2181
2181
  const mark = ctx.job.marks ?? ctx.job.markName ?? ctx.job.name ?? "";
2182
- 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
2183
2183
  const tmp = P.formNeighbourhood + ".tmp";
2184
2184
  writeFileSync(tmp, json);
2185
2185
  renameSync(tmp, P.formNeighbourhood);
2186
- let exact = 0, floor = 0, added = 0, seededFrom = "", seeds = [];
2186
+ let exact = 0, floor = 0, added = 0, seededFrom = "", seeds = [], notSearched = 0;
2187
2187
  try {
2188
2188
  const o = JSON.parse(json);
2189
- 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);
2190
2190
  floor = o.variant_floor?.counts?.floor ?? 0;
2191
2191
  added = o.variant_floor?.counts?.model_added ?? 0;
2192
2192
  seededFrom = String(o.seeded_from ?? "");
@@ -2194,11 +2194,11 @@ function deriveFormNeighbourhood(ctx) {
2194
2194
  } catch { /* counts are telemetry only */ }
2195
2195
  // The SEED SET is logged by name, because it is the input that decides whether two runs of the same
2196
2196
  // matter can produce the same floor — a floor count alone cannot tell a stable run from a re-anchored one.
2197
- 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 } : {}) });
2198
2198
  // The fallback is not a quiet degrade: it means this run's variant floor rests on the mark alone
2199
2199
  // because the stage named no usable element. Loud, so the absence is a finding and not a shrug.
2200
2200
  if (seededFrom.startsWith("job mark"))
2201
- 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)`);
2202
2202
  } catch (e) {
2203
2203
  // The reason is carried WHOLE. It used to be truncated to 80 characters into the runLog and
2204
2204
  // 100 into the note, which cut the part that says WHICH cause fired — and the empty floor this path
@@ -2206,7 +2206,7 @@ function deriveFormNeighbourhood(ctx) {
2206
2206
  // compiles and freezes with no floor entries and no error, looking healthy. This line is the only
2207
2207
  // record that the mechanical floor is missing from the run, so it says the whole of why.
2208
2208
  const why = String(e.message);
2209
- 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`);
2210
2210
  runLog(P.runDir, { event: "form-neighbourhood-skipped", reason: `derive_failed:${why}`, mark: markText(ctx.job.marks ?? ctx.job.markName ?? ctx.job.name ?? "") });
2211
2211
  }
2212
2212
  }
@@ -2297,7 +2297,7 @@ async function verifyAndRecordHouseElement(ctx, opts = {}) {
2297
2297
  const { regions } = resolvePlanRegions(registerJurisdictions(ctx.job, ctx.profile), caps);
2298
2298
  const rec = resolveRecordExecutor({
2299
2299
  lister: opts?.recordLister ?? null, adapter: activeProvider(),
2300
- agentId: ctx.agentId ?? null, sessionKey: `prelim-${ctx.run.slug}-${ctx.run.codename}`,
2300
+ agentId: ctx.agentId ?? null, sessionKey: `clearance-${ctx.run.slug}-${ctx.run.codename}`,
2301
2301
  recordLog: runRecordLogPath(P.runDir),
2302
2302
  fixtureDir: ctx.job?.registerFixtures?.records ?? null,
2303
2303
  });
@@ -2385,7 +2385,12 @@ function attachRegisterPlan(ctx, { frozenOnly = false } = {}) {
2385
2385
  // sweep); fatal on a provider whose regions[] is mandatory, where every entry then errored on its
2386
2386
  // count probe and the whole plan joined MISSING at fan-in (review finding 11).
2387
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) },
2388
- 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,
2389
2394
  // phase 3 — the plan is compiled AGAINST THE ACTIVE PROVIDER's declared capabilities, so the
2390
2395
  // frozen artifact is executable by construction: the OR-stack split uses that provider's width,
2391
2396
  // jurisdictions are translated into its office vocabulary (EU→EM on Compumark), and a predicate
@@ -3833,7 +3838,7 @@ export function digestDispatchExtra(ctx, { trigger = "fresh", willRun = true, ex
3833
3838
  // The old `findFloorBreaches` read ⭐-marked lines out of the manifest TEXT, tokenised them, dropped
3834
3839
  // generic words and matched the remainder against coverage-row text. That join is why it needed free text
3835
3840
  // at all, and it is what conversion 3 made unreachable. This one reads `search_floor` — axis names, closed
3836
- // 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
3837
3842
  // both sides type. No tokens, no matching, nothing to go quietly wrong.
3838
3843
  //
3839
3844
  // A BREACH IS: an axis this mark's floor obliges, carrying a row labelled `coverage-limited`. That label
@@ -3859,7 +3864,7 @@ export function findFloorBreaches(ledger, floorAxes) { // @internal
3859
3864
  * a pass, because the whole mechanism is opt-in — a run with no designation owes no floor, and a run whose
3860
3865
  * manifest cannot be read has no designation to honour. The refusal for an absent or unparseable manifest
3861
3866
  * belongs to the stage that writes it and already exists there — verify.mjs:1335 runs the same parser
3862
- * through `checkSiblingJson` and fails prelim-variants with `variantmodel_missing`. Checked, because
3867
+ * through `checkSiblingJson` and fails clearance-variants with `variantmodel_missing`. Checked, because
3863
3868
  * "something else refuses it" is exactly the assumption that turns a swallowed error into a silent pass.
3864
3869
  */
3865
3870
  export function readFloorAxes(paths) { // @internal
@@ -3868,7 +3873,7 @@ export function readFloorAxes(paths) { // @internal
3868
3873
  }
3869
3874
 
3870
3875
  // A never-active axis self-writes a harmless "not applicable" digest — the skill MANDATES the exact
3871
- // 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"`,
3872
3877
  // `reason:"not applicable — <why>"`), so the coverage-ledger row lands as `<axis> / axis not applicable`.
3873
3878
  // It is `deferred` only because a non-search must not be dressed as a clean — it is NOT floor work left
3874
3879
  // open. Keyed on the self-digest scope (deterministic, skill-dictated), never a genuine floor's
@@ -4059,7 +4064,7 @@ export async function runSaturationProbeCodeSide(ctx, planExec) { // @internal
4059
4064
  const label = `register-unit:${a}`;
4060
4065
  const out = P.registerUnit(a);
4061
4066
  const bandPath = P.registerBand(a);
4062
- 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}`;
4063
4068
  // crash-resume idempotency — mirrors stageOnce's skip: output present + valid ⇒ never re-execute.
4064
4069
  const forced = forceFromActive(ctx, "register-unit");
4065
4070
  // — hoisted above the skip return. Below it, this member's dispatch row carries boundaries and its
@@ -4179,7 +4184,7 @@ function writeSatProbeAuditNote({ ctx, out, bandPath, entries }) {
4179
4184
 
4180
4185
  // ── stage contracts (2026-07-30 review round) ──────────────────────────────────────────────────────
4181
4186
  // _driver/stage-contracts.json records, PER STAGE, the output contract the prompt THIS driver
4182
- // 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
4183
4188
  // `contract:{romanization:1}`). Written at DISPATCH time only — a skipped stage never gains one — so
4184
4189
  // it is evidence of PROMPT VINTAGE, which is the only honest gate for a new validation rule:
4185
4190
  // - crash-resume: the skip check re-validates the completed output BEFORE any dispatch, marker
@@ -4343,7 +4348,7 @@ async function stageOnce(name, ctx, opts = {}) {
4343
4348
  // skips — i.e. on the babysit surface, in the middle of the shift where someone is watching it.
4344
4349
  runLog(P.runDir, { event: "skip", stage: label, trigger: "skip", model: won?.model ?? resolveModel(model), output: outputMeta(out), ...stageWallFields(tDispatch) });
4345
4350
  (ctx.skippedStages ??= []).push(label); // V4-1: the lint receipt states which artifacts this delivery inherited
4346
- 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)
4347
4352
  // `won ? won.key : …` (NOT `won?.key ?? …`): a recovered CODE-SIDE winner carries key:null on purpose —
4348
4353
  // falling back to opts.sessionKey there would re-mint the phantom session the null exists to suppress.
4349
4354
  return { ok: true, skipped: true, sessionKey: won ? won.key : opts.sessionKey, model: won?.model ?? model };
@@ -4354,6 +4359,25 @@ async function stageOnce(name, ctx, opts = {}) {
4354
4359
  // output under the rules it is being minted under. See recordStageContract's doc block.
4355
4360
  if (def.contract) recordStageContract(P.runDir, name, def.contract);
4356
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
+
4357
4381
  // — THE PLACEMENT FORM IS ON DISK BEFORE THE SEAT IS ASKED ANYTHING, and it is written at the SAME
4358
4382
  // choke point every dispatch funnels through: the main path, the stale-repair path and the frame-reopen
4359
4383
  // path all reach the seat through here, and three separate pre-dispatch writes would be three chances
@@ -4491,7 +4515,7 @@ async function stageOnce(name, ctx, opts = {}) {
4491
4515
  thinking,
4492
4516
  // opts.sessionKey lets a followup RESUME the exact key a prior run won on (winning-key hardening); else
4493
4517
  // the canonical base key. (A followup with a stale base key would resume a failed attempt — see runStage.)
4494
- sessionKey: opts.sessionKey ?? `prelim-${ctx.run.slug}-${ctx.run.codename}-${name}${keyAxis}`,
4518
+ sessionKey: opts.sessionKey ?? `clearance-${ctx.run.slug}-${ctx.run.codename}-${name}${keyAxis}`,
4495
4519
  timeoutSec: derivedLimit.sec ?? def.timeoutSec,
4496
4520
  derivedLimit, // recorded on every attempt row: the size it was derived from, and the number
4497
4521
  stallSec: def.stallSec, // per-stage stall override (heavy stages); undefined → global CLEAROTRON_STALL_MS
@@ -4580,6 +4604,8 @@ async function stageOnce(name, ctx, opts = {}) {
4580
4604
  });
4581
4605
  // P2 — record WHAT this output was produced from, so a later resume can tell whether it still holds.
4582
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.
4583
4609
  if (r.ok) recordTransition(ctx, name); // bare name → fan-out axes collapse to one display step (monotonic)
4584
4610
  // PR-4 — document-growth tripwire, keyed on TRIGGER so it covers BOTH growth loops (the lint-repair
4585
4611
  // "re-emit the COMPLETE updated file" cycle AND the skeptic/refutation corrective loop — the 2026-07-28 postmortem run's
@@ -4770,7 +4796,7 @@ function must(r, name) {
4770
4796
  * absence reads as an empty cause — which is the whole defect, one level along. PURE.
4771
4797
  *
4772
4798
  * — VISIBLE IN THE STRING, not only in a sibling key. `reason` was a bare slice, so a cut sentence
4773
- * 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"
4774
4800
  * — and the reader who stat'd that directory got ENOENT and diagnosed a working mechanism as a broken
4775
4801
  * one. Nothing had been lost: `reasonFull` carried the whole path on all three and `reasonTruncated` was
4776
4802
  * true on all three. The field that gets read FIRST simply never said it was short. `abbrev` is this
@@ -5390,7 +5416,7 @@ export function recordConnotationAudit(run, P) { // @internal
5390
5416
  // ── — DOES A CHARGED RATING CARRY ITS GROUNDS? RECORDED, NOT ENFORCED ───────────────────────
5391
5417
  //
5392
5418
  // Doctrine already asks for this in as many words: "rule `loaded` and use the note to state plainly
5393
- // 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
5394
5420
  // seat does not do it — e2e READ all 24 `loaded` notes in the corpus (not keyword-probed them) and
5395
5421
  // every one describes the material instead. `grounds-grammar.mjs` was built to measure exactly that,
5396
5422
  // proven against those real notes and against a planted violation, AND CALLED BY NOTHING. A check
@@ -6254,7 +6280,7 @@ function refuteRegistryCheckExtra(ctx) {
6254
6280
  const P = ctx.paths;
6255
6281
  const narrative = existsSync(P.narrative) ? readFileSync(P.narrative, "utf8") : "";
6256
6282
  if (!narrative) return "";
6257
- 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;
6258
6284
  const probs = [
6259
6285
  ...findRegistryArithmeticIssues(narrative).map((a) => `${a.block}: ${a.detail}`),
6260
6286
  ...findRegistryViolations(narrative, recordsByUri).filter((v) => v.kind === "mismatch")
@@ -7127,7 +7153,7 @@ function injectDeferralCoverage(P, runDir, note) {
7127
7153
  // ── THE REMEDY CLAUSE NAMES A PRODUCT, AND FOR A WHILE IT NAMED A DELETED ONE ───────────────────────
7128
7154
  //
7129
7155
  // This row reaches a CLIENT REPORT. It read "…not searched at this level — available at Depth 5
7130
- // (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
7131
7157
  // the remedy for a coverage limit the same sentence had just disclosed. `injectScriptScopeCoverage`
7132
7158
  // calls the decision on EVERY run, and it was driven — it fires for a worldwide scope, a CN-named scope
7133
7159
  // and the ja lane — so this was delivered prose telling a reader to buy something that does not exist.
@@ -7141,7 +7167,7 @@ const WORLDWIDE_SCOPE_RE = /^(worldwide|global|all|all[- ]jurisdictions)$/i;
7141
7167
  /** What a synthesis-authored Stage-1.5 coverage row looks like when it has ALREADY made this
7142
7168
  * disclosure — see the suppression in injectScriptScopeCoverage. Both the current vocabulary and the
7143
7169
  * retired one, because a resumed run carries whichever was current when its synthesis ran. */
7144
- 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"]);
7145
7171
 
7146
7172
  /** The disclosure vocabulary for ONE candidate lane, derived (never tabulated). Returns
7147
7173
  * `{area, note, territories, marker}` — `marker` is the lowercased script prefix the suppression
@@ -7265,14 +7291,14 @@ export function injectScriptScopeCoverage(P, runDir, note, { searchPolicy = null
7265
7291
  const rowText = (c) => `${c?.area ?? ""} ${c?.note ?? ""}`.toLowerCase();
7266
7292
  const added = [];
7267
7293
  for (const p of pending) {
7268
- // 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
7269
7295
  // covering the disclosure, which was harmless while zh was the only lane and is a silent row-
7270
7296
  // eater the moment there are two: on a CN+JP scope the zh row lands first, its own note ended in
7271
- // "(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
7272
7298
  // deference to a synthesis-authored Stage-1.5 row requires the row to be about THIS lane.
7273
7299
  //
7274
7300
  // TWO VOCABULARIES ON PURPOSE. `native-language investigation` is what the offering calls it now
7275
- // 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
7276
7302
  // RESUMED from before carries a synthesis row written in it — dropping the old token would
7277
7303
  // put a second, duplicate disclosure into a report that already makes the same one.
7278
7304
  const already = doc.coverage.some((c) => {
@@ -7683,7 +7709,7 @@ export function buildOnlyYouSection(actions, findings, { nowMs = Date.now(), wit
7683
7709
  // blocking review goes." That REVERSES T3, which retired "delivered-with-open-questions" and
7684
7710
  // is itself recorded as an owner-approved decision — both are his, and this is the standing one.
7685
7711
  //
7686
- // 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`
7687
7713
  // has described it all along — "delivered … as a prominent Reviewer's open questions section at the
7688
7714
  // top of the body (the driver passes them in)" — and `:295` lists it as a required section with
7689
7715
  // "Never omit it to look more finished." deleted the driver's half and left that text
@@ -8013,7 +8039,7 @@ export function assembleReportMd(P, findings, cardOrdinals, { grouped = [], byRi
8013
8039
  } catch { /* never-kill: a malformed findings.json leaves the overview untouched (its own gates own that) */ }
8014
8040
  // ── T3a — THE REVIEWER'S OPEN POINTS, AT THE TOP OF THE BODY ──────────────────────────────────────
8015
8041
  //
8016
- // `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
8017
8043
  // (the driver passes them in)". That sentence has been true of the contract and false of the code
8018
8044
  // since deleted the driver's half; this is the half coming back.
8019
8045
  //
@@ -8989,7 +9015,7 @@ async function pipelineInner(job, opts = {}) {
8989
9015
  // Phase 0 done in code (slug/codename/run-dir/customer). Phase 1+2 stages:
8990
9016
  must(await stage("matter-frame", ctx), "matter-frame");
8991
9017
  await deriveIntakeAsks(ctx); // A6: freeze the intake-ask register (one save-only followup if the section is missing)
8992
- must(await stage("prelim-variants", ctx), "prelim-variants");
9018
+ must(await stage("clearance-variants", ctx), "clearance-variants");
8993
9019
  deriveScopeLedgerJson(ctx); // frame-omission design: code-derive scope-ledger.json from the validated prose (never-kill)
8994
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)
8995
9021
  await verifyAndRecordHouseElement(ctx, opts); // ask the register who owns the frame's proposed house element, BEFORE the plan is compiled from it
@@ -9803,7 +9829,7 @@ async function pipelineInner(job, opts = {}) {
9803
9829
  const dispatchStartedMs = Date.now();
9804
9830
  try {
9805
9831
  const r = await planExec({ planPath: P.registerPlan, axis: a, outputPath: P.registerBand(a), ...(qids?.length ? { qids } : {}) },
9806
- { 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) });
9807
9833
  outcome = r?.ok ? "ok" : `failed: ${String(r?.cause ?? "unknown").slice(0, 140)}`;
9808
9834
  } catch (err) { outcome = `threw: ${String(err?.message ?? err).slice(0, 140)}`; }
9809
9835
  // — MEASURE WHETHER THE HOLE CLOSED, not whether the phone was answered. `outcome` is the
@@ -9890,7 +9916,7 @@ async function pipelineInner(job, opts = {}) {
9890
9916
  // production default is ON.
9891
9917
  if (process.env.CLEAROTRON_BAND_TRUTH_GATE !== "0"
9892
9918
  && unitKey[a] && existsSync(P.registerBand(a)) && (ctx.registerPlan?.entries ?? []).some((e) => e?.axis === a)) {
9893
- const laneKey = `prelim-${run.slug}-${run.codename}-register-unit-${a}`;
9919
+ const laneKey = `clearance-${run.slug}-${run.codename}-register-unit-${a}`;
9894
9920
  const bandHasQids = (() => {
9895
9921
  try { const b = JSON.parse(readFileSync(P.registerBand(a), "utf8")); return Array.isArray(b) && b.some((x) => x && typeof x === "object" && x.qid); }
9896
9922
  catch { return false; /* parse defects are the stage validator's problem, not this gate's */ }
@@ -9963,7 +9989,7 @@ async function pipelineInner(job, opts = {}) {
9963
9989
  if (!digestLocked && repairLedger.canAttempt("taint-rerun", a, { max: taintRerunMax, epoch: repairEpoch })) {
9964
9990
  note(`register-unit ${a}: taint-rerun — one fresh re-run (fresh session; a killed session is never warm-resumed)`);
9965
9991
  const r = await stage("register-unit", { ...ctx, axis: a },
9966
- { 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}` });
9967
9993
  repairLedger.record("taint-rerun", a, r.ok ? "ok" : `failed: ${String(r.fail ?? "unknown").slice(0, 100)}`, { epoch: repairEpoch, max: taintRerunMax });
9968
9994
  repairs.push(`taint-rerun:${r.ok ? "ok" : `failed: ${String(r.fail ?? "unknown").slice(0, 60)}`}`);
9969
9995
  if (r.ok && r.sessionKey) unitKey[a] = r.sessionKey;
@@ -10288,7 +10314,7 @@ async function pipelineInner(job, opts = {}) {
10288
10314
  runLog(run.runDir, { event: "plan-qids-missing", axis: a, qids: entries.map((e) => e.qid), action: "fresh-execute-plan" });
10289
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`);
10290
10316
  const freshMsg = repairFollowup("register-unit:plan-join-fresh", { axis: a, registerPlan: P.registerPlan, bandPath: P.registerBand(a) });
10291
- 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" });
10292
10318
  if (!rf.ok) note(`register-unit ${a}: fresh execute_plan call failed (${rf.fail}) — the plan-unexecuted StageFailure below holds the line`);
10293
10319
  continue;
10294
10320
  }
@@ -11113,7 +11139,7 @@ async function pipelineInner(job, opts = {}) {
11113
11139
  note(`frame-reopen: ${a} timed out — ONE fresh SCOPED retry through the fallback chain (backup provider + retries)`);
11114
11140
  const retryMsg = repairFollowup("register-unit:frame-reopen-retry", { paths: P, axis: a, directives: regDirectives, reopenFetchCap,
11115
11141
  supplementalLane: !!ctx.registerPlan?.contract?.supplemental_lane });
11116
- 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" });
11117
11143
  if (r2.ok && existsSync(P.registerUnit(a)) && readFileSync(P.registerUnit(a), "utf8") !== beforeUnit) {
11118
11144
  changedAxes.push(a); regChanged = true; regMechFail = null;
11119
11145
  note(`frame-reopen: ${a} fresh scoped retry closed the gap`);
@@ -11884,7 +11910,7 @@ async function pipelineInner(job, opts = {}) {
11884
11910
  // violating after that one recovery pass is disclosed as unexamined + clamps the verdict (owner
11885
11911
  // decision 2026-07-22 — the terminal branch below); the gate never silently passes a drop.
11886
11912
  {
11887
- const runPrefix = `prelim-${run.slug}-${run.codename}-`;
11913
+ const runPrefix = `clearance-${run.slug}-${run.codename}-`;
11888
11914
  // Re-read the findings + recompute the fetched universe on EACH check (the recovery re-digest rewrites
11889
11915
  // the findings AND may record_fetch the flagged URIs — both must be picked up on the post-recovery
11890
11916
  // check). V4-1: the fetched universe is the DELIVERED ARTIFACT SET's records (run-dir _records/ ∪ this
@@ -12206,7 +12232,7 @@ async function pipelineInner(job, opts = {}) {
12206
12232
  regions: ctx.registerPlan?.regions ?? [], entries,
12207
12233
  }, null, 2) + "\n");
12208
12234
  const r = await planExec({ planPath: ccPlanPath, axis: CROWD_CONTEXT_AXIS, outputPath: ccBandPath },
12209
- { 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) });
12210
12236
  if (!r?.ok) throw new Error(String(r?.cause ?? "crowd-context plan execution failed").slice(0, 200));
12211
12237
  const blocks = JSON.parse(readFileSync(ccBandPath, "utf8"));
12212
12238
  return Array.isArray(blocks) ? blocks : [];
@@ -12246,7 +12272,7 @@ async function pipelineInner(job, opts = {}) {
12246
12272
  // on fetched records) as a structured aim-attention artifact for synthesis. Best-effort; NEVER a
12247
12273
  // rating rule (explicitly not the rejected escalation filter) — absence writes nothing.
12248
12274
  try {
12249
- const { records } = assembleRunRecords(run.runDir, `prelim-${run.slug}-${run.codename}-`);
12275
+ const { records } = assembleRunRecords(run.runDir, `clearance-${run.slug}-${run.codename}-`);
12250
12276
  const signals = extractEnforcerSignals(records);
12251
12277
  if (signals.length) {
12252
12278
  writeFileSync(`${P.enforcerSignals}.tmp`, JSON.stringify({ signals }, null, 2) + "\n");
@@ -12788,7 +12814,7 @@ async function pipelineInner(job, opts = {}) {
12788
12814
  status: r?.screen?.status ?? r?.status, live_status: r?.screen?.live_status });
12789
12815
  }
12790
12816
  } catch { /* no merged band (legacy run) — fetched-record facts alone rank */ }
12791
- const srPrefix = `prelim-${run.slug}-${run.codename}-`;
12817
+ const srPrefix = `clearance-${run.slug}-${run.codename}-`;
12792
12818
  let srRecords = assembleRunRecords(run.runDir, srPrefix).records;
12793
12819
  const srFetcher = opts.recordFetcher ?? defaultRecordFetcher;
12794
12820
  const srLedger = ctx.repairLedger ?? (ctx.repairLedger = createRepairLedger(run.runDir, { log: (o) => runLog(run.runDir, o) }));
@@ -12862,7 +12888,7 @@ async function pipelineInner(job, opts = {}) {
12862
12888
  const carriedUris = (pf?.findings ?? []).flatMap((f) =>
12863
12889
  Array.isArray(f?.owner?.registrations) ? f.owner.registrations : []
12864
12890
  ).map((r) => r?.uri && (normalizeRecordUri(r.uri) || String(r.uri).trim())).filter(Boolean);
12865
- 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()];
12866
12892
  const rfMd = existsSync(P.registerFindings) ? readFileSync(P.registerFindings, "utf8") : "";
12867
12893
  const violations = findRecallRegressionViolations({
12868
12894
  knownConflicts, searchedNames: namesForRecall, carriedUris, fetchedUris,
@@ -13221,22 +13247,22 @@ async function pipelineInner(job, opts = {}) {
13221
13247
  // lawyer-judged/disclosed residue only.
13222
13248
  if (coverageInsufficient || frameResidual || screenGateGap || seniorGap || registerGap || deadlineGap) {
13223
13249
  // THESE SENTENCES REACH A CLIENT AND THEY ARE NOT OURS TO WRITE (owner, 2026-09-17). One of
13224
- // them — the screen-gate line, which says a mark "could not be record_fetched" — carries an
13250
+ // them — the screen-gate line, which says a mark "could not be record_fetched" — carried an
13225
13251
  // engine identifier into the list a client reads as the conditions on their result, by a route
13226
- // `terminalClampDecision` guards and this one does not. A reader's sentence for it was written
13227
- // here and refused: the objection was the class, not the wording. It is on the owner's design
13228
- // table as audit item 25, and until he rules, this stays exactly as it was rather than carrying
13229
- // a caveat a developer composed.
13230
- const reasons = [];
13231
- const machinery = (reason) => reasons.push(reason);
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); };
13232
13258
  if (coverageInsufficient) machinery(`the lawyer judged a material slice not fully cleared: ${coverageJudgment.reason || "register coverage gap"}`);
13233
13259
  if (frameGap) machinery("the blind frame-diff flagged a dominant-element omission the reopen pass did not close");
13234
13260
  else if (frameDeferrals.length) machinery(`follow-ups left open this run: ${frameDeferrals.map((d) => plainDirective(d.directive)).slice(0, 3).join(", ")}`);
13235
- if (screenGateGap) machinery(`${sgUnresolved.length} in-scope mark(s) dropped on goods could not be record_fetched (unverified): ${sgUnresolved.map((g) => g.mark).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)
13236
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(", ")}`);
13237
13263
  if (registerGap) {
13238
- if (regGap.deferred.length) machinery(`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`);
13239
- if (regGap.taintAxes.length) machinery(`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)
13240
13266
  // Named regressions (2026-07-22): `<MARK> (<owner> — <canonical uri>)` — a bare mark name
13241
13267
  // shipped "ION, ION, ION" (three indistinguishable strings); the identity is front-loaded
13242
13268
  // because the delivered statement truncates from the tail.
@@ -13268,7 +13294,7 @@ async function pipelineInner(job, opts = {}) {
13268
13294
  // APPEND (dedup by exact text) — the legalActions arm may already have recorded conditions,
13269
13295
  // and this callable runs more than once (degenerate re-ask, lint repair, post-consolidation).
13270
13296
  const freshMachinery = reasons.filter((r) => !clampReasons.includes(r));
13271
- 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
13272
13298
  clampReasons.push(...freshMachinery);
13273
13299
  // The reason KINDS distinguish coverage/frame/screen-gate/senior-right/register residue for the
13274
13300
  // report bound line and the client conditions row (merged — legalActions survives).
@@ -13386,7 +13412,7 @@ async function pipelineInner(job, opts = {}) {
13386
13412
  // review goes." This REVERSES T3 (H3/H5), whose flip to fail-on-BLOCKING is itself recorded
13387
13413
  // itself an owner-approved decision. Both are his; this is the standing one, and the
13388
13414
  // history is kept here rather than deleted because a reader who finds only one of them will conclude
13389
- // 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
13390
13416
  // change makes it true again.)
13391
13417
  //
13392
13418
  // COPPER-SPIRE IS STILL ANSWERED, AND NOT BY REFUSING. That failure was a BLOCKING verdict reaching
@@ -14281,7 +14307,7 @@ async function pipelineInner(job, opts = {}) {
14281
14307
  // the spec's "blocks delivery", approved 2026-06-10: delivering nothing is worse than delivering good
14282
14308
  // work with one clearly-labelled defect — and code-produced defects would deadlock a block.)
14283
14309
  {
14284
- const lintPrefix = `prelim-${run.slug}-${run.codename}-`;
14310
+ const lintPrefix = `clearance-${run.slug}-${run.codename}-`;
14285
14311
  // V4-1: the gates evaluate the artifact set assembled for THIS delivery — run-dir _records/
14286
14312
  // (inherited or prior-session) ∪ this-session ledger rows — never the session-prefixed ledger
14287
14313
  // alone (empty on a fork ⇒ the vacuous registry pass that shipped a false citation).
@@ -14886,7 +14912,7 @@ async function pipelineInner(job, opts = {}) {
14886
14912
  Array.isArray(f?.owner?.registrations) ? f.owner.registrations : []
14887
14913
  ).map((r) => r?.uri && (normalizeRecordUri(r.uri) || String(r.uri).trim())).filter(Boolean);
14888
14914
  const executedQids = (ctx.planExecution?.executed ?? []).map((x) => x.qid);
14889
- 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()];
14890
14916
  const regLedgerRows = loadCoverageLedger(run.runDir).rows;
14891
14917
  // judgment-relocation (2026-06-23): the search-shape gate (findFloorShapeGaps) was DELETED here too —
14892
14918
  // sufficiency is judgment's call (coverage_judgment), not a re-parse of the ledger at the surface. This
@@ -15175,7 +15201,7 @@ async function pipelineInner(job, opts = {}) {
15175
15201
  let providerTally = null;
15176
15202
  try {
15177
15203
  const provider = activeProvider().id;
15178
- const usage = tallyRegisterCalls(DEFAULT_LEDGER_PATH, `prelim-${run.slug}-${run.codename}-`);
15204
+ const usage = tallyRegisterCalls(DEFAULT_LEDGER_PATH, `clearance-${run.slug}-${run.codename}-`);
15179
15205
  runLog(run.runDir, { event: "provider-usage", provider, ...usage });
15180
15206
  writeRunStatus(ctx, { providerUsage: { [provider]: usage } });
15181
15207
  providerTally = { [provider]: usage };
@@ -15814,6 +15840,46 @@ function resolveRun(job, opts) {
15814
15840
 
15815
15841
  // Rebuild the ctx a single stage needs WITHOUT re-running upstream: axes from the persisted manifest, verdict
15816
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
+
15817
15883
  export function reconstructCtx(job, opts) { // @internal
15818
15884
  const run = resolveRun(job, opts);
15819
15885
  const P = paths(run.runDir);
@@ -15996,7 +16062,7 @@ export async function repairStale(job, opts = {}) { // @internal
15996
16062
 
15997
16063
  // --experiment <stage>: re-run ONE stage SANDBOXED — no canonical ARTIFACT is read back or overwritten. The
15998
16064
  // stage reads COPIES of its canonical inputs from a shadow dir and writes its output there; a distinct
15999
- // `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
16000
16066
  // provider-usage prefix (experiment Corsearch calls are not billed to the canonical run). Single-model by
16001
16067
  // design (the canonical primary unless --model overrides) so the comparison is clean.
16002
16068
  //
@@ -16193,7 +16259,7 @@ async function runExperimentInner(job, opts) {
16193
16259
  // so a run with no case-law layer must arrive as null here too, never as an absent key.
16194
16260
  for (const f of inlineDecl.fields) shadowCtx[f] = resolved[f] ?? null;
16195
16261
  }
16196
- 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}`;
16197
16263
  let { text: extra, ids: extraIds } = experimentExtra(shadowCtx, name, opts);
16198
16264
 
16199
16265
  // — THE CORRECTIVE PASS, on a COLD turn, and it says which pass it reproduced.
@@ -16455,8 +16521,13 @@ export function retiredEnvWarnings(env = process.env) {
16455
16521
  // ABSOLUTE PATHS on purpose: this line is read hours later, possibly from a different directory, and a
16456
16522
  // relative path that silently resolves somewhere else would be a worse answer than no line at all.
16457
16523
  export function resumeCommand({ script, jobPath, codename, agent = null }) { // @internal
16458
- if (!script || !jobPath || !codename) return null;
16459
- 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}`;
16460
16531
  }
16461
16532
 
16462
16533
  // The stderr lines a non-clean exit owes its reader, as an array (empty = say nothing further).
@@ -16555,7 +16626,7 @@ if (isEntrypoint(import.meta.url)) void (async () => {
16555
16626
  let a;
16556
16627
  try { a = parseArgv(process.argv.slice(2)); }
16557
16628
  catch (e) { console.error(`error: ${e.message}`); console.error(USAGE); process.exit(2); }
16558
- 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
16559
16630
  // composition guards
16560
16631
  if (a.fromStage && !a.codename) { console.error("error: --from requires --resume <codename>"); process.exit(2); }
16561
16632
  if (a.experiment && !a.codename) { console.error("error: --experiment requires --resume <codename>"); process.exit(2); }
@@ -16577,8 +16648,47 @@ if (isEntrypoint(import.meta.url)) void (async () => {
16577
16648
  // mistake a first-time reader is likeliest to make. Kept guarded here through the rebase — the
16578
16649
  // pool check above is a separate refusal and must not swallow this one.
16579
16650
  let job;
16580
- try { job = JSON.parse(readFileSync(a.job, "utf8")); }
16581
- 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
+ }
16582
16692
  // Manual runs: if --agent is given, root the run-dir in that agent's workspace too (mirrors the runner).
16583
16693
  const base = a.agent
16584
16694
  ? { agent: a.agent, studioRoot: config.studioRootForAgent(a.agent), archiveRoot: config.archiveRootForAgent(a.agent) }
@@ -16590,7 +16700,10 @@ if (isEntrypoint(import.meta.url)) void (async () => {
16590
16700
  ? { ...base, codename: a.codename, experiment: a.experiment, model: a.model, instructions: a.instructions, axis: a.axis, label: a.label, dispatchTrigger: a.dispatchTrigger }
16591
16701
  : { ...base, codename: a.codename, fromStage: a.fromStage };
16592
16702
  const script = resolve(fileURLToPath(import.meta.url));
16593
- 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); };
16594
16707
 
16595
16708
  // ── the laptop case: a closed lid, a shutdown, a ^C ────────────────────────────────────────────────
16596
16709
  // Until now this CLI had NO signal handler: node's default die-now killed the process mid-stage, the run
@@ -16693,3 +16806,28 @@ if (isEntrypoint(import.meta.url)) void (async () => {
16693
16806
  //
16694
16807
  // Move it to the top the day those citations name symbols instead of numbers — which is what
16695
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
+ }