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
@@ -573,7 +573,194 @@ export function mintSupplementalQid({ prefix, term, used }) {
573
573
  * driver/register-capabilities.mjs). Omitted ⇒ the pre-phase-3 corsearch-shaped
574
574
  * behaviour, byte-identical (no entry gains a key, no jurisdiction is translated).
575
575
  */
576
- export function compileRegisterPlan({ manifest, job, form = null, skillVersion = "", capabilities = null, unavailableOffices = [] }) {
576
+ /**
577
+ * The receipt that ARMS the house-element exclusion, and the only thing that may.
578
+ *
579
+ * A SEPARATE FILE FROM THE FRAME'S PROPOSAL, deliberately, and for the reason the digest's accounting
580
+ * stamp is separate from its facts: the proposal is what a model said, the receipt is what the register
581
+ * answered, and a reader who cannot tell those apart cannot tell a judgement from evidence. Absent means
582
+ * the question was never asked, which is the same as unverified and excludes nothing.
583
+ */
584
+ export const HOUSE_ELEMENT_RECEIPT = "house-element.json";
585
+
586
+ /**
587
+ * Verify that the client actually owns the proposed house element, on the register, by owner.
588
+ *
589
+ * IT SITS BESIDE THE TRANSFORM IT GATES, for the reason `accountingArmed` sits beside the refusal it
590
+ * arms: the gate and the thing gated go stale together or not at all, and a reader meeting one finds the
591
+ * other. Nothing else may arm this exclusion.
592
+ *
593
+ * FAIL-CLOSED ON EVERY PATH, and that is the whole design. The frame PROPOSED this element from its
594
+ * reading of the matter; acting on the proposal alone would drop an element from a client's search on a
595
+ * model's assertion, and an element nobody swept is a clean report over unswept ground — the one defect
596
+ * that reaches a client as a confident wrong answer rather than as a visible failure. So every way of
597
+ * not knowing lands in the same place: not verified, no exclusion, the element searched in full, and a
598
+ * reason on the receipt saying which way it was. An outage, an unknown client name, a lookup that threw,
599
+ * a dead registration, a registration in some other class — none of them excludes anything.
600
+ *
601
+ * `lookup` is injected, exactly as `runOwnerChecks` takes its `exec` and `countRegisterHits` its
602
+ * `counter`: the fixture path that makes this product testable at no cost covers this call too, and a
603
+ * test never reaches a provider. NEVER THROWS and never rejects.
604
+ *
605
+ * @param owners the client's own names — the profile's trading names and the matter's customer. Empty
606
+ * is a real answer and it means NOT VERIFIED: with no name to match an owner against,
607
+ * "the client owns it" cannot be established by anything this function can see.
608
+ * @returns the receipt, always. `verified: true` is the only value that may arm an exclusion.
609
+ */
610
+ export async function verifyHouseElementOwnership({
611
+ element, classes = [], owners = [], lookup, now = () => new Date().toISOString(),
612
+ }) {
613
+ const el = String(element ?? "").trim();
614
+ const wanted = (Array.isArray(classes) ? classes : []).map(String).map((c) => c.trim()).filter(Boolean);
615
+ const names = (Array.isArray(owners) ? owners : []).map((o) => String(o ?? "").trim()).filter(Boolean);
616
+ const receipt = (verified, reason, records = []) =>
617
+ ({ verified, element: el, owners_checked: names, classes: wanted, records, reason, ts: now() });
618
+
619
+ if (!el) return receipt(false, "house_element_absent: nothing was proposed");
620
+ if (!names.length)
621
+ return receipt(false, "client_owner_unknown: this run holds no trading name for the client, so an owner on the register cannot be matched to it");
622
+ if (!wanted.length)
623
+ return receipt(false, "instructed_classes_absent: ownership is only decisive in the classes the matter is instructed in");
624
+ if (typeof lookup !== "function")
625
+ return receipt(false, "lookup_unavailable: no register lookup was wired, so ownership was never asked");
626
+
627
+ let rows = [];
628
+ try {
629
+ const r = await lookup({ element: el, owners: names, classes: wanted });
630
+ if (!r?.ok) return receipt(false, `lookup_did_not_answer: ${String(r?.reason ?? "no reason given").slice(0, 200)}`);
631
+ rows = Array.isArray(r.records) ? r.records : [];
632
+ } catch (e) {
633
+ return receipt(false, `lookup_threw: ${String(e?.message ?? e).slice(0, 200)}`);
634
+ }
635
+
636
+ // A MATCH IS ALL THREE AT ONCE — the client's own name, alive, in an instructed class. Checking them
637
+ // separately would let a dead registration in class 9 and a live one in class 25 held by someone else
638
+ // combine into an ownership nobody has.
639
+ const norm = (v) => String(v ?? "").toLowerCase().replace(/[^a-z0-9]+/g, " ").trim();
640
+ const ours = names.map(norm).filter(Boolean);
641
+ const live = (st) => { const t = norm(st); return Boolean(t) && !/(dead|expired|cancell?ed|withdrawn|refused|lapsed|abandoned)/.test(t); };
642
+ const matched = rows.filter((r) => {
643
+ const owner = norm(r?.owner_name);
644
+ if (!owner || !ours.some((o) => owner === o || owner.includes(o) || o.includes(owner))) return false;
645
+ if (!live(r?.status)) return false;
646
+ const rc = (Array.isArray(r?.classes) ? r.classes : []).map(String).map((c) => c.trim());
647
+ return rc.some((c) => wanted.includes(c));
648
+ });
649
+
650
+ if (!matched.length)
651
+ return receipt(false, `no_live_owned_registration: ${rows.length} record(s) came back and none is a live registration held by this client in an instructed class`, []);
652
+ // The records are the EVIDENCE the report states the exclusion on, so they ride the receipt.
653
+ return receipt(true, `verified: ${matched.length} live registration(s) held by this client in an instructed class`,
654
+ matched.map((r) => ({ record_id: String(r?.record_id ?? r?.uri ?? ""), owner_name: String(r?.owner_name ?? ""),
655
+ status: String(r?.status ?? ""), classes: (Array.isArray(r?.classes) ? r.classes : []).map(String) })));
656
+ }
657
+
658
+ /**
659
+ * The manifest with the client's own house element taken out of the conflict analysis. PURE.
660
+ *
661
+ * THE DEFECT (production run, 2026-09-16). The mark was the client's own famous house mark followed by
662
+ * a tagline. `dominant_element` was the house element, so the machine-built one-letter mutation forms
663
+ * were built from it — and because those mutations are ordinary short words they pull every mark
664
+ * containing them across four registers. Two such queries put 1,154 records into a 2,146-record band,
665
+ * 54% of it, reachable from nothing else. The reviewing lawyer's method for the same matter was three
666
+ * searches: the whole phrase, the shorter phrase, the last word alone.
667
+ *
668
+ * WHAT IS NOT DONE HERE, AND IT IS THE POINT. This does not decide that the client owns the element —
669
+ * it is called only where the driver has already verified ownership on the register by owner and written
670
+ * its receipt. A `house` argument that arrived from a model's assertion would be an element nobody
671
+ * searched because a model said it was safe.
672
+ *
673
+ * THE WHOLE PHRASE SURVIVES, and that is the lawyer's method rather than a softening of it. `mark` is
674
+ * untouched, so the exact search for the full phrase still runs. What stops is treating the house
675
+ * element as an AXIS — its mutations, its forms, its own dominant-element sweep — because that is where
676
+ * the band came from, not from the one exact query.
677
+ *
678
+ * Returns `{ manifest, confirmation, refused }`. `refused` non-null means the exclusion was NOT applied
679
+ * and the returned manifest is the input: the caller plans as it would have with no receipt at all.
680
+ */
681
+ export function excludeHouseElement(manifest, house) {
682
+ const element = String(house?.element ?? "").trim();
683
+ const remainder = String(house?.remainder ?? "").trim();
684
+ const keep = (why) => ({ manifest, confirmation: null, refused: why });
685
+ if (!element || !remainder) return keep("house_element_incomplete");
686
+
687
+ const words = remainder.split(/\s+/).filter(Boolean);
688
+ // ── THE FLOOR IS THE JOIN TO THIS MANIFEST'S OWN MARK ───────────────────────────────────────────
689
+ //
690
+ // The direction that reaches a client is naming too MUCH as the house element: ownership verifies
691
+ // perfectly, nothing distinctive is left, and no count downstream catches it because "queries on the
692
+ // house element: 0" is satisfied by a plan holding no queries at all. The frame refuses the shapes it
693
+ // can see — an empty remainder, a remainder equal to the element, an element containing it.
694
+ //
695
+ // WHAT THE FRAME CANNOT SEE IS THIS MARK. The proposal is made against the matter, the exclusion is
696
+ // applied against a compiled manifest, and nothing until here has joined the two. A remainder that is
697
+ // not part of the mark being planned means the receipt and the manifest are describing different
698
+ // things — a re-frame, a second ratified form, a resumed run whose manifest moved — and cutting the
699
+ // dominant element down to a word that is not in the mark would leave the plan searching something
700
+ // the client never applied for. So the exclusion applies only where both halves are in the mark.
701
+ const lcMark = String(manifest?.mark ?? "").toLowerCase();
702
+ if (!lcMark) return keep("house_element_no_mark_to_join");
703
+ if (!lcMark.includes(remainder.toLowerCase())) return keep("house_element_remainder_not_in_mark");
704
+ if (!lcMark.includes(element.toLowerCase())) return keep("house_element_not_in_mark");
705
+
706
+ const lcEl = element.toLowerCase();
707
+ const lcWords = words.map((w) => w.toLowerCase());
708
+ // A word of the remainder, by word boundary rather than by containment: `includes` would count the
709
+ // remainder word "on" inside an unrelated mutation and keep a query this exists to drop.
710
+ const namesRemainder = (v) => {
711
+ const t = String(v ?? "").toLowerCase();
712
+ return lcWords.some((w) => new RegExp(`(^|[^\\p{L}\\p{N}])${w.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}([^\\p{L}\\p{N}]|$)`, "u").test(t));
713
+ };
714
+
715
+ // THE DOMINANT WORD OF THE REMAINDER: longest, ties broken by the LAST of them. The lawyer's own
716
+ // phrasing was "the last word alone", and on the matter that produced this the two agree; longest is
717
+ // the better rule where they do not, because a trailing article is last and carries nothing.
718
+ let dominant = words[0];
719
+ for (const w of words) if (w.length >= dominant.length) dominant = w;
720
+
721
+ const variants = (Array.isArray(manifest?.variants) ? manifest.variants : []).filter((v) => namesRemainder(v?.value));
722
+ const next = {
723
+ ...manifest,
724
+ // UNTOUCHED: the exact search for the full phrase is the first of the lawyer's three.
725
+ mark: manifest?.mark,
726
+ dominant_element: dominant,
727
+ elements: (Array.isArray(manifest?.elements) ? manifest.elements : []).filter((e) => String(e?.value ?? "").trim().toLowerCase() !== lcEl),
728
+ // The shorter phrase is the second of the three, and it has to be PUSHED: with `dominant_element`
729
+ // now a single word and `mark` the full phrase, nothing else in the compile would search the
730
+ // remainder as a phrase on its own.
731
+ // `exact-phrase` because that is what it is, and because the category vocabulary is CLOSED —
732
+ // `parseVariantManifestModel` refuses anything outside it, so a descriptive category invented here
733
+ // would fail the manifest on its way into the compile.
734
+ variants: [{ category: "exact-phrase", value: remainder,
735
+ rationale: "the distinctive remainder once the client's own registered element is set aside" },
736
+ ...variants],
737
+ };
738
+ // REQUIREMENT 3: the element is still checked ONCE, as a confirmation of the client's own live
739
+ // registrations rather than as a conflict search — so the exclusion is evidenced on the report by a
740
+ // query that ran, not by a sentence saying one would have.
741
+ const confirmation = { axis: "incumbent-class", predicate: "owner", term: element,
742
+ expected_kind: "enumerate", provenance: "mark", house_element_confirmation: true };
743
+ return { manifest: next, confirmation, refused: null };
744
+ }
745
+
746
+ export function compileRegisterPlan({ manifest, job, form = null, skillVersion = "", capabilities = null, unavailableOffices = [], houseElement = null }) {
747
+ // ── AN EXCLUDED ELEMENT'S FORM BAND MUST BE UNREACHABLE, NOT MERELY UNASKED-FOR ─────────────────
748
+ //
749
+ // THE DEFECT THIS CLOSES, found by following the seam rather than by a failing arm. `bandFor` falls
750
+ // back to `elements[0]` when it cannot find the element it was asked for. The house-element exclusion
751
+ // changes `dominant_element` to the remainder's dominant word — a word the form neighbourhood, derived
752
+ // earlier from the original manifest, may carry no band for. The lookup would then MISS and the
753
+ // fallback would hand back the first element's band, which is the house element's: the exclusion would
754
+ // appear to work, `dominant_element` would read correctly on the plan, and the one-letter mutation
755
+ // floor of the very element being excluded would compile anyway. Silently, and it is the whole flood.
756
+ //
757
+ // So the element is removed from the form document here, where every `bandFor` call in this compile
758
+ // reads it. Unreachable beats un-asked-for: a fallback cannot select what is not there.
759
+ if (houseElement && form?.elements) {
760
+ const lcHouse = String(houseElement).trim().toLowerCase();
761
+ const kept = form.elements.filter((e) => String(e?.element ?? "").trim().toLowerCase() !== lcHouse);
762
+ form = { ...form, elements: kept };
763
+ }
577
764
  const classes = (job?.classes ?? []).map(String).filter(Boolean);
578
765
  if (!classes.length) throw new Error("register_plan_classes_missing: a plan is always class-scoped — compile with the matter's in-scope Nice classes");
579
766
  const caps = capabilities ?? null;
@@ -792,7 +979,7 @@ export function compileRegisterPlan({ manifest, job, form = null, skillVersion =
792
979
  // visible from the definition rather than inferred from an absence.
793
980
  const STRIPPED_CATEGORIES = new Set(["phonetic", "transliteration", "visual"]);
794
981
  // — THE DOCTRINE'S OWN DISPATCH TABLE, NOW BINDABLE. The universal-categories table
795
- // (prelim-variants SKILL.md) states the mode per tag: `exact-element` sweeps default, `plural-root`
982
+ // (clearance-variants SKILL.md) states the mode per tag: `exact-element` sweeps default, `plural-root`
796
983
  // is a root (the contains match is its whole purpose), and `formative-family` is "never exact-only".
797
984
  // Until the enum accepted these tags the mandate bound to nothing — measured: three root-shaped
798
985
  // strings dispatched exact, 4/2/4 records, the family they exist to reach retrieved zero times.
@@ -15,7 +15,7 @@
15
15
  // ordering, and renewal/expiry cycle arithmetic (renewals fall at year 10/20/… from registration — the
16
16
  // check that catches "registration 2013 … renewed 2025" from the document alone).
17
17
 
18
- import { readFileSync, writeFileSync, mkdirSync, existsSync, readdirSync, openSync, readSync, closeSync } from "node:fs";
18
+ import { readFileSync, writeFileSync, mkdirSync, existsSync, readdirSync, openSync, readSync, closeSync } from "node:fs"; import { runPrefixSpellings } from "../shared/pre-rename-spellings.mjs";
19
19
  import { join } from "node:path";
20
20
  import { driverDir, ensureDriverDir } from "../shared/driver-dir.mjs"; // — one definition of where `_driver/` is
21
21
  import { ledgerPath, runRecordLogPath, ledgerDeprecationNotice, retiredGlobalRecordLogNotice }
@@ -34,8 +34,8 @@ function stripGatewayNs(s) {
34
34
  return typeof s === "string" ? s.replace(/^agent:[^:]+:/, "") : "";
35
35
  }
36
36
  function rowMatchesRun(row, runPrefix) {
37
- return stripGatewayNs(row.sessionKey).startsWith(runPrefix)
38
- || stripGatewayNs(row.sessionId ?? "").startsWith(runPrefix);
37
+ return runPrefixSpellings(runPrefix).some((rp) => stripGatewayNs(row.sessionKey).startsWith(rp)
38
+ || stripGatewayNs(row.sessionId ?? "").startsWith(rp)); // either spelling: a run resumed across the rename
39
39
  }
40
40
 
41
41
 
@@ -586,7 +586,7 @@ export const REPAIR_COMPOSERS = [
586
586
  //
587
587
  // `samplesForStage` IS REQUIRED, and for the reason `*:lint-repair`'s note above gives rather than the
588
588
  // one it looks like: a `stage: "*"` composer is walked for EVERY recording stage, so the fixed sample's
589
- // `record_frame_diff` is read as an order handed to blind-frame, matter-frame, prelim-variants,
589
+ // `record_frame_diff` is read as an order handed to blind-frame, matter-frame, clearance-variants,
590
590
  // report-overview, report-card and doubt-closure — six ordered-but-not-granted findings that are
591
591
  // artifacts of the SAMPLE, not of the tree. At dispatch the tool is always the walking stage's own,
592
592
  // because it is derived from `out`. Removing this hook reproduces all six.
@@ -116,7 +116,7 @@ export function failingTarget(lastFail, files = []) {
116
116
  if (!named) return null;
117
117
  const list = (Array.isArray(files) ? files : [files]).filter(Boolean).map(String);
118
118
  if (list.length <= 1) return list[0] ?? null;
119
- // longest suffix match wins: "x/register-findings.md" identifies ".../prelim-search/x/register-findings.md"
119
+ // longest suffix match wins: "x/register-findings.md" identifies ".../clearance-search/x/register-findings.md"
120
120
  const hit = list.find((f) => f === named || f.endsWith(`/${named}`) || named.endsWith(`/${f}`));
121
121
  return hit ?? null;
122
122
  }
@@ -22,14 +22,14 @@
22
22
  // an INTENDED fix (then --update on the merged result).
23
23
  //
24
24
  // Env: CLEAROTRON_REPLAY_ROOTS colon-separated corpus roots
25
- // (default: <workspaceRoot>/workspace-*/studio/prelim-search — live slugs + archive/)
26
- // CLEAROTRON_REPLAY_SNAPSHOT snapshot path (default: ~/.prelim-replay-snapshot.json)
25
+ // (default: <workspaceRoot>/workspace-*/studio/clearance-search — live slugs + archive/)
26
+ // CLEAROTRON_REPLAY_SNAPSHOT snapshot path (default: ~/.clearance-replay-snapshot.json)
27
27
 
28
28
  import "../shared/env-local.mjs"; // — FIRST: the CLEAROTRON_* translation must land before any
29
29
  // module-top capture below it evaluates. A call in this file's BODY
30
30
  // would run too late — that was the repair that left this open.
31
31
  import { readFileSync, writeFileSync, readdirSync, existsSync, statSync } from "node:fs";
32
- import { join, basename } from "node:path";
32
+ import { join, basename } from "node:path"; import { studioDirFor } from "../shared/pre-rename-spellings.mjs";
33
33
  import { DRIVER_DIR, driverDir } from "../shared/driver-dir.mjs"; //
34
34
  import { homedir } from "node:os";
35
35
  import { validators } from "./verify.mjs";
@@ -62,7 +62,7 @@ function looksLikeRunDir(p) {
62
62
  return n.includes(DRIVER_DIR) || n.some((f) => FILE_CHECKS[f]);
63
63
  }
64
64
 
65
- // Corpus roots → sorted run dirs. Layout per root (a workspace's studio/prelim-search):
65
+ // Corpus roots → sorted run dirs. Layout per root (a workspace's studio/clearance-search):
66
66
  // <slug>/<date>-<codename>/ (live slugs)
67
67
  // archive/<YYYY-MM>/<slug>/<date>-<codename>/ (archived)
68
68
  export function discoverRuns(roots) {
@@ -219,12 +219,12 @@ function main() {
219
219
  const args = new Set(process.argv.slice(2));
220
220
  // ON-DISK NAME, NOT A PRODUCT NAME: an install that never set the variable already has this file, so
221
221
  // renaming the default points the reader at one that does not exist. Ruling.
222
- const snapshotPath = process.env.CLEAROTRON_REPLAY_SNAPSHOT || join(homedir(), ".prelim-replay-snapshot.json");
222
+ const snapshotPath = process.env.CLEAROTRON_REPLAY_SNAPSHOT || [join(homedir(), ".prelim-replay-snapshot.json"), join(homedir(), ".clearance-replay-snapshot.json")].find((f, i) => i === 1 || existsSync(f)); // the file the install already has wins
223
223
  const roots = process.env.CLEAROTRON_REPLAY_ROOTS
224
224
  ? process.env.CLEAROTRON_REPLAY_ROOTS.split(":").filter(Boolean)
225
225
  : names(config.workspaceRoot)
226
226
  .filter((d) => config.agentIdFromWorkspaceName(d) != null)
227
- .map((d) => join(config.workspaceRoot, d, "studio", "prelim-search"));
227
+ .map((d) => studioDirFor(join(config.workspaceRoot, d)));
228
228
 
229
229
  const runDirs = discoverRuns(roots);
230
230
  if (!runDirs.length) {
@@ -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
  // report-overview-record.mjs — the recording transport for the report shell.
4
4
  //
5
- // Conversion 4, after blind-frame, skeptic, frame-diff, matter-frame and prelim-variants
5
+ // Conversion 4, after blind-frame, skeptic, frame-diff, matter-frame and clearance-variants
6
6
  //. It is the FIRST conversion whose artifact a client reads. report-overview.md is not an internal
7
7
  // input that a later stage consumes — it is the front-matter and the Actions section of the delivered
8
8
  // report, so a render defect here reaches a lawyer's desk rather than a test log.
@@ -181,7 +181,7 @@ export function renderReportOverview(model, identity) {
181
181
  const id = identity ?? {};
182
182
  const fm = [
183
183
  "---",
184
- "type: prelim-clearance",
184
+ "type: clearance-clearance",
185
185
  id.matter ? `matter: ${id.matter}` : "",
186
186
  id.title ? `title: ${id.title}` : "",
187
187
  id.client ? `client: ${id.client}` : "",
@@ -81,8 +81,8 @@ export const RESULT_NOUN_FIELDS = Object.freeze([
81
81
  + "is the fact the disclosure downstream depends on" },
82
82
  { file: "driver/pipeline.mjs", noun: "settled", sites: 5, atWriteSite: 4, verdict: "result",
83
83
  why: "counts off the union and the doubt ledger" },
84
- { file: "driver/pipeline.mjs", noun: "verified", sites: 3, atWriteSite: 1, verdict: "result",
85
- why: "`rows.filter(r => r.verified).length`, where each row's flag is `srRecords.has(senior.uri)` — a lookup, not a call" },
84
+ { file: "driver/pipeline.mjs", noun: "verified", sites: 4, atWriteSite: 2, verdict: "result",
85
+ why: "two sites, both measured states rather than calls returning. (1) `rows.filter(r => r.verified).length`, where each row's flag is `srRecords.has(senior.uri)` — a lookup, not a call. (2) the house-element ownership row's `verified: receipt.verified`, which the verifier sets only where a returned register record matched the client's own name AND was live AND carried an instructed class; the lookup CALL returning is a separate field on the same receipt (`reason`), and an unanswered lookup writes this false" },
86
86
  { file: "driver/repairs.mjs", noun: "closed", sites: 2, atWriteSite: 1, verdict: "result",
87
87
  why: "`effect.closed`, measured against `effect.asked`" },
88
88
  { file: "driver/repairs.mjs", noun: "outcome", sites: 1, atWriteSite: 1, verdict: "result",
@@ -14,11 +14,14 @@
14
14
  // the provider's four separately-priced token kinds.)
15
15
  //
16
16
  // ── WHAT A "DISPATCH" IS ──────────────────────────────────────────────────────────────────────────
17
- // One model invocation: one row in `_driver/<stage>.jsonl` carrying a `model` (gateway.mjs writes one
18
- // per ATTEMPT, so retries are separate dispatches and retry waste is counted, not averaged away). The
19
- // direct-API jx lanes bypass the gateway and write `_driver/jx-completions.jsonl` in the same
20
- // {model, usage} shape; they are dispatches too, under stage `jx-completions`. `run.jsonl` is skipped
21
- // (run events, not dispatches) — same file selection as tokens.mjs, deliberately.
17
+ // One model invocation: one row in `_driver/<stage>.jsonl` that tokens.mjs's `isAttemptRow` counts as
18
+ // a provider attempt (gateway.mjs writes one per ATTEMPT, so retries are separate dispatches and retry
19
+ // waste is counted, not averaged away). The direct-API jx lanes bypass the gateway and write
20
+ // `_driver/jx-completions.jsonl` in the same {model, usage} shape; they are dispatches too, under stage
21
+ // `jx-completions`. `run.jsonl` is skipped (run events, not dispatches) — same file selection as
22
+ // tokens.mjs, deliberately, and the SAME ROW TEST as tokens.mjs, imported rather than copied: a jx row
23
+ // whose turn ran and named no model carries no `model`, only `modelActual: null`, and a census that still
24
+ // asked for a `model` counted no dispatch and no tokens for a turn the token rollup counted with both.
22
25
  //
23
26
  // ── ZERO SEMANTICS: THE THING THIS MODULE EXISTS TO GET RIGHT ─────────────────────────────────────
24
27
  // tokens.mjs sums `usage` with `u.output || 0`, so a dispatch whose usage is null contributes zero AND
@@ -32,7 +35,7 @@
32
35
  // measured — the dispatch journalled a usage object (from the provider's own result envelope)
33
36
  // streamed — usage present but RECONSTRUCTED from the stream (`signals.usageStreamed`), because the
34
37
  // turn died before its result event. A real measurement, a weaker one, counted apart.
35
- // unmeasured — the row is a dispatch (it has a model) and carries no usage at all. THE KILLED TURNS.
38
+ // unmeasured — the row is a dispatch and carries no usage at all. THE KILLED TURNS.
36
39
  // `tokensComplete` is false whenever `unmeasured > 0`, at run level and per stage, and
37
40
  // `unmeasuredDispatches[]` names which ones so a reader can see what the total is missing.
38
41
  //
@@ -92,14 +95,18 @@
92
95
  // so a per-record basis is the only honest shape. `tokens.mjs` keys its rollup on the requested alias
93
96
  // for the same reason and is likewise untouched.
94
97
  //
95
- // Pure by contract: `runEconomics()` reads the run dir and nothing else — no env, no config, no network,
96
- // no driver imports. `stampRunEconomics()` is the only part that writes.
98
+ // Pure by contract: `runEconomics()` reads the run dir and nothing else — no env, no config, no network.
99
+ // Its only driver import is the attempt-row test it shares with tokens.mjs; the log and status writers
100
+ // serve `stampRunEconomics()`, the only part that writes.
97
101
 
98
102
  import { readdirSync, readFileSync, writeFileSync } from "node:fs";
99
103
  import { join } from "node:path";
100
104
  import { driverDir } from "../shared/driver-dir.mjs"; //
101
105
  import { runLog, note } from "./log.mjs";
102
106
  import { writeRunStatus } from "./progress.mjs";
107
+ // tokens.mjs imports this module too (isCodeSide, stampRunEconomics). The cycle is safe because each side
108
+ // reads the other's bindings only inside functions, never while the module is loading.
109
+ import { isAttemptRow } from "./tokens.mjs";
103
110
 
104
111
  /**
105
112
  * The provider's separately-priced token kinds, in the driver's own `usage` vocabulary (gateway.mjs /
@@ -178,7 +185,23 @@ function billingKeyOf(rec) {
178
185
  // is rather than dragged into "unknown" beside genuinely unstamped legacy rows.
179
186
  const engine = isCodeSide(rec) ? "code" : String(rec.engine ?? "unknown");
180
187
  const authMode = isCodeSide(rec) ? "not-provider-billed" : String(rec.authMode ?? "unknown");
181
- const model = String(rec.modelUsed ?? rec.model ?? "unknown");
188
+ // A TURN THAT NAMED NO MODEL (a jx row with `modelActual: null` and no `model`) is keyed the way the
189
+ // token rollup keys it (modelKey in tokens.mjs): `<engine>/no-model-reported`, a name that says the
190
+ // model is missing. Read through the old `?? "unknown"` it landed beside legacy rows nobody stamped,
191
+ // and its byBilling bucket named a different model from the rollup's byModel for the same turn.
192
+ //
193
+ // A COPY OF modelKey's RULE, NOT A SHARED ONE: tokens.mjs does not export it. So the test for "no stamp"
194
+ // is modelKey's own, a non-empty string `modelUsed`, and not `modelUsed == null`: under that looser test
195
+ // a row stamped `modelUsed: ""` keyed its bucket as the empty string while the rollup keyed the same
196
+ // turn `<engine>/no-model-reported`. Two copies of one rule drifting apart is how the census and the
197
+ // rollup came to disagree about what an attempt is, so the tests hold these two copies to each other on
198
+ // the rows the engine writes. They still part on a row no writer produces: no model and no engine, or
199
+ // engine `anthropic-agent`. This key names the missing model there, while modelKey resolves the absent
200
+ // model through the catalog before it asks whether one exists, and buckets the row as `undefined`.
201
+ const stamped = typeof rec.modelUsed === "string" && rec.modelUsed;
202
+ const model = !stamped && typeof rec.model !== "string"
203
+ ? `${typeof rec.engine === "string" && rec.engine ? rec.engine : "unknown"}/no-model-reported`
204
+ : String(rec.modelUsed ?? rec.model ?? "unknown");
182
205
  return { engine, authMode, model, key: `${engine}|${authMode}|${model}` };
183
206
  }
184
207
 
@@ -223,11 +246,19 @@ function foldBilling(bucketMap, rec, cls) {
223
246
  // future engine whose name began that way, which is how a vendor claim becomes a guess. An engine this
224
247
  // table does not know is reported BY NAME and blocks the single-vendor claim, because "I do not know who
225
248
  // billed this" and "one vendor" are different answers and only one of them is safe to print.
249
+ //
250
+ // THE NATIVE-LANGUAGE ROWS STAMP THE VENDOR ITSELF. jxBillingStamp (jx-lanes.mjs) writes as the row's
251
+ // engine the provider the engine door resolved, `anthropic` or `openai` (engine/auth.mjs), so those two
252
+ // names are engines this table must place. Without them an Anthropic-only run with a native-language turn
253
+ // named `anthropic` as an engine that bills to no vendor, in the same sentence that named anthropic as its
254
+ // one vendor. Two exact names, still a closed table.
226
255
  export const ENGINE_VENDORS = Object.freeze({
227
256
  "anthropic-agent": "anthropic",
228
257
  "anthropic-direct": "anthropic",
229
258
  "anthropic-completions": "anthropic",
230
259
  "openai-agent": "openai",
260
+ "anthropic": "anthropic",
261
+ "openai": "openai",
231
262
  });
232
263
  /** The vendor an engine bills to, or null when the table does not name one. */
233
264
  export const vendorOf = (engine) => ENGINE_VENDORS[String(engine ?? "")] ?? null;
@@ -469,7 +500,7 @@ export function runEconomics(runDir, { now = null, bytesPerOutputToken = BYTES_P
469
500
  let sawDeclaredNoOutput = false;
470
501
 
471
502
  for (const rec of rows) {
472
- if (!rec || typeof rec.model !== "string") continue; // only dispatch rows carry a model
503
+ if (!isAttemptRow(rec)) continue; // only provider attempts are dispatches (tokens.mjs, isAttemptRow)
473
504
  const cls = classesOf(rec.usage);
474
505
  const streamed = cls != null && rec.signals?.usageStreamed === true;
475
506