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
package/bin/start.mjs CHANGED
@@ -84,8 +84,10 @@ import { writeSecretFile } from "../shared/secret-file.mjs"; // one atomic wri
84
84
  // below: to COMPOSE the units' environment and to GUARD it before this command reports success. The
85
85
  // tables are handed in rather than imported by it, because the register table lives in a CLI entry and
86
86
  // the driver must not point at `bin/`.
87
- import { runRequiredNames, missingRequirements } from "../driver/run-requirements.mjs";
88
- import { ENGINE_BINARIES, DEFAULT_ENGINE_ID as RUN_DEFAULT_ENGINE } from "../driver/driver.config.mjs";
87
+ import { runRequiredNames, missingRequirements, CLOUD_ROUTES } from "../driver/run-requirements.mjs";
88
+ import { CLOUD_SWITCH, cloudsSwitchedOn } from "../driver/engine/auth.mjs"; // a switch compared the way the program reads it: on or off
89
+ import { ENGINE_BINARIES, DEFAULT_ENGINE_ID as RUN_DEFAULT_ENGINE, resolveEngineProgram } from "../driver/driver.config.mjs";
90
+ import { unitEnvironment, unitValue } from "../driver/unit-environment.mjs"; // the PATH the worker unit will run with, read the way doctor reads it
89
91
 
90
92
  /**
91
93
  * The tables the requirements authority needs — resolved at CALL time, never at module scope.
@@ -104,13 +106,14 @@ import { ENGINE_BINARIES, DEFAULT_ENGINE_ID as RUN_DEFAULT_ENGINE } from "../dri
104
106
  * driver/test/a-backgrounded-install-can-actually-run-a-clearance.test.mjs refuses a static import of
105
107
  * onboard from this file, so the cycle cannot come back quietly.
106
108
  */
107
- async function runTables() {
109
+ export async function runTables() {
108
110
  const { PROVIDERS } = await import("./onboard.mjs");
109
- return { registers: PROVIDERS, engines: ENGINE_BINARIES, defaultEngine: RUN_DEFAULT_ENGINE };
111
+ return { registers: PROVIDERS, engines: ENGINE_BINARIES, defaultEngine: RUN_DEFAULT_ENGINE, resolveEngine: resolveEngineProgram };
110
112
  }
111
113
  import { spawn, spawnSync, execFileSync } from "node:child_process";
112
114
  import { storeInRepo, storeOutsideRepoMessage, storeCommitRefusal } from "../shared/store-in-repo.mjs"; //
113
115
  import { stdioConnectOffer } from "../shared/stdio-connect.mjs";
116
+ import { wslTarget } from "../shared/wsl.mjs"; // — and which distribution a row should start the server in
114
117
  import { ensureDemoProgram } from "../shared/permanent-install.mjs"; // — a demo from npx keeps its own copy
115
118
  import { mergeEnvFile } from "../shared/env-file-merge.mjs";
116
119
  import { mcpOriginFor } from "../shared/lane-address.mjs"; // — one author for the origin
@@ -188,6 +191,89 @@ export function homeEnvUpdate(homeText, union) {
188
191
  return mergeEnvFile(homeText, union, { refresh: LAUNCHER_MINTED });
189
192
  }
190
193
 
194
+ /**
195
+ * The services' settings file as this start will leave it, and the run's settings in it that differ from
196
+ * the configuration this command holds. PURE, and the one reading `--background` uses for both its guard
197
+ * and its write, so a test drives start's own answer rather than a copy of it.
198
+ *
199
+ * THE GUARD CHECKED A MERGE THE FILE DOES NOT PERFORM. It read `{ ...file, ...union }`, where this
200
+ * command's value wins, while the add-only merge keeps the file's line, an empty one included. So a file
201
+ * holding `CLAUDE_CODE_USE_FOUNDRY=` passed the guard on this command's `=1`, kept its empty line, and
202
+ * every search was refused at the order wall. `reads` is the merged text, parsed: what the units read.
203
+ *
204
+ * AND WHAT THE FILE KEEPS IS SAID, never replaced. The merge adds only what the file lacks, and that is
205
+ * deliberate: the file is the running product's configuration, an operator edits it to change what the
206
+ * services do, and a start that rewrote it from this command's configuration would undo that edit. What
207
+ * was wrong was the silence. A machine moved from one cloud to another, or holding a rotated key, started
208
+ * again and reported the file complete while the services kept the old account.
209
+ * `differ` names the run settings on which the two disagree, so start can say which. Names only, never a
210
+ * value.
211
+ *
212
+ * WHICH DISAGREEMENTS, BY THE KIND OF SETTING, and never by which side holds the value. A healthy install
213
+ * can keep its register key, its engine's path and its research key in the units' file alone, because
214
+ * that file is where start tells an operator to change what the services use; this command's shell then
215
+ * holds none of them, and naming them on every start would warn about a working install. So a setting
216
+ * counts only when this command holds a value the file contradicts: a rotated key, or a line kept empty.
217
+ * The billing word and the settings that pick a cloud are the exception, and count in either direction,
218
+ * because the file holding one this command does not is exactly the machine billing an account nobody
219
+ * chose any more. They are compared as the program reads them: an unset billing word is the subscription,
220
+ * and a switch is on or off, so `1` beside `true` is not a difference.
221
+ *
222
+ * BUT ONLY WHERE THIS COMMAND HOLDS AN OPINION. A configuration that sets no billing word says nothing
223
+ * about how the services pay: an api-key, Codex api-key or cloud install whose billing lives in the units'
224
+ * file alone, where start's own remedy offers to put it, was warned about its billing word and its switch
225
+ * on every start. So with no billing word here, neither the word nor the cloud settings are compared. Under
226
+ * `cloud` with no cloud named here, which cloud is not compared either. And under `subscription` or
227
+ * `api-key` a cloud setting this command holds counts as unset, because the run door refuses a switch
228
+ * beside either word and start never carries one: naming it sent the operator to put into the file a
229
+ * switch every search would then be refused over. A cloud the FILE holds under those words still counts.
230
+ */
231
+ export function unitsFileAfterStart(homeText, union, { config = {}, tables = {} } = {}) {
232
+ const merged = homeEnvUpdate(homeText, union);
233
+ const reads = parseEnvFile(merged.text);
234
+ const ours = (k) => String(union[k] ?? config[k] ?? "").trim();
235
+ const theirs = (k) => String(reads[k] ?? "").trim();
236
+ const words = new Set(Object.values(tables.engines ?? {}).map((e) => e?.authEnv).filter(Boolean));
237
+ const switches = Object.values(CLOUD_SWITCH);
238
+ const as = (k, v) => words.has(k) ? (v.toLowerCase() || "subscription")
239
+ : switches.includes(k) ? cloudsSwitchedOn({ [k]: v }).length > 0 : v;
240
+ const ourWord = ([...words].map(ours).find(Boolean) ?? "").toLowerCase();
241
+ const ourRoutes = Object.fromEntries(CLOUD_ROUTES.map((k) => [k, ours(k)]));
242
+ const ourCloud = cloudsSwitchedOn(ourRoutes).length > 0 || ourRoutes.ANTHROPIC_BASE_URL !== "";
243
+ const route = (k) => CLOUD_ROUTES.includes(k);
244
+ const opinion = (k) => ourWord !== "" && (!route(k) || ourWord !== "cloud" || ourCloud);
245
+ const oursAs = (k) => as(k, route(k) && ourWord !== "cloud" ? "" : ours(k));
246
+ const eitherWay = (k) => words.has(k) || route(k);
247
+ const names = new Set([...runRequiredNames(config, tables), ...runRequiredNames(reads, tables), ...CLOUD_ROUTES]);
248
+ const differ = [...names].filter((k) => !LAUNCHER_MINTED.includes(k)
249
+ && (eitherWay(k) ? opinion(k) && oursAs(k) !== as(k, theirs(k)) : ours(k) !== "" && ours(k) !== theirs(k)));
250
+ return { merged, reads, differ };
251
+ }
252
+
253
+ /**
254
+ * What start says about `differ`, the settings on which the units' file and this command's configuration
255
+ * disagree. PURE, so the words are driven rather than read from source. Names only, never a value.
256
+ *
257
+ * BOTH PLACES, because the add-only merge makes one of them a trap. This said to edit the units' file and
258
+ * restart. An operator who moved the services from one cloud to another that way, with this command's
259
+ * configuration still naming the first, had the first cloud's switch added back on the next start, since
260
+ * the file no longer held its line: two clouds on, and every search refused. Deleting that line, as the
261
+ * notice said, only repeated it. So it names the file the units read and the configuration start adds from,
262
+ * `cliEnv` (envFileRead(), or null when this command read no file and its shell is the configuration).
263
+ */
264
+ export function keptSettingsNotice(differ, { homeEnv, cliEnv = null } = {}) {
265
+ if (!differ?.length) return [];
266
+ const one = differ.length === 1;
267
+ return [
268
+ ` ⚠ ${homeEnv} and this command's configuration differ on ${one ? "this setting" : "these settings"}, and the units use what the file says:`,
269
+ ` ${differ.join(", ")}`,
270
+ ` This command only adds a setting the file has no line for; it never replaces one. To change what the units`,
271
+ ` use, change ${one ? "it" : "them"} in both places, then restart them:`,
272
+ ` ${homeEnv}\n the file the units read`,
273
+ ` ${cliEnv ?? "this command's environment"}\n what this command adds from: a line the file above lacks is added back from here on the next start`,
274
+ ];
275
+ }
276
+
191
277
  /**
192
278
  * What a `--background` run has ALREADY DONE when systemd refuses to enable a unit.
193
279
  *
@@ -1709,22 +1795,19 @@ if (isMain) {
1709
1795
  // said everything was fine, and a lawyer's search that died at its first stage with a stack trace,
1710
1796
  // delivering "nothing was delivered. Clearotron has been notified" on a box that notified nobody.
1711
1797
  //
1712
- // CHECKED AGAINST `union`, WHICH IS WHAT THE FILE WILL SAY — plus what the file ALREADY says, since
1713
- // `mergeEnvFile` is add-only and an operator's existing line wins. Checking `process.env` here would
1798
+ // CHECKED AGAINST WHAT THE FILE WILL SAY: the file's own lines, with `union` added where the file has
1799
+ // no line, because `mergeEnvFile` is add-only and an existing line wins, an empty one included. That
1800
+ // is `unitsFileAfterStart`, the same reading the write below uses. Checking `process.env` here would
1714
1801
  // measure this shell rather than the units, and pass on exactly the box that fails.
1802
+ let homeText = "";
1803
+ try { homeText = readFileSync(HOME_ENV, "utf8"); } catch (e) { if (e.code !== "ENOENT") fatal(`${HOME_ENV} exists but could not be read (${e.code}).`); }
1804
+ const unitsFile = unitsFileAfterStart(homeText, union, { config: process.env, tables: RUN_TABLES });
1715
1805
  //
1716
1806
  // BLOCKING REFUSES; NARROWING IS SAID OUT LOUD AND STARTS ANYWAY. A research key this box does not
1717
1807
  // hold means the three clearance searches refuse at preflight and a Knockout search still runs and
1718
1808
  // discloses what it skipped — so refusing to start over it would take a box that can serve a real
1719
1809
  // product and make it serve none. The operator is told which products this install can fill.
1720
1810
  {
1721
- const already = {};
1722
- try {
1723
- for (const line of readFileSync(HOME_ENV, "utf8").split("\n")) {
1724
- const m = /^\s*([A-Za-z_][A-Za-z0-9_]*)\s*=(.*)$/.exec(line);
1725
- if (m) already[m[1]] = m[2];
1726
- }
1727
- } catch { /* no file yet — the union is the whole of it */ }
1728
1811
  // ONE COMPOSER for "where do I set these", used by the start-time refusal and by the order-time
1729
1812
  // announcement below it. Two copies of this sentence is how one of them comes to name a file the
1730
1813
  // reader cannot use.
@@ -1739,7 +1822,29 @@ if (isMain) {
1739
1822
  : `Set them in ${homeEnv} — the file the units read. This command read no environment file of `
1740
1823
  + `its own, so that is the address. \`${invoke("install")}\` writes them for you IN A TERMINAL.`;
1741
1824
  };
1742
- const willRead = { ...already, ...union };
1825
+ const willRead = { ...unitsFile.reads };
1826
+ // ── AND THE PATH THE WORKER WILL SEARCH FOR THE ENGINE'S PROGRAM ─────────────────────────────
1827
+ //
1828
+ // The check asks the engine resolver, and a resolver handed no PATH finds only a path setting or
1829
+ // the copy setup installed. So a `claude` on the units' PATH was announced as "every run is
1830
+ // refused", and the run found it and ran. The PATH is the worker unit's own, read from the file
1831
+ // placed below: the worker runs the clearance, and every shipped unit sets
1832
+ // `Environment=PATH=%h/…`, which the unit reader expands to this home. Never this shell's PATH,
1833
+ // which the units do not inherit.
1834
+ //
1835
+ // THE SETTINGS FILE IS READ WITH IT, because on systemd a PATH in that file wins over the unit's own
1836
+ // `Environment=PATH=` line, wherever the two lines sit (systemd.exec(5)), and the unit reader
1837
+ // applies that rule. Nothing Clearotron writes puts a PATH there, but a hand-edited file can, and
1838
+ // then the worker searches that PATH and not the unit's. A file that is not there yet reads as
1839
+ // empty: this command is about to write it, and the unit's PATH is then the one in force.
1840
+ {
1841
+ let text = null;
1842
+ try { text = readFileSync(join(REPO, "driver", "systemd", "clearotron-worker.service"), "utf8"); } catch { /* the install loop below names a missing unit */ }
1843
+ const settingsFile = (p) => { if (p !== HOME_ENV) return ""; try { return readFileSync(p, "utf8"); } catch { return ""; } };
1844
+ const workerPath = unitValue(unitEnvironment({ units: [{ name: "clearotron-worker.service", text }],
1845
+ readEnvFile: settingsFile, home: homedir() }), "PATH").value;
1846
+ if (workerPath) willRead.PATH = workerPath;
1847
+ }
1743
1848
  const miss = missingRequirements(willRead, RUN_TABLES);
1744
1849
  // ── — WHICH HALF OF `blocking` MAY REFUSE A START ─────────────────────────
1745
1850
  //
@@ -1769,9 +1874,9 @@ if (isMain) {
1769
1874
  //
1770
1875
  // BOTH FILES, and that is the difference from the port refusals, which say `~/.env` "is NOT read
1771
1876
  // here". They are right: nothing in that file reaches a port decision. Here both are true. The
1772
- // block above reads HOME_ENV into `already` and merges it into `willRead`, so a value set there
1773
- // satisfies this check on the next run; and the CLI's own file reaches it too, through the
1774
- // `runRequiredNames(process.env, …)` loop that copies its values into `union`. Driven rather than
1877
+ // block above reads HOME_ENV into `willRead`, with its lines winning as they do in the file, so a
1878
+ // value set there satisfies this check on the next run; and the CLI's own file reaches it too,
1879
+ // through the `runRequiredNames(process.env, …)` loop that copies its values into `union`. Driven rather than
1775
1880
  // read: three blocking names cleared from the CLI's file alone, and the refusal came back naming
1776
1881
  // a fourth that the first three had newly required.
1777
1882
  //
@@ -1817,26 +1922,32 @@ if (isMain) {
1817
1922
  // it"), not about which gate happened to print it, and the arms in
1818
1923
  // `a-refusal-names-the-file-when-the-command-it-offers-cannot-be-run.test.mjs` measure it here now.
1819
1924
  //
1820
- // BOTH FILES, because both genuinely reach the check: `~/.env` is merged into `already` above,
1821
- // and this command's own file reaches it through the carry loop. `envFileRead()` for the second,
1822
- // never a path composed here — null means this process read no file of its own (a systemd start,
1925
+ // BOTH FILES, because both genuinely reach the check: `~/.env` is read into `willRead` above,
1926
+ // and this command's own file reaches it through the carry loop where `~/.env` has no line.
1927
+ // `envFileRead()` for the second, never a path composed here — null means this process read no file of its own (a systemd start,
1823
1928
  // or CLEAROTRON_NO_ENV_FILE=1) and then HOME_ENV is the only honest address there is.
1824
1929
  say(` Nothing has been installed that cannot run, and nothing has been spent.`);
1825
1930
  say(` ${orderRemedy(HOME_ENV)}`);
1826
1931
  }
1827
1932
  for (const r of miss.narrowing)
1828
1933
  say(` ⚠ ${r.name} is not set — ${r.why}`);
1934
+ // ── WHAT THE FILE KEEPS, SAID BY NAME ──────────────────────────────────────────────────────────
1935
+ //
1936
+ // The merge never replaces a line (see `unitsFileAfterStart`), so a setting changed in this
1937
+ // command's configuration after the first start does not reach the units. A machine moved from one
1938
+ // cloud to another went on billing the first, and a rotated key stayed at its first value, while this
1939
+ // command reported the file complete. Said here, by name and never by value, naming both places the
1940
+ // setting lives (see `keptSettingsNotice` for why one is not enough).
1941
+ for (const line of keptSettingsNotice(unitsFile.differ, { homeEnv: HOME_ENV, cliEnv: envFileRead() })) say(line);
1829
1942
  }
1830
1943
 
1831
- let homeText = "";
1832
- try { homeText = readFileSync(HOME_ENV, "utf8"); } catch (e) { if (e.code !== "ENOENT") fatal(`${HOME_ENV} exists but could not be read (${e.code}).`); }
1833
1944
  // THE TRIGGER KEY IS MINTED HERE, SO IT IS REWRITTEN HERE. Everything else in this union is
1834
1945
  // collected — the operator's credentials, their paths — and add-only is what stops a launcher from
1835
1946
  // losing them. The trigger key is the opposite: this process mints it, thirty days at a time, and a
1836
1947
  // value written once and never again runs down to expiry on a server nobody has touched. When it
1837
1948
  // lapses every Start stops, and the failure arrives as an upstream refusal that reads like an engine
1838
1949
  // fault rather than an expired key card.
1839
- const merged = homeEnvUpdate(homeText, union);
1950
+ const { merged } = unitsFile;
1840
1951
  if (merged.added.length || merged.refreshed.length) {
1841
1952
  writeSecretFile(HOME_ENV, merged.text);
1842
1953
  const parts = [];
@@ -2412,11 +2523,24 @@ if (isMain) {
2412
2523
  // THE WORKSPACE AND POOL THE SERVICES WERE HANDED, not this process's environment: a demo reads no env
2413
2524
  // file, so its own line named no workspace and the connector fell back to the real install's.
2414
2525
  // A demo run from npx names its own copy of the program, which a cache clean does not remove.
2415
- const connect = stdioConnectOffer({ workDir: paths.workspace, reportsDir: paths.pool, ...(demoProgramRoot ? { installRoot: demoProgramRoot } : {}) });
2416
- say(" Connect your assistant to this install — one line, no address and no sign-in:");
2417
- say("");
2418
- say(` ${connect.command}`);
2526
+ // AND WHICH SIDE OF A WSL INSTALL THE READER IS ON, because this is where he read the line from. An
2527
+ // install inside WSL can be reached from Windows and from inside the distribution, by two different
2528
+ // commands; printing one of them unheaded is how a reader pastes the wrong one. The target is read
2529
+ // here and passed, because this module is pure by design and reads no environment of its own.
2530
+ const connect = stdioConnectOffer({ workDir: paths.workspace, reportsDir: paths.pool, wsl: wslTarget(), ...(demoProgramRoot ? { installRoot: demoProgramRoot } : {}) });
2531
+ // "one line" IS DELETED RATHER THAN MADE CONDITIONAL. Under WSL two lines are printed, one per side,
2532
+ // and the count was never the point of the sentence — what it promises is no address and no sign-in,
2533
+ // which is true on both sides and on every other install.
2534
+ say(" Connect your assistant to this install — no address and no sign-in:");
2419
2535
  say("");
2536
+ // THE HEADINGS COME WITH THE PAIR, from the composer. Nothing is written here: the page prints these
2537
+ // same two words above these same two commands, and a second author is how the two surfaces drift.
2538
+ if (connect.variants) {
2539
+ for (const v of connect.variants) { say(` ${v.heading}`); say(` ${v.text}`); say(""); }
2540
+ } else {
2541
+ say(` ${connect.command}`);
2542
+ say("");
2543
+ }
2420
2544
  say(` Check it: ${connect.verify}`);
2421
2545
  say("");
2422
2546
  // ── WHERE TO TYPE THE THINGS JUST PRINTED ( — F31) ───────────────────────
package/bin/update.mjs CHANGED
@@ -51,7 +51,7 @@ import { existsSync } from "node:fs";
51
51
  import { join, dirname, resolve } from "node:path";
52
52
  import { fileURLToPath } from "node:url";
53
53
 
54
- import { config } from "../driver/driver.config.mjs";
54
+ import { config, ENGINE_BINARIES, enginesFolder, engineInstallArgs } from "../driver/driver.config.mjs";
55
55
  import { isInsideCheckout } from "../shared/inside-checkout.mjs"; // — one copy of the rule
56
56
  import { overlayReport, renderOverlayReport, treeFiles } from "../shared/doctrine-overlay.mjs";
57
57
  import { liveRunHolds } from "../driver/deploy-live-run-guard.mjs"; // — one live-run test, shared with deploy-preflight
@@ -180,6 +180,34 @@ export function isGitCheckout(repo = REPO, exists = existsSync) {
180
180
  return exists(join(repo, ".git"));
181
181
  }
182
182
 
183
+ /**
184
+ * The engine programs setup installed for this user, which `clearotron update` refreshes: every engine
185
+ * whose package is in the engines folder. Setup installs only the one the reader chose, so this is usually
186
+ * one, and none where the machine's own copy was found first or the reader declined the install.
187
+ */
188
+ export function enginesToRefresh({ dir = enginesFolder(), exists = existsSync } = {}) {
189
+ return Object.values(ENGINE_BINARIES)
190
+ .filter((e) => e.package && exists(join(dir, "node_modules", ...e.package.split("/"), "package.json")));
191
+ }
192
+
193
+ /**
194
+ * Run setup's install again for each of them, in the same folder. The same command, because re-running an
195
+ * install with a `>=` range moves the program to the newest its vendor publishes (driver.config.mjs, above
196
+ * engineInstallArgs). Returns 0, or the first failing exit code.
197
+ */
198
+ function refreshEngines(engines, dir = enginesFolder()) {
199
+ if (!engines.length) return 0;
200
+ say(`\n Refreshing the engine program Clearotron installed in ${dir}.`);
201
+ for (const e of engines) {
202
+ const rc = runInCheckout("npm", engineInstallArgs(e, dir));
203
+ if (rc !== 0) {
204
+ console.error(`\n npm could not refresh ${e.package}. \`clearotron doctor\` says which copy a run would use now.`);
205
+ return rc;
206
+ }
207
+ }
208
+ return 0;
209
+ }
210
+
183
211
  function runInCheckout(cmd, args) {
184
212
  say(`\n $ ${cmd} ${args.join(" ")}`);
185
213
  const r = spawnSync(cmd, args, { cwd: REPO, stdio: "inherit" });
@@ -301,22 +329,38 @@ export async function update(argv = process.argv.slice(2)) {
301
329
  }
302
330
 
303
331
  if (packaged) {
332
+ // ONE NPM RUN, WITH THE LAUNCHER PUT BACK AFTER IT, and both branches below install through it, so
333
+ // neither can run npm and forget the second half. npm puts its own link back at
334
+ // `<prefix>/bin/clearotron` on every install, over the launcher the install wrote, and that link runs
335
+ // whichever `node` is first on PATH. Put the launcher back, but only over npm's link or our own:
336
+ // anything else there was not ours before this update either.
337
+ const reinstall = () => {
338
+ const rc = runInCheckout("npm", packaged.npmArgs);
339
+ if (rc !== 0) return rc;
340
+ const kind = inspectShim(shimPath()).kind;
341
+ if (kind === "npm-link" || kind === "ours" || kind === "ours-other-install") {
342
+ const shim = installShim();
343
+ if (!shim.ok) console.error(`\n The update worked, but the launcher at ${shim.path ?? "~/.local/bin/clearotron"} could not be written back: ${shim.detail}.`);
344
+ }
345
+ return 0;
346
+ };
304
347
  if (packaged.current) {
305
- say(`\n This install is ${packaged.installed}, and nothing newer is published (${packaged.tag}: ${packaged.version}). Nothing was touched.\n`);
348
+ say(`\n This install is ${packaged.installed}, and nothing newer is published (${packaged.tag}: ${packaged.version}).`);
349
+ // CURRENT STILL REFRESHES THE ENGINE PROGRAM setup installed: it moves on its vendor's schedule, not
350
+ // this package's. A machine's own copy on PATH updates itself and is used first.
351
+ const engines = enginesToRefresh();
352
+ if (!engines.length) { say(" Nothing was touched.\n"); return 0; }
353
+ const rc = refreshEngines(engines);
354
+ if (rc !== 0) return rc;
355
+ say("\n Clearotron itself was already current; restart the services so they use the refreshed program.\n");
306
356
  return 0;
307
357
  }
308
358
  if (packaged.unread) say(`\n npm did not say which versions are published, so this follows the ${packaged.tag} channel.`);
309
359
  say(`\n Updating this install at ${packaged.prefix} from ${packaged.installed ?? "an unreadable version"} to clearotron@${packaged.spec}.`);
310
- const rc = runInCheckout("npm", packaged.npmArgs);
360
+ const rc = reinstall();
311
361
  if (rc !== 0) return rc;
312
- // npm puts its own link back at `<prefix>/bin/clearotron` on every install, over the launcher the
313
- // install wrote, and that link runs whichever `node` is first on PATH. Put the launcher back, but only
314
- // over npm's link or our own: anything else there was not ours before this update either.
315
- const kind = inspectShim(shimPath()).kind;
316
- if (kind === "npm-link" || kind === "ours" || kind === "ours-other-install") {
317
- const shim = installShim();
318
- if (!shim.ok) console.error(`\n The update worked, but the launcher at ${shim.path ?? "~/.local/bin/clearotron"} could not be written back: ${shim.detail}.`);
319
- }
362
+ const refreshed = refreshEngines(enginesToRefresh());
363
+ if (refreshed !== 0) return refreshed;
320
364
  say("\n Updated. An assistant starts the new version the next time it launches Clearotron; restart the services for the portal.\n");
321
365
  return 0;
322
366
  }
@@ -393,6 +437,9 @@ export async function update(argv = process.argv.slice(2)) {
393
437
  console.error(` the custom instructions could not be read — ${e.message}`);
394
438
  }
395
439
 
440
+ const refreshed = refreshEngines(enginesToRefresh());
441
+ if (refreshed !== 0) return refreshed;
442
+
396
443
  say("\n Up to date.\n");
397
444
  return 0;
398
445
  }
package/build-info.json CHANGED
@@ -1,4 +1,4 @@
1
1
  {
2
- "commit": "3783f1439565cab9c10a48fe49d7b24f1c777c25",
3
- "version": "0.3.2-beta.7"
2
+ "commit": "7f7f7668374023298def34129e3007303163c5ed",
3
+ "version": "0.3.2-beta.9"
4
4
  }
package/docs/DELIVERY.md CHANGED
@@ -18,7 +18,8 @@ ignores the value.
18
18
 
19
19
  ## The outbox
20
20
 
21
- `config.outboxDir` = `CLEAROTRON_OUTBOX_DIR` (default `<CLEAROTRON_WORK_DIR>/prelim-outbox`).
21
+ `config.outboxDir` = `CLEAROTRON_OUTBOX_DIR` (default `<CLEAROTRON_WORK_DIR>/clearance-outbox`; the
22
+ earlier `clearance-outbox` is still read, so markers written before the rename still drain).
22
23
  All packets are written atomically (`.tmp` + rename) — a watcher never sees a half-written
23
24
  file. Every packet carries a `ts` ISO timestamp. Watch the dir for `*.pending` (systemd
24
25
  `.path`, inotify, or poll); a periodic scan of run `status.json` files is the recommended
package/docs/INTAKE.md CHANGED
@@ -23,7 +23,7 @@ Resolution (the runner drains **all** of these; `config.queueDirs`):
23
23
  | Source | Path | Use |
24
24
  |---|---|---|
25
25
  | `CLEAROTRON_QUEUE_DIR` env | one explicit dir | **headless deployments** (the product default; no agent workspaces needed). Jobs here run as `config.defaultAgent`. |
26
- | workspace scan | `<CLEAROTRON_WORK_DIR>/workspace-<agent>/studio/prelim-search/queue` | legacy/agent-adjacent deployments; the agent identity is derived from the queue location |
26
+ | workspace scan | `<CLEAROTRON_WORK_DIR>/workspace-<agent>/studio/clearance-search/queue` | legacy/agent-adjacent deployments; the agent identity is derived from the queue location |
27
27
 
28
28
  The enqueue CLI resolves its target the same way: `--queue-dir` flag → `CLEAROTRON_QUEUE_DIR` →
29
29
  the default agent's workspace queue.
@@ -32,7 +32,7 @@ malformed one is refused before anything is written.
32
32
  | `--name` | the legal name. Required. |
33
33
  | `--domains` | comma-separated email domains that resolve to this company |
34
34
  | `--platforms` | marketplaces their searches cover. Omitted, the default's platforms apply and are named in the output. |
35
- | `--framework` | their risk framework, as `skills/prelim-search/<file>.md`. Omitted, the default applies and is named in the output. |
35
+ | `--framework` | their risk framework, as `skills/clearance-search/<file>.md`. Omitted, the default applies and is named in the output. |
36
36
  | `--industry` | free text, shown on their profile |
37
37
  | `--context` | a file whose contents become this company's context pack |
38
38
  | `--dry-run` | say exactly what would be written, and write nothing |
@@ -27,7 +27,7 @@ Two doctrines govern everything below:
27
27
  sequenceDiagram
28
28
  autonumber
29
29
  participant A as Forwarding agent<br/>(integrator platform)
30
- participant Q as Per-agent queue dir<br/>(studio/prelim-search/queue)
30
+ participant Q as Per-agent queue dir<br/>(studio/clearance-search/queue)
31
31
  participant S as systemd<br/>(.path + 90s .timer)
32
32
  participant R as runner.mjs
33
33
  participant P as pipeline.mjs
@@ -89,7 +89,7 @@ an unknown company proceeds on the generic profile with a late-bind watch (§4).
89
89
 
90
90
  **Matter-level dedup** (`runner.mjs`). Queue-file dedup is per *message*; a "please
91
91
  proceed" reply in an already-handled thread arrives under a new message-id. The driver therefore
92
- keeps a matter ledger (`studio/prelim-search/.matter-ledger.jsonl`) and parks as `.duplicate` any
92
+ keeps a matter ledger (`studio/clearance-search/.matter-ledger.jsonl`) and parks as `.duplicate` any
93
93
  job within the window (a fixed 24 hours) that matches a prior entry by
94
94
  exact signature (`forwarder|mark|classes|customer|ref`, plus a `|level:<product>` dimension on any
95
95
  non-baseline product) or by same conversation-thread with agreeing mark *and* agreeing product. The
@@ -108,7 +108,7 @@ winner (`runner.mjs`). A `.pid` sidecar records `<pid>:<starttime>` (starttime t
108
108
  **Run identity is minted before any spend.** The codename (`adjective-noun`) is minted at dispatch
109
109
  and written atomically to `<id>.processing.meta` *before* the pipeline starts (`runner.mjs`).
110
110
  A crash anywhere after that resumes the *same* run directory instead of re-spending a fresh run.
111
- Run dirs are `<workspace>/studio/prelim-search/<slug>/<date>-<codename>`, slug =
111
+ Run dirs are `<workspace>/studio/clearance-search/<slug>/<date>-<codename>`, slug =
112
112
  `tmp<n>-<kebab-mark>` (or `noref<6-hex>-<mark>` when no reference was given; `phase0.mjs`).
113
113
 
114
114
  **Orphan reclaim** (`runner.mjs`) runs once per drain. A `.processing` whose claimer is
@@ -177,7 +177,7 @@ seeding. Frozen sidecars are never silently re-derived; a corrupt one crashes lo
177
177
  ```mermaid
178
178
  flowchart TD
179
179
  subgraph HEAD["Phase 1-2 head (fatal)"]
180
- MF[matter-frame] --> PV[prelim-variants]
180
+ MF[matter-frame] --> PV[clearance-variants]
181
181
  PV --> DER["code derivations:<br/>scope ledger · form neighbourhood ·<br/>register plan freeze · recall probes"]
182
182
  end
183
183
  DER --> GRID["grid spec dictated by code<br/>(terms × platforms × connotation; A1 split)"]
@@ -217,7 +217,7 @@ flowchart TD
217
217
 
218
218
  Reading order for the phases, with what code decides at each:
219
219
 
220
- 1. **Head stages** — `matter-frame` then `prelim-variants`, both fatal. Code then derives the
220
+ 1. **Head stages** — `matter-frame` then `clearance-variants`, both fatal. Code then derives the
221
221
  scope ledger, the *form neighbourhood* (the model picks the distinctive token; the machine
222
222
  generates the complete mechanical variant floor), freezes the register plan
223
223
  (`_driver/register-plan.json`, frozen for the life of *this run* — a resume never re-plans, and a
@@ -445,7 +445,7 @@ node pipeline.mjs --resume <codename> --experiment <stage> [--label <t>]
445
445
  - **`--from`** forces stages at or after the named ordinal even if their outputs validate; earlier
446
446
  stages still skip. A `--from synthesis` fork deliberately does *not* lock the digest.
447
447
  - **`--experiment`** runs one stage in a shadow dir (`_experiments/<ts>-<tag>/`) on copies of its
448
- inputs, under a `prelim-exp-…` session key that is excluded from the run's provider-usage
448
+ inputs, under a `clearance-exp-…` session key that is excluded from the run's provider-usage
449
449
  attribution. The canonical run is untouched.
450
450
  - **Orphan self-resume**: a manually resumed run that parks has no queue sidecars; the runner scans
451
451
  run dirs for due, payload-complete `.postponed` sentinels not owned by any queue and resumes them