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
@@ -45,7 +45,7 @@ accounts their email is granted (`CLEAROTRON_ACCESS_FILE` — the same guest lis
45
45
  Enrolment is therefore the portal's: no second credential to mint, rotate or revoke, and revoking portal
46
46
  access revokes this with it. **Off unless `CLIENT_MCP_ACCOUNT_ACCESS=1`.**
47
47
 
48
- **Who turns that on. The installer, since 2026-09-03** — owner ruling, settled
48
+ **Who turns that on. The installer, since 2026-09-03** — ruling, settled
49
49
  point 2. `render-units.mjs --apply` and `npx 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
@@ -79,7 +79,7 @@ under the report, not just its prose. It projects named structured fields and en
79
79
  them — `mcp-server/lib/evidence.mjs` states that there is no code path forwarding free prose, and
80
80
  that is the one declared exception to the scrub.
81
81
 
82
- **The audit chain is open by owner ruling, 2026-08-27** ("I don't see why we don't open it or just
82
+ **The audit chain is open by ruling, 2026-08-27** ("I don't see why we don't open it or just
83
83
  give it to clients. Ignore the call spend."). The same lawyer who needs the records also has to be
84
84
  able to show *how* the answer was reached, so the decision chain is client product now. Unlike the
85
85
  evidence layer this one does forward prose — a chain of reasoning is prose — so it is bounded a
package/docs/E2E.md CHANGED
@@ -6,6 +6,12 @@ How to prove a deployment of this product works end to end, at four cost tiers.
6
6
  doubles as the **dev/prod instance split**: a dev instance is just a second env-file pointing every
7
7
  data-plane path somewhere isolated — the code is identical.
8
8
 
9
+ > **This page is written for a checkout, and the installed package is not one.** The published package
10
+ > deliberately excludes the test tree, so `driver/test/` does not exist in an npm install — Tier 0 and
11
+ > the `$0` mock engine below are reachable only from a clone. Everything from Tier 1 down applies to an
12
+ > installed deployment as written. If a path here starting `driver/test/` is not on your disk, that is
13
+ > the reason, and it is not a mistake in your configuration.
14
+
9
15
  ## Tier 0 — offline, in-repo (already in `npm run test:full`, $0)
10
16
 
11
17
  `driver/test/pipeline.anthropic.test.mjs` runs the full clearance pipeline on the production engine
@@ -38,10 +44,10 @@ CLEAROTRON_WORK_DIR=/home/you/trademark-dev/workspace
38
44
  CLEAROTRON_REPORTS_DIR=/home/you/trademark-dev/pool
39
45
  CLEAROTRON_OUTBOX_DIR=/home/you/trademark-dev/outbox
40
46
  CLEAROTRON_AI=anthropic-agent
41
- CLEAROTRON_CLAUDE_PATH=/home/you/clearotron/driver/test/mock-claude.mjs # $0 mock engine; ABSOLUTE — see below
47
+ CLEAROTRON_CLAUDE_PATH=/home/you/clearotron/driver/test/mock-claude.mjs # $0 mock engine; CHECKOUT ONLY, and ABSOLUTE — see below
42
48
  MOCK_VERDICT=CLEAR
43
49
  MOCK_SKEPTIC=no flags surfaced # clean skeptic pass under the mock (quotes optional; both work)
44
- CLEAROTRON_DATABASE=corsearch # REQUIRED, no default (tracker issue 503) — the mock never calls it
50
+ CLEAROTRON_DATABASE=corsearch # REQUIRED, no default — the mock never calls it
45
51
  CORSEARCH_SESSION_KEY=dev-offline # the credential preflight wants it set; never fetches under the mock
46
52
  CLEAROTRON_SATPROBE_CODESIDE=0 # the probe dials the provider; a mock run cannot
47
53
  CLEAROTRON_BAND_TRUTH_GATE=0 # the gate evidences bands against the production call ledger
@@ -51,6 +57,10 @@ CLEAROTRON_CUSTOMERS_DIR= # unset ⇒ the dem
51
57
  # grants file every token-less caller resolves to internal read-all across every customer.
52
58
  ```
53
59
 
60
+ **From an installed package there is no such file at all** — the test tree is excluded from it, so
61
+ before reading the next paragraph check that the path exists. That check is worth making first because
62
+ the failure below looks the same and has a different cause.
63
+
54
64
  `CLEAROTRON_CLAUDE_PATH` must be absolute, and this is the one that catches everybody: the engine child is
55
65
  spawned with the RUN DIRECTORY as its cwd, so`driver/test/mock-claude.mjs` is looked for
56
66
  *inside the run* and never found. A run now refuses at preflight, before it creates a run directory,
@@ -41,7 +41,7 @@ store, and writes nothing — so the first real run is one you have already read
41
41
 
42
42
  ## The framework is always set, and the output says which one
43
43
 
44
- Owner ruling, 2026-08-29: the risk framework is mandatory at onboarding, with a default backup so
44
+ Ruling, 2026-08-29: the risk framework is mandatory at onboarding, with a default backup so
45
45
  onboarding is never blocked. The command therefore sets a framework on every bundle it writes, and
46
46
  prints which one it used.
47
47
 
package/docs/PORTAL.md CHANGED
@@ -19,11 +19,12 @@ enrolment — and none of it is done by installing this repo.
19
19
 
20
20
  ## The model
21
21
 
22
- | Who | Sees |
22
+ | Who | Sees and does |
23
23
  |---|---|
24
- | Staff (email domain ∈ `PORTAL_STAFF_DOMAINS`) | everything, with an explicit **acting-for** account on scoped routes (never an implicit firm-wide default) |
25
- | Enrolled client (email in the grants file) | exactly their accounts: their searches, their runs, their released client reports |
26
- | Anyone else | 403 at the door; cross-account probes read as **404** (existence never leaks) |
24
+ | A person with access to everything | every organisation, company and person, naming a company on scoped routes (never an implicit install-wide default) |
25
+ | A person with access to an organisation or a company | everything below those points: its companies and their runs and reports, and — for an organisation — its Generic |
26
+ | Run clearances · Manage | the two switches each person holds: start and stop clearances; add people, add companies and change settings — both inside the person's access |
27
+ | Anyone else | 403 at the door; probes outside a person's access read as **404** (existence never leaks) |
27
28
 
28
29
  The enrolment substrate IS the grants file (`CLEAROTRON_ACCESS_FILE`, [the operations runbook](architecture/06-operations-runbook.md#access-control-and-instance-isolation)
29
30
  for the shape and `examples/grants.example.json` for a runnable one) — portal
@@ -33,12 +34,12 @@ whichever door this instance runs, then granted here; the model is stated once i
33
34
  [docs/SECURITY.md](SECURITY.md). On a proxied instance that means
34
35
  `MCP_ALLOWED_EMAIL_DOMAINS` and/or `MCP_ALLOWED_EMAILS` (no default — the portal
35
36
  refuses to start with neither set, and combines the two as a UNION rather than the verifier's default
36
- intersection) must admit the CLIENT identities as well as the staff domain: the staff domain in the
37
- domain list, and individually named client addresses in `MCP_ALLOWED_EMAILS` rather than their whole
38
- domain — admitting a consumer domain wholesale to enrol one client would put every address on it
39
- through the door, leaving the grants file as the only wall. Untagged and `generic` runs are staff-only on
40
- every client surface — a run with no account tag belongs to no client, so it is shown to none of them.
41
- Multi-account clients enter the door normally and pick an account (`/portal/api/me` returns the list).
37
+ intersection) must admit every person in the guest list: your own domain in the domain list, and
38
+ individually named outside addresses in `MCP_ALLOWED_EMAILS` rather than their whole domain — admitting a
39
+ consumer domain wholesale to enrol one person would put every address on it through the door, leaving
40
+ the grants file as the only wall. A run with no company tag is shown only to a person with access to
41
+ everything, and a Generic run only inside the organisation it was filed under. A person with several
42
+ companies enters the door normally and picks one (`/portal/api/me` returns the list).
42
43
  The one-shot confirmation store is in memory, which bounds the deployment to **one portal process per
43
44
  instance**: a second process would not see the first's CONSUMED jtis, so the one-shot guarantee fails
44
45
  OPEN rather than closed — the token itself verifies anywhere (both processes hold the same
@@ -108,7 +109,7 @@ What that command starts, for anyone who needs to drive the portal on its own:
108
109
  ```bash
109
110
  # the portal with the LOCAL identity source (no CF Access, loopback only)
110
111
  PORTAL_AUTH_MODE=local PORTAL_LOCAL_USER=cli@celta.example \
111
- PORTAL_STAFF_DOMAINS=example-firm.com CLEAROTRON_ACCESS_FILE=$HOME/trademark-dev/grants.json \
112
+ CLEAROTRON_ACCESS_FILE=$HOME/trademark-dev/grants.json \
112
113
  PORTAL_SECRET="$(openssl rand -base64 32)" CLEAROTRON_REPORTS_DIR=$HOME/trademark-dev/pool \
113
114
  CLEAROTRON_WORK_DIR=$HOME/trademark-dev/workspace node driver/portal-service.mjs
114
115
  # → http://127.0.0.1:18802/portal/login
@@ -121,8 +122,8 @@ and restarting mints a new passphrase.
121
122
 
122
123
  What local mode does **not** change is who sees what. It produces an email address and stops;
123
124
  `makePrincipal` and the `assertPrincipal` chokepoint judge it exactly as they judge a
124
- proxy-verified address, so `PORTAL_LOCAL_USER` must ALSO be enrolled — a staff domain for the
125
- staff view, a grants row for the client view. Sign in as an address the roster does not know and every
125
+ proxy-verified address, so `PORTAL_LOCAL_USER` must ALSO have an entry in the guest list —
126
+ `"everything": true` under `people` for the whole install. Sign in as an address the roster does not know and every
126
127
  page refuses it at the door, which is the correct answer and is warned about at boot. The roster itself
127
128
  stays mandatory: local mode has a population of one, and one is still a population.
128
129
 
package/docs/SECURITY.md CHANGED
@@ -10,7 +10,7 @@ here corresponds to shipped behavior; when hardening changes, change this file i
10
10
  | Surface | Trust | Guard |
11
11
  |---|---|---|
12
12
  | stdio MCP (`mcp-server/server.mjs`) | local/full ("ops") | OS user boundary — run it AS the operator account; it is the only surface on which `what_if_run` EXECUTES (`visibleTools` keeps what-if out of the HTTP listing for ops, but the CallTool chokepoint gates on `authorize()` alone, which admits it for any ops token not `--verbs`-scoped) |
13
- | Client MCP (`mcp-server/http-server-client.mjs`) | signed-in client / account key | a client account's `what_if_run` ENQUEUES rather than executes (owner ruling 2026-08-27) — it never imports the engine, and `driver/whatif-worker.mjs` spawns the sandbox from an OS service process. A confirmation token is unsigned, so the call must ALSO name its `runId`: the account gate keys on it, and `whatIfEnqueue` refuses a token naming a different run. The `model` argument is refused to a client. |
13
+ | Client MCP (`mcp-server/http-server-client.mjs`) | signed-in client / account key | a client account's `what_if_run` ENQUEUES rather than executes (ruling 2026-08-27) — it never imports the engine, and `driver/whatif-worker.mjs` spawns the sandbox from an OS service process. A confirmation token is unsigned, so the call must ALSO name its `runId`: the account gate keys on it, and `whatIfEnqueue` refuses a token naming a different run. The `model` argument is refused to a client. |
14
14
  | HTTP MCP (`mcp-server/http-server.mjs`) | authenticated remote | auth-BEFORE-data; fail-closed construction; inner scoped tokens |
15
15
  | Report "Ask your AI" links | external report recipients | run-bound `user` tokens minted at publish; client layer only |
16
16
  | Dev portal (`driver/dev-portal.mjs`) | dev only | loopback-only (throws on any other host); never production serving |
@@ -33,28 +33,27 @@ Three deployment shapes, and they are the whole set:
33
33
  | **Shared or hosted** | `PORTAL_AUTH_MODE=auth-proxy` — any login system in front that authenticates in the browser and forwards a verifiable JWT per request (`cf-access` is the older word for this and still works) | Admit the address at your login system, **then** grant it in the guest list — both halves, always |
34
34
  | **Neither configured** | the service refuses to start | — |
35
35
 
36
- Exactly two roles exist: **staff**, admitted by an email-domain rule (`PORTAL_STAFF_DOMAINS`), and
37
- **client**, admitted by a named grant in the guest list (`CLEAROTRON_ACCESS_FILE`). `makePrincipal` →
38
- `assertPrincipal` is the only path to a decision and no identity source may reach past it. Grants are
39
- created in the guest-list file by whoever administers the box — `npm run grant` is the editor for it —
40
- never from a browser. The file is re-read per request, so a grant lands without a restart.
41
-
42
- **A staff domain is a grant to everyone at that domain, so nothing derives one for you.** Every address
43
- at a listed domain that gets past the sign-in door is staff, and staff sees every brand owner on the
44
- instance. The install therefore derives `PORTAL_STAFF_DOMAINS` only from a sign-in address that names no
45
- second person — the `<account>@localhost` form a single-user machine gets by default. Given a real
46
- address it states the rule it would create, in the words the People & access screen will later use,
47
- and refuses to write it: set `PORTAL_STAFF_DOMAINS` yourself, or answer the question `clearotron install`
48
- asks. A webmail or shared provider is refused outright, because a rule built from one admits the public.
49
- The People & access screen names the setting the rule came from and the file it is written in, so a
50
- rule can be traced and undone by whoever finds it.
51
-
52
- **That check is on the DERIVATION, not on the value.** A `PORTAL_STAFF_DOMAINS` you set yourself is
53
- taken as written and never classified: `PORTAL_STAFF_DOMAINS=gmail.com` is accepted, and it admits
54
- everyone at that provider who gets past the sign-in door. This is deliberate — a value someone typed is
55
- a decision already taken, and the defect being fixed was a rule created with nobody asked — but it means
56
- the protection is against the accident and not against the configuration. Read what you set, or set
57
- nothing and answer the question the install asks.
36
+ A person is admitted by their own entry in the guest list (`CLEAROTRON_ACCESS_FILE`) and by nothing
37
+ else. The guest list gives each person access to points on one tree — the whole install, an
38
+ organisation, or one company — and two switches, **Run clearances** and **Manage**. A person sees
39
+ everything below the points they were given and nothing else; both switches off is a person who can read
40
+ and start nothing. In the portal, `makePrincipal` → `assertPrincipal` is the only path to a decision and
41
+ no identity source may reach past it; the connector resolves a key's person through the same function
42
+ (`resolvePerson`, `shared/scope.mjs`), so the two doors cannot disagree about one address. The file is
43
+ re-read per request, so a change lands without a restart. `npm run grant` edits it, and so does anyone
44
+ with Manage from the People page, inside their own access.
45
+
46
+ **Nothing is admitted by the part of an address after its `@`.** The staff-by-domain rule
47
+ (`PORTAL_STAFF_DOMAINS`) is gone and the setting is ignored. A `*@domain` entry in the guest list still
48
+ names every address at that domain and may hold Run clearances; it may never hold Manage or access to
49
+ everything, and the guest list is refused at load if it tries, because a domain-wide Manage is the staff
50
+ rule by another name.
51
+
52
+ **Two organisations are invisible to each other because they are sibling branches.** A company belongs
53
+ to exactly one organisation, and the guest list is refused at load if a company is listed under two. A
54
+ clearance run with no company — Generic — is filed under the organisation it was started in and is
55
+ visible only inside it. A Generic run filed before organisations existed is visible only to a person
56
+ with access to everything.
58
57
 
59
58
 
60
59
  - **On the proxy door**, every HTTP request re-validates a JWT from the fronting auth proxy. The
@@ -81,7 +80,7 @@ nothing and answer the question the install asks.
81
80
  - Four principal kinds: **ops** (write verbs; automation/operator), **user** (read-only, pinned to
82
81
  exactly ONE run — report recipients), **account** (a signed-in client across the accounts their
83
82
  identity is granted: the client layer, the evidence layer — `list_evidence` / `list_searches` /
84
- `get_search_coverage` — the AUDIT CHAIN (owner ruling 2026-08-27: `read_artifact` over the chain
83
+ `get_search_coverage` — the AUDIT CHAIN (ruling 2026-08-27: `read_artifact` over the chain
85
84
  artifacts named in `ACCOUNT_ARTIFACTS`, `list_findings` on the raw `kind` path, `get_finding`,
86
85
  `get_run`, `trace`, `decision_timeline`), WHAT-IF as a queued sandbox job (`what_if_plan`,
87
86
  `what_if_run`, `what_if_result`), and the run lifecycle on their own runs, and nothing else. All of it accountSafe and deliberately NOT clientSafe, because a report link is forwardable
@@ -173,7 +173,7 @@ timeout shot, warm patch, backoff), the lane-wedge re-dispatch, and the rate-lim
173
173
  | `CLEAROTRON_AI_BILLING` | `subscription` (default) \| `api-key` | 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. **`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`. **Fail-loud either way:** `api-key` with no `ANTHROPIC_API_KEY` / `CODEX_API_KEY` throws, and never silently bills the subscription. The resolved mode is stamped on every stage telemetry row (`engine`, `authMode`, `apiBilled`). |
174
174
  | `CLEAROTRON_CLAUDE_PATH` · `CLEAROTRON_CODEX_PATH` | `claude` / `codex` on `PATH` | 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 box that runs both sets both — they were one variable until it met a box needing two different paths. Give it an **absolute** path if the binary is not on `PATH` — stage subprocesses run with cwd set to the run directory, so a relative path does not resolve there, and `npx clearotron doctor` refuses one. |
175
175
  | `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. |
176
- | `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 (owner 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. |
176
+ | `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. |
177
177
  | ↳ 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. |
178
178
  | `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. |
179
179
  | `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. |
@@ -282,9 +282,10 @@ because the rule is about what PRODUCT CODE reads, not about what a run reads.
282
282
  | Var | Default | Meaning |
283
283
  |---|---|---|
284
284
  | `DEMO_PORT` | `18900` | Port `npx clearotron demo` serves the replayed report on. `--port` overrides it. |
285
- | `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 (owner 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. |
285
+ | `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. |
286
286
  | `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`. |
287
- | `CLEAROTRON_DEMO_PROFILES` | unset | `1` makes the profile loader return the bundled demo account, which a fresh install does not resolve (owner 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` accounts either way. Effect class `harness`; the full contract is its row in `.env.example`. |
287
+ | `CLEAROTRON_DEMO_PROFILES` | unset | `1` makes the profile loader return the bundled demo account, 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` accounts either way. Effect class `harness`; the full contract is its row in `.env.example`. |
288
+ | `CLEAROTRON_ORGANISATION_NAME` | unset | Your organisation's name, written quoted by `npx clearotron install` directly after the sign-in address. The first `clearotron start` files it as the first organisation in the grants file (`CLEAROTRON_ACCESS_FILE`) when that file holds none; from then on the grants file holds the name, renaming is an edit there, and this is not read. Unset ⇒ no organisation is invented. `clearotron start --organisation <name>` supplies it for one start, and a demo never reads it. Effect class `deployment`; the full contract is its row in `.env.example`. |
288
289
  | `PORTAL_LOCAL_CREDENTIAL` | `~/.cordillera/portal-local-credential.json` | Where local sign-in keeps its passphrase DIGEST. `npx clearotron demo` points it inside the demo's own base directory, so a demo mints its own passphrase instead of inheriting a digest minted for another address — and removing the demo stays one `rm -rf`. |
289
290
  | `PORTAL_LOCAL_PASSPHRASE` | unset | **NEVER set this in a file.** An internal one-shot handoff, not an operator control: on a first FOREGROUND start the supervisor mints the passphrase and hands it to the portal it spawns *at the spawn call*, so the closing summary can print the value beside the address rather than sending a first-time reader back into eleven startup log lines for the one value in this product that cannot be read back. It is deliberately absent from the composed child environments, because that composition is what `--background` writes into the units' env file — a passphrase there would be a permanent plaintext copy on disk and the product's own sentence, "it is stored only as a digest", would stop being true. Setting it in any env file recreates exactly that. Lost passphrase: `clearotron passphrase --reset`. |
290
291
  | `PORTAL_URL` | `http://127.0.0.1:18802`, or built from `PORTAL_SERVICE_HOST`/`PORTAL_SERVICE_PORT` | Where the deploy tick's live-surface check expects to reach the portal. |
@@ -293,7 +294,7 @@ because the rule is about what PRODUCT CODE reads, not about what a run reads.
293
294
  | `CLEAROTRON_RELEASE_WAIT_MS` | 25 minutes | How long a requested cut waits for the version pull request to merge itself before giving up. **Read only by the release workflow.** A rehearsal sets it to `0` so the wiring is exercised without holding a runner. Giving up is a quiet success by design — the scheduled run underneath catches a cut whose wait expired — so this budget failing shows up as a slow job rather than a red one. |
294
295
  | `CLEAROTRON_AGENT_MCP_URL` | unset (⇒ `null`) | The API-key MCP door advertised to a signed-in client. Null until that door is deployed, and the UI keeps its honest empty state rather than inventing a URL. |
295
296
  | `CLEAROTRON_AGENTS` | derived | Comma-separated agent ids for `scripts/purge-runs.mjs` to sweep. |
296
- | `CLEAROTRON_E2E_DIR` | **none — the script refuses without it** | The config repo's `e2e/` directory. There is one suite and it is not in this repo (owner ruling 2026-08-07), so the comparison script names the variable rather than defaulting anywhere. |
297
+ | `CLEAROTRON_E2E_DIR` | **none — the script refuses without it** | The config repo's `e2e/` directory. There is one suite and it is not in this repo (ruling 2026-08-07), so the comparison script names the variable rather than defaulting anywhere. |
297
298
  | `CLEAROTRON_E2E_EXPECT_DEMO_ROSTER` | unset | `1` makes the live-surface check REQUIRE the bundled demo roster. For a box that is meant to ship the demos; off elsewhere, so a real deployment is not failed for lacking them. |
298
299
 
299
300
  ### Gates that are OFF by default
@@ -345,7 +346,7 @@ cannot be read as one list.
345
346
 
346
347
  ### Retired — set these and nothing happens
347
348
 
348
- `tracker issue 1838` deleted the settings below. Nothing in any environment set them, so each became the constant it
349
+ The settings below were deleted. Nothing in any environment set them, so each became the constant it
349
350
  had always resolved to. **They are listed because an operator whose `.env` still carries one needs to
350
351
  know it is inert** — an unread setting is indistinguishable from a setting that works.
351
352
 
@@ -363,6 +364,12 @@ know it is inert** — an unread setting is indistinguishable from a setting tha
363
364
  | `CLEAROTRON_SUPPLEMENTAL_PER_CALL` | 12 supplemental queries per call |
364
365
  | `CLEAROTRON_WALL_RESCUE_QUIESCE_MS` | 60 seconds waiting for a killed tree to quiesce |
365
366
 
367
+ **`PORTAL_STAFF_DOMAINS` is retired differently, because deployments did set it.** Everyone at a listed
368
+ email domain used to be staff. Who may use the portal is now each person's own entry in the grants file
369
+ (`CLEAROTRON_ACCESS_FILE`), with `"everything": true` under `people` for anyone who should see every
370
+ company, and no domain admits anyone. A portal that still finds the setting starts, and says at boot that
371
+ it is ignored and what replaced it.
372
+
366
373
  ## Change management
367
374
 
368
375
  - **Live env flip (no deploy):** caps, backoffs, feature gates, A/B toggles — edit the deployment's
@@ -69,7 +69,7 @@ Recurring confusion, settled: "we configure the MCP servers at the edge" is true
69
69
  fills the same three roles, and the product requires none of them by name.
70
70
 
71
71
  1. **Edge auth (T5, dashboard):** the tunnel routes each public hostname to a loopback port, and a
72
- CF Access app decides *who* may reach it (staff domain gate; client per-email policy; dev app).
72
+ CF Access app decides *who* may reach it (your own domain; outside people per email; dev app).
73
73
  2. **Local verification (T4, unit files):** every service *independently re-verifies* the CF Access
74
74
  JWT. For that it needs the team + the app's AUD tag — so `CF_ACCESS_TEAM`/`CLEAROTRON_OIDC_AUDIENCE`/
75
75
  `MCP_ALLOWED_EMAIL_DOMAINS` appear in unit files as **mirrors of the dashboard**, not a second
@@ -105,12 +105,12 @@ by name in §5 below and in the upgrade table in INSTALL.md.
105
105
  who has not read the code. Vendor credentials keep the vendor’s name (`SIGNA_API_KEY`,
106
106
  `PERPLEXITY_API_KEY`, `ANTHROPIC_API_KEY`) — that already says who you bought them from.
107
107
 
108
- **THERE IS ONE SPELLING.** The compatibility window that read both closed on 2026-08-26 by owner ruling:
108
+ **THERE IS ONE SPELLING.** The compatibility window that read both closed on 2026-08-26 by ruling:
109
109
  no migration, no legacy support. A deployment is REBUILT from the install rather than carried across,
110
110
  which is also the only thing that proves the install works for a new reader — a migrated box tests a path
111
111
  no new user ever walks.
112
112
 
113
- **A retired spelling is not checked for either** — owner ruling, same day, asked directly. The premise
113
+ **A retired spelling is not checked for either** — ruling, same day, asked directly. The premise
114
114
  is what makes the absence correct rather than careless: a machine reaches this code through the install,
115
115
  the install writes the names in force, and the boxes that predate the rename are rebuilt rather than
116
116
  deployed onto. There is no population left holding the old lines.
@@ -294,7 +294,7 @@ it. Set-but-unreadable, malformed, or below the size floor **throws**: a truncat
294
294
  smaller blocklist, and a smaller blocklist reads as a cleaner repo. **Whether the real table is required
295
295
  is the CALLER's declaration, never the environment's**: `publication-scan.mjs` asks for it in its own
296
296
  source and refuses by name before it runs a single check if it cannot reach it. There is no switch —
297
- `CLEAROTRON_REQUIRE_BLOCKLIST` was deleted by owner ruling, because a variable that decides whether a scrub
297
+ `CLEAROTRON_REQUIRE_BLOCKLIST` was deleted by ruling, because a variable that decides whether a scrub
298
298
  guard looks for anything real has a silent OFF position, and silent-off is the one state this module
299
299
  exists to make impossible: without the table, "matched nothing" and "had nothing to match on" are the
300
300
  same green.
@@ -328,7 +328,7 @@ which collided with a co-hosted warm-MCP block, so keep any unit override that n
328
328
  `CLIENT_MCP_SESSION_TTL_MS`, `CLIENT_MCP_SESSION_MAX`, `CLIENT_MCP_RATE_PER_MIN`, `CLIENT_MCP_URL`,
329
329
  `CLIENT_MCP_TOKEN_ONLY`, `CLIENT_MCP_AUTH_DISABLED` / `CLIENT_MCP_DEV` (dev seams — never set in prod). CF mirrors (T5): `CF_ACCESS_TEAM`, `CLEAROTRON_OIDC_AUDIENCE`,
330
330
  `CLEAROTRON_CLIENT_OIDC_AUDIENCE`, `MCP_ALLOWED_EMAIL_DOMAINS`, `MCP_ALLOWED_EMAILS`. Admin services:
331
- `PORTAL_SERVICE_PORT` (18802) / `PORTAL_SERVICE_HOST`, `PORTAL_STAFF_DOMAINS`, `PORTAL_MCP_URL`,
331
+ `PORTAL_SERVICE_PORT` (18802) / `PORTAL_SERVICE_HOST`, `PORTAL_MCP_URL`,
332
332
  `PORTAL_RATE_PER_MIN`, `PORTAL_LOCAL_WORKER` (set only by `bin/start.mjs` when it supervises a worker,
333
333
  and what licenses the portal to say a queued job is waiting for one — a deployed instance drains via
334
334
  systemd, writes no heartbeat, and must keep saying "waiting to start" rather than invent an alarm),
@@ -363,7 +363,7 @@ catch, so it has to be deliberate. The client door also passes `authHeader` into
363
363
  it did not before, so its seam would have been settable and inert.
364
364
 
365
365
  **Named in full rather than as a prefix**, because a row that abbreviates a family documents nothing an
366
- operator can search for — and `tracker issue 692`'s own second arm fails exactly that, which is how this table came
366
+ operator can search for — and the rule's own second arm fails exactly that, which is how this table came
367
367
  to be written out.
368
368
 
369
369
  | service | issuer | JWKS | identity claim | token header |
@@ -391,7 +391,7 @@ the test box and production both bind 127.0.0.1 behind a tunnel.
391
391
 
392
392
  Local mode adds two values and no third: `PORTAL_LOCAL_USER` is the one email address that signs in
393
393
  (mandatory in that mode — the service refuses to start without it, and the address must ALSO be enrolled
394
- in `CLEAROTRON_ACCESS_FILE` or on a staff domain, because signing in is not being enrolled), and
394
+ in `CLEAROTRON_ACCESS_FILE`, because signing in is not being enrolled), and
395
395
  `PORTAL_LOCAL_CREDENTIAL` optionally relocates the credential file, which otherwise lives at
396
396
  `~/.cordillera/portal-local-credential.json` (mode 0600, never in the repository and never inside the
397
397
  pool or the archive). `PORTAL_SECRET` is required in BOTH modes and signs both token families — the
@@ -283,7 +283,7 @@ run dir and the ledgers. The auth model (four principal kinds; run-bound client
283
283
  exactly `brief`, `read_artifact` gated to the report, and `list_findings` gated to the curated card
284
284
  groups) is documented in [09 — Security and data](09-security-and-data.md).
285
285
 
286
- **A signed-in client account reads the audit chain** (owner ruling 2026-08-27). The audit trail is
286
+ **A signed-in client account reads the audit chain** (ruling 2026-08-27). The audit trail is
287
287
  what makes a clearance defensible, and the person who has to defend the filing is the client's
288
288
  lawyer — so `audit`, `narrative`, the record artifacts and a register axis are readable through
289
289
  `read_artifact`, the raw `list_findings` path returns the AT#/F#/NR# records, and `get_run`, `trace`
@@ -255,6 +255,11 @@ wrong one exits `MODULE_NOT_FOUND` whose last line is the node version: piped th
255
255
  almost exactly like a quiet success, and the ritual's own success test — *"and then `git diff` is
256
256
  empty"* — is satisfied **precisely when nothing ran**.
257
257
 
258
+ **Three of these regen commands live in the test tree, which the published package excludes**, so they
259
+ are runnable from a checkout and not from an install. That is the right boundary — regenerating a
260
+ baseline is a contributor's act — but it is worth saying here rather than leaving a reader to find an
261
+ absent file and diagnose their configuration.
262
+
258
263
  | File | Regen | What it records |
259
264
  |---|---|---|
260
265
  | `driver/contract-e3-baseline.json` | `node driver/test/contract-e3-baseline.mjs --write` | structure-as-text per stage dispatch and per skill file |
@@ -111,6 +111,124 @@ wanted. A framework and its manifest are checked as a pair.
111
111
  Alongside it, `workedExamplesPath` sets the analysis depth target — worked clearances calibrated
112
112
  under that framework. Absent, the generic set applies.
113
113
 
114
+ ### Writing your own, step by step
115
+
116
+ **Nothing here is a code change.** Two files go into your own store; a profile points at one of them.
117
+
118
+ **1. Write the deck.** A markdown document. It is reasoned *with*, so write it the way you would brief a
119
+ colleague: what each band means, what it turns on, what to do about it. Give **every band a heading of
120
+ its own**, spelled exactly as you will spell it in the manifest, and under each heading write the rungs
121
+ as bold-led bullets:
122
+
123
+ ```markdown
124
+ ## High
125
+
126
+ - **What it is.** A live registration for a similar mark in a class the client will trade in.
127
+ - **What it turns on.** Whether the goods actually overlap, and whether the owner is using the mark.
128
+ - **What to do.** Advise against adoption unless the owner's non-use can be established.
129
+ ```
130
+
131
+ That shape is not decoration. The profile screen extracts what the bands mean from these headings and
132
+ bullets, and **it is all or nothing**: one band without a heading, or one heading with no bold-led
133
+ bullet, and the box explaining your bands silently does not render at all — while the title and the
134
+ coloured pills still do, so the page looks finished. Frameworks in this repository have shipped in
135
+ exactly that state, which is why there is now a command that tells you before a client sees it.
136
+
137
+ **2. Write the manifest**, beside the deck and named after it: `your-framework.md` needs
138
+ `your-framework.manifest.json`. The path is derived, never configured, so the two cannot drift apart.
139
+
140
+ ```json
141
+ {
142
+ "schema_version": 1,
143
+ "framework_key": "your-firm-2026",
144
+ "title": "Your firm's clearance risk framework",
145
+ "source_deck": "Where this came from, and when it was last reviewed",
146
+ "entity_label": "the company",
147
+ "bands": [
148
+ { "label": "Very High", "tone": "severe" },
149
+ { "label": "High", "tone": "high" },
150
+ { "label": "Moderate", "tone": "medium" },
151
+ { "label": "Manageable","tone": "low" }
152
+ ],
153
+ "structure": { "kind": "bands" }
154
+ }
155
+ ```
156
+
157
+ Every key above is required and **no other key is allowed** — an unknown one is refused by name rather
158
+ than ignored. `schema_version` is `1`. `framework_key` is lowercase letters, digits and hyphens.
159
+ `bands` runs **most severe first**; that order is the framework's severity order everywhere the engine
160
+ names a risk. A band label may contain letters, spaces, slashes and hyphens, and **no digits** — a band
161
+ called "Level 3" invites arithmetic where judgement is wanted. `tone` is one of `severe`, `high`,
162
+ `medium`, `low`, `minimal`, and it chooses a colour, nothing else. `entity_label` is how your deck names
163
+ the client side in prose. If your deck is a matrix rather than a ladder, say
164
+ `"structure": { "kind": "matrix" }` — the matrix itself lives in the deck prose, never here.
165
+
166
+ **The manifest carries vocabulary and order only.** No threshold, no mapping table, no decision rule.
167
+ Those belong in the deck, where they are read as reasoning rather than applied as arithmetic.
168
+
169
+ **3. Put both files in your own store** and point a profile at the deck with `frameworkPath`. Client
170
+ rubrics deliberately do not live inside a checkout of this product.
171
+
172
+ **4. Check it before it is in force**, with the pre-flight. It opens both files exactly as a run would,
173
+ prints what they declare, and where the deck and the manifest disagree it names the band and says what
174
+ the deck did not do. It creates nothing, rates nothing and contacts nobody.
175
+
176
+ ```
177
+ clearotron framework skills/prelim-search/your-framework.md
178
+ ```
179
+
180
+ ```
181
+ Framework: skills/prelim-search/your-framework.md
182
+ deck /srv/clearotron-config/skills/prelim-search/your-framework.md
183
+ read from the configured store
184
+ manifest /srv/clearotron-config/skills/prelim-search/your-framework.manifest.json
185
+ read from the configured store
186
+
187
+ It declares itself "Your firm's clearance risk framework" (your-firm-2026), a bands-shaped
188
+ framework rating the company.
189
+
190
+ The ladder, highest risk first:
191
+ 1. Very High severe
192
+ 2. High high
193
+ 3. Moderate medium
194
+ 4. Manageable low
195
+
196
+ What the deck defines:
197
+ ✓ Very High Advise against adoption; the owner is likely to enforce.
198
+ ✗ High the section under this band's heading states no rungs — a bands-shaped
199
+ deck writes each rung as a top-level `- **Label.** text` bullet
200
+
201
+ Not ready:
202
+ 1 of 4 bands are named in the manifest and not defined in the deck. The profile screen shows
203
+ what the bands mean only when EVERY band is defined, so one miss empties the whole box.
204
+ ```
205
+
206
+ It exits 0 when the two agree and 1 when they do not, so it can gate a deployment. `clearotron
207
+ brandowner add --dry-run` prints the same report for the framework it would set.
208
+
209
+ **It also tells you which file answered.** Resolution looks in your store first and falls back to this
210
+ repository, and the repository ships decks under names a customer may well have chosen too. A deck that
211
+ went missing from your store is therefore replaced by ours rather than reported absent — same band
212
+ words, different rubric, nothing raised anywhere. When that happens the report says so, above the
213
+ verdict, and the profile screen writes a line to the log.
214
+
215
+ **Then open the profile screen** for a company using it, and confirm you see the framework's title, your
216
+ band names in your order, and the box explaining what each band means.
217
+
218
+ ### What is checked, and what is not
219
+
220
+ | | |
221
+ |---|---|
222
+ | The deck file exists | checked, and a run refuses without it |
223
+ | The manifest parses, and its keys and band labels are legal | checked, by name |
224
+ | The deck's headings and bullets match the manifest's bands | checked, by `clearotron framework` — and by the test suite, for every framework this installation can reach |
225
+ | Which file answered when your store and this repository both have one | reported by `clearotron framework`, and written to the log at view time |
226
+ | Whether the rubric is any good | **not checked, and cannot be** |
227
+
228
+ That last row is the one to hold on to. A framework is reasoned with on every search the company ever
229
+ runs, and nothing reads it for sense. **A framework that is subtly wrong produces confident ratings that
230
+ look exactly like right ones.** Have it read by whoever would sign the advice, before it is pointed at.
231
+
114
232
  ---
115
233
 
116
234
  ## 3. Client profiles
@@ -26,8 +26,8 @@ at runtime.
26
26
  This bounds the set at about 35 front doors rather than 100 stubs, and a stub is what rots.
27
27
  - **Decisions go in `docs/decisions/`.** A ruling gets a numbered ADR so the next reader finds it settled.
28
28
  - **No hand-kept changelogs, at any level.** Git holds the history, and a hand-kept log rots. A
29
- per-directory one was never written and stays banned. **Amended 2026-08-31 (owner ruling on tracker
30
- issue 2055: the landed release-pipeline decision is master, and the old doc is cleaned up to match
29
+ per-directory one was never written and stays banned. **Amended 2026-08-31 (ruling:
30
+ the landed release-pipeline decision is master, and the old doc is cleaned up to match
31
31
  it, never the machinery bent to the doc):** the root `CHANGELOG.md` is the release pipeline's
32
32
  MACHINE-COMPILED output — assembled by `scripts/release-version.mjs` from the pending release notes
33
33
  when a release is cut, plain-language-gated, and public-facing (the website receives it). It is not
@@ -19,7 +19,7 @@ clearance, and change the code. Nothing else.** In particular it carries no prod
19
19
  material, no worked internal mechanics for a lane, and no historical narrative.
20
20
 
21
21
  Four of the five documents above are withheld. They stay in this repository, which is the archive; they do
22
- not cross at the cut. **`CHANGELOG.md` left this list on 2026-08-31 (owner ruling:
22
+ not cross at the cut. **`CHANGELOG.md` left this list on 2026-08-31 (ruling:
23
23
  the landed release-pipeline decision is master).** The 803-word hand-written file this record reviewed was
24
24
  already absent; what carries the name now is the release pipeline's machine-compiled, plain-language,
25
25
  public-facing changelog — release history a reader of the public repository is meant to have, which the
@@ -38,7 +38,7 @@ neither is on disk, and neither was ever added or deleted in either repository's
38
38
  record that names enforcement which was never written is worse than one that names none, because a
39
39
  reader stops looking. What follows is what actually holds the line.
40
40
 
41
- **The drop list itself does not ship.** It stays with the archive by owner ruling, 2026-08-31, so the
41
+ **The drop list itself does not ship.** It stays with the archive by ruling, 2026-08-31, so the
42
42
  published tree carries no copy — and neither, today, does any other tree. `CUT_RECORD_PRESENT` is false
43
43
  everywhere the code runs.
44
44
 
@@ -1,5 +1,75 @@
1
1
  # clearotron-driver
2
2
 
3
+ ## 0.3.0-beta.1
4
+
5
+ ### Minor Changes
6
+
7
+ - f75266d: New: People, in the sidebar for anyone with Manage, lists who can use the installation, and adds a person.
8
+
9
+ Enter their email address, choose what they may do and what they can see, and save. A sentence under the form says what they will and will not see, before you save. They sign in the same way you do; Clearotron issues no passwords.
10
+
11
+ An installation that signs one person in on its own machine cannot hold a second. People says so, and links to how to put a login system in front of it.
12
+ - f75266d: New: Each person now has two permissions, Run clearances and Manage, instead of a staff or client role.
13
+
14
+ A person sees everything below the points they were given: the whole installation, an organisation, or a single company. Someone without Run has no New clearance. Someone without Manage has no People page and cannot add companies.
15
+
16
+ The company menu groups companies by organisation when you can see more than one. Each organisation has its own Generic, listed first and marked Default. The top bar names your organisation when you can see exactly one.
17
+ - f75266d: New: Clearances with no company set up now have a daily allowance per organisation, 20 unless the Generic profile sets another.
18
+
19
+ Anyone with Run clearances on a whole organisation can start them. One organisation's clearances never use up another's, and a person with access to the whole installation is not limited.
20
+ - c4d2768: New: You can now choose which company a clearance is for on the page itself, and set up new companies in the browser.
21
+
22
+ Those screens used to refuse to render until a company was chosen. They told you to pick one at the top left. That is not where the control is when the sidebar is collapsed. They now show you the companies instead. Each one says what it sells, how many marketplaces it covers, and which territories it defaults to.
23
+
24
+ Setting up a company is a page, not a document. It needs a name. Everything else has a default, and the screen says what that default is. It refuses before writing anything, and says why. A name it cannot make a key from, a key already in use, or an email address another company claims. A company you create can run its first search straight away, with no restart, and stop or cancel that search like any other.
25
+
26
+ The product now says company throughout. It used to say brand owner, account, client and customer for the same thing. The firm running the installation is named separately, in the top bar.
27
+
28
+ Companies created through the settings page were saved without a risk framework. Their matters were then rated under the house default, with nothing on screen saying so. Every company created now carries one, and says which.
29
+ - f75266d: New: The setup wizard now asks for the organisation's name after the sign-in address, and the person who installs starts with access to everything.
30
+
31
+ `clearotron grant add` sets a person's two permissions with `--run` and `--manage`; with neither, the person can look and start nothing.
32
+ - f75266d: For operators: Who may sign in is now decided by each person's own entry in the guest list, and nothing else. On a local installation, the next `clearotron start` gives whoever signs in access to everything if the guest list has no `people` section.
33
+
34
+ - For operators: Any other installation with people on it needs one edit to its guest list (`CLEAROTRON_ACCESS_FILE`) before upgrading.
35
+ - For operators: Anyone admitted because of their email domain needs an entry under a new `people` section: `"everything": true`, `"run": true`, `"manage": true`.
36
+ - For operators: Each person who starts clearances needs `"run": true`, and each person who adds people or companies needs `"manage": true`. A person with no entry can see what their access covers, and start nothing.
37
+ - For operators: An organisation whose `accounts` is `"*"` needs the list of companies it holds instead. Until then the portal refuses to start, and names the entry. A company may be listed under one organisation only.
38
+ - For operators: `PORTAL_STAFF_DOMAINS` is ignored from this version on, and the portal says so at startup.
39
+ - For operators: After upgrading, sign in to check, and run `clearotron connect` again for each person whose assistant uses a key.
40
+
41
+ ### Patch Changes
42
+
43
+ - 8ff52d4: Fixed: Installed under a folder named with `#` or `%`, setup's register check now reads the provider's cost instead of calling it unknown. It built the address of the provider's own declaration by hand, and those characters broke it. It now uses the address Node builds, which is also what Windows needs.
44
+ - 350e0ed: New: `clearotron framework <your-framework.md>` reads a risk framework and its manifest, and reports what they declare.
45
+
46
+ Run it before either rates a matter. It prints the ladder in the framework's own order, the company the deck names, and the shape it is. Where the deck and the manifest disagree, it names the band and says what the deck did not do. It creates nothing, rates nothing and contacts nobody, and it exits non-zero when the pair is not ready. `clearotron brandowner add --dry-run` prints the same report.
47
+
48
+ Getting the deck's shape wrong used to fail quietly. The profile screen showed the framework's title and your band colours, and silently omitted the box saying what the bands mean. The new command answers that question directly, using the screen's own read of the deck.
49
+
50
+ Fixed: a framework served from the product's own files, while you have a configuration store set, now says so.
51
+
52
+ Your store is looked in first, and the product's files answer when it is silent. The product ships decks under names you may also have chosen. So a deck that went missing from your store was replaced by ours rather than reported absent. Same band words, different rubric, nothing raised anywhere. The profile screen now writes one line naming what happened, and the new command reports it.
53
+
54
+ Fixed: the house triage ladder's profile page explains its bands again.
55
+
56
+ Its band sections stated their meanings as plain paragraphs, which the screen does not read. Every company without a framework of its own saw band colours and no explanation. The wording is unchanged.
57
+ - cfb9a9f: Fixed: `clearotron doctor` now says which companies your portal's trigger key can start.
58
+
59
+ The key carries a list of the companies it may start runs for. A company added after the key was minted is outside it. Doctor reported the key's expiry and never its coverage. So the one command whose job is to tell you what a machine is configured for said nothing about it.
60
+
61
+ It reads the roster the services read, not the one a command-line process resolves, and it says which. Those two can disagree, and when they do the difference is the whole answer.
62
+
63
+ The line is a note, not a failure. Your portal takes a fresh credential at the start of every call, so a company outside the key is not normally refused. It is refused when the portal cannot take a fresh one, and the line says so and gives you the command to widen the key.
64
+ - ba2899b: Fixed: On reports searched through Signa, each register finding now links to the office's own page for that record, where the office publishes one. Singapore publishes no such page, so its registrations are cited by number, and the report says why. A number that an office's page cannot take is cited the same way.
65
+ - c4d2768: New: The configuration guide now explains how to write your own risk framework, step by step.
66
+
67
+ It gives the manifest in full. It says which fields are required, and what each one may contain. It shows the shape a deck needs for the profile screen to explain your bands.
68
+
69
+ That shape was undocumented, and getting it wrong fails quietly. The screen still shows the framework's title and your band colours. The box saying what the bands mean does not appear at all.
70
+
71
+ The guide also says what is checked and what is not. Nothing reads your rubric for sense. A framework that is subtly wrong produces confident ratings that look exactly like right ones.
72
+
3
73
  ## 0.2.4
4
74
 
5
75
  ### Patch Changes