clearotron 0.2.4 → 0.3.0-beta.1

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 (235) hide show
  1. package/.env.example +13 -2
  2. package/CONTRIBUTING.md +1 -1
  3. package/INSTALL.md +62 -38
  4. package/bin/brandowner.mjs +18 -169
  5. package/bin/clearotron.mjs +3 -1
  6. package/bin/connect.mjs +28 -19
  7. package/bin/disconnect.mjs +3 -3
  8. package/bin/example.mjs +7 -7
  9. package/bin/framework-preflight.mjs +49 -0
  10. package/bin/grant.mjs +151 -93
  11. package/bin/onboard.mjs +220 -138
  12. package/bin/start.mjs +146 -137
  13. package/bin/stop.mjs +2 -2
  14. package/bin/update.mjs +1 -1
  15. package/build-info.json +2 -2
  16. package/docs/CLIENT-MCP.md +2 -2
  17. package/docs/E2E.md +12 -2
  18. package/docs/ONBOARDING.md +1 -1
  19. package/docs/PORTAL.md +14 -13
  20. package/docs/SECURITY.md +23 -24
  21. package/docs/architecture/04-configuration-reference.md +12 -5
  22. package/docs/architecture/05-config-governance.md +7 -7
  23. package/docs/architecture/07-quality-and-audit.md +1 -1
  24. package/docs/architecture/08-development-guide.md +5 -0
  25. package/docs/configuration.md +118 -0
  26. package/docs/decisions/0004-documentation-structure.md +2 -2
  27. package/docs/decisions/0006-what-the-public-repository-carries.md +2 -2
  28. package/driver/CHANGELOG.md +70 -0
  29. package/driver/ask-ledger.mjs +2 -2
  30. package/driver/cancel.mjs +27 -0
  31. package/driver/case-law-sources.mjs +3 -3
  32. package/driver/company-bundle.mjs +261 -0
  33. package/driver/compare.mjs +1 -1
  34. package/driver/compose-read.mjs +2 -2
  35. package/driver/config-inventory.mjs +2 -2
  36. package/driver/contract-audit.mjs +1 -1
  37. package/driver/contract-e3-backlog.mjs +1 -1
  38. package/driver/contract-vocabulary.mjs +1 -1
  39. package/driver/declination-call.mjs +1 -1
  40. package/driver/deliver-trigger.sh +3 -3
  41. package/driver/dev-portal.mjs +3 -1
  42. package/driver/digest-queue.mjs +1 -1
  43. package/driver/disposition-tool.mjs +1 -1
  44. package/driver/doc-constants.mjs +1 -1
  45. package/driver/drain-posture.mjs +2 -2
  46. package/driver/drainer-identity.mjs +1 -1
  47. package/driver/driver.config.mjs +44 -9
  48. package/driver/effective-scope.mjs +30 -1
  49. package/driver/effort-model.mjs +6 -6
  50. package/driver/engine/CONTRACT.md +2 -2
  51. package/driver/engine/anthropic-agent.mjs +11 -11
  52. package/driver/engine/jx-turn.mjs +1 -1
  53. package/driver/engine/mcp/gather-config.mjs +29 -4
  54. package/driver/engine/mcp/recording-server.mjs +73 -1
  55. package/driver/engine/openai-agent.mjs +1 -1
  56. package/driver/engine/probe.mjs +28 -4
  57. package/driver/enqueue-schema.mjs +23 -3
  58. package/driver/findings-model.mjs +2 -2
  59. package/driver/flag-snapshot.mjs +2 -2
  60. package/driver/floor-duty.mjs +2 -2
  61. package/driver/frame-diff-model.mjs +1 -1
  62. package/driver/framework-preflight.mjs +143 -0
  63. package/driver/gateway.mjs +9 -1
  64. package/driver/hit-list.mjs +1 -1
  65. package/driver/jx-lanes.mjs +1 -1
  66. package/driver/jx.mjs +1 -1
  67. package/driver/knockout-assess-record.mjs +1 -1
  68. package/driver/knockout-review-record.mjs +435 -0
  69. package/driver/order-probe.mjs +1 -1
  70. package/driver/outbox-backoff.mjs +2 -2
  71. package/driver/owner-use-check.mjs +2 -2
  72. package/driver/package.json +1 -1
  73. package/driver/pipeline-knockout.mjs +105 -8
  74. package/driver/pipeline.mjs +81 -30
  75. package/driver/plain-register.mjs +77 -3
  76. package/driver/portal-access.mjs +141 -74
  77. package/driver/portal-config-view.mjs +59 -70
  78. package/driver/portal-report.mjs +4 -4
  79. package/driver/portal-service.mjs +348 -141
  80. package/driver/portal-upstream.mjs +105 -17
  81. package/driver/predelivery-lint.mjs +43 -18
  82. package/driver/product-rows.mjs +1 -1
  83. package/driver/products.mjs +1 -1
  84. package/driver/profile-page.html +30 -5
  85. package/driver/profile-service.mjs +197 -26
  86. package/driver/profiles.mjs +48 -1
  87. package/driver/publish/index.mjs +31 -13
  88. package/driver/publish/knockout.mjs +9 -5
  89. package/driver/publish/office-record-links.mjs +189 -0
  90. package/driver/publish/parse.mjs +3 -3
  91. package/driver/publish/publish-inputs.mjs +26 -0
  92. package/driver/publish/render-knockout.mjs +42 -42
  93. package/driver/publish/render.mjs +29 -4
  94. package/driver/publish/report-data.mjs +2 -2
  95. package/driver/publish/seed-pool.mjs +1 -1
  96. package/driver/publish/templates/report.css +8 -8
  97. package/driver/publish/xlsx.mjs +49 -7
  98. package/driver/queue-watch-verdict.mjs +2 -2
  99. package/driver/recipe-service.mjs +1 -1
  100. package/driver/record-carry.mjs +1 -1
  101. package/driver/reference-score.mjs +1 -1
  102. package/driver/reference-strip-signatures.mjs +1 -1
  103. package/driver/register-availability.mjs +4 -3
  104. package/driver/register-count.mjs +3 -3
  105. package/driver/register-records.mjs +1 -1
  106. package/driver/repair-composers.mjs +1 -1
  107. package/driver/repairs.mjs +3 -3
  108. package/driver/replay-archive.mjs +1 -1
  109. package/driver/report-card-record.mjs +1 -1
  110. package/driver/result-noun-fields.mjs +5 -0
  111. package/driver/roster-verdict.mjs +48 -5
  112. package/driver/run-activity.mjs +1 -1
  113. package/driver/run-requirements.mjs +18 -5
  114. package/driver/runner.mjs +24 -15
  115. package/driver/search-policy.mjs +8 -8
  116. package/driver/senior-rights.mjs +1 -1
  117. package/driver/skills/prelim-search/delivery-contract.md +1 -1
  118. package/driver/skills/prelim-search/risk-framework-triage.md +10 -7
  119. package/driver/stages-knockout.mjs +72 -6
  120. package/driver/stages.mjs +10 -10
  121. package/driver/suite-census.json +238 -70
  122. package/driver/synthesis-record.mjs +2 -2
  123. package/driver/systemd/clearotron-client-mcp.service +3 -3
  124. package/driver/systemd/clearotron-deploy.service +2 -2
  125. package/driver/systemd/clearotron-mcp-face.service +1 -1
  126. package/driver/systemd/clearotron-portal.service +3 -3
  127. package/driver/systemd/clearotron-worker.service +5 -5
  128. package/driver/systemd/install-census.mjs +1 -1
  129. package/driver/systemd/render-units.mjs +9 -9
  130. package/driver/terminal-clamp.mjs +1 -1
  131. package/driver/trigger-cap.mjs +18 -2
  132. package/driver/unit-inventory.mjs +8 -8
  133. package/driver/usage-ledger.mjs +5 -3
  134. package/driver/verify-knockout.mjs +7 -7
  135. package/driver/verify.mjs +5 -5
  136. package/driver/whatif-memo-run.mjs +1 -1
  137. package/driver/whatif-queue.mjs +3 -3
  138. package/driver/whatif-worker.mjs +2 -2
  139. package/examples/README.md +1 -1
  140. package/examples/grants.example.json +25 -24
  141. package/mcp-server/CHANGELOG.md +10 -0
  142. package/mcp-server/http-server.mjs +3 -3
  143. package/mcp-server/key-socket.mjs +1 -1
  144. package/mcp-server/lib/audit-view.mjs +3 -3
  145. package/mcp-server/lib/brief.mjs +3 -3
  146. package/mcp-server/lib/driver.mjs +1 -1
  147. package/mcp-server/lib/events.mjs +1 -1
  148. package/mcp-server/lib/http-handler.mjs +2 -2
  149. package/mcp-server/lib/instructions.mjs +2 -2
  150. package/mcp-server/lib/knockout.mjs +1 -1
  151. package/mcp-server/lib/ops.mjs +6 -3
  152. package/mcp-server/lib/options.mjs +15 -4
  153. package/mcp-server/lib/plan.mjs +7 -6
  154. package/mcp-server/lib/runs.mjs +10 -0
  155. package/mcp-server/lib/whatif.mjs +5 -5
  156. package/mcp-server/package.json +1 -1
  157. package/mcp-server/packs/README.md +1 -1
  158. package/mcp-server/remote/client-mcp-apikey.service +1 -1
  159. package/mcp-server/remote/client-mcp.service +2 -2
  160. package/mcp-server/remote/trademark-artifacts-http.service +1 -1
  161. package/mcp-server/server.mjs +38 -24
  162. package/package.json +2 -2
  163. package/portal-ui/dist/assets/{index-KFAHMgdT.js → index-CWTHP0sH.js} +3471 -1901
  164. package/portal-ui/dist/assets/{index-1ziUJX1E.css → index-KpytsmNH.css} +79 -26
  165. package/portal-ui/dist/index.html +2 -2
  166. package/portal-ui/package.json +1 -1
  167. package/providers/_shared/lane-probe.mjs +9 -3
  168. package/providers/jx-subclass/lookup.mjs +1 -1
  169. package/providers/oauth-mcp-bridge/CHANGELOG.md +4 -0
  170. package/providers/oauth-mcp-bridge/package.json +1 -1
  171. package/providers/oauth-mcp-bridge/systemd/courtlistener-mcp.service +1 -1
  172. package/scripts/citation-drift-report.mjs +1 -1
  173. package/scripts/citation-line-check.mjs +2 -2
  174. package/scripts/drive-env-check.mjs +1 -1
  175. package/scripts/e2e.mjs +5 -5
  176. package/scripts/env-audit.mjs +13 -1
  177. package/scripts/headless-page.mjs +5 -5
  178. package/scripts/live-surface-check.mjs +26 -2
  179. package/scripts/mint-names-in-force.mjs +19 -5
  180. package/scripts/mint-reference-strip-backlog.mjs +1 -1
  181. package/scripts/mint-suite-census.mjs +37 -10
  182. package/scripts/pack-publishable.mjs +1 -1
  183. package/scripts/preinstall-node-check.mjs +1 -1
  184. package/scripts/release-await-cut.mjs +3 -3
  185. package/scripts/release-cut-decision.mjs +1 -1
  186. package/scripts/release-dist-tag.mjs +1 -1
  187. package/scripts/release-install-check.mjs +1 -1
  188. package/scripts/release-notes-lint.mjs +1 -1
  189. package/scripts/release-publish-guard.mjs +1 -1
  190. package/scripts/release-version-pr-checks.mjs +2 -2
  191. package/scripts/release-version.mjs +61 -5
  192. package/scripts/render-brand-banner.mjs +1 -1
  193. package/scripts/render-check.mjs +2 -2
  194. package/scripts/repo-writes.mjs +1 -1
  195. package/scripts/report-frame-check.mjs +1 -1
  196. package/scripts/report-screenshot.mjs +2 -2
  197. package/scripts/retire-bare-refs.mjs +1 -1
  198. package/scripts/revisit-render-check.mjs +1 -1
  199. package/scripts/score.mjs +1 -1
  200. package/scripts/strip-titles-and-attributions.mjs +389 -0
  201. package/scripts/strip-tracker-citations.mjs +122 -5
  202. package/scripts/test-run.mjs +4 -4
  203. package/scripts/third-party-notices.mjs +1 -1
  204. package/scripts/verify-publishable.mjs +1 -1
  205. package/shared/access-audience.mjs +2 -2
  206. package/shared/anon-overlay.mjs +1 -1
  207. package/shared/brand.mjs +15 -1
  208. package/shared/bundle-freshness.mjs +1 -1
  209. package/shared/bundle-rebuild.mjs +1 -1
  210. package/shared/checkout-move.mjs +2 -2
  211. package/shared/client-door.mjs +8 -8
  212. package/shared/connect-clients.mjs +7 -7
  213. package/shared/connector-signin-probe.mjs +1 -1
  214. package/shared/env-aliases.mjs +1 -1
  215. package/shared/env-local.mjs +5 -5
  216. package/shared/grants-edit.mjs +76 -0
  217. package/shared/install-auth.mjs +1 -1
  218. package/shared/listen.mjs +3 -3
  219. package/shared/mcp-challenge.mjs +1 -1
  220. package/shared/names-in-force.mjs +2 -1
  221. package/shared/onboarding-store.mjs +19 -2
  222. package/shared/reference-guard-classes.mjs +44 -2
  223. package/shared/register-selection.mjs +1 -1
  224. package/shared/scope.mjs +223 -56
  225. package/shared/secret-file.mjs +1 -1
  226. package/shared/server-units.mjs +1 -1
  227. package/shared/staff-domain.mjs +45 -78
  228. package/shared/summary-blocks.mjs +2 -2
  229. package/shared/systemd-failure.mjs +3 -3
  230. package/shared/tracked-files.mjs +1 -1
  231. package/shared/trigger-lane.mjs +1 -1
  232. package/shared/tty-style.mjs +1 -1
  233. package/shared/usage-block.mjs +1 -1
  234. package/shared/vacuous-pass.mjs +1 -1
  235. package/shared/verb-shim.mjs +1 -1
@@ -14,7 +14,7 @@
14
14
  // values; what changes is that it arrives through the call instead of being authored as a file, which
15
15
  // is what lets the joins below run at write time.
16
16
  //
17
- // ── WHAT THIS DOES *NOT* CONVERT, AND IT IS AN OWNER RULING, NOT AN OVERSIGHT ──────────────────────
17
+ // ── WHAT THIS DOES *NOT* CONVERT, AND IT IS A RULING, NOT AN OVERSIGHT ──────────────────────
18
18
  //
19
19
  // The per-finding write-ups are NOT rendered from `findings.json`. `stages.mjs`' contract element for
20
20
  // them classes the second authoring `mechanical:code-rendered` and its `why` records ruling D3 on
@@ -465,7 +465,7 @@ export function driverReadsFor(runDir) {
465
465
  * THE STATE THAT ENUMERATION MISSED is the corrective pass. `prepareDeclinationSpec` runs AGAIN before
466
466
  * it, and the surface can have grown in between, so the corrective pass is ordered against a LARGER owed
467
467
  * set than the main pass answered. If that call is refused, `rollbackCorrectivePass` restores the
468
- * pre-corrective `findings.json` and — by T3b's owner ruling, deliberately — THE RUN DELIVERS. The
468
+ * pre-corrective `findings.json` and — by T3b's ruling, deliberately — THE RUN DELIVERS. The
469
469
  * document that ships satisfied the OLD owed set; the records added since are unaccounted, and no
470
470
  * acceptance boundary ever saw a call about them, because the only call that was ordered against them
471
471
  * was the one that got refused.
@@ -1,6 +1,6 @@
1
1
  # GENERIC UNIT — safe to sync verbatim on deploy: no deployment-specific value lives here.
2
2
  #
3
- # ── INSTALLED WITH THE PRODUCT (owner ruling 2026-09-03, tracker issue 2148, settled point 2) ───────
3
+ # ── INSTALLED WITH THE PRODUCT (ruling 2026-09-03, settled point 2) ───────────────────────────
4
4
  #
5
5
  # This is the CLIENT door: the only surface that accepts a key acting for a brand owner's account. It is
6
6
  # a SEPARATE PROCESS from the engine door (clearotron-mcp-face.service), which refuses an account key
@@ -45,7 +45,7 @@ Type=simple
45
45
  EnvironmentFile=%h/.env
46
46
  Environment=PATH=%h/.local/bin:%h/.npm-global/bin:/usr/local/bin:/usr/bin:/bin
47
47
  Environment=CLEAROTRON_NO_ENV_FILE=1
48
- # ── NO PORT, NO HOST, NO ALLOW-LIST HERE (tracker issue 191) ─────────────────────────────────────────
48
+ # ── NO PORT, NO HOST, NO ALLOW-LIST HERE ─────────────────────────────────────────────────────────────
49
49
  #
50
50
  # This unit used to carry three Environment= lines pinning the host, the port and the allow-list, and it
51
51
  # was the ONLY one of the four that pinned anything a box can legitimately differ on. `Environment=` is
@@ -55,7 +55,7 @@ Environment=CLEAROTRON_NO_ENV_FILE=1
55
55
  # It took a running door down on a shared box: two installs, two ports, one baked value, and the
56
56
  # reinstall pointed the second install's door at the first's port. Worse, the allow-list travelled with
57
57
  # it — pinned to 18811 whatever port the door actually bound — which is the `Invalid Host header`
58
- # failure of tracker issue 130 one layer down, produced by our own unit rather than by a tunnel.
58
+ # failure of the missing session bus one layer down, produced by our own unit rather than by a tunnel.
59
59
  #
60
60
  # The values live in %h/.env, which BOTH install paths write through one authority (`enablePlan`), and
61
61
  # the code's own defaults are the same 127.0.0.1 and 18811 these lines carried — so a box that has
@@ -15,9 +15,9 @@ Environment=CLEAROTRON_NO_ENV_FILE=1
15
15
 
16
16
  # `clearotron update` is the whole mechanism, and it already owns both refusals:
17
17
  #
18
- # 1. it refuses over a configuration store kept INSIDE the checkout (tracker issue 1723) — an upgrade there is a
18
+ # 1. it refuses over a configuration store kept INSIDE the checkout — an upgrade there is a
19
19
  # merge conflict at best and a silent overwrite of the operator's own doctrine at worst;
20
- # 2. it refuses while a run is queued, claimed or in flight (tracker issue 1883) — `npm ci` rebuilds node_modules
20
+ # 2. it refuses while a run is queued, claimed or in flight — `npm ci` rebuilds node_modules
21
21
  # under whatever is running, and a clearance assembled from halves of two builds does not fail, it
22
22
  # answers wrongly and says nothing.
23
23
  #
@@ -1,6 +1,6 @@
1
1
  # GENERIC UNIT — safe to sync verbatim on deploy: no deployment-specific value lives here. See
2
2
  # clearotron-portal.service for why this unit exists and why it takes an EnvironmentFile rather than
3
- # explicit Environment= lines (tracker issue 1925, owner ruling 2026-08-26).
3
+ # explicit Environment= lines (ruling 2026-08-26).
4
4
  #
5
5
  # This is the engine door the portal's Start button calls over real MCP. It is the OTHER of the two
6
6
  # children bin/start.mjs supervises, and the other one that had no unit.
@@ -2,14 +2,14 @@
2
2
  # listener and every hostname come from EnvironmentFile=%h/.env, which is where a SERVER's configuration
3
3
  # lives (INSTALL.md §3; render-units.mjs defaults --env to the same file).
4
4
  #
5
- # ── WHY THIS UNIT EXISTS (tracker issue 1925) ─────────────────────────────────────────────────────────
5
+ # ── WHY THIS UNIT EXISTS ──────────────────────────────────────────────────────────────────────────────
6
6
  #
7
7
  # It did not, and that was the gap under the whole issue. `bin/start.mjs` supervises exactly two children
8
8
  # — this process and the MCP face — and NOTHING ran either under systemd. So the two processes the door
9
9
  # configures had no service to attach to, a hosted deployment kept them alive with a hand launcher, and
10
10
  # `clearotron start`'s refusal had nothing to point at.
11
11
  #
12
- # ── ONE CONFIGURATION PER SERVER BOX (owner ruling, 2026-08-26) ───────────────────────────────────────
12
+ # ── ONE CONFIGURATION PER SERVER BOX (ruling, 2026-08-26) ───────────────────────────────────────
13
13
  #
14
14
  # `EnvironmentFile=%h/.env`, like prelim-driver.service, and the CF Access audience lives there. The
15
15
  # alternative — explicit `Environment=` lines carrying the audience, as profile-service.service used to —
@@ -21,7 +21,7 @@ Description=Clearotron portal — the UI, the API, the sign-in and the config su
21
21
  Documentation=https://github.com/CordilleraSarl/Clearotron/blob/main/docs/architecture/02-architecture.md
22
22
  After=network-online.target
23
23
  Wants=network-online.target
24
- # ── THE ENGINE DOOR FIRST, WHERE THERE IS ONE (tracker issue 222) ─────────────────────────────────────
24
+ # ── THE ENGINE DOOR FIRST, WHERE THERE IS ONE ─────────────────────────────────────────────────────────
25
25
  #
26
26
  # The units carried no ordering between them, so a simultaneous restart of the set started this process
27
27
  # before the door it calls. Its boot probe then logged an outage — in the present tense, with "a
@@ -5,7 +5,7 @@
5
5
  # queue and credential comes from EnvironmentFile=%h/.env, which is where a SERVER's configuration lives
6
6
  # (INSTALL.md §3; render-units.mjs defaults --env to the same file).
7
7
  #
8
- # ── WHY THIS UNIT EXISTS (tracker issue 1863) ────────────────────────────────────────────────────────
8
+ # ── WHY THIS UNIT EXISTS ─────────────────────────────────────────────────────────────────────────────
9
9
  #
10
10
  # It did not, and its absence was the one that cost a report rather than a restart. `bin/start.mjs`
11
11
  # supervises THREE children — the portal, the engine door, and this worker (`:904`). Units shipped for
@@ -18,7 +18,7 @@
18
18
  # box reports healthy while no client ever receives a report. That is the failure the retired `.path`
19
19
  # unit's own header warned about, arriving through the other door.
20
20
  #
21
- # ── WHY ExecStart IS THE ENTRYPOINT AND NOT `clearotron start` (owner ruling, 2026-08-31) ────────────
21
+ # ── WHY ExecStart IS THE ENTRYPOINT AND NOT `clearotron start` (ruling, 2026-08-31) ────────────
22
22
  #
23
23
  # "The simple shape wins, implemented the standard way." Per standard service practice the unit invokes
24
24
  # the server entrypoint directly under its own EnvironmentFile; it does not shell through the
@@ -28,7 +28,7 @@
28
28
  #
29
29
  # ── ONE DRAINER, AND THE LOCK IS WHAT ENFORCES IT ────────────────────────────────────────────────────
30
30
  #
31
- # A second claimant on one queue is this lane's recurring defect (tracker issue 1883). This unit takes no
31
+ # A second claimant on one queue is this lane's recurring defect. This unit takes no
32
32
  # special precaution against it because none would work: the guarantee is CLEAROTRON_RUN_LOCK_DIR, which
33
33
  # this worker and a hand-started `node driver/runner.mjs` already share, so the cap holds across both.
34
34
  # What this unit must not do is run beside a FOREGROUND `clearotron start`, which supervises its own
@@ -48,7 +48,7 @@ EnvironmentFile=%h/.env
48
48
  Environment=PATH=%h/.local/bin:%h/.npm-global/bin:/usr/local/bin:/usr/bin:/bin
49
49
  Environment=CLEAROTRON_NO_ENV_FILE=1
50
50
  ExecStart=/usr/bin/node ${CLEAROTRON_CHECKOUT_DIR}/driver/runner.mjs --watch
51
- # ── REFRESH THE CAPABILITY PAGE'S CAPTURE AT EVERY START (tracker issue 170) ─────────────────────────
51
+ # ── REFRESH THE CAPABILITY PAGE'S CAPTURE AT EVERY START ─────────────────────────────────────────────
52
52
  # The capability page renders `pool/_state/prelim-flag-snapshot.json`, and until this line its only
53
53
  # writers were a RUN and the `clearotron start` launcher. A hosted box runs neither: these units are the
54
54
  # entrypoint, and a deployment being CONFIGURED runs nothing by definition. So the page reported the old
@@ -57,7 +57,7 @@ ExecStart=/usr/bin/node ${CLEAROTRON_CHECKOUT_DIR}/driver/runner.mjs --watch
57
57
  # every service restarted, and the page still said Signa.
58
58
  #
59
59
  # This unit can write it because it carries `EnvironmentFile=%h/.env` — one configuration per server box,
60
- # owner ruling 2026-08-26, which removed the second source of truth rather than defending against it.
60
+ # ruling 2026-08-26, which removed the second source of truth rather than defending against it.
61
61
  # `-` prefix: a snapshot is a diagnostic surface, and failing to write one must never fail the service.
62
62
  ExecStartPost=-/usr/bin/node ${CLEAROTRON_CHECKOUT_DIR}/driver/flag-snapshot.mjs
63
63
  #
@@ -4,7 +4,7 @@
4
4
  //
5
5
  // ── why this exists ─────────────────────────────────────────────────────────────
6
6
  //
7
- // Tracker issue 122 named a family rather than a bug: four values this product needs are each written
7
+ // This names a family rather than a bug: four values this product needs are each written
8
8
  // by exactly one command, and for three of them that command is not the documented install. A box built
9
9
  // strictly from the document therefore came up with correct units, correct code, and a value nothing
10
10
  // ever set — and each one failed as something else. A 502 on submit. An empty screen. A door that would
@@ -4,7 +4,7 @@
4
4
  //
5
5
  // render-units.mjs — RESOLVE THE UNITS CONFIGURATION CANNOT REACH, AND WRITE THE RESOLVED COPIES.
6
6
  //
7
- // Owner ruling 2026-08-25 (option B): the installer writes resolved unit copies into
7
+ // Ruling 2026-08-25 (option B): the installer writes resolved unit copies into
8
8
  // ~/.config/systemd/user/, and the tracked units stay generic.
9
9
  //
10
10
  // WHY THIS EXISTS AT ALL. Most shipped units take what differs per box from `EnvironmentFile=%h/.env`
@@ -231,7 +231,7 @@ if (isEntrypoint(import.meta.url)) {
231
231
  }
232
232
  if (APPLY) {
233
233
  if (laneNote) console.log(laneNote);
234
- // ── AND WHAT THIS INSTALL STILL HAS NOT GOT (tracker issue 133) ─────────────────────────────────
234
+ // ── AND WHAT THIS INSTALL STILL HAS NOT GOT ─────────────────────────────────────────────────────
235
235
  //
236
236
  // The nine values above are the ones this command RESOLVES. They are not the values the units
237
237
  // refuse to start over, and the gap between those two lists is how a documented install produced a
@@ -301,7 +301,7 @@ export async function writeInstallEnv(envFile) {
301
301
  // change, and the reader meets it in the output of the command that does it rather than discovering
302
302
  // it in a file.
303
303
  const fresh = signingSecretIfAbsent(fileEnv.TRADEMARK_MCP_TOKEN_SECRET ?? process.env.TRADEMARK_MCP_TOKEN_SECRET, { randomBytes });
304
- // ── HELD, NOT PRINTED (tracker issue 194) ─────────────────────────────────────────────────────────
304
+ // ── HELD, NOT PRINTED ─────────────────────────────────────────────────────────────────────────────
305
305
  //
306
306
  // These lines used to print here, and the refusal below exits before anything reaches disk — so a
307
307
  // refused run announced "Written to the env file at mode 600" about two files it never wrote. That
@@ -321,7 +321,7 @@ export async function writeInstallEnv(envFile) {
321
321
  " file at mode 600, and never printed.");
322
322
  }
323
323
 
324
- // ── AND THE PORTAL'S SECRET, WHICH THE SAME ARGUMENT ALWAYS COVERED (tracker issue 122) ───────────
324
+ // ── AND THE PORTAL'S SECRET, WHICH THE SAME ARGUMENT ALWAYS COVERED ───────────────────────────────
325
325
  //
326
326
  // The block above was written for the signing secret alone, and `PORTAL_SECRET` is the identical case
327
327
  // one door along: 32 random bytes, minted by `bin/start.mjs` and by nothing else, with no value a
@@ -329,7 +329,7 @@ export async function writeInstallEnv(envFile) {
329
329
  // strictly from the document got a portal unit that exited at boot, and the only route to a working
330
330
  // one was running the local-install command the instructions say a server does not need.
331
331
  //
332
- // That is the family tracker issue 122 is about: a value whose sole writer is a command outside the
332
+ // That is the family this is about: a value whose sole writer is a command outside the
333
333
  // documented install. Naming the variable in a warning does not help, because there is no action
334
334
  // behind the name — the reader cannot invent 32 random bytes that the portal will accept.
335
335
  //
@@ -392,10 +392,10 @@ export async function writeInstallEnv(envFile) {
392
392
  }
393
393
  for (const [k, v] of Object.entries(doorPlan.settings)) want[k] = v;
394
394
 
395
- // ── THE SECRETS ARE ON DISK'S SIDE OF THE REFUSAL NOW (tracker issue 194) ─────────────────────────
395
+ // ── THE SECRETS ARE ON DISK'S SIDE OF THE REFUSAL NOW ─────────────────────────────────────────────
396
396
  for (const line of announcements) console.log(line);
397
397
 
398
- // ── A DERIVED VALUE MAY NOT GO STALE (tracker issue 197) ──────────────────────────────────────────
398
+ // ── A DERIVED VALUE MAY NOT GO STALE ──────────────────────────────────────────────────────────────
399
399
  //
400
400
  // Driven on a greenfield install: `--apply`, then an operator edits `CLIENT_MCP_HTTP_PORT` by hand
401
401
  // and runs `--apply` again. The door binds the new port; the allow-list still names the old one;
@@ -413,13 +413,13 @@ export async function writeInstallEnv(envFile) {
413
413
  //
414
414
  // `CLEAROTRON_CHECKOUT_DIR` is deliberately not here either, for the same reason one issue over.
415
415
  //
416
- // ── BOTH DOORS, NOT ONE (tracker issue 192) ───────────────────────────────────────────────────────
416
+ // ── BOTH DOORS, NOT ONE ───────────────────────────────────────────────────────────────────────────
417
417
  //
418
418
  // This block used to name the client door alone. The engine door's allow-list is the same derivable
419
419
  // `host:port` shape, refuses to start without a value in the same words, and was composed by NOTHING
420
420
  // on a hosted install — `bin/start.mjs` injects one into its own children's environment, which no
421
421
  // systemd unit inherits. So the documented install asked an operator for a value while writing the
422
- // identical one next to it. That asymmetry was found by the census tracker issue 122 asked for, on
422
+ // identical one next to it. That asymmetry was found by the census that called for it, on
423
423
  // its first run, which is what that check is for.
424
424
  //
425
425
  // The pair is DATA now (`DOOR_ALLOW_LISTS`), and the composition is one function taking the public
@@ -2,7 +2,7 @@
2
2
  // Copyright 2026 Cordillera Sàrl. Additional terms under section 7 of the AGPL-3.0 apply — see ADDITIONAL-TERMS.md
3
3
  // terminal-clamp.mjs — WHAT A TERMINAL GUARD DOES WHEN IT FINDS THE REPORT INCOMPLETE.
4
4
  //
5
- // Owner ruling, 2026-08-27, verbatim intent: **reports always ship**. When a terminal guard finds the
5
+ // Ruling, 2026-08-27, verbatim intent: **reports always ship**. When a terminal guard finds the
6
6
  // report incomplete at delivery, the engine sends it with the gap patched conservatively and the defect
7
7
  // named in the run record. It never withholds.
8
8
  //
@@ -55,12 +55,28 @@ export function triggerCapGap({ accounts = null, roster = [] } = {}) {
55
55
  /**
56
56
  * The one sentence every surface says about a gap, so three of them cannot say three different things.
57
57
  *
58
+ * IT WAS TWO UNTIL THE THIRD WAS WIRED. This function's own comment claimed three call sites and there
59
+ * were two — the create command and the portal's boot log. `doctor`, the command whose whole job is to
60
+ * report what a machine is configured for, said nothing at all about a cap that decides which companies
61
+ * can be started. The claim was aspirational and is now accurate.
62
+ *
58
63
  * The wording is the portal boot check's, kept deliberately: it was measured to be the thing that would
59
64
  * have saved the owner an afternoon, and rewording it per surface is how the good half of a defect gets
60
65
  * lost while the fix ships.
66
+ *
67
+ * IT SAID THE RUNS WOULD BE REFUSED, AND THAT STOPPED BEING TRUE. The portal now re-takes its engine
68
+ * credential against the roster as it stands at the start of every call, so a stale cap no longer
69
+ * refuses anything on the ordinary path. The gap is still worth saying — the portal falls back to this
70
+ * token when it cannot re-mint, and then the refusal is real — but the sentence has to describe the
71
+ * fallback rather than the normal case, or it sends an operator to re-mint a credential for a failure
72
+ * that will not happen. Changed HERE rather than at the three call sites, which is the whole reason
73
+ * this function exists.
61
74
  */
62
75
  export function triggerCapWarning({ uncovered, union }) {
63
- return `the roster contains account(s) the trigger token cannot start: ${uncovered.join(", ")}. `
64
- + `Runs for them will be refused at the engine door. Re-mint with --accounts ${union.join(",")} `
76
+ return `the roster contains account(s) this token cannot start: ${uncovered.join(", ")}. `
77
+ + `The portal re-takes its engine credential at the start of every call, so runs for them are not `
78
+ + `normally refused; they are refused when it CANNOT re-mint — an unreadable signing secret, or a `
79
+ + `store it cannot list — because it then falls back to this token rather than widening the cap. `
80
+ + `Re-mint with --accounts ${union.join(",")} `
65
81
  + `(union of the current cap and the roster — check it before using it).`;
66
82
  }
@@ -75,7 +75,7 @@
75
75
  /** Where a unit is expected to be installed. "none" is a claim, not an absence — see ORPHANED below. */
76
76
  export const BOXES = Object.freeze(["prod", "test", "dev"]);
77
77
 
78
- // ── RESOLVED UNITS (owner ruling 2026-08-25 — option B) ──────────────────────
78
+ // ── RESOLVED UNITS (ruling 2026-08-25 — option B) ──────────────────────
79
79
  //
80
80
  // `resolved: [...]` names tracked files the INSTALLER rewrites before installing. Three units carry
81
81
  // `@NAME@` placeholders because configuration cannot reach them: a `.path` unit reads no environment at
@@ -88,7 +88,7 @@ export const BOXES = Object.freeze(["prod", "test", "dev"]);
88
88
  // everything declared here really carries one. A one-way check would let a new placeholder ship
89
89
  // undeclared, which is the same shape as the undeclared units this file was written to make impossible.
90
90
 
91
- // ── RETIRED UNITS (; owner ruling 2026-08-26, restated 2026-08-31) ────────────
91
+ // ── RETIRED UNITS (; ruling 2026-08-26, restated 2026-08-31) ────────────
92
92
  //
93
93
  // `retired: { ruled, filesStayUntil, why }` says a unit's posture has been ruled away while its tracked
94
94
  // files are still in the tree. It is a FACT about a decision, not a pointer at a replacement — the
@@ -99,12 +99,12 @@ export const BOXES = Object.freeze(["prod", "test", "dev"]);
99
99
  // `filesStayUntil` is a BOX, and it is the whole reason the files are still here. Deleting a tracked
100
100
  // file whose unit that box is running today removes the only tracked description of a live service —
101
101
  // the mistake this file's own header records being caught once already, for `profile-service` and
102
- // `prelim-outbox`. Production is on pre-sweep code and is rebuilt rather than migrated (owner ruling
102
+ // `prelim-outbox`. Production is on pre-sweep code and is rebuilt rather than migrated (ruling
103
103
  // 2026-08-26), so the rebuild is the event that releases the files, and until then entry and tree
104
104
  // disagree ON PURPOSE.
105
105
  //
106
106
  // THE REBUILD HAPPENED, and the first two entries this field ever carried have gone with their files.
107
- // Measured on the production install 2026-09-07 (tracker issue 265): seven units, all under the current
107
+ // Measured on the production install 2026-09-07: seven units, all under the current
108
108
  // names, none of the retired ones, with a clearance delivered end to end. `prelim-driver.*` and
109
109
  // `prelim-outbox.*` left the tree in the same change that removed their entries. Read the paragraph
110
110
  // above as the rule and this one as the event that discharged it — without this line a reader concludes
@@ -141,7 +141,7 @@ export const UNIT_INVENTORY = Object.freeze([
141
141
  // held deliberately, a check that says so for as long as the hold lasts is the behaviour to want.
142
142
  },
143
143
  {
144
- // ── `tracked` WAS A CLAIM ABOUT A FILE THAT HAS NEVER EXISTED (tracker issue 175) ──────────────
144
+ // ── `tracked` WAS A CLAIM ABOUT A FILE THAT HAS NEVER EXISTED ──────────────────────────────────
145
145
  //
146
146
  // This entry named `profile-service.service` as tracked. `git log --all` on that path is empty: it is
147
147
  // not in the tree, not in the tarball, and has never been in this repository's history. So
@@ -170,7 +170,7 @@ export const UNIT_INVENTORY = Object.freeze([
170
170
  // calls the sanctioned way to say the repo does not carry one.
171
171
  //
172
172
  // ONE THING THIS DOES NOT SETTLE, left visible rather than decided quietly. The note calls the unit
173
- // "generic since tracker issue 1925" — an argument that a placeholder COULD now ship and be compared,
173
+ // "generic since the placeholder ruling" — an argument that a placeholder COULD now ship and be compared,
174
174
  // because the reason its siblings cannot (real CF Access values inline) may no longer hold here. That
175
175
  // is a change which ADDS a file, measured against the deployed copy, and it is not this one. Two
176
176
  // prose references in driver/systemd/render-units.mjs also describe this file as though it were in
@@ -247,7 +247,7 @@ export const UNIT_INVENTORY = Object.freeze([
247
247
  //
248
248
  // Until then it was an orphan BY DESIGN: `clearotron connect` installed and started it on demand,
249
249
  // because starting it turned on client-account access and a unit that came up with everything else
250
- // would have made that consent meaningless (; owner ruling 2026-08-31, "On demand
250
+ // would have made that consent meaningless (; ruling 2026-08-31, "On demand
251
251
  // is fine"). He superseded that knowingly — the door auto-starts and the per-account key is the
252
252
  // gate, not whether a process runs.
253
253
  //
@@ -278,7 +278,7 @@ export const UNIT_INVENTORY = Object.freeze([
278
278
  + "directly (owner ruling 2026-08-31). Ships tracked, runs nowhere yet — production takes it at "
279
279
  + "its rebuild, which is the same event that lets the retired units' files leave the tree.",
280
280
  orphanReason: "AWAITING A BOX, not an orphan by design. It is the hosted posture's drainer, and the "
281
- + "hosted posture is an opt-in step no install performs (tracker issue 1888). It must NOT be "
281
+ + "hosted posture is an opt-in step no install performs. It must NOT be "
282
282
  + "started beside `clearotron start`, which supervises its own worker — the two postures are "
283
283
  + "alternatives, and running both puts a second claimant on one queue.",
284
284
  },
@@ -98,7 +98,9 @@ export function matterLedgerPath(qdir) { return join(dirname(qdir), ".matter-led
98
98
  * No paths in the return value: it is spread into client-facing JSON by both the portal and the MCP
99
99
  * preview, and server paths are not a client's business.
100
100
  */
101
- export function accountUsage({ queueDirs, account, now = Date.now() }) {
101
+ export function accountUsage({ queueDirs, account, organisation = null, now = Date.now() }) {
102
+ // A Generic run counts in the lane of the organisation it was filed under, as checkRunCaps counts it.
103
+ const inLane = (key, org) => (key ?? null) === account && (account !== "generic" || (org ?? null) === organisation);
102
104
  const dayKey = new Date(now).toISOString().slice(0, 10);
103
105
  const monthKey = new Date(now).toISOString().slice(0, 7);
104
106
  let today = 0, thisMonth = 0, queued = 0;
@@ -113,7 +115,7 @@ export function accountUsage({ queueDirs, account, now = Date.now() }) {
113
115
  read = true;
114
116
  for (const line of rows) {
115
117
  let e; try { e = JSON.parse(line); } catch { continue; }
116
- if (e.profileKey !== account || typeof e.ts !== "number") continue;
118
+ if (!inLane(e.profileKey, e.organisation) || typeof e.ts !== "number") continue;
117
119
  const stamp = new Date(e.ts).toISOString();
118
120
  if (stamp.slice(0, 7) === monthKey) thisMonth++;
119
121
  // `today` must count exactly what the WALL counts, or the screen lies. checkRunCaps skips failed
@@ -132,7 +134,7 @@ export function accountUsage({ queueDirs, account, now = Date.now() }) {
132
134
  // which the old inline regex here missed (isLiveQueueMarker is the one definition;).
133
135
  try {
134
136
  for (const f of readdirSync(qdir).filter(isLiveQueueMarker)) {
135
- try { if ((JSON.parse(readFileSync(join(qdir, f), "utf8")).profileKey ?? null) === account) queued++; }
137
+ try { const j = JSON.parse(readFileSync(join(qdir, f), "utf8")); if (inLane(j.profileKey, j.tenant)) queued++; }
136
138
  catch { /* mid-rename — skip */ }
137
139
  }
138
140
  } catch { /* no queue dir */ }
@@ -117,7 +117,7 @@ export function knockoutReceipts(runDir, marks) {
117
117
  return { ok: failures.length === 0, failures, checked };
118
118
  }
119
119
 
120
- // ── ratingQualifier: the closed SECOND axis (owner ruling, 2026-08-06) ────────────────────────
120
+ // ── ratingQualifier: the closed SECOND axis (ruling, 2026-08-06) ────────────────────────
121
121
  //
122
122
  // The band is the rating; the qualifier is the only sub-gradation the doctrine allows, and until now it
123
123
  // was free text on the mark row — validated by nothing, rendered verbatim on the client page
@@ -163,9 +163,9 @@ export const READ_FIELDS = Object.freeze(["basis", "factors", "counterFactors",
163
163
  export const REGISTER_CLAIM_RE =
164
164
  /\b(?:register|registry|registers|trademark office)\s+(?:overlay\s+|search(?:es)?\s+|check(?:s)?\s+|count(?:s)?\s+|data\s+)?(?:has|have|was|were|is|are)\s+(?:not\s+|n't\s+|yet\s+)?(?:been\s+)?(?:run|searched|checked|counted|performed|conducted|reviewed|carried out)\b/i;
165
165
 
166
- // ── THE SUMMARY'S SHAPE (tracker issues 1934, 2056) ──────────────────────────────────────────────────
166
+ // ── THE SUMMARY'S SHAPE ──────────────────────────────────────────────────
167
167
  //
168
- // Owner ruling 2026-08-31: "keep the length, add the structure, so long as length is consistent more or
168
+ // Ruling 2026-08-31: "keep the length, add the structure, so long as length is consistent more or
169
169
  // less." Two rules follow, and they are deliberately unlike each other.
170
170
  //
171
171
  // 1. AN H1 IS A CORRECTNESS DEFECT, refused outright. `# ` opens a SECTION of the delivered report:
@@ -360,7 +360,7 @@ export const validators = {
360
360
  if (!rating) return { ok: false, reason: `mark "${m.name}": rating is required` };
361
361
  if (ladder.length && bandIdx(ladder, rating) < 0)
362
362
  return { ok: false, reason: `knockout_band_unknown:${m.name}: rating "${rating}" is not in the frozen ladder (${ladder.join(" / ")}) — rate in the framework's own vocabulary` };
363
- // the second axis, closed here because this is where the first one is closed ( owner ruling)
363
+ // the second axis, closed here because this is where the first one is closed ( ruling)
364
364
  if (m.ratingQualifier != null && !normalizeKnockoutQualifier(m.ratingQualifier))
365
365
  return { ok: false, reason: `knockout_qualifier_unknown:${m.name}: ratingQualifier "${m.ratingQualifier}" is not one of ${KNOCKOUT_RATING_QUALIFIERS.join(" / ")} — the qualifier is a closed sub-gradation that can only cap a band, and anything else belongs in the band word itself` };
366
366
  if (!Array.isArray(m.bullets) || m.bullets.length < 1 || m.bullets.length > 5)
@@ -419,7 +419,7 @@ export const validators = {
419
419
  // paragraph names every mark, so publish blanked it rather than put other clients' marks on this
420
420
  // client's page, and nothing owed a replacement.
421
421
  //
422
- // Owner ruling: every mark's own report opens with a model-authored paragraph about that mark.
422
+ // Ruling: every mark's own report opens with a model-authored paragraph about that mark.
423
423
  // Required rather than optional, because the failure it replaces was a page that opened with
424
424
  // NOTHING and looked deliberate — publish blanked the batch paragraph on a per-mark document (it
425
425
  // names other clients' marks) and no one field owed a replacement.
@@ -540,8 +540,8 @@ export function registerSurfacedFilings(recordsPath, { read = readFileSync } = {
540
540
  /**
541
541
  * The survivor sentence's signature, and the engine's own caveats.
542
542
  *
543
- * MOVED HERE FROM pipeline-knockout SO THERE IS ONE DERIVATION, for the identical reason tracker issue
544
- * 1926 moved `registerSurfacedFilings`: the producer and the verifier were asking different questions of
543
+ * MOVED HERE FROM pipeline-knockout SO THERE IS ONE DERIVATION, for the identical reason an earlier
544
+ * change moved `registerSurfacedFilings`: the producer and the verifier were asking different questions of
545
545
  * the same array, and two predicates that must agree forever is how they stop agreeing.
546
546
  *
547
547
  * `pipeline-knockout.mjs` re-exports `SURVIVOR_BOUNDARY_RE` so its existing importers are untouched; the
package/driver/verify.mjs CHANGED
@@ -144,7 +144,7 @@ function nonEmpty(content, min = MIN) {
144
144
  * ── WHY THE LABEL STAYS AND THE MEMBER IS APPENDED ──────────────────────────────────────────────────
145
145
  *
146
146
  * The obvious fix — emit the failing marker INSTEAD of the label — silently breaks the corrective hints.
147
- * `correctionHint` branches on the label text (`gateway.mjs:2181` on `findings+ledger`, `:2188` on
147
+ * `correctionHint` in gateway.mjs branches on the label text (its `findings+ledger` arm, and the arm on
148
148
  * `negative-results|coverage-ledger|audit-trail|findings-heading`), so a renamed token would fall through
149
149
  * to a generic hint. That comment records that arm being removed once already on a reading that was
150
150
  * true for only one lane, and put back. Appending keeps every existing matcher matching — they all test
@@ -166,7 +166,7 @@ function needs(content, markers, label, names = []) {
166
166
  }
167
167
  return ok();
168
168
  }
169
- // ── THE STRUCTURAL SECTION CONTRACT (tracker issue 129) ────────────────────────────────────────────
169
+ // ── THE STRUCTURAL SECTION CONTRACT ────────────────────────────────────────────────────────────────
170
170
  //
171
171
  // THE CLASS. Every gate below keys on prose the MODEL composes, so each is one phrasing drift away
172
172
  // from killing a run that produced the section perfectly. Measured across three runs and two engines:
@@ -192,7 +192,7 @@ function needs(content, markers, label, names = []) {
192
192
  // nothing else. Once seats emit anchors reliably, the fallback can be measured and retired; that is a
193
193
  // later decision needing evidence, not this change.
194
194
  //
195
- // The label is unchanged on purpose: `correctionHint` (gateway.mjs:2181, :2188) branches on the label
195
+ // The label is unchanged on purpose: `correctionHint` in gateway.mjs branches on the label
196
196
  // TEXT, so renaming a token here silently downgrades the seat's corrective hint to a generic one.
197
197
  const SECTION_ANCHOR_RE = (name) =>
198
198
  new RegExp(`<!--\\s*clearotron:section\\s*=\\s*${name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\s*-->`, "i");
@@ -1571,7 +1571,7 @@ export const validators = {
1571
1571
  //
1572
1572
  // The frame is rendered by the driver now, from a typed `record_matter_frame` call. Two of the checks
1573
1573
  // below would become the driver comparing its own render against its own input — a guard that CANNOT
1574
- // FAIL, which reads as coverage and is worse than no guard (owner ruling, 2026-08-17). So each one is
1574
+ // FAIL, which reads as coverage and is worse than no guard (ruling, 2026-08-17). So each one is
1575
1575
  // stated here, by token, with what happened to it:
1576
1576
  //
1577
1577
  // `meaning_angles_missing` — DELETED on the recorded path. `acceptMatterFrame` refuses a call that
@@ -1867,7 +1867,7 @@ export const validators = {
1867
1867
  // artifact passing, and a fresh dispatch under this code is held to the new floor. A sibling that
1868
1868
  // EXISTS is parsed strictly regardless of vintage (present-and-malformed is always a defect).
1869
1869
  placement: (p, c) => {
1870
- // THE LAST PROSE-KEYED GATE ON A SEAT-WRITTEN ARTIFACT (tracker issue 129).
1870
+ // THE LAST PROSE-KEYED GATE ON A SEAT-WRITTEN ARTIFACT.
1871
1871
  //
1872
1872
  // 129 named three sites and all three had stopped being members before it was filed: conversions 5
1873
1873
  // and 11 moved report-cards/<ord>.md and register-findings.md to driver renders, and audit.md was
@@ -1,6 +1,6 @@
1
1
  // SPDX-License-Identifier: AGPL-3.0-only
2
2
  // Copyright 2026 Cordillera Sàrl. Additional terms under section 7 of the AGPL-3.0 apply — see ADDITIONAL-TERMS.md
3
- // whatif-memo-run.mjs — the door onto the memo composer (tracker issue 132).
3
+ // whatif-memo-run.mjs — the door onto the memo composer.
4
4
  //
5
5
  // WHY THIS FILE EXISTS. Everything a memo needs was already in the tree and nothing could reach it:
6
6
  // whatif-memo.mjs composes one, whatIfRefusal admits `kind: "memo"` on a finished run, decodeOp
@@ -2,7 +2,7 @@
2
2
  // Copyright 2026 Cordillera Sàrl. Additional terms under section 7 of the AGPL-3.0 apply — see ADDITIONAL-TERMS.md
3
3
  // whatif-queue.mjs — the RUN-LOCAL queue a client's what-if waits in.
4
4
  //
5
- // Owner ruling 2026-08-27 opened what-if to clients. The remote surfaces cannot run it directly and that
5
+ // Ruling 2026-08-27 opened what-if to clients. The remote surfaces cannot run it directly and that
6
6
  // is a configuration fact rather than a policy: http-server.mjs states in its own header that they "NEVER
7
7
  // shell", and lib/whatif.mjs imports driver/pipeline.mjs LAZILY so the read-only face never loads the
8
8
  // engine at all. The only thing that runs the engine remotely is the queue, through the worker. So a
@@ -124,13 +124,13 @@ function pathFor(runDir, id, suffix) { return join(whatIfQueueDir(runDir), `${id
124
124
  */
125
125
  export function enqueueWhatIf(runDir, { op, requestedBy = null, account = null, now = Date.now() }) {
126
126
  if (!op || typeof op !== "object" || !op.runId) throw new Error("enqueueWhatIf: op must carry runId and stage");
127
- // ── A MEMO CARRIES NO STAGE, AND THIS GUARD USED TO REQUIRE ONE (tracker issue 132) ──────────────
127
+ // ── A MEMO CARRIES NO STAGE, AND THIS GUARD USED TO REQUIRE ONE ──────────────────────────────────
128
128
  //
129
129
  // Two consecutive guards, mutually exclusive for a memo. decodeOp refuses a memo token that CARRIES
130
130
  // a stage — "a memo re-runs no stage, so a token carrying one was not planned as a memo" — and this
131
131
  // line refused one that did not. So every memo minted by whatIfPlan was admitted by whatIfEnqueue's
132
132
  // kind-aware refusal and then thrown out by the very next call, on the one non-test path there is
133
- // (mcp-server/lib/whatif.mjs:202, reached from the live tool handler at server.mjs:443).
133
+ // (`whatIfEnqueue()` in `mcp-server/lib/whatif.mjs`, reached from the live `what_if_run` handler in `mcp-server/server.mjs`).
134
134
  //
135
135
  // WHY IT SURVIVED A FIX THAT WAS ABOUT EXACTLY THIS. The earlier repair opened whatIfRun's memo
136
136
  // branch and was proved by arms that INJECT the composer — routing coverage. A routing arm cannot
@@ -50,7 +50,7 @@ export const WHATIF_MAX_CONCURRENT = Math.max(1, Number(process.env.CLEAROTRON_W
50
50
  // list of markers kept here. This file used to carry its own list, and it included `.failed` where the
51
51
  // MCP door's copy did not — so a failed run was planned, enqueued and then refused here. See the
52
52
  // declaration for what that cost and why `failed` is now eligible.
53
- // ── THE MARKERS ARE KIND-INDEPENDENT; THE VERDICT IS NOT (tracker issue 132) ────────────────────────
53
+ // ── THE MARKERS ARE KIND-INDEPENDENT; THE VERDICT IS NOT ────────────────────────────────────────────
54
54
  //
55
55
  // This used to render the verdict here, calling whatIfRefusal with NO `kind` — so it defaulted to
56
56
  // "stage" and refused every memo on a delivered run with "what-if runs on live runs only", the third
@@ -136,7 +136,7 @@ async function settleOne(entry, exec) {
136
136
  const result = await exec({ confirmationToken });
137
137
  // A memo carries no stage — it re-runs none — so the old form logged a literal "undefined" for
138
138
  // every memo the worker settled. The kind is what distinguishes the two here, and an operator
139
- // reading the drain log needs to know which of them just ran (tracker issue 132).
139
+ // reading the drain log needs to know which of them just ran.
140
140
  const what = job.op?.kind === "memo" ? "memo" : `${job.op.stage}${job.op.axis ? `:${job.op.axis}` : ""}`;
141
141
  note(`[whatif] ${id} ${result?.ok === false ? "failed" : "done"} (${what})`);
142
142
  return finishWhatIf(runDir, id, { ok: result?.ok !== false, op: job.op, result, error: result?.ok === false ? (result.fail ?? "the experiment did not complete") : null });
@@ -5,7 +5,7 @@ The two input files you copy. Nothing here needs a credential and nothing here c
5
5
  | Path | What it is |
6
6
  |---|---|
7
7
  | `job.euipo.json` | the whole input contract for one clearance — 14 fields, ready to copy and edit |
8
- | `grants.example.json` | who may read which account's runs: three tenants, three grant shapes |
8
+ | `grants.example.json` | who may see and do what: two organisations, and one of each kind of person |
9
9
 
10
10
  ## The finished runs moved to `demo/`
11
11
 
@@ -1,40 +1,41 @@
1
1
  {
2
2
  "tenants": {
3
- "the-firm": {
4
- "accounts": "*",
5
- "users": {
6
- "principal@firm.example": "*",
7
- "associate@firm.example": [
8
- "demo-brand-owner"
9
- ]
10
- }
11
- },
12
- "brand-owner-direct": {
3
+ "demo-org": {
4
+ "name": "Demo Org",
13
5
  "accounts": [
14
6
  "demo-brand-owner"
15
7
  ],
16
8
  "users": {
17
- "*@brand-owner.example": "*"
9
+ "*@demo-org.example": "*",
10
+ "consultant@outside.example": [
11
+ "demo-brand-owner"
12
+ ]
18
13
  }
19
14
  },
20
15
  "evaluation": {
21
- "accounts": [
22
- "demo-brand-owner"
23
- ],
16
+ "name": "Evaluation",
17
+ "accounts": [],
24
18
  "users": {
25
19
  "reviewer@partner.example": "*",
26
- "observer@partner.example": [
27
- "demo-brand-owner"
28
- ]
20
+ "observer@partner.example": "*"
29
21
  }
22
+ }
23
+ },
24
+ "people": {
25
+ "you@install.example": {
26
+ "run": true,
27
+ "manage": true,
28
+ "everything": true
30
29
  },
31
- "demo-org": {
32
- "accounts": [
33
- "demo-brand-owner"
34
- ],
35
- "users": {
36
- "*@demo-org.example": "*"
37
- }
30
+ "*@demo-org.example": {
31
+ "run": true
32
+ },
33
+ "consultant@outside.example": {
34
+ "run": true
35
+ },
36
+ "reviewer@partner.example": {
37
+ "run": true,
38
+ "manage": true
38
39
  }
39
40
  }
40
41
  }
@@ -1,5 +1,15 @@
1
1
  # trademark-artifacts-mcp
2
2
 
3
+ ## 0.3.0-beta.1
4
+
5
+ ### Patch Changes
6
+
7
+ - b8ec213: Fixed: a company you create in the browser now appears in your assistant's list of companies straight away.
8
+
9
+ It used to appear only after the service restarted, although a search could already be started for it. Its projects were missing from the list in the same way, and so was its account in the search options.
10
+
11
+ The live surface check no longer reports a company outside the portal's trigger key as the service and the store disagreeing. It names the key's gap on a line of its own, with the command to widen the key.
12
+
3
13
  ## 0.2.4
4
14
 
5
15
  ## 0.2.3