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
@@ -398,15 +398,40 @@ export const leadRouteFor = (id) => {
398
398
  * invitation to paste it inside WSL, where it does not work. Somebody took the invitation from Claude
399
399
  * Code inside a distribution and got CONNECTION_CLOSED (measured 2026-09-16 on 0.3.2-beta.1).
400
400
  *
401
- * There is exactly ONE launcher per host shape today, and under WSL it is the Windows-side one, so this
402
- * step names the side it is for rather than offering both. Building the second launcher — the plain
403
- * `node` line for an assistant running inside the distribution — is its own piece of work; until it
404
- * exists, saying which side this one is for is the whole of what can honestly be said.
401
+ * AND NOW IT SAYS ONLY THAT THE INSTALL IS IN WSL, because the rows below it say the rest.
402
+ *
403
+ * While there was one launcher, this step carried which side it was for and that the other side could
404
+ * not use it. Both sides are now offered, each headed with the side it is for, and those two sentences
405
+ * turned false where they stand: the reader is told an assistant inside the distribution cannot connect,
406
+ * directly above the row that connects it. What is left is the clause that is still true and still
407
+ * needed — the rows name Linux paths and the reader has to know why. The rest is DELETED rather than
408
+ * reworded, because the headings already say it and nothing here is ours to write.
405
409
  */
406
- export const WSL_STEP = "This install runs inside WSL, and the command below starts the server in there for you. "
407
- + "It is for an assistant running on the Windows side — Claude Desktop, or Claude Code in PowerShell. "
408
- + "An assistant running inside this WSL terminal cannot use it: start the server from the WSL terminal "
409
- + "yourself instead.";
410
+ export const WSL_STEP = "This install runs inside WSL.";
411
+
412
+ /**
413
+ * ONE ROW PER SIDE, where a launcher has two.
414
+ *
415
+ * `stdioConnectFor` answers a WSL install with both launchers under `variants`, headed in the product's
416
+ * own words. Each becomes a row of its own here, carrying its side's heading as the row's line and its
417
+ * side's command as the row's copy — so the reader sees which command is theirs before they press it,
418
+ * and does not paste the Windows one into a terminal already inside the distribution.
419
+ *
420
+ * The heading takes the row's line rather than a slot of its own: every surface that draws a step draws
421
+ * that line already — the page above the copy block, the terminal beside the number — so both sides get
422
+ * the headings from one author and neither needs new markup to show them.
423
+ *
424
+ * Anywhere else this is the identity. One launcher stays one row, with the line its author wrote.
425
+ */
426
+ const splitSides = (step) => {
427
+ const variants = step.copy?.stdio?.variants;
428
+ if (!variants) return [step];
429
+ return variants.map((v) => ({
430
+ ...step,
431
+ text: v.heading,
432
+ copy: { ...step.copy, text: v.text, stdio: { ...step.copy.stdio, text: v.text, variants: null } },
433
+ }));
434
+ };
410
435
 
411
436
  export function whatItNeeds(client, have = {}, route = client?.lead) {
412
437
  if (!client) return null;
@@ -494,10 +519,13 @@ export function whatItNeeds(client, have = {}, route = client?.lead) {
494
519
  // ON WSL, "THIS COMPUTER" IS THE LINUX INSIDE WINDOWS. The line names WSL paths, so it runs in the WSL
495
520
  // terminal where Clearotron is installed. Pasted into PowerShell it fails, and an assistant running on
496
521
  // Windows itself could not start a program at a Linux path anyway.
497
- if (have.wsl) steps.unshift({ text: WSL_STEP });
498
- const first = steps.find((s) => s.copy)?.copy;
522
+ // AND A LAUNCHER WITH TWO SIDES BECOMES TWO ROWS, under the step that says this install is in WSL.
523
+ const rows = steps.flatMap(splitSides);
524
+ if (have.wsl) rows.unshift({ text: WSL_STEP });
525
+ // The Windows-side row leads, so `command` and `stdio` answer what they have always answered.
526
+ const first = rows.find((s) => s.copy)?.copy;
499
527
  // `command` and `stdio` ride for the terminal, which prints a disk offer's copy by its shape.
500
- return { client, served: true, route, steps, launch: client.launch ?? null, enables: null, ...evidence,
528
+ return { client, served: true, route, steps: rows, launch: client.launch ?? null, enables: null, ...evidence,
501
529
  command: first?.text ?? null, stdio: first?.stdio ?? null, address: null, key: null,
502
530
  note: "Nothing to sign up for and nothing to open up — this assistant runs the software itself, from the copy already on this machine." };
503
531
  }
@@ -16,10 +16,15 @@
16
16
  // of the same allowlist agree on the day they are written and drift afterwards; that is the defect
17
17
  // was filed about one field over, and the fix there was the same — import the function, never
18
18
  // re-derive the answer.
19
- export const DEPLOYMENT_BOXES = ["prod", "test"];
19
+ // `preprod` JOINED 2026-09-17. It is a packaged install like production, on its own account, which takes
20
+ // every published version before production does. Until it could name itself it ran with the variable
21
+ // UNSET — and an unset box does not fail loudly: `live-surface-check` SKIPS its expected-units check
22
+ // when the box is null, so the one install that most wants that check was the one not getting it.
23
+ // Setting `prod` instead would have made the box lie about which box it is.
24
+ export const DEPLOYMENT_BOXES = ["prod", "preprod", "test"];
20
25
 
21
26
  /**
22
- * @returns {"prod"|"test"|null} the self-declared box, or null when unset or unrecognised.
27
+ * @returns {"prod"|"preprod"|"test"|null} the self-declared box, or null when unset or unrecognised.
23
28
  *
24
29
  * Read at CALL time, deliberately. `shared/brand.mjs` builds its value at module scope and that is
25
30
  * exactly why a rename reached the report and not the portal — portal-service is outside
@@ -35,7 +35,7 @@
35
35
  // its own population needs an exception list — which would rebuild this issue's defect inside its fix:
36
36
  //
37
37
  // · `driver/stage-freshness.mjs` creates a CHILD, `join(runDir, "_driver", STAMP_DIR)`.
38
- // · `driver/pipeline.mjs:15945 shadowDir` passes a shadow dispatch sandbox under `_experiments/`, not a run
38
+ // · `driver/pipeline.mjs shadowDir` passes a shadow dispatch sandbox under `_experiments/`, not a run
39
39
  // directory. It is a run-dir-SHAPED base, which is why the parameter is `base` and not `runDir`.
40
40
  //
41
41
  // ── WHAT THIS DELIBERATELY DOES NOT DO ────────────────────────────────────────────────────────────
@@ -2,7 +2,7 @@
2
2
  // Copyright 2026 Cordillera Sàrl. Additional terms under section 7 of the AGPL-3.0 apply — see ADDITIONAL-TERMS.md
3
3
  // env-aliases.mjs — ONE SPELLING, and the settings that no longer exist at all.
4
4
  //
5
- // A variable name must mean something to someone who has never read this code. `PRELIM` names nothing
5
+ // A variable name must mean something to someone who has never read this code. `CLEARANCE` names nothing
6
6
  // to anyone — it is the internal codename of the first product this engine shipped — so the names an
7
7
  // installer types are `CLEAROTRON_*`. Vendor keys are deliberately untouched: `SIGNA_API_KEY` already
8
8
  // says who you bought it from.
@@ -17,6 +17,7 @@
17
17
  // private roster). This file owns only the matching — it names no identity of its own beyond the
18
18
  // platform tokens ALLOWED_CONTEXT has to spell out, which is why it declares itself in
19
19
  // DECLARATION_SOURCES.
20
+ import { createHash } from "node:crypto";
20
21
 
21
22
  // ── the platform and public-register exemption ────────────────────────────────────────────────────
22
23
  //
@@ -72,6 +73,26 @@ export const ALLOWED_TOKEN_CONTEXT =
72
73
 
73
74
  export const ALLOWED_LINE_CONTEXT = /owner-bound|watchlist owner/i;
74
75
 
76
+ // ── THE CLOUD, AS A BILLING ROUTE ──────────────────────────────────────────────────────────────────
77
+ //
78
+ // Claude can be paid for through a cloud account, and one of the three clouds is Azure. The roster
79
+ // retires the same word as a client, so every sentence about paying through that cloud was refused as a
80
+ // retired identity: 67 of the 99 hits over the tree on 2026-09-18, and 11 lines of a release's squash
81
+ // message.
82
+ //
83
+ // EXACTLY AS WIDE AS THE EVIDENCE, AND NO WIDER. Only the names the product itself ships for that route
84
+ // are exempt: the cloud's name, its Foundry service, and the three clouds named as a list. Case-sensitive,
85
+ // because that is how the product spells them. The bare word is never exempt, in any casing, and a
86
+ // sentence that used it as shorthand for the cloud was reworded to say Azure instead.
87
+ //
88
+ // AND NOT IN CAPTURED DATA. A frozen demo run and a captured register fixture hold third parties' own
89
+ // text: a course title that happens to name the cloud is not a billing route, so there the rule does not
90
+ // apply and the hit is reported as it always was.
91
+ export const CLOUD_ROUTE_CONTEXT = /\bMicrosoft Azure\b|\bMicrosoft Foundry\b|\bGoogle(?: Cloud)?,\s+Microsoft(?: Azure)?\s+or\b/;
92
+
93
+ /** A frozen demo run, anywhere under `demo/<product>/run/`: what the engine read, captured whole. */
94
+ const FROZEN_RUN_RE = /(?:^|\/)demo\/[^/]+\/run\//;
95
+
75
96
  /**
76
97
  * Kept as the union so nothing that merely ASKS "is this line exempt at all" has to learn the split.
77
98
  * Callers deciding whether to REPORT a hit must use the two above — this one cannot tell you which
@@ -155,20 +176,27 @@ export function matchSpans(name, line, suffixable) {
155
176
  *
156
177
  * Takes the RAW line and does its own unescaping, so no caller can forget that step.
157
178
  */
158
- export function reportableOnLine(name, rawLine, suffixable) {
179
+ export function reportableOnLine(name, rawLine, suffixable, { cloudRoute = true } = {}) {
159
180
  if (ALLOWED_LINE_CONTEXT.test(rawLine)) return false;
160
181
  const text = unescapeBoundaries(rawLine);
161
- return matchSpans(name, text, suffixable).some((sp) => !tokenExemptionCovers(text, sp));
182
+ return matchSpans(name, text, suffixable).some((sp) => !tokenExemptionCovers(text, sp, { cloudRoute }));
162
183
  }
163
184
 
164
- function tokenExemptionCovers(line, span) {
165
- for (const m of String(line).matchAll(new RegExp(ALLOWED_TOKEN_CONTEXT.source, "gi"))) {
166
- const s = m.index, e = m.index + m[0].length;
167
- if (span.start < e && s < span.end) return true; // overlap, in either direction
185
+ function tokenExemptionCovers(line, span, { cloudRoute = true } = {}) {
186
+ const contexts = [new RegExp(ALLOWED_TOKEN_CONTEXT.source, "gi")];
187
+ if (cloudRoute) contexts.push(new RegExp(CLOUD_ROUTE_CONTEXT.source, "g"));
188
+ for (const rx of contexts) {
189
+ for (const m of String(line).matchAll(rx)) {
190
+ const s = m.index, e = m.index + m[0].length;
191
+ if (span.start < e && s < span.end) return true; // overlap, in either direction
192
+ }
168
193
  }
169
194
  return false;
170
195
  }
171
196
 
197
+ /** Whether the cloud-route exemption applies in this file: everywhere but captured third-party data. */
198
+ export const cloudRouteApplies = (file) => !isCapturedRegisterData(file) && !FROZEN_RUN_RE.test(String(file ?? ""));
199
+
172
200
  // ── CAPTURED PUBLIC-REGISTER DATA ─────────────────────────────────────────────────────────
173
201
  //
174
202
  // `providers/*/test/fixtures/` holds vendor responses captured live from public trademark registers.
@@ -208,6 +236,32 @@ export const CAPTURED_DATA_GUIDANCE =
208
236
  + "`node scripts/check-capture.mjs <file>` before writing; or (b) a genuine identifier of ours — remove "
209
237
  + "it. Do NOT exempt the path: captured data is the one place a real identifier could hide unseen.";
210
238
 
239
+ // ── A PUBLISHED DICTIONARY, PINNED TO ITS BYTES ─────────────────────────────────────────────
240
+ //
241
+ // The roster retires ordinary English words, and a dictionary is every ordinary English word, so the
242
+ // two collide by construction and, like captured register data, the collision arrives on a tree
243
+ // nobody touched: the private roster gains an entry and the word list goes red. Unlike captured data,
244
+ // nothing of ours can be in it. It is a published third-party list (the file's header names it and its
245
+ // licence) that no client, matter or run ever wrote to.
246
+ //
247
+ // SO THE EXEMPTION IS FOR THESE BYTES, NOT THIS PATH. Each entry pins the file's SHA-256 as it was
248
+ // vetted. Change one line, a name slipped in among the words, and the hash no longer matches: the file
249
+ // is scanned in full again like any other, and the pin moves only in a commit that says it re-vetted.
250
+ // A name hidden in a dictionary is therefore still a finding, and a name anywhere else never was
251
+ // exempt.
252
+ export const PINNED_DICTIONARIES = Object.freeze({
253
+ "driver/wordlists/en.txt": Object.freeze({
254
+ sha256: "8021ad10f94da2a549f60db93fcf761156458fd682e1095e81f93db6843fb636",
255
+ what: "SCOWL size 50, American: the one-letter neighbourhood filter's ordinary-word list",
256
+ }),
257
+ });
258
+
259
+ /** Is this file a pinned dictionary whose bytes are exactly the ones that were vetted? */
260
+ export function isPinnedDictionary(file, text) {
261
+ const pin = PINNED_DICTIONARIES[String(file ?? "")];
262
+ return Boolean(pin) && createHash("sha256").update(String(text)).digest("hex") === pin.sha256;
263
+ }
264
+
211
265
  /**
212
266
  * Sweep a corpus for retired identities.
213
267
  *
@@ -227,6 +281,7 @@ export function scanCorpus(files, readFn, { retired, suffixable, vetted = () =>
227
281
  for (const f of files) {
228
282
  const t = readFn(f);
229
283
  if (!t) continue;
284
+ if (isPinnedDictionary(f, t)) continue;
230
285
  // In captured register data the hit is reported by ENTRY NUMBER, never by value. The
231
286
  // ordinary format prints the retired name and its twin, which is right where the fix is "delete
232
287
  // this" — but a fixture collision is expected to fire on a tree nobody changed, and printing the
@@ -239,7 +294,7 @@ export function scanCorpus(files, readFn, { retired, suffixable, vetted = () =>
239
294
  entry += 1;
240
295
  // A LINE-scoped exemption skips the line; a TOKEN-scoped one must OVERLAP the match it excuses.
241
296
  // Both live in reportableOnLine, so the declaration test cannot answer this differently.
242
- if (!reportableOnLine(name, line, suffixable)) continue;
297
+ if (!reportableOnLine(name, line, suffixable, { cloudRoute: cloudRouteApplies(f) })) continue;
243
298
  if (vetted(f, name)) continue;
244
299
  hits.push(captured
245
300
  ? `${f}:${i + 1}: match table entry #${entry} of ${retired.length} (sentinels first, then the roster) `
@@ -302,8 +357,8 @@ const ADDRESS = /([A-Za-z0-9._%+-]+)@(?:cordillera\.(?:ch|test)|clearotron\.ai)\
302
357
  const INTERNAL_MARKER = /\bINTERNAL[ -]ONLY\b/;
303
358
  const INTERNAL_PHRASE = /\bfor internal use only\b/i;
304
359
 
305
- // THE SECOND STAMPED FORM, and is why it exists. `driver/skills/prelim-search/worked-examples.md`
306
- // opened with `INTERNAL — Gold-standard worked examples for prelim-search SYNTHESIS.` and shipped: the
360
+ // THE SECOND STAMPED FORM, and is why it exists. `driver/skills/clearance-search/worked-examples.md`
361
+ // opened with `INTERNAL — Gold-standard worked examples for clearance-search SYNTHESIS.` and shipped: the
307
362
  // marker above requires the word ONLY, and a document stamped `INTERNAL —` carries the identical
308
363
  // classification without it.
309
364
  //
@@ -361,6 +416,7 @@ export function scanOperatorIdentity(files, readFn, { withheld = () => false } =
361
416
  for (const f of files) {
362
417
  const t = readFn(f);
363
418
  if (!t) continue;
419
+ if (isPinnedDictionary(f, t)) continue;
364
420
  // A WITHHELD PATH DOES NOT SHIP, and pattern 4 is about what a shipped surface says to its reader.
365
421
  // The withheld design tree holds twenty-one hits of exactly this shape and every one is correct in
366
422
  // place: working notes, withheld wholesale by the list in shared/withheld-paths.mjs, and rewriting
@@ -32,3 +32,25 @@ export const SENTINELS = [
32
32
  // and `Brindlow-Reach`. One row carries it so the option itself is exercised rather than merely
33
33
  // declared — an unexercised option is a setting nobody has watched work.
34
34
  export const SUFFIXABLE = new Set(["Brindlow"]);
35
+
36
+ /**
37
+ * WHICH TABLE THE SWEEP WAS ARMED WITH, said out loud so a workflow can require it.
38
+ *
39
+ * The public tree can only ever run this sweep in sentinel mode — the real roster lives outside the
40
+ * repository by design, and the arm that sweeps here is wired to SENTINELS by construction. That is
41
+ * fine, and it is exactly why the mode has to be stated: a sweep with an empty table returns the same
42
+ * clean zero as a sweep that worked, so "it ran, and here is which table it ran against" is the only
43
+ * form of the claim a reader can act on.
44
+ *
45
+ * It is printed by the arm that actually sweeps the corpus, never at import. A marker that appeared
46
+ * merely because a module was loaded would survive the arm being deleted, which is the one thing it
47
+ * exists to catch — the guard stopping and every run staying green.
48
+ */
49
+ export const SENTINEL_MODE_MARKER = "[repo-guard] blocklist mode=sentinel";
50
+
51
+ /**
52
+ * The line the sweep prints when it read a REAL roster instead. That roster is private and never
53
+ * reaches this repository's runners by design, so on a public run the line does not mean a stronger
54
+ * check: it means the roster was loaded into a public log. The workflow refuses it.
55
+ */
56
+ export const TABLE_MODE_MARKER = "[repo-guard] blocklist mode=table";
@@ -3,7 +3,7 @@
3
3
  // names-in-force.mjs — GENERATED by scripts/mint-names-in-force.mjs. Do not edit by hand.
4
4
  //
5
5
  // Every `CLEAROTRON_*` name this build reads. It exists so the retired-spelling check can name the
6
- // replacement for an old `PRELIM_*` line rather than only saying the old one is dead — see
6
+ // replacement for an old `CLEARANCE_*` line rather than only saying the old one is dead — see
7
7
  // shared/env-aliases.mjs. Re-mint with:
8
8
  //
9
9
  // node scripts/mint-names-in-force.mjs
@@ -23,7 +23,6 @@ export const NAMES_IN_FORCE = Object.freeze([
23
23
  "CLEAROTRON_AI",
24
24
  "CLEAROTRON_AI_BILLING",
25
25
  "CLEAROTRON_AI_PATH",
26
- "CLEAROTRON_AZURE_MODEL",
27
26
  "CLEAROTRON_BAND_RESPONSE_CHARS",
28
27
  "CLEAROTRON_BAND_RUN_DIR",
29
28
  "CLEAROTRON_BAND_TRUTH_GATE",
@@ -41,7 +40,9 @@ export const NAMES_IN_FORCE = Object.freeze([
41
40
  "CLEAROTRON_COMMONLAW_SPLIT",
42
41
  "CLEAROTRON_COUNT_FIRST",
43
42
  "CLEAROTRON_CUSTOMERS_DIR",
43
+ "CLEAROTRON_CUT_PR",
44
44
  "CLEAROTRON_CUT_REF",
45
+ "CLEAROTRON_CUT_REQUESTED",
45
46
  "CLEAROTRON_DATABASE",
46
47
  "CLEAROTRON_DEFAULT_AGENT",
47
48
  "CLEAROTRON_DELIVERY",
@@ -54,6 +55,7 @@ export const NAMES_IN_FORCE = Object.freeze([
54
55
  "CLEAROTRON_E2E_EVIDENCE_DIR",
55
56
  "CLEAROTRON_E2E_EXPECT_DEMO_ROSTER",
56
57
  "CLEAROTRON_EDGE_ORIGIN",
58
+ "CLEAROTRON_ENGINES_DIR",
57
59
  "CLEAROTRON_ENGINE_CWD",
58
60
  "CLEAROTRON_ENGINE_MAX_BUFFER",
59
61
  "CLEAROTRON_ENUMERATE_CEILING",