clearotron 0.4.1-beta.0 → 0.4.1-beta.2

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 (79) hide show
  1. package/THIRD-PARTY-NOTICES.md +1 -1
  2. package/bin/README.md +1 -1
  3. package/bin/clearotron.mjs +2 -0
  4. package/bin/mcp.mjs +80 -0
  5. package/bin/start.mjs +14 -1
  6. package/build-info.json +2 -2
  7. package/demo/README.md +1 -1
  8. package/docs/architecture/04-configuration-reference.md +0 -1
  9. package/docs/architecture/06-operations-runbook.md +4 -3
  10. package/docs/architecture/08-development-guide.md +3 -1
  11. package/driver/CHANGELOG.md +17 -0
  12. package/driver/citation-census.json +3 -3
  13. package/driver/compare.mjs +1 -1
  14. package/driver/contract-audit.mjs +6 -6
  15. package/driver/contract-dictation-registry.mjs +1 -1
  16. package/driver/contract-e3-backlog.mjs +17 -16
  17. package/driver/contract-vocabulary.mjs +138 -125
  18. package/driver/declination-call.mjs +5 -1
  19. package/driver/dev-portal.mjs +2 -1
  20. package/driver/engine/mcp/codex-config.mjs +7 -2
  21. package/driver/engine/mcp/gather-config.mjs +4 -0
  22. package/driver/engine/mcp/recording-server.mjs +1 -1
  23. package/driver/engine/mcp/supplemental.mjs +26 -1
  24. package/driver/engine/openai-agent.mjs +13 -3
  25. package/driver/findings-model.mjs +80 -13
  26. package/driver/flag-snapshot.mjs +2 -1
  27. package/driver/foreign-record-links.mjs +50 -0
  28. package/driver/frame-diff-model.mjs +1 -1
  29. package/driver/gateway.mjs +4 -4
  30. package/driver/jx.mjs +1 -1
  31. package/driver/knockout-review-record.mjs +55 -1
  32. package/driver/named-band.mjs +2 -0
  33. package/driver/owner-screen.mjs +7 -4
  34. package/driver/package.json +1 -1
  35. package/driver/pipeline-knockout.mjs +14 -12
  36. package/driver/pipeline.mjs +118 -20
  37. package/driver/portal-service.mjs +5 -4
  38. package/driver/products.mjs +1 -1
  39. package/driver/profiles.mjs +4 -3
  40. package/driver/publish/publish-inputs.mjs +2 -2
  41. package/driver/publish/xlsx.mjs +1 -1
  42. package/driver/register-count.mjs +2 -2
  43. package/driver/register-coverage.mjs +1 -1
  44. package/driver/register-plan.mjs +13 -2
  45. package/driver/result-noun-fields.mjs +3 -3
  46. package/driver/scope-rules.mjs +2 -2
  47. package/driver/stage-context.mjs +9 -0
  48. package/driver/stages-knockout.mjs +6 -5
  49. package/driver/stages.mjs +23 -23
  50. package/driver/suite-census.json +97 -31
  51. package/driver/synthesis-record.mjs +14 -5
  52. package/driver/verify.mjs +1 -1
  53. package/mcp-server/CHANGELOG.md +8 -0
  54. package/mcp-server/CONNECT.md +9 -3
  55. package/mcp-server/demo-mode.mjs +10 -0
  56. package/mcp-server/lib/runs.mjs +7 -1
  57. package/mcp-server/lib/scrub.mjs +1 -1
  58. package/mcp-server/package.json +1 -1
  59. package/mcp-server/packs/README.md +1 -1
  60. package/mcp-server/server.mjs +16 -2
  61. package/package.json +3 -2
  62. package/portal-ui/package.json +3 -3
  63. package/providers/_shared/count.mjs +5 -1
  64. package/providers/_shared/enumerate.mjs +66 -21
  65. package/providers/_shared/execute-plan.mjs +13 -4
  66. package/providers/oauth-mcp-bridge/CHANGELOG.md +8 -0
  67. package/providers/oauth-mcp-bridge/package.json +1 -1
  68. package/providers/signa/src/capabilities.js +15 -0
  69. package/providers/signa/src/core.js +55 -26
  70. package/scripts/e2e-first-time.mjs +3 -3
  71. package/scripts/env-audit.mjs +3 -2
  72. package/scripts/freeze-example-run.mjs +8 -8
  73. package/scripts/mcp-registry-listing.mjs +199 -0
  74. package/scripts/mint-plugin-folder.mjs +65 -0
  75. package/scripts/release-version.mjs +44 -0
  76. package/scripts/score.mjs +1 -1
  77. package/shared/register-floor.mjs +19 -0
  78. package/shared/trigger-lane.mjs +1 -1
  79. package/skills/clearotron-ops/SKILL.md +81 -24
@@ -110,7 +110,7 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
110
110
  SOFTWARE.
111
111
  ```
112
112
 
113
- ## @modelcontextprotocol/sdk@1.31.0
113
+ ## @modelcontextprotocol/sdk@1.32.1
114
114
 
115
115
  - **Licence declared:** `MIT`
116
116
  - **Repository:** https://github.com/modelcontextprotocol/typescript-sdk
package/bin/README.md CHANGED
@@ -20,7 +20,7 @@ and is what you want when you are working in this tree.
20
20
  | `signa-sync.mjs` | — | `node bin/signa-sync.mjs` | One vendor read (`GET /v1/offices`, paged). Needs `SIGNA_API_KEY`. |
21
21
 
22
22
  `example.mjs` replays `demo` through the ordinary publisher into `~/trademark-demo/pool` and
23
- serves it on `127.0.0.1:18900`. Every path the demo pool could collide with is checked by realpath and by
23
+ serves it on `127.0.0.1:18860`. Every path the demo pool could collide with is checked by realpath and by
24
24
  containment before anything is written, because a demo runs on deployed machines too.
25
25
 
26
26
  `onboard.mjs` validates each answer through the door the engine itself uses before it persists anything,
@@ -65,6 +65,7 @@ export const VERBS = {
65
65
  passphrase: ["bin/passphrase.mjs"],
66
66
  sync: ["bin/uspto-sync.mjs"],
67
67
  update: ["bin/update.mjs"],
68
+ mcp: ["bin/mcp.mjs"],
68
69
  };
69
70
 
70
71
  export const SUMMARY = {
@@ -87,6 +88,7 @@ export const SUMMARY = {
87
88
  passphrase: "report or RESET the portal's local sign-in — the recovery for a lost passphrase",
88
89
  sync: "build or update the free US register index (a large download, and hours of ingest)",
89
90
  update: "bring this install up to date — and REFUSE to do it over the top of your own configuration",
91
+ mcp: "serve this install's searches to an assistant over MCP, or the demo's where there is no install",
90
92
  };
91
93
 
92
94
  /**
package/bin/mcp.mjs ADDED
@@ -0,0 +1,80 @@
1
+ #!/usr/bin/env node
2
+ // SPDX-License-Identifier: AGPL-3.0-only
3
+ // Copyright 2026 Cordillera Sàrl. Additional terms under section 7 of the AGPL-3.0 apply — see ADDITIONAL-TERMS.md
4
+ //
5
+ // mcp.mjs — `clearotron mcp`: the MCP server over stdio, started from the package itself.
6
+ //
7
+ // An install's own connect lines run the server from that install's copy (shared/stdio-connect.mjs), so
8
+ // an assistant never keeps an old server after an upgrade, and they stay as they are. This verb is for a
9
+ // machine with no install, and for a listing that launches the published package with `npx`.
10
+ //
11
+ // WHICH RUNS IT READS. An install's, wherever one is found; the demo's four sample runs where none is.
12
+ // Standard output belongs to the protocol from its first byte, so nothing here prints to it.
13
+ import { existsSync, readFileSync } from "node:fs";
14
+ import { homedir } from "node:os";
15
+ import { join } from "node:path";
16
+ import { parseEnv } from "node:util";
17
+ import { isEntrypoint } from "../shared/is-entrypoint.mjs";
18
+ import { demoMode } from "../mcp-server/demo-mode.mjs";
19
+
20
+ /** The two settings the install's own connect lines hand the server: where runs are, and where reports are. */
21
+ export const INSTALL_FOLDERS = Object.freeze(["CLEAROTRON_WORK_DIR", "CLEAROTRON_REPORTS_DIR"]);
22
+
23
+ /** The demo's own base, and where it keeps its runs and its access log: the layout `installPaths` gives it. */
24
+ export const demoBase = (home = homedir()) => join(home, "trademark-demo");
25
+ export const demoWorkspace = (home = homedir()) => join(demoBase(home), "workspace");
26
+
27
+ /**
28
+ * WHAT COUNTS AS AN INSTALL, and so whether this server reads an install's runs or the demo's. PURE.
29
+ *
30
+ * An install is any one of these:
31
+ * - a work folder or a reports folder named in this process's environment, in the settings file a
32
+ * packaged install writes (`~/.config/clearotron/.env`), or in the file a background install's
33
+ * services read (`~/.env`);
34
+ * - either of those files carrying any Clearotron setting at all, which is an install configured and
35
+ * not yet pointed at its folders: the server then reads its default and names what is missing;
36
+ * - the default workspace, `~/trademark/workspace`, on disk.
37
+ * The demo writes neither file and keeps its runs under its own base, so a machine that has only run the
38
+ * demo still reads as no install. An install never gets the demo: a misconfigured one is refused by name.
39
+ *
40
+ * Returns the folders to hand the server (only what is set, and never over the environment's own value),
41
+ * or `demo: true` where there is no install.
42
+ */
43
+ export function mcpTarget({ env = process.env, home = homedir(), exists = existsSync, read = (p) => readFileSync(p, "utf8") } = {}) {
44
+ const parsed = (p) => { try { return parseEnv(read(p)); } catch { return null; } };
45
+ const settings = parsed(join(home, ".config", "clearotron", ".env"));
46
+ const services = parsed(join(home, ".env"));
47
+ const set = (v) => (typeof v === "string" && v.trim() ? v.trim() : null);
48
+ const folders = {};
49
+ for (const k of INSTALL_FOLDERS) {
50
+ const v = set(env[k]) ?? set(settings?.[k]) ?? set(services?.[k]);
51
+ if (v) folders[k] = v;
52
+ }
53
+ const configures = (file) => Boolean(file) && Object.keys(file).some((k) => /^(CLEAROTRON|TRADEMARK|PORTAL)_/.test(k));
54
+ const install = Object.keys(folders).length > 0 || configures(settings) || configures(services)
55
+ || exists(join(home, "trademark", "workspace"));
56
+ return install ? { demo: false, env: folders } : { demo: true, env: {} };
57
+ }
58
+
59
+ async function main() {
60
+ const target = mcpTarget();
61
+ if (target.demo) {
62
+ // The four sample runs, copied where the connector walks, under the demo's own base: nothing is
63
+ // written anywhere an install reads (driver/demo-container.mjs).
64
+ const { seedDemoRuns } = await import("../driver/demo-container.mjs");
65
+ const workspace = demoWorkspace();
66
+ seedDemoRuns({ workspace, examplesDir: join(import.meta.dirname, "..", "demo") });
67
+ process.env.CLEAROTRON_WORK_DIR = workspace;
68
+ // The door's access log too, which would otherwise open the install's own telemetry folder.
69
+ if (!set(process.env.TRADEMARK_MCP_AUDIT_LOG)) process.env.TRADEMARK_MCP_AUDIT_LOG = join(demoBase(), "telemetry", "trademark-mcp-access.jsonl");
70
+ demoMode.on = true;
71
+ } else {
72
+ for (const [k, v] of Object.entries(target.env)) if (!set(process.env[k])) process.env[k] = v;
73
+ }
74
+ // The server starts through the entry that refuses an old Node in one plain line before it loads.
75
+ await import("../mcp-server/serve.mjs");
76
+ }
77
+
78
+ const set = (v) => typeof v === "string" && v.trim() !== "";
79
+
80
+ if (isEntrypoint(import.meta.url)) await main();
package/bin/start.mjs CHANGED
@@ -2445,6 +2445,18 @@ if (isMain) {
2445
2445
  const clientDoor = adoptedClientDoor
2446
2446
  ? null
2447
2447
  : start("the client door", "mcp-server/http-server-client.mjs", envs.client, { fatal: false });
2448
+ // ── THE BANNER NAMES THIS DOOR ONLY ONCE IT ANSWERS ─────────────────────────────────────────────────
2449
+ //
2450
+ // The engine door and the portal are each awaited above before anything is printed. This door was
2451
+ // not: the banner below was composed the moment it was spawned, while it was still loading, and
2452
+ // announced an address that refused connections. Measured 2026-10-09, in a container with no network
2453
+ // running as root and on a host as a normal user: a request sent as the banner printed got no
2454
+ // connection, and the door's first answer came about half a second later. Its own output said nothing
2455
+ // was wrong, because nothing was; it had not finished starting.
2456
+ //
2457
+ // Same probe and same budget as the other two, and non-fatal like the door itself. A door that exits,
2458
+ // or does not answer within the budget, takes the NOT RUNNING branch below and the portal keeps serving.
2459
+ const clientDoorAnswered = clientDoor ? await healthy(`http://${HOST}:${ports.client}/healthz`, clientDoor) : false;
2448
2460
 
2449
2461
  // ── 6. one URL ─────────────────────────────────────────────────────────────────────────────────────
2450
2462
 
@@ -2471,7 +2483,8 @@ if (isMain) {
2471
2483
  // AN ADOPTED DOOR IS RUNNING. Without this the banner reported "NOT RUNNING …
2472
2484
  // its output above says why" about a door that is up and serving, and pointed the reader at output
2473
2485
  // that does not exist — the one sentence on this screen a reader would act on, and false.
2474
- const doorRunning = adoptedClientDoor || clientDoor?.child?.exitCode === null;
2486
+ // AND A SPAWNED DOOR IS RUNNING ONLY ONCE IT HAS ANSWERED: alive is not listening (see the probe above).
2487
+ const doorRunning = adoptedClientDoor || (clientDoor?.child?.exitCode === null && clientDoorAnswered);
2475
2488
  if (doorRunning) {
2476
2489
  say(` Client door http://${HOST}:${ports.client}/mcp — a client's assistant connects here.`);
2477
2490
  if (adoptedClientDoor)
package/build-info.json CHANGED
@@ -1,4 +1,4 @@
1
1
  {
2
- "commit": "4f2133e937195c6875d4be055c76383ceaca0138",
3
- "version": "0.4.1-beta.0"
2
+ "commit": "08583753fbde693fdecdc8e16f11781dae789f55",
3
+ "version": "0.4.1-beta.2"
4
4
  }
package/demo/README.md CHANGED
@@ -16,7 +16,7 @@ carries no `report.md` at all, because for that lane the markdown is an output o
16
16
  an input to it.
17
17
 
18
18
  Each directory is named by the product that produced it — the id in the run's own frozen search policy,
19
- not a label chosen afterwards. `clearotron demo` replays the first; `clearotron demo --product <id>`
19
+ not a label chosen afterwards. `clearotron demo` replays all four; `clearotron demo --product <id>`
20
20
  replays a named one; `clearotron demo --run-dir <dir>` replays any frozen run from anywhere.
21
21
 
22
22
  ## The marks are invented; the data behind them is real
@@ -301,7 +301,6 @@ because the rule is about what PRODUCT CODE reads, not about what a run reads.
301
301
 
302
302
  | Var | Default | Meaning |
303
303
  |---|---|---|
304
- | `DEMO_PORT` | `18900` | Port `npx clearotron demo` serves the replayed report on. `--port` overrides it. |
305
304
  | `CLEAROTRON_DEMO` | unset | `1` puts this install in the DEMO posture. **Ordering is real**: the four products are listed and orderable, the form, the plan and the confirmation are the product's own, and the confirmation resolves to a finished report that already exists rather than dispatching — no engine turn, no register call, no queue entry, no run directory (ruling 2026-08-31, superseding the greyed-control ruling of the same day). A product the demo carries no finished report for refuses and names which one. It also re-aims two boot warnings written for an operator of a real deployment at the visitor who is not one, from one place (`driver/demo-posture.mjs`). **Set by `npx clearotron demo`, not by an operator** — it is passed explicitly to the two processes that have a reason to know (the portal and the MCP door; the worker is not told, because a demo never queues anything for it to drain), and those run with `CLEAROTRON_NO_ENV_FILE=1`, so a stray `.env` can neither put a live install into demo mode nor take a demo out of one. Anything but the literal `1` is not a demo. Replaces `PORTAL_DEMO`, which named only one of the processes that has to know. |
306
305
  | `CLEAROTRON_TEST_FIXTURE_PROFILES` | unset | `1` makes the profile loader return the three suite fixtures, which are refused from every roster otherwise. Set by `scripts/test-run.mjs`, never by an operator; an explicit `includeTestFixtures` argument beats it. Effect class `harness`; the full contract is its row in `.env.example`. |
307
306
  | `CLEAROTRON_DEMO_PROFILES` | unset | `1` makes the profile loader return the bundled demo company, which a fresh install does not resolve (ruling 2026-09-08). Set by `clearotron demo`, `start --demo` and the suite runner, never by an operator. The gate is on the bundled layer, so a deployment's own configured store keeps its `demoData` companies either way. Effect class `harness`; the full contract is its row in `.env.example`. |
@@ -301,9 +301,10 @@ node mcp-server/mint-token.mjs --scope ops --sub <principal-name> \
301
301
  - The token is printed once and stored nowhere. Losing it means minting another.
302
302
 
303
303
  **Revoking one.** Every mint prints a `jti`. Write that line into the file named by
304
- `TRADEMARK_MCP_TOKEN_DENYLIST` (one `jti` per line, `#` comments allowed) and the next verification
305
- refuses the token. A **missing** denylist file means nothing is revoked, never everything — a denylist
306
- must not be able to take all authentication down.
304
+ `TRADEMARK_MCP_TOKEN_DENYLIST`, or into `~/.config/clearotron/token-denylist` when the setting is unset
305
+ (one `jti` per line, `#` comments allowed), and the next verification refuses the token. A named list
306
+ that is missing or unreadable refuses every key, and so does a default list that exists but cannot be
307
+ read. An absent default list means nothing has been revoked.
307
308
 
308
309
  **Rotating the signing secret.** Verification accepts `TRADEMARK_MCP_TOKEN_SECRET` or
309
310
  `TRADEMARK_MCP_TOKEN_SECRET_PREVIOUS`; minting always signs with the current one. Move the old value
@@ -10,7 +10,9 @@ How to work on the driver without breaking the properties that make it trustwort
10
10
 
11
11
  - **Node ≥ 22** (every workspace `package.json` declares it; CI pins 22.23.2). The repo is an npm
12
12
  workspace — `npm ci` at the root installs `driver`, `mcp-server`, `providers/oauth-mcp-bridge`
13
- and `portal-ui` together. Production deploys install with `--omit=dev --ignore-scripts`.
13
+ and `portal-ui` together. An install from a git checkout updates with `npm ci --ignore-scripts` and
14
+ keeps development packages, because the portal is rebuilt from them. A packaged install updates
15
+ through `npm install --global`.
14
16
  - **Tests**: `npm test` in the driver dir runs the *contributor* tier
15
17
  (`scripts/test-run.mjs` → `scripts/test-fast.mjs` over `test/*.test.mjs`), which leaves out the
16
18
  slowest end-to-end files so a first run finishes in minutes. `npm run test:full` is the
@@ -1,5 +1,22 @@
1
1
  # clearotron-driver
2
2
 
3
+ ## 0.4.1-beta.2
4
+
5
+ ### Patch Changes
6
+
7
+ - Fixed: A clearance no longer stops before its report when findings link to an office's own page instead of the register record.
8
+
9
+ ## 0.4.1-beta.1
10
+
11
+ ### Patch Changes
12
+
13
+ - Fixed: A report repaired just before delivery keeps its coverage notes, such as a script that was not searched, in the report and the audit workbook.
14
+ - Fixed: A search too broad for the register to count is no longer reported as one that could not be run. The report shows it as too large to read, with the register's own figure.
15
+ - Fixed: On Signa, a crowded search over several spellings now counts each spelling alone, so a rare spelling is no longer reported as crowded.
16
+ - New: `npx clearotron mcp` starts Clearotron's MCP server for an assistant; with no install it answers from the demo's sample runs, and says so.
17
+ - Fixed: The address `clearotron start` and `clearotron demo` print for a client's assistant now answers as soon as it is printed.
18
+ - Fixed: The audit workbook's coverage summary names each area in words, not the engine's identifiers.
19
+
3
20
  ## 0.4.1-beta.0
4
21
 
5
22
  ### Patch Changes
@@ -1,5 +1,5 @@
1
1
  {
2
- "citations": 415,
3
- "files": 3437,
4
- "checkable": 186
2
+ "citations": 257,
3
+ "files": 3469,
4
+ "checkable": 245
5
5
  }
@@ -212,7 +212,7 @@ export function compareCmd(opts) {
212
212
  if (isEntrypoint(import.meta.url)) {
213
213
  let a;
214
214
  //: the refusal must print the usage, or "unknown flag --experiement" leaves the reader guessing
215
- // which spelling was wanted. Same wiring as enqueue.mjs:155.
215
+ // which spelling was wanted. Same wiring as main() in enqueue.mjs.
216
216
  try { a = parseArgv(process.argv.slice(2)); }
217
217
  catch (e) { console.error(`error: ${e.message}\n\n${USAGE}`); process.exit(2); }
218
218
  try {
@@ -158,14 +158,14 @@ const ARROW_TOKEN_RE = /=>\s*[`"']([a-z][a-z0-9]*(?:_[a-z0-9]+)+)/g;
158
158
  const REASON_TOKEN_RE = /reason:\s*[`"']([a-z][a-z0-9]*(?:_[a-z0-9]+)+)/g;
159
159
 
160
160
  // A CITED SITE MUST BE A MINT. The extractor records the FIRST line a pattern matched, and the
161
- // patterns read comment text as readily as code: `no_status` was cited at coverage-form.mjs:802, the
162
- // JSDoc `@returns {Array<{reason:"no_status"|"form_damaged", …` annotation seventeen lines above the line
163
- // that writes it. Every such ruling cites its mint, so a citation drawn from a sentence about the code
164
- // is a ruling about a sentence.
161
+ // patterns read comment text as readily as code: `no_status` was cited at the JSDoc `@returns
162
+ // {Array<{reason:"no_status"|"form_damaged", …` annotation of findCoverageFormViolations() in coverage-form.mjs,
163
+ // seventeen lines above the line that writes it. Every such ruling cites its mint, so a citation drawn
164
+ // from a sentence about the code is a ruling about a sentence.
165
165
  //
166
166
  // Comment-ONLY lines, never a trailing `//` after code — a token minted on a line that also carries a
167
167
  // comment is still minted there. Measured before it was trusted: across all 30 covered sources this moves
168
- // exactly one citation (coverage-form.mjs:802 -> :828) and loses NO token, so the census does not shrink.
168
+ // exactly one citation (`no_status`'s, annotation -> mint) and loses NO token, so the census does not shrink.
169
169
  // That measurement is the load-bearing half: a filter that quietly dropped a comment-only token would
170
170
  // take its coverage obligation with it, and the tripwire would read the absence as clean.
171
171
  const COMMENT_ONLY_LINE = /^\s*(?:\/\/|\*|\/\*)/;
@@ -265,7 +265,7 @@ export const E3_KINDS = ["literal-json-skeleton", "exactly-these-keys", "dictate
265
265
  *
266
266
  * other — the dictate is not in the text being linted. Two shapes found: a stage message that
267
267
  * DELEGATES its whole envelope to a skill file ("dictated keys + closed enums per the skill",
268
- * stages.mjs:974 for blind-frame — a lint reading the message sees nothing and the skeleton
268
+ * blind-frame's message until 2026-08-17 — a lint reading the message sees nothing and the skeleton
269
269
  * escapes); and a field DICTATED THEN OVERWRITTEN, where the instruction annotates its own
270
270
  * fields as driver-replaced in the model's own reading (delivery-contract.md:34-37).
271
271
  *
@@ -131,7 +131,7 @@ export const TOOL_ORDER_BACKLOG = [
131
131
  closes: "#865 — same shared file, same split grant." },
132
132
  { stage: "register-digest", tool: "register_batch_screen", site: "driver/skills/clearance-register/digest.md",
133
133
  closes: "#865 — the digest's OWN doctrine, not a shared file: this one is the stage being told to "
134
- + "batch-screen with a tool its grant does not carry. gather-config.mjs:196 RECORDING already records that "
134
+ + "batch-screen with a tool its grant does not carry. toolGroupsForStage() in gather-config.mjs already records that "
135
135
  + "this stage's prompt once ordered live register checks; the order outlived the note." },
136
136
  { stage: "register-unit", tool: "band_record", site: "driver/skills/clearance-register/SKILL.md",
137
137
  closes: "#865 — same shared file, same split grant." },
@@ -106,11 +106,11 @@ export const E3_BACKLOG = [
106
106
  // Two `literal-json-skeleton` and two `exactly-these-keys`, all four stamped "NOTHING ON THE PLAN
107
107
  // REMOVES THIS". The conversion removed them:
108
108
  //
109
- // stages.mjs:926 the dispatch dictated variant-manifest.json key by key and enum by enum;
109
+ // stages.mjs (message) the dispatch dictated variant-manifest.json key by key and enum by enum;
110
110
  // `record_clearance_variants`'s schema IS that shape now, so the key-set and
111
111
  // enum families are unreachable from a typed call rather than caught after
112
112
  // the file is written.
113
- // stages.mjs:774-808 the same skeleton's category enum, same fate.
113
+ // stages.mjs (message) the same skeleton's category enum, same fate.
114
114
  // clearance-variants SKILL.md the `### Scope ledger` markdown table and its column contract. The rows
115
115
  // arrive typed; the driver renders the table AND serialises
116
116
  // scope-ledger.json from them through one shared function.
@@ -152,7 +152,7 @@ export const E3_BACKLOG = [
152
152
  where: "driver/skills/blind-frame/SKILL.md:62-83",
153
153
  surface: "skill-file",
154
154
  evidence: "A JSON OBJECT with EXACTLY these keys:\\n```json\\n{\\n \"schema_version\": 1,\\n \"dominant_element\": \"the spine, verbatim\",\\n \"variants\": [{\"value\": \"KORPHI\", \"direction\": \"drop\", \"rationale\": \"…\"}],\\n \"fields\": […],\\n \"sources\": […],\\n \"ranking_basis\": \"goods-overlap\"\\n}\\n``` … `direction` is EXACTLY on",
155
- reparsedBy: "driver/verify.mjs validators.blindFrame — emits invalid_file:…:blindframe_* on an off-enum or missing key (stages.mjs:952-957 doc block)",
155
+ reparsedBy: "driver/verify.mjs validators.blindFrame — emits invalid_file:…:blindframe_* on an off-enum or missing key (the blind-frame stage's doc block in stages.mjs)",
156
156
  removedByMove: "NOTHING ON THE #850 PLAN REMOVES THIS",
157
157
  },
158
158
  // ── DISCHARGED 2026-08-17 by, and the row said this could not happen ──────────────────────────
@@ -180,8 +180,8 @@ export const E3_BACKLOG = [
180
180
  {
181
181
  stage: "common-law-half",
182
182
  kind: "dictated-line-shape",
183
- // CONSOLIDATED 2026-08-16 from THREE rows (common-law stages.mjs:1031, common-law-half :1112 and
184
- // :1146). Those three existed because the dictation was AUTHORED at three sites in stages.mjs. M1 made
183
+ // CONSOLIDATED 2026-08-16 from THREE rows (one in common-law's message in stages.mjs, two in
184
+ // common-law-half's). Those three existed because the dictation was AUTHORED at three sites in stages.mjs. M1 made
185
185
  // it one: authored once in renderConnotationObligations, and reaching the seat through the perplexity
186
186
  // MCP server (`tellObligations` in driver/engine/mcp/perplexity-server.mjs) rather than a stage message. Three rows
187
187
  // pointing at one block would fabricate two authored sites the surface does not have.
@@ -295,7 +295,7 @@ export const E3_BACKLOG = [
295
295
  where: "driver/stages.mjs:2351-2353",
296
296
  surface: "stage-message",
297
297
  evidence: "PLACEMENT FORM (MANDATORY): record every placement in <path> — {\"rows\":[…]} … · A REGISTER candidate: {\"select\":\"<one record URI it holds>\",\"tier\":\"…\",\"reason\":\"…\"} (+ optional \"borderline\":true) … · A COMMON-LAW candidate …: {\"kind\":\"seat\",\"mark\",\"owner\",\"jurisdiction\",\"records\":[],\"tier\",\"reason\"}",
298
- reparsedBy: "driver/placement-form.mjs (SELECT_ROW_FIELDS at placement-form.mjs:93, the seat-row contract at 97-105, formRowKey/rowIsSettled/renderEntry at 117-141) via validators.placement. The same field list is ALSO carried in the driver-written form's own seat_row_contract, so the shape exists twice",
298
+ reparsedBy: "driver/placement-form.mjs (SELECT_ROW_FIELDS at placement-form.mjs:93, the seat-row contract SEAT_ROW_CONTRACT, formRowKey/rowIsSettled/renderEntry) via validators.placement. The same field list is ALSO carried in the driver-written form's own seat_row_contract, so the shape exists twice",
299
299
  removedByMove: "M1 removes the opaque `select` URI (ordinal selection) — it does not remove the JSON skeleton, the retract shape, or the kind:\"seat\" row",
300
300
  },
301
301
  {
@@ -313,7 +313,7 @@ export const E3_BACKLOG = [
313
313
  where: "driver/skills/placement-inquiry/SKILL.md:58-66",
314
314
  surface: "skill-file",
315
315
  evidence: "**2. The structured mirror** `…/placements.json` … `{\"schema_version\":1,\"placements\":[...]}`, ONE object per placed candidate, keys EXACTLY `{\"mark\",\"owner\",\"jurisdiction\",\"records\",\"tier\",\"reason\"}` plus the optional `\"borderline\"` … `tier` — EXACTLY one of `headline-candidate` / `sheet-2` / `watch",
316
- reparsedBy: "driver/placement-model.mjs. AND IT IS STALE: #562 made placements.json driver-rendered, and stages.mjs:2409 says \"DO NOT WRITE placements.json (the driver renders it from this form)\" — the skill file the stage is ordered to \"read and follow exactly\" dictates the key set of a file the message forbids it to write. Two contracts in one dispatch",
316
+ reparsedBy: "driver/placement-model.mjs. AND IT IS STALE: #562 made placements.json driver-rendered, and placement-inquiry's message in stages.mjs says \"DO NOT WRITE placements.json (the driver renders it from this form)\" — the skill file the stage is ordered to \"read and follow exactly\" dictates the key set of a file the message forbids it to write. Two contracts in one dispatch",
317
317
  removedByMove: "NOTHING ON THE #850 PLAN REMOVES THIS",
318
318
  },
319
319
  {
@@ -322,13 +322,14 @@ export const E3_BACKLOG = [
322
322
  where: "driver/skills/placement-inquiry/SKILL.md:42",
323
323
  surface: "skill-file",
324
324
  evidence: "Use these section headings, in this order: **Band reconciliation** …, the four placement tiers (**Headline candidates**, **Sheet 2 / register watch**, **Watchlist annex**, **Out-of-scope / filtered**), **Disagreements / flags surfaced to downstream**, **Coverage rulings & open questions** …, and **O",
325
- reparsedBy: "driver/pipeline.mjs — the PLACEMENT RULINGS TAIL block handed to register-digest is lifted from these named sections (stages.mjs:2561 references it); driver/skills/clearance-register/digest.md:342-352 re-parses the Disagreements section into its own table",
325
+ reparsedBy: "driver/pipeline.mjs — the PLACEMENT RULINGS TAIL block handed to register-digest is lifted from these named sections (register-digest's message in stages.mjs references it); driver/skills/clearance-register/digest.md:342-352 re-parses the Disagreements section into its own table",
326
326
  removedByMove: "NOTHING ON THE #850 PLAN REMOVES THIS",
327
327
  },
328
328
  // RETIRED 2026-08-16 — register-digest's no-form arm. M6 DELETED THE DICTATION ON 2026-08-14 AND THIS
329
329
  // ROW OUTLIVED IT BY TWO DAYS. Verified at source rather than from the epitaph: the arm is absent from
330
330
  // the composed message, `git grep "NO coverage form"` on origin/main returns exactly ONE hit and it is
331
- // the COMMENT recording the deletion (stages.mjs:2195), and skill-contract-enumerations.test.mjs
331
+ // the COMMENT recording the deletion ("A NO-FORM arm stood here until M6", in register-digest's message in
332
+ // stages.mjs), and skill-contract-enumerations.test.mjs
332
333
  // composes the dispatch under both stamp states and asserts the two texts are equal.
333
334
  //
334
335
  // The surviving prose arms in verify.mjs are NOT dead code and must not be tidied away with it: they
@@ -447,7 +448,7 @@ export const E3_BACKLOG = [
447
448
  where: "driver/skills/frame-diff/SKILL.md:44-48",
448
449
  surface: "skill-file",
449
450
  evidence: "A directive may carry a structured `remedy`:\\n```json\\n\"remedy\": { \"terms\": [\"TROPICAL WAVO\", \"ISLAND WAVO\"], \"nice_classes\": [\"5\", \"32\"], \"regions\": [] }\\n```",
450
- reparsedBy: "driver/pipeline.mjs runSupplementalSweeps — the remedy lint refuses a label-shaped term; stages.mjs:2827 restates the same shape in the message (\"THE ASK CONTRACT, stated at BOTH levels\")",
451
+ reparsedBy: "driver/pipeline.mjs runSupplementalSweeps — the remedy lint refuses a label-shaped term; frame-diff's message in stages.mjs restates the same shape (\"THE ASK CONTRACT, stated at BOTH levels\")",
451
452
  removedByMove: "NOTHING ON THE #850 PLAN REMOVES THIS",
452
453
  },
453
454
  {
@@ -524,7 +525,7 @@ export const E3_BACKLOG = [
524
525
  where: "driver/stages.mjs:3283",
525
526
  surface: "stage-message",
526
527
  evidence: "use_check = {\"source\",\"quality\"}: … quality: OPTIONAL, EXACTLY one of owner-site / independent / register-mirror … own_rights = {\"source\"}",
527
- reparsedBy: "driver/verify.mjs:988 checkFindingsSibling (finding_use_check_missing); driver/own-rights.mjs:19-22",
528
+ reparsedBy: "driver/verify.mjs checkFindingsSibling (finding_use_check_missing); driver/own-rights.mjs:19-22",
528
529
  removedByMove: "NOTHING ON THE #850 PLAN REMOVES THIS",
529
530
  },
530
531
  {
@@ -593,7 +594,7 @@ export const E3_BACKLOG = [
593
594
  where: "driver/stages.mjs:3115 (restated at driver/skills/clearance-search/synthesis-rules.md:428)",
594
595
  surface: "stage-message",
595
596
  evidence: "END that finding's actual-use line with a literal \"- **Use-check source:** <result URL | \"perplexity_research — no result\">\" line",
596
- reparsedBy: "driver/verify.mjs validators.narrative (spec-11 hard reject); the repair hint re-dictates the literal at driver/gateway.mjs:2153",
597
+ reparsedBy: "driver/verify.mjs validators.narrative (spec-11 hard reject); the repair hint re-dictates the literal in correctionHint() in gateway.mjs",
597
598
  removedByMove: "NOTHING ON THE #850 PLAN REMOVES THIS",
598
599
  },
599
600
  {
@@ -602,7 +603,7 @@ export const E3_BACKLOG = [
602
603
  where: "driver/stages.mjs:3128 (restated at driver/skills/clearance-search/synthesis-rules.md:475)",
603
604
  surface: "stage-message",
604
605
  evidence: "END that finding's reasoning with a literal \"- **Own-rights source:** <record URI(s) | \"no applicant-owned registrations in the searched register material\">\" line",
605
- reparsedBy: "driver/own-rights.mjs:19-22 — \"This module only requires the 'Own-rights source:' line to exist\"; repair hint at driver/gateway.mjs:2370 (the `own_rights_missing` branch; re-verified 2026-08-29 — the old :1736 predated this branch and pointed into the A4 repeat-signature block)",
606
+ reparsedBy: "driver/own-rights.mjs, in its header — \"This module only requires the 'Own-rights source:' line to exist\"; repair hint in correctionHint() in gateway.mjs (the `own_rights_missing` branch; re-verified 2026-08-29 — the old :1736 predated this branch and pointed into the A4 repeat-signature block)",
606
607
  removedByMove: "NOTHING ON THE #850 PLAN REMOVES THIS",
607
608
  },
608
609
  {
@@ -629,7 +630,7 @@ export const E3_BACKLOG = [
629
630
  where: "driver/stages.mjs:3502",
630
631
  surface: "stage-message",
631
632
  evidence: "ALSO write the RETRIEVAL RECORD to <path> — a JSON OBJECT with EXACTLY these keys: {\"schema_version\":2,\"queries\":[{\"query\":\"<the search you dispatched, verbatim>\",\"jurisdiction\":\"…\",\"results\":<how many hits it returned, or null when the query never reached a source — never 0 for a query you could not send>}, …],\"citations\":[{\"proceeding\":\"…\",\"forum\":\"…\",\"jurisdiction\":\"…\",\"decided\":\"…\"",
632
- reparsedBy: "driver/verify.mjs validators.caseLaw — the ledger arm, armed by the stage-contract marker `citations` (stages.mjs:1822)",
633
+ reparsedBy: "driver/verify.mjs validators.caseLaw — the ledger arm, armed by the stage-contract marker `citations` (the case-law stage's `contract: { citations: 1 }` in stages.mjs)",
633
634
  removedByMove: "M5 moves `queries[]` to the call log (and is itself blocked: tool-calls.jsonl records no arguments); the envelope, schema_version, `read` enum and `citations[]` skeleton survive M5 entirely",
634
635
  },
635
636
  {
@@ -638,7 +639,7 @@ export const E3_BACKLOG = [
638
639
  where: "driver/stages.mjs:3510",
639
640
  surface: "stage-message",
640
641
  evidence: "EVERY \"Grounded profile\" section MUST start its body with the line \"- ord: <N>\" naming which finding it grounds (use the ordinal from this list; a profile that grounds no listed finding omits the line)",
641
- reparsedBy: "driver/publish/parse.mjs:339, parseCaseLawProfiles() in parse.mjs (\"the optional '- ord: <N>' first body line … gives an EXACT join\"); driver/findings-model.mjs:274 /^-\\s*ord:\\s*(\\d+)\\s*$/m; driver/publish/index.mjs:778 runOrigins",
642
+ reparsedBy: "driver/publish/parse.mjs:339, parseCaseLawProfiles() in parse.mjs (\"the optional '- ord: <N>' first body line … gives an EXACT join\"); driver/findings-model.mjs:274 /^-\\s*ord:\\s*(\\d+)\\s*$/m; driver/publish/index.mjs publishReport (joinCaseLawProfiles)",
642
643
  removedByMove: "NOTHING ON THE #850 PLAN REMOVES THIS",
643
644
  },
644
645
  {
@@ -697,7 +698,7 @@ export const E3_BACKLOG = [
697
698
  // `[on: -]` case went from a value to an ABSENCE — you omit the field — which is the one part a
698
699
  // reader could get wrong from the old wording, since there is no value meaning "no finding".
699
700
  evidence: "**AND EVERY FLAG CARRIES WHICH FINDING IT IS ABOUT** — the `on` field, an array of ordinals. Same rule as `kind`: you send the values, the driver renders the token.",
700
- reparsedBy: "driver/verify.mjs:864 CORRECTION_ON_RE = /\\[on:\\s*([0-9,\\s-]*?)\\s*\\]/i. SKILL-FILE ONLY — the stage message at stages.mjs:1844-1877 never mentions `[on:]`. This is #850's \"the element shape is in the skill file, not the stage message\" in its purest form: an E3 lint reading stages.mjs alone sees the [kind:] token and misses its twin",
701
+ reparsedBy: "driver/verify.mjs:864 CORRECTION_ON_RE = /\\[on:\\s*([0-9,\\s-]*?)\\s*\\]/i. SKILL-FILE ONLY — the stage message (narrative-refutation's, in stages.mjs) never mentions `[on:]`. This is #850's \"the element shape is in the skill file, not the stage message\" in its purest form: an E3 lint reading stages.mjs alone sees the [kind:] token and misses its twin",
701
702
  removedByMove: "NOTHING ON THE #850 PLAN REMOVES THIS",
702
703
  },
703
704
  {