clearotron 0.3.2-beta.12 → 0.3.2-beta.13

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 (95) hide show
  1. package/CONTRIBUTING.md +6 -5
  2. package/INSTALL.md +3 -4
  3. package/README.md +2 -1
  4. package/bin/example.mjs +12 -1
  5. package/bin/onboard.mjs +16 -2
  6. package/bin/start.mjs +6 -3
  7. package/build-info.json +2 -2
  8. package/docs/CLIENT-MCP.md +6 -6
  9. package/docs/DELIVERY.md +3 -3
  10. package/docs/ONBOARDING.md +1 -1
  11. package/docs/PORTAL.md +3 -3
  12. package/docs/RELEASES.md +1 -1
  13. package/docs/SECURITY.md +1 -1
  14. package/docs/architecture/04-configuration-reference.md +4 -4
  15. package/docs/architecture/05-config-governance.md +10 -10
  16. package/docs/architecture/06-operations-runbook.md +3 -3
  17. package/docs/architecture/07-quality-and-audit.md +1 -1
  18. package/docs/architecture/08-development-guide.md +2 -2
  19. package/docs/architecture/09-security-and-data.md +1 -1
  20. package/docs/decisions/0002-no-dark-functionality.md +1 -1
  21. package/docs/decisions/0006-what-the-public-repository-carries.md +3 -3
  22. package/driver/CHANGELOG.md +13 -0
  23. package/driver/ask-ledger.mjs +1 -1
  24. package/driver/band-shape.mjs +1 -1
  25. package/driver/bundled-demos.mjs +2 -2
  26. package/driver/card-budget.mjs +2 -2
  27. package/driver/case-law-ledger.mjs +2 -2
  28. package/driver/connotation-search.mjs +5 -5
  29. package/driver/contract-e3-backlog.mjs +13 -13
  30. package/driver/coverage-form.mjs +2 -2
  31. package/driver/demo-container.mjs +26 -2
  32. package/driver/disposition-tool.mjs +2 -2
  33. package/driver/engine/CONTRACT.md +3 -3
  34. package/driver/engine/mcp/gather-config.mjs +1 -1
  35. package/driver/engine/openai-agent.mjs +2 -2
  36. package/driver/findings-model.mjs +21 -6
  37. package/driver/gateway.mjs +1 -1
  38. package/driver/package.json +1 -1
  39. package/driver/pipeline-knockout.mjs +1 -1
  40. package/driver/pipeline.mjs +4 -4
  41. package/driver/placement-union.mjs +1 -1
  42. package/driver/portal-mcp-client.mjs +1 -1
  43. package/driver/portal-report.mjs +4 -1
  44. package/driver/portal-service.mjs +17 -5
  45. package/driver/predelivery-lint.mjs +9 -4
  46. package/driver/progress.mjs +37 -1
  47. package/driver/publish/render.mjs +6 -6
  48. package/driver/record-discard.mjs +1 -1
  49. package/driver/register-count.mjs +1 -1
  50. package/driver/register-digest-record.mjs +1 -1
  51. package/driver/report-card-record.mjs +2 -2
  52. package/driver/roster-verdict.mjs +2 -2
  53. package/driver/search-policy.mjs +1 -1
  54. package/driver/skeptic-record.mjs +1 -1
  55. package/driver/stages.mjs +1 -1
  56. package/driver/suite-census.json +27 -9
  57. package/driver/systemd/README.md +1 -1
  58. package/driver/unit-inventory.mjs +2 -2
  59. package/driver/verify.mjs +1 -1
  60. package/mcp-server/CHANGELOG.md +4 -0
  61. package/mcp-server/CONNECT.md +8 -8
  62. package/mcp-server/lib/runs.mjs +1 -1
  63. package/mcp-server/package.json +1 -1
  64. package/mcp-server/serve.mjs +27 -0
  65. package/package.json +1 -1
  66. package/portal-ui/package.json +1 -1
  67. package/providers/free-tier/src/capabilities.js +2 -2
  68. package/providers/jx/src/core.js +3 -3
  69. package/providers/jx/src/turn-envelope.mjs +1 -1
  70. package/providers/oauth-mcp-bridge/CHANGELOG.md +4 -0
  71. package/providers/oauth-mcp-bridge/package.json +1 -1
  72. package/providers/perplexity/README.md +1 -1
  73. package/providers/signa/src/capabilities.js +2 -2
  74. package/providers/signa/src/core.js +2 -2
  75. package/providers/uspto-local/src/core.js +1 -1
  76. package/providers/uspto-local/src/sync.js +1 -1
  77. package/scripts/README.md +2 -6
  78. package/scripts/citation-anchor-report.mjs +1 -1
  79. package/scripts/citation-line-check.mjs +3 -3
  80. package/scripts/e2e.mjs +1 -1
  81. package/scripts/env-audit.mjs +1 -1
  82. package/scripts/env-classify.mjs +1 -1
  83. package/scripts/pack-publishable.mjs +1 -1
  84. package/scripts/release-artifact-seal.mjs +2 -2
  85. package/scripts/settings-render-check.mjs +5 -1
  86. package/scripts/strip-tracker-citations.mjs +4 -4
  87. package/scripts/test-run.mjs +46 -3
  88. package/shared/browser-temp-root.mjs +10 -3
  89. package/shared/client-door.mjs +18 -6
  90. package/shared/connect-clients.mjs +2 -0
  91. package/shared/identifier-scan.mjs +2 -2
  92. package/shared/invocation.mjs +1 -1
  93. package/shared/reference-guard-classes.mjs +5 -3
  94. package/shared/stdio-connect.mjs +39 -18
  95. package/shared/writing-standard-classes.mjs +2 -3
package/CONTRIBUTING.md CHANGED
@@ -60,9 +60,10 @@ Four more things run for free:
60
60
  | `npx clearotron demo` | Replays `demo/` — a real run on a fictional mark — through the real publisher into `~/trademark-demo/pool` and serves it. No keys, no model, no engine. |
61
61
 
62
62
  **About `npx clearotron demo`.** `npx clearotron demo --no-open --once` publishes the sample and exits without
63
- touching a browser. `npx clearotron demo --pool <dir>`
64
- puts the pool somewhere else, and `npx clearotron demo --run-dir <dir>` replays a finished run of your
65
- own instead of the shipped example.
63
+ touching a browser. Without `--once` it serves the portal until it is stopped: `--no-open` keeps it from
64
+ opening a browser, `--port <n>` moves its three doors to `n`, `n+1` and `n+2`, and `--keep` leaves its folder
65
+ behind when it stops. `npx clearotron demo --pool <dir>` puts the pool somewhere else, and
66
+ `npx clearotron demo --run-dir <dir>` replays a finished run of your own instead of the shipped example.
66
67
 
67
68
  Fix a bug, add a jurisdiction, add a doctrine test case: all of it is reachable from here.
68
69
 
@@ -90,7 +91,7 @@ this tree does not carry has to be declared with its reason, and the publication
90
91
  fails an undeclared one.
91
92
 
92
93
  **`driver/skills/**` is engine input, not documentation.** Those Markdown files are the prompt payload
93
- served to the model at run time — `synthesis-rules.md` is a 16,000-word program. Editing them for
94
+ served to the model at run time — `synthesis-rules.md` is a 12,000-word program. Editing them for
94
95
  brevity, tone or tidiness changes what a clearance concludes. Nothing in this section, and nothing in
95
96
  any writing pass over the documentation, applies to them.
96
97
 
@@ -192,7 +193,7 @@ with no runner assigned and no steps recorded, and every job that gates on it is
192
193
  this repository: two runs on one SHA, thirty minutes apart, the push run green on every job and the
193
194
  scheduled one red having run nothing.
194
195
 
195
- That is **could-not-look**, not a fault, and this tool says so rather than making you open the run and
196
+ That is **a check that could not look**, not a fault, and this tool says so rather than making you open the run and
196
197
  read timings to find out. It does not block on it — nothing ran, so nothing can have regressed.
197
198
 
198
199
  **It also does not call it green.** A run that never ran told you nothing about `main`, and the last
package/INSTALL.md CHANGED
@@ -242,16 +242,15 @@ refuses for every real user.
242
242
 
243
243
  Two further constraints, both of which stop a package being cut from just anywhere:
244
244
 
245
- - **The de-identification scan needs a full clone.** It refuses a shallow one (exit 2 — could-not-look,
246
- not a pass) because it walks history it cannot see.
245
+ - **The de-identification scan needs a full clone.** It refuses a shallow one (exit 2 — a check that could
246
+ not look, not a pass) because it walks history it cannot see.
247
247
  - **It also needs its table**, passed with `--blocklist` (or `CLEAROTRON_IDENTIFIER_BLOCKLIST`), and
248
248
  that table lives in the private config store rather than in this repository. Without it the scan
249
249
  exits 2 rather than arming a weaker rule set quietly.
250
250
 
251
251
  Both refusals are correct and neither is a workaround to route around.
252
252
 
253
- **Every `clearotron` command in this document is written `npx clearotron …`, and that is not a
254
- stylistic choice.** This repository *is* the `clearotron` package, and npm links a package's `bin` into
253
+ This repository *is* the `clearotron` package, and npm links a package's `bin` into
255
254
  `node_modules/.bin` only for its **dependencies** — never for the package itself. So after `npm install`
256
255
  succeeds there is no `clearotron` on your `PATH` and none in `node_modules/.bin`; a bare `clearotron`
257
256
  would be `command not found` with nothing having failed.
package/README.md CHANGED
@@ -33,7 +33,8 @@ the address in your browser. No sign-up, no credentials, no network calls to us.
33
33
  The demo runs for as long as that window stays open, and removes everything it made when you close it —
34
34
  nothing of it is left on the machine, and running it again later starts clean. If you want to keep the
35
35
  sample reports after closing the window, run `npx clearotron demo --keep`; it prints the one command
36
- that removes the folder when you are done with it.
36
+ that removes the folder when you are done with it. `npx clearotron demo --once` publishes the reports and
37
+ exits, and keeps the folder the same way.
37
38
 
38
39
  **Then install it.**
39
40
 
package/bin/example.mjs CHANGED
@@ -47,7 +47,7 @@ import { fileURLToPath, pathToFileURL } from "node:url";
47
47
  import { spawn } from "node:child_process";
48
48
  import { BRAND } from "../shared/brand.mjs"; // — the installer's own name, from the tenant seam
49
49
  import { envFrom } from "../shared/env-aliases.mjs"; // — resolves EITHER spelling; names the retired one because that is the live-writable half
50
- import { isFrozen, demoChildren, demoInventory, prepareSample } from "../driver/demo-container.mjs"; // — one definition of what a frozen demo is, for the player AND the gate
50
+ import { isFrozen, demoChildren, demoInventory, prepareSample, releaseDemoCopies } from "../driver/demo-container.mjs"; // — one definition of what a frozen demo is, for the player AND the gate
51
51
  import { ensureDemoProgram, demoProgramEnv } from "../shared/permanent-install.mjs";
52
52
 
53
53
  const REPO = join(dirname(fileURLToPath(import.meta.url)), "..");
@@ -168,6 +168,14 @@ if (!sampleDirs.length || !isFrozen(sampleDir)) {
168
168
  // rest are published. An unreadable file inside one sample used to throw out of the copy here and take
169
169
  // every demo down with a stack trace (measured on a published beta, 2026-09-11).
170
170
  const failures = ALL ? inventory.unusable.map((u) => ({ name: u.name, why: u.why })) : [];
171
+ // A STOP WHILE THE SAMPLES ARE COPIED OR PUBLISHED still takes the copies with it. The copies go on exit
172
+ // (driver/demo-container.mjs), and a signal's default ends the process without one. Taken off again once
173
+ // the copies are gone, before the portal's own handlers are installed.
174
+ const stopEarly = [["SIGINT", 2], ["SIGTERM", 15], ["SIGHUP", 1]].map(([sig, no]) => {
175
+ const h = () => process.exit(128 + no);
176
+ process.once(sig, h);
177
+ return [sig, h];
178
+ });
171
179
  const samples = [];
172
180
  for (const dir of sampleDirs) {
173
181
  const r = prepareSample(dir, { repoRoot: REPO });
@@ -277,6 +285,9 @@ for (const s0 of samples) {
277
285
  failures.push({ name: s0.name, why: String(e?.message ?? e) });
278
286
  }
279
287
  }
288
+ // PUBLISHED, SO THE COPIES THEY WERE PUBLISHED FROM GO NOW, whatever comes next.
289
+ releaseDemoCopies();
290
+ for (const [sig, h] of stopEarly) process.off(sig, h);
280
291
  if (!results.length) {
281
292
  die(`demo: no demo could be replayed.`, "", ...failures.map((f) => ` ${f.name}: ${f.why}`));
282
293
  }
package/bin/onboard.mjs CHANGED
@@ -3344,8 +3344,22 @@ export async function runCheck() {
3344
3344
  sock.once("error", (e) => done(e.code === "ECONNREFUSED" ? false : null));
3345
3345
  });
3346
3346
  } catch { /* stays null — nobody could ask */ }
3347
+ // WHOSE LISTENER. Only this install's own evidence counts: its unit running (which binds this very
3348
+ // port), or a live start of this install whose record names the port. Anything else on the port is
3349
+ // somebody's process, and on a shared box usually another account's door.
3350
+ let doorOwnListener = null;
3351
+ if (doorListening === true) {
3352
+ if (doorActive === true) doorOwnListener = true;
3353
+ else {
3354
+ try {
3355
+ const { clientDoorPort: portOf } = await import(pathToFileURL(join(REPO, "shared", "client-door.mjs")).href);
3356
+ const { readRunning } = await import(pathToFileURL(join(REPO, "shared", "running-start.mjs")).href);
3357
+ if (readRunning().some((r) => r?.ports?.client === portOf(doorEnv))) doorOwnListener = true;
3358
+ } catch { /* stays null — an unread record is not a claim either way */ }
3359
+ }
3360
+ }
3347
3361
  const door = doorState({ env: doorEnv, unitDir, exists: existsSync, active: doorActive,
3348
- listening: doorListening, activeState: doorActiveState, subState: doorSubState });
3362
+ listening: doorListening, ownListener: doorOwnListener, activeState: doorActiveState, subState: doorSubState });
3349
3363
  // EVERY COMMAND THROUGH `invoke`. Doctor's own guard runs every command doctor
3350
3364
  // prints from a directory that is not the install; a literal `clearotron start` in that text is
3351
3365
  // `command not found` for a reader with no shim, and the guard caught exactly that the moment this
@@ -4272,7 +4286,7 @@ try {
4272
4286
  // Enter yield the empty string, `present("")` is false, and askValue loops with "A value is needed
4273
4287
  // here." — so a reader taking the header at its word ("Enter takes the default in brackets") on the
4274
4288
  // one prompt that advertises empty as an answer could not leave it. Driven on the merged tree:
4275
- // 60 enters, killed at the cap, every cycle this prompt. Same defect class as 's
4289
+ // 60 enters, killed at the cap, every cycle this prompt. Same defect class as the
4276
4290
  // engine-menu loop, arriving one commit after it was fixed, in a prompt this branch's own sibling
4277
4291
  // added.
4278
4292
  //
package/bin/start.mjs CHANGED
@@ -1610,9 +1610,12 @@ if (isMain) {
1610
1610
  // — `clearotron demo` hands over to this — so fixing the player alone left the defect where it was.
1611
1611
  // ONE SAMPLE AT A TIME: one whose files cannot be read is left out and named below, and the others
1612
1612
  // seed. Copied in one call, a single unreadable file emptied the whole archive.
1613
- const { publishContainer, seedDemoRuns } = await import("../driver/demo-container.mjs");
1613
+ const { publishContainer, seedDemoRuns, releaseDemoCopies } = await import("../driver/demo-container.mjs");
1614
1614
  const container = publishContainer(join(REPO, "demo"), { repoRoot: REPO });
1615
- const seed = await seedPool({ pool: paths.pool, examplesDir: container.dir, republish: republishRun });
1615
+ let seed;
1616
+ // THE COPY GOES AS SOON AS THE POOL IS SEEDED FROM IT, and on the way out if seeding throws.
1617
+ try { seed = await seedPool({ pool: paths.pool, examplesDir: container.dir, republish: republishRun }); }
1618
+ finally { releaseDemoCopies(); }
1616
1619
  // AND AS RUNS, so the assistant this demo's connect line wires has them to list, brief and open. Under
1617
1620
  // the demo's own workspace only: nothing of it reaches an install started afterwards. Their report
1618
1621
  // links are stamped with this portal's address, the one the Open line prints.
@@ -2565,7 +2568,7 @@ if (isMain) {
2565
2568
  // THE HEADINGS COME WITH THE PAIR, from the composer. Nothing is written here: the page prints these
2566
2569
  // same two words above these same two commands, and a second author is how the two surfaces drift.
2567
2570
  if (connect.variants) {
2568
- for (const v of connect.variants) { say(` ${v.heading}`); say(` ${v.text}`); say(""); }
2571
+ for (const v of connect.variants) { say(` ${v.heading}`); say(` ${v.text}`); if (v.hint) say(` ${v.hint}`); say(""); }
2569
2572
  } else {
2570
2573
  say(` ${connect.command}`);
2571
2574
  say("");
package/build-info.json CHANGED
@@ -1,4 +1,4 @@
1
1
  {
2
- "commit": "92dc979bd176adeedc0dbe2518fbf24388bc6072",
3
- "version": "0.3.2-beta.12"
2
+ "commit": "f107c38f60f6cd5536cd30edb3d2b3ad8a1d1d97",
3
+ "version": "0.3.2-beta.13"
4
4
  }
@@ -46,10 +46,10 @@ uses). Enrolment is therefore the portal's: no second credential to mint, rotate
46
46
  access revokes this with it. **Off unless `CLIENT_MCP_ACCOUNT_ACCESS=1`.**
47
47
 
48
48
  **Who turns that on. The installer, since 2026-09-03** — ruling, settled
49
- point 2. `render-units.mjs --apply` and `npx clearotron start --background` both write the settings this
49
+ point 2. `render-units.mjs --apply` and `clearotron start --background` both write the settings this
50
50
  door refuses to start without and then place and enable `clearotron-client-mcp.service`. The settings
51
51
  come from one authority, `enablePlan` in `shared/client-door.mjs`, which is also what
52
- `npx clearotron connect` calls.
52
+ `clearotron connect` calls.
53
53
 
54
54
  **This supersedes the 2026-08-31 ruling** *"On demand is fine"*, under which nothing at install and no
55
55
  rebuild's enable list could start this unit, because starting it WAS the consent that opened
@@ -57,10 +57,10 @@ access for each company's people. The owner changed the posture knowingly: **the
57
57
  gate, not whether a process runs.** A door with no key issued refuses everything, which is the same
58
58
  protection by a mechanism that does not depend on a reader finding a verb.
59
59
 
60
- **`npx clearotron disconnect` therefore revokes a person, not a service** (Q3). It writes the caller's key
60
+ **`clearotron disconnect` therefore revokes a person, not a service** (Q3). It writes the caller's key
61
61
  ids to the denylist and strikes them from the record; it does not stop the unit and does not touch
62
62
  `CLIENT_MCP_ACCOUNT_ACCESS`, which is the whole install's setting. Cutting everyone off is
63
- `npx clearotron disconnect --everyone`, which states how many keys and how many people that is before
63
+ `clearotron disconnect --everyone`, which states how many keys and how many people that is before
64
64
  acting — and does not stop the service either.
65
65
 
66
66
  An `account` principal reaches **eighteen** tools, for its own companies only — everything carrying
@@ -117,8 +117,8 @@ Four things about it are worth knowing before you offer it:
117
117
  - **The caller cannot choose the model.** The tier is cost and method both, and it is the one argument on
118
118
  the one tool that spends. Express the change with `instructions`.
119
119
  - **Nothing bounds the spend, by ruling.** `start_run` is stamped `clientPrincipal: true` at the
120
- chokepoint so `runCaps.dailyRuns` bites it; a what-if job carries no such stamp, because the owner
121
- ruled spend controls out ("ignore the call spend"). Every experiment records what it spent; no door
120
+ chokepoint so `runCaps.dailyRuns` bites it; a what-if job carries no such stamp, because spend controls
121
+ were deliberately left out of it. Every experiment records what it spent; no door
122
122
  refuses the next one. Concurrency IS bounded — `CLEAROTRON_WHATIF_MAX_CONCURRENT`, default 1 — because
123
123
  letting a free experiment occupy the box while a paid clearance waits is a different question from
124
124
  spend, and the ruling did not touch it.
package/docs/DELIVERY.md CHANGED
@@ -284,12 +284,12 @@ So the checks that run are exactly those whose whole input is model-authored tex
284
284
  **permission-prose**, **scope-numbers-in-prose**, **counting-consistency**,
285
285
  **wipo-designation-language**, **prescription-prose**. Every other clearance check is listed in the
286
286
  receipt under `notApplicable` with the reason its input does not exist here
287
- (`KNOCKOUT_ABSENT_BY_DESIGN` in `driver/predelivery-lint.mjs`). That is a third state (/),
287
+ (`KNOCKOUT_ABSENT_BY_DESIGN` in `driver/predelivery-lint.mjs`). That is a third state,
288
288
  and it is the point: a check that cannot apply is recorded as absent, never as a silent pass, and
289
289
  never as a `pass:false` that would project to the lawyer as a defect.
290
290
 
291
- **Why the lane cannot simply skip the lint.** The lint receipt IS the workbook's QC record
292
- (/), so a lane that writes no`_driver/predelivery-lint.json` produces an *empty* QC record
291
+ **Why the lane cannot simply skip the lint.** The lint receipt IS the workbook's QC record,
292
+ so a lane that writes no`_driver/predelivery-lint.json` produces an *empty* QC record
293
293
  rather than a deliberately-absent one — and an empty record reads as "not evaluated", never
294
294
  "passed". And since recorded defects reach the reviewing lawyer only through the projected
295
295
  machine-check sheet on the audit workbook, a lane with nothing to project is a lane whose
@@ -21,7 +21,7 @@ installer.
21
21
  ## The command
22
22
 
23
23
  ```
24
- npx clearotron brandowner add <key> --name "<legal name>" [options]
24
+ clearotron brandowner add <key> --name "<legal name>" [options]
25
25
  ```
26
26
 
27
27
  `<key>` is the bundle's filename and its identity everywhere else. It is validated on the way in, so a
package/docs/PORTAL.md CHANGED
@@ -99,7 +99,7 @@ There were two ways in and only one of them proved anything: the auth-proxy edge
99
99
  and handed every caller the same synthetic address. **Both switches are deleted**, along with
100
100
  `PORTAL_DEV_EMAIL`. Local mode replaces them with a real sign-in.
101
101
 
102
- **Use `npx clearotron start`.** It is the documented way to run this product on one machine
102
+ **Use `clearotron start`.** It is the documented way to run this product on one machine
103
103
  ([INSTALL.md](../INSTALL.md) §6) and it assembles everything below for you: the portal, the MCP face the Start button
104
104
  calls, a minted ops key, the grants file, the saved-search store and the data-plane paths — one command,
105
105
  one URL, `Ctrl-C` stops both processes. Nothing here needs an `*_AUTH_DISABLED` variable, and the
@@ -143,7 +143,7 @@ its dev bypass to provide that**:`TRADEMARK_MCP_AUTH_MODE=token` runs it with a
143
143
  access key and no auth proxy — loopback only, and refused outright alongside
144
144
  `TRADEMARK_MCP_AUTH_DISABLED`, which authenticates nobody. Mint the key with
145
145
  `mint-token.mjs --scope ops --sub portal --verbs start_run,stop_run --accounts foxglade`, or let
146
- `npx clearotron start` mint one in memory at every start and never write it down.
146
+ `clearotron start` mint one in memory at every start and never write it down.
147
147
 
148
148
  ## Putting your own login provider in front
149
149
 
@@ -177,7 +177,7 @@ the model.
177
177
  1. Put the proxy in front of the portal's port and make it require sign-in. The portal must not be
178
178
  reachable except through it — an origin anyone can reach directly is an origin with no door.
179
179
  2. Set `PORTAL_AUTH_MODE=auth-proxy` and the four values above in `.env`.
180
- 3. `npx clearotron doctor` — the **Portal door** section reports which door is configured and which of
180
+ 3. `clearotron doctor` — the **Portal door** section reports which door is configured and which of
181
181
  the four values are present, by name. It never prints their values.
182
182
  4. Sign in through the proxy once and confirm the portal shows your address rather than a passphrase
183
183
  box.
package/docs/RELEASES.md CHANGED
@@ -16,7 +16,7 @@ npm install -g clearotron@beta # newest — cut when there is something wort
16
16
  | | `latest` (stable) | `beta` |
17
17
  |---|---|---|
18
18
  | **Version looks like** | `0.2.0` | `0.2.1-beta.4` |
19
- | **Cut when** | a beta has passed a full clearance run and a from-scratch install by somebody who has never seen the product, and the owner says go | when a change lands that is worth testing, or while a stable is being prepared |
19
+ | **Cut when** | a beta has passed a full clearance run and a from-scratch install by somebody who has never seen the product, and the maintainers decide to ship it | when a change lands that is worth testing, or while a stable is being prepared |
20
20
  | **Promises** | it installed and ran a real clearance end to end before it was published | it built, and the automated suite passed |
21
21
  | **Use it if** | you are running this for real work | you want a fix that landed today, or you are helping test |
22
22
 
package/docs/SECURITY.md CHANGED
@@ -123,7 +123,7 @@ what the mechanism guarantees.*
123
123
  - One OPERATOR issuance path: `mint-token.mjs` (prints once, stores nothing; `sub` names the
124
124
  principal in every audit line; the `jti` printed at mint time is the revocation handle). Two
125
125
  automatic minters sit beside it on the same `mintToken`: the clearance publisher mints the report
126
- link's run-bound `user` token at publish, and `npx clearotron start` mints the portal's verb-scoped,
126
+ link's run-bound `user` token at publish, and `clearotron start` mints the portal's verb-scoped,
127
127
  company-capped ops token in memory at every start. Neither prints, and neither is written down.
128
128
  - **Revocation**: denylist file checked on every verification; missing file = nothing revoked (the
129
129
  denylist can never take all auth down). **Rotation**: two-secret window, flag-day-free.
@@ -176,11 +176,11 @@ timeout shot, warm patch, backoff), the lane-wedge re-dispatch, and the rate-lim
176
176
  |---|---|---|
177
177
  | `CLEAROTRON_AI` | `anthropic-agent` (default) \| `openai-agent` | Compute engine (a registered provider adapter). `anthropic-agent` = `claude -p`; `openai-agent` = `codex exec` (both off-gateway, same normalized contract). An **unregistered** value — a typo, or the gateway-runtime adapter removed in the extraction — **fails loud**: no silent wrong-provider run. Production default is unchanged. |
178
178
  | `CLEAROTRON_AI_BILLING` | `subscription` (default) \| `api-key` \| `cloud` | Billing mode for the selected engine. ONE variable for both, and only the LIVE engine's setting is read — it fills each engine's billing knob, and the engine that is not selected is never consulted. **`anthropic-agent`:** subscription **deletes `ANTHROPIC_API_KEY` from the child env** so `claude -p` uses OAuth subscription credentials (a present key would override them); `api-key` keeps the key — the scale setting and standing fallback; **`cloud`** bills Claude per use through the reader's own cloud account, named by the Claude program's own switch — `CLAUDE_CODE_USE_VERTEX`, `CLAUDE_CODE_USE_FOUNDRY` or `CLAUDE_CODE_USE_BEDROCK`, or `ANTHROPIC_BASE_URL` alone for a gateway (see the credentials table below) — and deletes `ANTHROPIC_API_KEY` as subscription does. **`openai-agent`:** subscription seeds `auth.json` into the per-run `CODEX_HOME` from `CLEAROTRON_OPENAI_AUTH_FILE` (default `~/.codex/auth.json`) and strips API keys; `api-key` keeps `CODEX_API_KEY`; `cloud` is refused. **Fail-loud, never a guess, never the subscription in its place:** `api-key` with no `ANTHROPIC_API_KEY` / `CODEX_API_KEY` throws; on `anthropic-agent`, `cloud` with two switches on, or with none and no `ANTHROPIC_BASE_URL`, throws, and so does a switch left on under `subscription` or `api-key`, because the program bills that cloud whatever this says; a value that is none of the three throws on both engines. `clearotron start` and `clearotron doctor` report each refusal against the settings the services read, naming a value that is not a billing mode by its setting and never quoting it, and the runner refuses the search when it is ordered, before anything is spent. The resolved mode is stamped on every stage telemetry row (`engine`, `authMode`, `apiBilled`, and `cloud`, null when no cloud bills), and the attempt row also carries `providerReported`, the program's own word for who served the turn. |
179
- | `CLEAROTRON_CLAUDE_PATH` · `CLEAROTRON_CODEX_PATH` | `claude` / `codex` on `PATH`, then the copy Clearotron installed | The engine binary to spawn, ONE PER ENGINE: `CLEAROTRON_CLAUDE_PATH` under `anthropic-agent`, `CLEAROTRON_CODEX_PATH` under `openai-agent`. Only the live engine's is read, and a machine that runs both sets both — they were one variable until it met a machine needing two different paths. A path set here must be **absolute**: stage subprocesses run with cwd set to the run directory, so a relative path does not resolve there, and `npx clearotron doctor` refuses one. **Unset**, or set to the bare word `claude` / `codex` (which means the same), the engine uses the program on `PATH` when the machine has one, and otherwise the copy Clearotron installed in `~/.local/share/clearotron/engines`: `clearotron install` offers to put the chosen engine's program there (`@anthropic-ai/claude-code` or `@openai/codex`, this platform's build only), and `clearotron update` refreshes it. Nothing is bundled into the package. A value naming anything else is used as given and never falls back. `clearotron doctor` says which copy it found, and its version when npm installed that copy. |
180
- | `CLEAROTRON_OPENAI_AUTH_FILE` | `~/.codex/auth.json` | The credentials file seeded into the per-run `CODEX_HOME` under subscription billing. Was exempted from the August 2026 rename as an `openai-agent` internal rather than an install-surface name; the owner's 2026-09-04 ruling **reversed that exemption** and renamed the whole namespace, so it carries the house prefix like everything else. One spelling, no exceptions. |
179
+ | `CLEAROTRON_CLAUDE_PATH` · `CLEAROTRON_CODEX_PATH` | `claude` / `codex` on `PATH`, then the copy Clearotron installed | The engine binary to spawn, ONE PER ENGINE: `CLEAROTRON_CLAUDE_PATH` under `anthropic-agent`, `CLEAROTRON_CODEX_PATH` under `openai-agent`. Only the live engine's is read, and a machine that runs both sets both — they were one variable until it met a machine needing two different paths. A path set here must be **absolute**: stage subprocesses run with cwd set to the run directory, so a relative path does not resolve there, and `clearotron doctor` refuses one. **Unset**, or set to the bare word `claude` / `codex` (which means the same), the engine uses the program on `PATH` when the machine has one, and otherwise the copy Clearotron installed in `~/.local/share/clearotron/engines`: `clearotron install` offers to put the chosen engine's program there (`@anthropic-ai/claude-code` or `@openai/codex`, this platform's build only), and `clearotron update` refreshes it. Nothing is bundled into the package. A value naming anything else is used as given and never falls back. `clearotron doctor` says which copy it found, and its version when npm installed that copy. |
180
+ | `CLEAROTRON_OPENAI_AUTH_FILE` | `~/.codex/auth.json` | The credentials file seeded into the per-run `CODEX_HOME` under subscription billing. Was exempted from the August 2026 rename as an `openai-agent` internal rather than an install-surface name; a decision of 2026-09-04 **reversed that exemption** and renamed the whole namespace, so it carries the house prefix like everything else. One spelling, no exceptions. |
181
181
  | `CLEAROTRON_OPENAI_MODEL_JUDGMENT` / `CLEAROTRON_OPENAI_MODEL_SWEEP` / `CLEAROTRON_OPENAI_MODEL_CHEAP` | `gpt-5.6-sol` / `gpt-5.6-terra` / `gpt-5.6-luna`. | Maps the driver's abstract `opus`/`sonnet`/`haiku` tiers onto the codex ladder, the way the anthropic engine maps onto its own (ruling 2026-09-02, superseding the single-model mapping). A tier that cannot hold its stage contract announces itself at that stage — `missing:negative-results`, `no_coverage_status_row`, `named_band_missing` in `_driver/<stage>.jsonl` — not at delivery. |
182
182
  | ↳ why they are three, and what the old measurement still says | superseded 2026-09-02, not erased | The three defaulted to `sol` alone on a MEASUREMENT, never a placeholder: on 2026-08-11/12, with `SWEEP=gpt-5.6-terra` / `CHEAP=gpt-5.6-luna`, every codex clearance died on structural-output gates — `missing:negative-results`, `no_coverage_status_row`, `named_band_missing` — over 2 scenarios × 3 stage families, **byte-identically on retry**, so no retry budget rescued it. That measurement stands for the code and the codex CLI **of that date**, and it is why the judgment tier keeps `sol`. Three weeks of stage-contract work and several codex minor versions sit between it and the 2026-09-02 ruling that split the three. **The symptom to match against this cause is unchanged**: those same three gates, as a fail row in`_driver/<stage>.jsonl`, inside the first few dispatches of a clearance and identical on every retry — a model incapable of a contract is not flaky at it. It reads as an engine bug when the only fault is this configuration. A cheap-tier experiment still belongs in the three constants in `driver/engine/openai-agent.mjs` where a reviewer sees it, not in an untraceable env line. |
183
- | `CLEAROTRON_DATABASE` | **REQUIRED — no default.** `corsearch` \| `clarivate` \| `signa` \| `euipo` \| `uspto-local` \| `free-tier` | Active register provider — one per run, set in the environment the deploy carries, in every environment including production. There is **no default**: unset resolves to `null` and every use of it throws, so a run refuses at start rather than calling a vendor nobody chose ( — the removed`corsearch` fallback named a vendor the deployment did not choose). Three are paid global sweeps; `euipo` (EU) and `uspto-local` (US) are free single-office sources, and `free-tier` composes those two as one register. Choosing a free value makes every territory outside its coverage a disclosed *deferred* row. Unknown ids throw loudly, and the gather layer throws at stage time for any provider without a built MCP server. |
183
+ | `CLEAROTRON_DATABASE` | **REQUIRED — no default.** `corsearch` \| `clarivate` \| `signa` \| `euipo` \| `uspto-local` \| `free-tier` | Active register provider — one per run, set in the environment the deploy carries, in every environment including production. There is **no default**: unset resolves to `null` and every use of it throws, so a run refuses at start rather than calling a vendor nobody chose (the removed `corsearch` fallback named a vendor the deployment did not choose). Three are paid global sweeps; `euipo` (EU) and `uspto-local` (US) are free single-office sources, and `free-tier` composes those two as one register. Choosing a free value makes every territory outside its coverage a disclosed *deferred* row. Unknown ids throw loudly, and the gather layer throws at stage time for any provider without a built MCP server. |
184
184
  | `CLEAROTRON_CODEX_SANDBOX_BYPASS` | unset | `1` runs `codex exec` with its own sandbox helper bypassed, for hosts where that helper cannot spawn. It removes a defence: with it set, the run dir is writable by the seat and only the deny-hook stands between a stage and `_driver/`. Set it because the host forces it, never for convenience. |
185
185
 
186
186
  ## Environment variable reference
@@ -225,7 +225,7 @@ deployment may override (verify live values per deployment).
225
225
  | `CLEAROTRON_RUN_LOCK_POLL_MS` | 15000 | Run-slot acquire poll cadence. |
226
226
  | `CLEAROTRON_ADMISSION_BUDGET_MS` | 7200000 (2 h) | Runner stops claiming new jobs after this per activation; leftovers re-trigger a fresh activation. |
227
227
  | `CLEAROTRON_QUEUE_SCAN_MS` | 10000 (min 1000) | Mid-drain re-scan for newly arrived jobs. |
228
- | `CLEAROTRON_WHATIF_MAX_CONCURRENT` | 1 (min 1) | How many queued what-ifs the runner drains at once. Deliberately NOT a run-slot: an experiment that took one from `CLEAROTRON_MAX_CONCURRENT_RUNS` could block an admitted paid clearance rather than merely share the box with it. This is a concurrency bound and not a spend control — the owner ruled spend controls out when he opened what-if to each company's people (2026-08-27), and nothing here refuses the next experiment. |
228
+ | `CLEAROTRON_WHATIF_MAX_CONCURRENT` | 1 (min 1) | How many queued what-ifs the runner drains at once. Deliberately NOT a run-slot: an experiment that took one from `CLEAROTRON_MAX_CONCURRENT_RUNS` could block an admitted paid clearance rather than merely share the box with it. This is a concurrency bound and not a spend control — spend controls were deliberately left out when what-if opened to each company's people (2026-08-27), and nothing here refuses the next experiment. |
229
229
  | `CLEAROTRON_STOP_GRACE_MS` | 60000 | Grace after first SIGTERM before exit(1). |
230
230
  | `CLEAROTRON_MAX_CLAIM_AGE_MS` | 172800000 (48 h; 0 disables) | Hard ceiling on a claim's age (from the `.pid` sidecar mtime) — beyond it, re-claim regardless of liveness. |
231
231
  | `CLEAROTRON_KNOCKOUT_VARIANT_CAP` | unset (⇒ the lane's own cap) | Ceiling on variants a knockout screens per name. Set only to bound an unusually wide batch; absent means the lane decides. |
@@ -97,7 +97,7 @@ product doc.
97
97
  | **Allowlist** (`{version, grants:[{email, customer}]}`) | T2 | git + PR on the `CLIENT_ACCESS_MAP` file | see §2 row 4 | LIVE, file-only; surfaced read-only at `admin.access` |
98
98
  | **Ops tokens** (scope ops/user, verbs, companies, TTL) | T4 | `mint-token.mjs` CLI; jti denylist file | operator-held tokens | LIVE, CLI |
99
99
 
100
- ## 4b. The install surface names ()
100
+ ## 4b. The install surface names
101
101
 
102
102
  The variables a **user or installer** ever types carry the product’s own prefix. They are listed
103
103
  by name in §5 below and in the upgrade table in INSTALL.md.
@@ -123,14 +123,14 @@ has taken it.
123
123
 
124
124
  **`CLEAROTRON_JX_LANES` was held back from the August 2026 rename** — it was RETIRED 2026-07-27
125
125
  (`pipeline.mjs` `RETIRED_ENV`, `jx-units.mjs`) and nothing reads it, so renaming a dead name looked like
126
- handing an operator a name that warns about nothing. The owner's 2026-09-04 ruling reversed that: the
126
+ handing an operator a name that warns about nothing. A decision of 2026-09-04 reversed that: the
127
127
  whole namespace carries one prefix, dead names included, because a tree spelled two ways costs more
128
128
  than a retired row spelled consistently. The per-lane `CLEAROTRON_NATIVE_LANGUAGE_<code>` switch in the same row **is** live and is
129
- fail-OPEN: unset means ON (§5.5,).
129
+ fail-OPEN: unset means ON (§5.5).
130
130
 
131
131
  Variables outside the install surface were left alone by the August rename — `CLEAROTRON_ENGINE_MAX_BUFFER`
132
- and its siblings were never in that window. **That separate decision was taken on 2026-09-04**: the owner
133
- ruled the rename global and pre-cut, so the internals carry the house prefix too and the public tree never
132
+ and its siblings were never in that window. **That separate decision was taken on 2026-09-04**: the rename
133
+ became global and pre-cut, so the internals carry the house prefix too and the public tree never
134
134
  shows the old namespace. No compatibility layer, no alias reading, no migration — greenfield, and our own
135
135
  boxes rebuild.
136
136
 
@@ -207,10 +207,10 @@ move once the same window has been read across more runs.
207
207
  `CLEAROTRON_BAND_TRUTH_GATE` (**never disable in prod — restores the fabrication**),
208
208
  `CLEAROTRON_FRAME_REOPEN` (+`CLEAROTRON_FRAME_REOPEN_MAX`=1, `CLEAROTRON_REOPEN_MAX_FETCH`=150),
209
209
  `CLEAROTRON_REGISTER_GAP_CLAMP`, `CLEAROTRON_RECALL_PROBES`, `CLEAROTRON_RECALL_TRIPWIRE`, `CLEAROTRON_WARM_RETRY`,
210
- `CLEAROTRON_MODEL_WIRE_CHECK` ( — fails a turn whose
210
+ `CLEAROTRON_MODEL_WIRE_CHECK` (fails a turn whose
211
211
  provider reports a different model FAMILY than the driver asked for; disarming it silences the refusal
212
212
  and never the record: `modelActual`/`modelMismatch` keep landing on every dispatch row), `CLEAROTRON_FORM_REPAIR`
213
- ( — repairs a form-class stage failure inside the dispatch, up to twice; disarmed, the defect
213
+ (repairs a form-class stage failure inside the dispatch, up to twice; disarmed, the defect
214
214
  falls through to the retry ladder exactly as it did before, visibly, and is never swallowed as
215
215
  "validated fine"). Policy knob: `CLEAROTRON_UNREACHABLE_SENIOR`
216
216
  (open-item|clamp). Enumerate: `CLEAROTRON_ENUMERATE_CEILING` (600/OR-stack),
@@ -359,7 +359,7 @@ systemd, writes no heartbeat, and must keep saying "waiting to start" rather tha
359
359
  repo** — `git grep process.env.CLIENT_ACCESS` here returns nothing, so they are governed here and
360
360
  never appear in the audit).
361
361
 
362
- **Which identity source the portal runs () — T4, and it is chosen by name, never inferred.**
362
+ **Which identity source the portal runs — T4, and it is chosen by name, never inferred.**
363
363
  `PORTAL_AUTH_MODE` selects the door: unset or `auth-proxy` (the default for a hosted deployment) means
364
364
  any login system in front that authenticates in the browser and forwards a verifiable JWT per request.
365
365
  **Any OIDC or JWT proxy is a choice per deployment** — for example Cloudflare Access, which is not a
@@ -367,7 +367,7 @@ special case in the code; `local` means one address and one passphrase on loopba
367
367
  exactly the same thing** — normalised where the mode is read rather than by an alias row, because
368
368
  `shared/env-aliases.mjs` maps variable NAMES and there is no value-alias mechanism.
369
369
 
370
- **Bringing your own login provider ( item 1, completed by) — T4.**`PORTAL_OIDC_ISSUER`,
370
+ **Bringing your own login provider — T4.**`PORTAL_OIDC_ISSUER`,
371
371
  `PORTAL_JWKS_URL`, `PORTAL_EMAIL_CLAIM` and `PORTAL_AUTH_HEADER` are the portal-side spelling of the four
372
372
  values the staff MCP face already reads as `TRADEMARK_MCP_OIDC_ISSUER`, `TRADEMARK_MCP_JWKS_URL`,
373
373
  `TRADEMARK_MCP_EMAIL_CLAIM` and `TRADEMARK_MCP_AUTH_HEADER`. `makeAccessVerifier` has always accepted them; the portal
@@ -432,7 +432,7 @@ synthetic identity would answer before the mandatory key ever ran. The mode is l
432
432
  travels in a header or the query string), requires `TRADEMARK_MCP_ALLOWED_HOSTS` exactly as the
433
433
  authenticated door does, and mirrors its mandatory `CLEAROTRON_ACCESS_FILE`.
434
434
 
435
- **The local install sets all of the above itself.**`npx clearotron start` (`bin/start.mjs`) is a supervisor:
435
+ **The local install sets all of the above itself.**`clearotron start` (`bin/start.mjs`) is a supervisor:
436
436
  it resolves one set of ports, derives `PORTAL_MCP_URL` and `TRADEMARK_MCP_ALLOWED_HOSTS` from them, mints
437
437
  the ops key in memory, and hands each child an explicit environment carrying `CLEAROTRON_NO_ENV_FILE=1`. So
438
438
  exactly one process in that tree reads `<repo>/.env` — the supervisor — and nothing a laptop runs needs
@@ -73,7 +73,7 @@ properties below, each of which was learned the expensive way:
73
73
  mid-run feeds one expensive run two different skill versions. **Call
74
74
  check that nothing is in flight and abort if anything is** — read every queue this
75
75
  deployment would drain plus the run-slot locks, and refuse on any of them. `--override
76
- "<reason>"` is the owner-ordered exception; the reason is printed into the deploy output, and a
76
+ "<reason>"` is the operator's deliberate exception; the reason is printed into the deploy output, and a
77
77
  blank one exits 2 rather than passing. This step used to be a line of prose asking a human to
78
78
  check, which is not a guard on the night it matters. Then **stop the four trigger units** for
79
79
  the deploy window and install an **EXIT trap that restarts them on every exit path**, success or
@@ -184,7 +184,7 @@ rather than stage compute. That gateway is no longer part of the product, so the
184
184
  to probe and the file went with them. Nothing invoked it: not `package.json`, not `bin/`, not
185
185
  `scripts/`, not a unit, not CI. It was run by hand, and it spent real money when it was.
186
186
 
187
- What replaces each half: the free path check is `npx clearotron doctor` plus the runner's own preflights
187
+ What replaces each half: the free path check is `clearotron doctor` plus the runner's own preflights
188
188
  (`preflightEngineBinary`, `preflightCredentials`, `preflightDeploymentUrls`), each of which refuses by
189
189
  name before a run dir exists. The billable half has no replacement and needs none — stage compute is
190
190
  exercised by real runs and the A/B harness, which is what its own note already said.
@@ -289,7 +289,7 @@ Copy it, point `CLEAROTRON_ACCESS_FILE` at your copy, sign in as one of its addr
289
289
  exactly that organisation's companies. Then delete it and write your own — it names nobody real, which also
290
290
  means it grants nothing you have.
291
291
 
292
- `npx clearotron start` (§6) writes an empty roster (`{"tenants": {}}`) into its state directory: your own staff
292
+ `clearotron start` (§6) writes an empty roster (`{"tenants": {}}`) into its state directory: your own staff
293
293
  address is admitted, nobody else is enrolled, and enforcement is already on.
294
294
 
295
295
  ### Ops tokens — a credential for the verbs that spend
@@ -269,7 +269,7 @@ write nothing and 7 that do:
269
269
  `what_if_run` → one sandboxed stage; `what_if_result` → collect a queued one) — **executing** is
270
270
  local stdio only, never remote, for any principal. The confirmation token is a deliberate-action
271
271
  handshake, not a crypto boundary; the security boundary is ops scope + local-only execution.
272
- Since the owner's 2026-08-27 ruling an `account` principal reaches all three, and `what_if_run` on that
272
+ Since 2026-08-27 an `account` principal reaches all three, and `what_if_run` on that
273
273
  path ENQUEUES into `<runDir>/_experiments/_queue/` rather than shelling — `driver/whatif-worker.mjs`,
274
274
  drained by the runner, is what spawns the sandbox. Because the token is unsigned, a call on that path must
275
275
  also name its `runId` so the grant check fires, and the enqueue refuses a token naming another run.
@@ -88,7 +88,7 @@ These are the things a well-meaning refactor breaks. Each is enforced somewhere;
88
88
 
89
89
  Mechanically a config change ([04](04-configuration-reference.md#model-tiers-and-resolution)); two
90
90
  traps and one law. Traps: an alias not registered in the engine's model map now **refuses the
91
- dispatch by name** ( — it used to run sonnet silently and log the alias you asked for, which is
91
+ dispatch by name** (it used to run sonnet silently and log the alias you asked for, which is
92
92
  the `fable` lesson turned into an error), and `CLEAROTRON_SYNTHESIS_MODEL` is read at module load (fine
93
93
  for the oneshot service, stale in long-lived processes). The law: any grade-moving
94
94
  change — family, effort, tier remap — ships only through the paid A/B against the reference
@@ -221,7 +221,7 @@ Four realities to respect:
221
221
 
222
222
  - **Exclusion is by filename convention only.** Anything named `*.test.mjs` runs in CI; billable or
223
223
  manual harnesses must not match the glob (historical one-off proofs with hard-coded dev paths are
224
- not kept; the last billable hand-run harness, `selftest.mjs`, was deleted at).
224
+ not kept; the last billable hand-run harness, `selftest.mjs`, was deleted).
225
225
  - **The `||=` env guards leak**: a shell exporting a real register credential or
226
226
  `CLEAROTRON_PLAN_DISPATCH=on` is *not* overridden by the harness — run the suite in a clean env.
227
227
  (CI is safe.)
@@ -83,7 +83,7 @@ are no root units; everything is `systemd --user`.
83
83
  are stripped), and **`account`** (a signed-in person on the client face — the runs of the
84
84
  companies they are granted, reached either by the CF sign-in with no token, or by a per-person API
85
85
  key, `scope: account`, whose companies are re-read from the grants file on every request rather than
86
- baked into it. Wider *reach* than a report link and, since the owner's 2026-08-27 ruling, more
86
+ baked into it. Wider *reach* than a report link and, since a decision of 2026-08-27, more
87
87
  *depth* too: the audit chain — the audit trail, the reasoning narrative, the record artifacts, a
88
88
  register axis, and the `get_run` / `trace` / `decision_timeline` decision walk. Model identity
89
89
  and billed counts stay sealed (`get_telemetry`, `get_provider_usage`), as does the reviewers'
@@ -60,7 +60,7 @@ Every `CLEAROTRON_*` name read by shipping code declares an effect class, and an
60
60
  whatever the declaration test enforces. Do not re-derive it by hand; that is how 172, 159 and 352 all
61
61
  came to describe the same repository.
62
62
 
63
- ## Addendum — 2026-08-20, on implementation ( item 8)
63
+ ## Addendum — 2026-08-20, on implementation
64
64
 
65
65
  All four switches are deleted, as ruled. One consequence above is narrowed by what the code turned out
66
66
  to be, and it is recorded here rather than in a pull request body, because this document is what the
@@ -1,6 +1,6 @@
1
1
  # 0006 — What the public repository carries
2
2
 
3
- **Accepted 2026-08-17, by the owner.**
3
+ **Accepted 2026-08-17.**
4
4
 
5
5
  ## Context
6
6
 
@@ -74,7 +74,7 @@ announces which mode it is in, once, so a reader of any run can tell which colum
74
74
  "change the code" is the clause it answers to and this repository's working practice is that agents change
75
75
  the code. `CLAUDE.md` is not carried: it was only ever a one-line pointer at `AGENTS.md`, a second copy of
76
76
  one subject is a future contradiction ([ADR-0004](0004-documentation-structure.md)), and the de-identified
77
- public cut names that file specifically. Ruled by the owner 2026-08-19, on the question raised against the
77
+ public cut names that file specifically. Decided 2026-08-19, on the question raised against the
78
78
  recovery — where the two files had been held back because the revert that stripped them recorded no
79
79
  decision either way, and `shared/withheld-paths.mjs` did not cover them.
80
80
 
@@ -89,7 +89,7 @@ vendor-branded duplicate of it does not, however small.
89
89
  citations across code, comments, env examples and docs; all but six were rewritten, and the six declared
90
90
  are this record's own, where naming what is dropped is the content.
91
91
  - **A binding to a withheld document is retired.** `driver/doc-constants.mjs` pinned four prose figures in
92
- `docs/KNOCKOUT.md` and `docs/REGISTER-HIT-COUNTS.md`; those rows are gone, and the test pinning 's
92
+ `docs/KNOCKOUT.md` and `docs/REGISTER-HIT-COUNTS.md`; those rows are gone, and the test pinning the
93
93
  one-file-states-it-twice case now anchors to the shape rather than to a named document.
94
94
  - **Withholding is reversible**, which is why this is a list rather than a `git rm`. This repository keeps
95
95
  every word.
@@ -1,5 +1,18 @@
1
1
  # clearotron-driver
2
2
 
3
+ ## 0.3.2-beta.13
4
+
5
+ ### Patch Changes
6
+
7
+ - Fixed: A conditional verdict whose conditions are all kept in the run record no longer shows an empty "conditional on" line. The rating word stands alone.
8
+ - Fixed: A running clearance's card and row show the stage it is in now, including a step back during a correction pass.
9
+ - Fixed: The portal's health check no longer includes an internal error message when the instructions store cannot be read. The message goes to the service log instead.
10
+ - Fixed: `doctor` no longer describes another program on the client door's port as this install's door. It says only that a process holds the port.
11
+ - Fixed: The connect lines name the exact Node your install runs on, and an older Node gets one plain line instead of a crash.
12
+ - Fixed: The demo no longer leaves copies of its samples in your temp directory, including when it is stopped mid-way.
13
+ - Fixed: Running the test suite no longer leaves demo copies in the machine's temporary directory.
14
+ - Fixed: On WSL, the Windows connect line names your Linux distribution, or says plainly to fill it in, instead of leaving it out.
15
+
3
16
  ## 0.3.2-beta.12
4
17
 
5
18
  ### Patch Changes
@@ -170,7 +170,7 @@ function endingForQids(qids, join, ts) {
170
170
  return null;
171
171
  }
172
172
 
173
- /** The {ending, handoff} pair for one cross-check directive. Only `recall` is subject to 's
173
+ /** The {ending, handoff} pair for one cross-check directive. Only `recall` is subject to the
174
174
  * discharge rule; every other net keeps the ending it always had. PURE. */
175
175
  function recallEnding(name, qid, join, ts) {
176
176
  const ending = endingForQids([qid], join, ts);
@@ -769,7 +769,7 @@ export function buildBandShape(band, { targets = [], inScopeClasses = [], crowdC
769
769
  }
770
770
 
771
771
  const fmtN = (n) => Number(n ?? 0).toLocaleString("en-US");
772
- const cell = (v) => String(v ?? "").replace(/\|/g, "\\|").replace(/\s+/g, " ").trim() || "—";
772
+ const cell = (v) => String(v ?? "").replace(/\\/g, "\\\\").replace(/\|/g, "\\|").replace(/\s+/g, " ").trim() || "—";
773
773
 
774
774
  // Bounded aggregate table: top rows by count, one honest remainder line — never for the floors.
775
775
  function topTable(md, title, cols, entries, cap = 30) {
@@ -3,8 +3,8 @@
3
3
  // bundled-demos.mjs — WHICH client bundles this repo ships, read from the directory that ships them.
4
4
  //
5
5
  // This was a hand-maintained triple. The directory grew a fourth bundle, the triple did not, and the
6
- // set-equality it feeds could no longer match anything — so the #83 detector, whose whole job is to
7
- // notice a door that has silently fallen back to the bundled roster, returned PASS on the #83
6
+ // set-equality it feeds could no longer match anything — so the roster-fallback detector, whose whole job is to
7
+ // notice a door that has silently fallen back to the bundled roster, returned PASS on exactly that
8
8
  // condition. A guard that cannot match reports that it found nothing wrong.
9
9
  //
10
10
  // The same shape had already produced two FALSE REFUSALS on this check, both from an exact match
@@ -74,13 +74,13 @@ function insertBulletAtSectionHead(text, headingRe, headingLine, bullet, { paren
74
74
  const m = text.match(headingRe);
75
75
  if (m) {
76
76
  const at = m.index + m[0].length;
77
- return `${text.slice(0, at)}\n${bullet}\n${text.slice(at).replace(/^\n/, "\n")}`;
77
+ return `${text.slice(0, at)}\n${bullet}\n${text.slice(at)}`;
78
78
  }
79
79
  if (parentHeadingRe) {
80
80
  const p = text.match(parentHeadingRe);
81
81
  if (p) {
82
82
  const at = p.index + p[0].length;
83
- return `${text.slice(0, at)}\n${headingLine}\n\n${bullet}\n${text.slice(at).replace(/^\n/, "\n")}`;
83
+ return `${text.slice(0, at)}\n${headingLine}\n\n${bullet}\n${text.slice(at)}`;
84
84
  }
85
85
  return `${text.replace(/\s*$/, "")}\n\n${parentHeadingLine}\n\n${headingLine}\n\n${bullet}\n`;
86
86
  }
@@ -114,8 +114,8 @@ export function parseCaseLawLedger(raw) {
114
114
  /**
115
115
  * The census a reader (and a scenario) asks the ledger for. PURE, and it never judges.
116
116
  *
117
- * `readByTerritory` is the one that answers the depth-dive condition: a dive names ONE territory
118
- * (/), so "did this dive read anything in its own territory" is a lookup, not an inference.
117
+ * `readByTerritory` is the one that answers the depth-dive condition: a dive names ONE territory,
118
+ * so "did this dive read anything in its own territory" is a lookup, not an inference.
119
119
  */
120
120
  export function caseLawRetrievalCensus(ledger) {
121
121
  const queries = ledger?.queries ?? [];