clearotron 0.4.0-beta.1 → 0.4.0-beta.3

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 (265) hide show
  1. package/INSTALL.md +4 -31
  2. package/README.md +5 -5
  3. package/THIRD-PARTY-NOTICES.md +5 -5
  4. package/bin/brandowner.mjs +1 -1
  5. package/bin/onboard.mjs +18 -74
  6. package/bin/start.mjs +58 -14
  7. package/bin/update.mjs +4 -1
  8. package/build-info.json +2 -2
  9. package/demo/full-country-search/run/_driver/search-policy.json +1 -1
  10. package/demo/full-country-search/run/status.json +1 -1
  11. package/demo/global-preliminary-search/run/_driver/search-policy.json +1 -1
  12. package/demo/global-preliminary-search/run/status.json +1 -1
  13. package/demo/knockout-search/run/_driver/search-policy.json +1 -1
  14. package/demo/knockout-search/run/status.json +1 -1
  15. package/demo/multi-country-focus-search/run/_driver/search-policy.json +1 -1
  16. package/demo/multi-country-focus-search/run/status.json +1 -1
  17. package/docs/README.md +1 -0
  18. package/docs/SECURITY-OWASP.md +298 -0
  19. package/docs/SECURITY.md +18 -13
  20. package/docs/architecture/02-architecture.md +5 -5
  21. package/docs/architecture/03-run-lifecycle.md +28 -29
  22. package/docs/architecture/04-configuration-reference.md +3 -4
  23. package/docs/architecture/05-config-governance.md +5 -7
  24. package/docs/architecture/06-operations-runbook.md +0 -12
  25. package/docs/architecture/07-quality-and-audit.md +12 -13
  26. package/docs/architecture/09-security-and-data.md +9 -4
  27. package/driver/CHANGELOG.md +69 -0
  28. package/driver/ask-ledger.mjs +2 -0
  29. package/driver/authority-trees.mjs +90 -8
  30. package/driver/band-shape.mjs +5 -5
  31. package/driver/case-law-ledger.mjs +19 -3
  32. package/driver/claim-liveness.mjs +30 -1
  33. package/driver/clearance-variants-record.mjs +10 -3
  34. package/driver/common-law-receipts.mjs +62 -12
  35. package/driver/commonlaw-carry.mjs +1 -1
  36. package/driver/compare.mjs +2 -2
  37. package/driver/connotation-search.mjs +30 -101
  38. package/driver/contract-arm2-baseline.json +6 -5
  39. package/driver/contract-e3-backlog.mjs +60 -60
  40. package/driver/contract-vocabulary.mjs +38 -31
  41. package/driver/coverage-form-io.mjs +13 -1
  42. package/driver/coverage-form.mjs +48 -4
  43. package/driver/coverage-ledger.mjs +17 -7
  44. package/driver/cross-check-wait.mjs +43 -0
  45. package/driver/declination-call.mjs +33 -9
  46. package/driver/declination-tool.mjs +27 -15
  47. package/driver/deferral-row.mjs +139 -0
  48. package/driver/degraded-parts.mjs +377 -0
  49. package/driver/deliver-trigger.sh +1 -1
  50. package/driver/demo-container.mjs +4 -1
  51. package/driver/dev-portal.mjs +4 -4
  52. package/driver/dispatch-record.mjs +2 -2
  53. package/driver/driver.config.mjs +201 -94
  54. package/driver/effective-scope.mjs +58 -2
  55. package/driver/engine/anthropic-agent.mjs +103 -27
  56. package/driver/engine/auth.mjs +2 -2
  57. package/driver/engine/child-record.mjs +11 -0
  58. package/driver/engine/cli-version.mjs +4 -3
  59. package/driver/engine/common.mjs +17 -2
  60. package/driver/engine/deny-authority-write.mjs +3 -1
  61. package/driver/engine/engine-env.mjs +174 -0
  62. package/driver/engine/engine-spawn.mjs +179 -0
  63. package/driver/engine/mcp/band-server.mjs +1 -1
  64. package/driver/engine/mcp/codex-config.mjs +112 -3
  65. package/driver/engine/mcp/declination-server.mjs +9 -8
  66. package/driver/engine/mcp/dispositions-server.mjs +2 -1
  67. package/driver/engine/mcp/fetch-server.mjs +14 -11
  68. package/driver/engine/mcp/gather-config.mjs +22 -10
  69. package/driver/engine/mcp/perplexity-server.mjs +96 -16
  70. package/driver/engine/mcp/probe-server.mjs +43 -21
  71. package/driver/engine/mcp/public-fetch.mjs +195 -0
  72. package/driver/engine/mcp/recording-server.mjs +78 -13
  73. package/driver/engine/mcp/stdio-server.mjs +12 -1
  74. package/driver/engine/mcp/supplemental.mjs +13 -13
  75. package/driver/engine/mcp/unit-note-server.mjs +50 -1
  76. package/driver/engine/openai-agent.mjs +109 -28
  77. package/driver/engine/probe.mjs +152 -19
  78. package/driver/enqueue-schema.mjs +3 -3
  79. package/driver/findings-model.mjs +64 -13
  80. package/driver/floor-duty.mjs +21 -5
  81. package/driver/form-neighbourhood.mjs +5 -2
  82. package/driver/framework-method.mjs +304 -0
  83. package/driver/gateway.mjs +44 -16
  84. package/driver/hand-off-exits.mjs +129 -0
  85. package/driver/jx.mjs +8 -5
  86. package/driver/knockout-assess-record.mjs +7 -1
  87. package/driver/knockout-frame-record.mjs +96 -34
  88. package/driver/knockout-review-record.mjs +7 -2
  89. package/driver/matter-frame-record.mjs +31 -2
  90. package/driver/methodology-witness.mjs +6 -3
  91. package/driver/named-band.mjs +1 -1
  92. package/driver/owner-use-check.mjs +45 -10
  93. package/driver/package.json +1 -1
  94. package/driver/partial-payload-baseline.json +4 -0
  95. package/driver/phase0.mjs +2 -2
  96. package/driver/pipeline-knockout.mjs +150 -36
  97. package/driver/pipeline.mjs +431 -606
  98. package/driver/placement-carry.mjs +20 -1
  99. package/driver/placement-form-io.mjs +18 -4
  100. package/driver/placement-form.mjs +52 -3
  101. package/driver/placement-union.mjs +32 -5
  102. package/driver/portal-report.mjs +3 -3
  103. package/driver/portal-service.mjs +4 -3
  104. package/driver/portal-static.mjs +11 -18
  105. package/driver/profile-page.html +5 -5
  106. package/driver/profiles.mjs +5 -5
  107. package/driver/progress.mjs +11 -4
  108. package/driver/provider-usage.mjs +1 -1
  109. package/driver/publish/index.mjs +94 -22
  110. package/driver/publish/knockout.mjs +149 -15
  111. package/driver/publish/profiles-page.mjs +2 -0
  112. package/driver/publish/publish-inputs.mjs +10 -4
  113. package/driver/publish/render-knockout.mjs +85 -31
  114. package/driver/publish/render.mjs +12 -7
  115. package/driver/publish/report-data.mjs +2 -0
  116. package/driver/publish/search-depth.mjs +77 -4
  117. package/driver/publish/templates/report.css +1 -1
  118. package/driver/publish/xlsx.mjs +34 -15
  119. package/driver/reasoning-tripwires.mjs +2 -160
  120. package/driver/recall-receipt.mjs +27 -0
  121. package/driver/record-carry.mjs +24 -8
  122. package/driver/reference-score.mjs +142 -20
  123. package/driver/reference-strip-signatures.mjs +11 -1
  124. package/driver/register-count.mjs +5 -3
  125. package/driver/register-digest-record.mjs +13 -0
  126. package/driver/register-plan.mjs +163 -78
  127. package/driver/register-records.mjs +27 -3
  128. package/driver/register-served.mjs +91 -0
  129. package/driver/registration-scripts.mjs +22 -0
  130. package/driver/remedy-accounting.mjs +38 -9
  131. package/driver/repair-composers.mjs +19 -3
  132. package/driver/repair-contract.mjs +1 -1
  133. package/driver/repairs.mjs +5 -2
  134. package/driver/reviewer-open-points.mjs +33 -0
  135. package/driver/rule-shape.mjs +8 -8
  136. package/driver/run-economics.mjs +2 -2
  137. package/driver/run-integrity.mjs +2 -2
  138. package/driver/runner.mjs +17 -5
  139. package/driver/scope-facts.mjs +89 -12
  140. package/driver/score-redaction.mjs +616 -0
  141. package/driver/search-policy.mjs +17 -0
  142. package/driver/skills/README.md +2 -2
  143. package/driver/skills/clearance-common-law/SKILL.md +29 -36
  144. package/driver/skills/clearance-common-law/perplexity-prompts.md +5 -27
  145. package/driver/skills/clearance-register/SKILL.md +14 -23
  146. package/driver/skills/clearance-register/digest.md +15 -31
  147. package/driver/skills/clearance-register/providers/corsearch.md +1 -1
  148. package/driver/skills/clearance-register/register-recipes.md +17 -65
  149. package/driver/skills/clearance-register/unit.md +36 -47
  150. package/driver/skills/clearance-search/SKILL.md +8 -10
  151. package/driver/skills/clearance-search/delivery-contract.md +0 -7
  152. package/driver/skills/clearance-search/firm-wide-reasoning.md +5 -4
  153. package/driver/skills/clearance-search/phase2-execution.md +6 -7
  154. package/driver/skills/clearance-search/risk-framework-triage.md +2 -2
  155. package/driver/skills/clearance-search/synthesis-rules.md +5 -5
  156. package/driver/skills/clearance-search/template-formatting.md +2 -2
  157. package/driver/skills/clearance-variants/SKILL.md +6 -6
  158. package/driver/skills/clearance-variants/transliteration-scripts.md +1 -1
  159. package/driver/skills/knockout-assess/SKILL.md +33 -1
  160. package/driver/skills/knockout-frame/SKILL.md +42 -10
  161. package/driver/skills/matter-frame/SKILL.md +12 -5
  162. package/driver/skills/narrative-refutation/SKILL.md +3 -1
  163. package/driver/skills/placement-inquiry/SKILL.md +4 -0
  164. package/driver/stage-context.mjs +23 -2
  165. package/driver/stages-knockout.mjs +114 -79
  166. package/driver/stages.mjs +88 -53
  167. package/driver/stray-artifacts.mjs +9 -5
  168. package/driver/suite-census.json +734 -152
  169. package/driver/synthesis-record.mjs +26 -4
  170. package/driver/systemd/README.md +5 -6
  171. package/driver/systemd/clearotron-worker.service +6 -1
  172. package/driver/tokens.mjs +2 -2
  173. package/driver/unit-inventory.mjs +58 -82
  174. package/driver/variant-manifest-model.mjs +21 -2
  175. package/driver/verify-knockout.mjs +163 -4
  176. package/driver/verify.mjs +40 -14
  177. package/driver/web-grid.mjs +150 -0
  178. package/driver/withheld-families.mjs +113 -4
  179. package/driver/worker-heartbeat.mjs +13 -2
  180. package/mcp-server/CHANGELOG.md +8 -0
  181. package/mcp-server/README.md +1 -1
  182. package/mcp-server/lib/knockout.mjs +1 -1
  183. package/mcp-server/lib/ops.mjs +10 -10
  184. package/mcp-server/lib/scrub.mjs +1 -1
  185. package/mcp-server/lib/trace.mjs +3 -2
  186. package/mcp-server/package.json +1 -1
  187. package/mcp-server/server.mjs +2 -2
  188. package/node_modules/brace-expansion/index.js +78 -22
  189. package/node_modules/brace-expansion/package.json +1 -1
  190. package/node_modules/readdir-glob/node_modules/brace-expansion/index.js +78 -22
  191. package/node_modules/readdir-glob/node_modules/brace-expansion/package.json +1 -1
  192. package/package.json +1 -1
  193. package/portal-ui/dist/assets/{index-D_O_55vK.js → index-BPAUjcI0.js} +1 -1
  194. package/portal-ui/dist/assets/{index-5CCwiJG7.css → index-D2wrw9cH.css} +18 -2
  195. package/portal-ui/dist/assets/plus-jakarta-sans-BUCHxqJ-.woff2 +0 -0
  196. package/portal-ui/dist/index.html +2 -15
  197. package/portal-ui/package.json +2 -2
  198. package/providers/_shared/enumerate.mjs +155 -50
  199. package/providers/_shared/execute-plan.mjs +62 -16
  200. package/providers/_shared/ledger.mjs +1 -1
  201. package/providers/_shared/plan-guards.mjs +7 -0
  202. package/providers/clarivate/src/core.js +54 -6
  203. package/providers/corsearch/README.md +1 -2
  204. package/providers/corsearch/src/core.js +2 -2
  205. package/providers/jx/src/core.js +0 -1
  206. package/providers/jx/src/judge.js +0 -1
  207. package/providers/jx/src/nativeread.js +0 -1
  208. package/providers/oauth-mcp-bridge/CHANGELOG.md +8 -0
  209. package/providers/oauth-mcp-bridge/README.md +8 -7
  210. package/providers/oauth-mcp-bridge/bridge.mjs +21 -5
  211. package/providers/oauth-mcp-bridge/package.json +1 -1
  212. package/providers/oauth-mcp-bridge/systemd/courtlistener-mcp.service +1 -1
  213. package/providers/oauth-mcp-bridge/warm-server.mjs +16 -5
  214. package/providers/perplexity/src/core.js +246 -21
  215. package/providers/signa/src/core.js +64 -39
  216. package/scripts/README.md +2 -1
  217. package/scripts/added-reference-check.mjs +2 -1
  218. package/scripts/backup-recall-stores.mjs +5 -5
  219. package/scripts/demo-evidence.mjs +2 -1
  220. package/scripts/drive-env-check.mjs +2 -1
  221. package/scripts/e2e-first-time.mjs +477 -0
  222. package/scripts/e2e-scenario-ops.mjs +464 -0
  223. package/scripts/e2e.mjs +215 -29
  224. package/scripts/env-audit.mjs +8 -0
  225. package/scripts/freeze-example-run.mjs +7 -7
  226. package/scripts/generated-files-are-current.mjs +2 -1
  227. package/scripts/hand-off-exits-probe.mjs +75 -0
  228. package/scripts/import-cycle-check.mjs +2 -1
  229. package/scripts/merge-shape-check.mjs +2 -1
  230. package/scripts/mint-reference-strip-backlog.mjs +36 -2
  231. package/scripts/package-size-budget.mjs +2 -1
  232. package/scripts/record-carry-probe.mjs +1 -1
  233. package/scripts/release-install-check.mjs +4 -1
  234. package/scripts/release-note-required.mjs +44 -10
  235. package/scripts/render-check.mjs +5 -2
  236. package/scripts/report-offline-render-check.mjs +169 -0
  237. package/scripts/score.mjs +171 -27
  238. package/scripts/strip-titles-and-attributions.mjs +2 -1
  239. package/scripts/strip-tracker-citations.mjs +2 -1
  240. package/scripts/test-full.mjs +2 -1
  241. package/scripts/test-run.mjs +10 -3
  242. package/scripts/third-party-notices.mjs +3 -1
  243. package/scripts/travelling-predicates.mjs +1 -1
  244. package/scripts/writing-standard-check.mjs +2 -1
  245. package/shared/brand-fonts.mjs +59 -0
  246. package/shared/brand.mjs +5 -5
  247. package/shared/browser-temp-root.mjs +3 -2
  248. package/shared/doctrine-overlay.mjs +1 -1
  249. package/shared/driver-dir.mjs +27 -2
  250. package/shared/fonts/OFL-fira-code.txt +93 -0
  251. package/shared/fonts/OFL-plus-jakarta-sans.txt +93 -0
  252. package/shared/fonts/README.md +37 -0
  253. package/shared/fonts/fira-code.woff2 +0 -0
  254. package/shared/fonts/plus-jakarta-sans.woff2 +0 -0
  255. package/shared/identifier-scan.mjs +31 -1
  256. package/shared/npm-cli.mjs +23 -0
  257. package/shared/os-advice.mjs +14 -2
  258. package/shared/path-seps.mjs +42 -0
  259. package/shared/process-table.mjs +71 -3
  260. package/shared/reference-guard-classes.mjs +30 -6
  261. package/shared/root-doc-commands.mjs +6 -2
  262. package/shared/running-start.mjs +24 -7
  263. package/shared/scope.mjs +2 -2
  264. package/shared/wsl.mjs +1 -1
  265. package/driver/known-conflicts.mjs +0 -345
package/INSTALL.md CHANGED
@@ -45,23 +45,10 @@ run is [mcp-server/CONNECT.md](mcp-server/CONNECT.md), and why something is the
45
45
  Node 20, or on 22.0 through 22.12, the install succeeds and the first US search fails with
46
46
  `ERR_UNKNOWN_BUILTIN_MODULE`, saying nothing about Node. `package.json` declares the floor, the
47
47
  install refuses below it before writing anything, and `nvm use` picks the pin up.
48
- - **macOS, Linux, or native Windows for the demo; WSL2 for a clearance.** `npx clearotron
49
- demo` runs anywhere Node does, native Windows included. A real clearance does not: the engine spawns
50
- each stage with POSIX path and process semantics, so on native Windows a clearance is refused before it
51
- starts, even with the program installed. Native Windows clearances are planned for a later
52
- release. Until then, on Windows,
53
- `wsl --install -d Ubuntu`, then `wsl -d Ubuntu`, and work through this page
54
- from **inside** that distribution. Name it: plain `wsl` can open a minimal image with no apt, no
55
- curl and no bash, and everything below assumes Ubuntu. A fresh Ubuntu has no Node at all, and
56
- apt's package is below the floor above, so `npx` answers "not found" before anything of ours runs.
57
- From the Ubuntu prompt:
58
-
59
- ```bash
60
- sudo apt update && sudo apt install -y curl
61
- curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.1/install.sh | bash
62
- . "$HOME/.nvm/nvm.sh" && nvm install 22 # 22.13 or newer, per the floor above
63
- npx clearotron install # offers to install the reasoning program if the machine has none, and shows you how to sign it in
64
- ```
48
+ - **macOS, Linux or Windows.** On Windows everything runs natively in PowerShell, with no WSL2, no Git
49
+ and no administrator rights: `npm install --global clearotron`, then `clearotron install`, which installs
50
+ the AI program you choose into your own user folder. On Windows and macOS Clearotron runs in the window
51
+ you start it from, and closing the window stops it.
65
52
 
66
53
  Run from `npx`, the install first installs Clearotron under `~/.local`, as `npm install -g --prefix
67
54
  ~/.local` would, and finishes from there. That way the `clearotron` command and your assistant's connection
@@ -163,20 +150,6 @@ with `sudo`; install under your home instead: `npm install -g clearotron --prefi
163
150
  **Upgrade production to stables only.** What each channel promises and how often one is cut:
164
151
  **[docs/RELEASES.md](docs/RELEASES.md)**.
165
152
 
166
- **Upgrading an install made before 0.2.2: pin the agent id first.** The default agent id changed from
167
- `clawdi` to `localagent`, and that id is part of a path — your runs live under
168
- `<workspaceRoot>/workspace-<agent>/studio/clearance-search/`. If you never set an agent id, the upgraded
169
- install reads a workspace that does not exist yet, and an empty workspace looks like an account with no
170
- runs rather than like a misconfiguration. Set **both** names in your environment file before starting
171
- it, because the register-search servers read their own:
172
-
173
- ```
174
- CLEAROTRON_DEFAULT_AGENT=clawdi
175
- CLEAROTRON_GATHER_AGENT=clawdi
176
- ```
177
-
178
- If your environment file already sets them, nothing changes. A first-time install needs neither.
179
-
180
153
  The rest of this section is about the two ways a package reaches you, which is a separate question from
181
154
  which version it is.
182
155
 
package/README.md CHANGED
@@ -42,14 +42,14 @@ exits, and keeps the folder the same way.
42
42
  npx clearotron install
43
43
  ```
44
44
 
45
- Node 22.13 or newer, on macOS or Linux. It needs no root: it puts the program under `~/.local`, with the
45
+ Node 22.13 or newer, on macOS, Linux or Windows. It needs no root: it puts the program under `~/.local`, with the
46
46
  `clearotron` command in `~/.local/bin`, then asks one question at a time and checks each credential
47
47
  before it saves it. Every command below uses the `clearotron` it installed — the same channel you
48
48
  installed from — so if `~/.local/bin` is not on your `PATH` yet, type the full path,
49
- `~/.local/bin/clearotron`, instead. **On Windows the demo above runs natively; a
50
- real clearance needs WSL2.** Native Windows clearances are planned for a later release. Until then the
51
- engine does not run on native Windows: it spawns each stage with POSIX path and process semantics, so
52
- a clearance is refused there before it starts.
49
+ `~/.local/bin/clearotron`, instead. **On Windows it runs natively, from PowerShell: no WSL2, no Git and
50
+ no administrator rights.** Install it with `npm install --global clearotron`, then run `clearotron install`.
51
+ It runs in the window you start it from, as on a Mac: closing the window stops a search, and the next
52
+ `clearotron start` picks it up again.
53
53
 
54
54
  Installing with npm's own global form instead is covered in [INSTALL.md](INSTALL.md) §2.
55
55
 
@@ -110,7 +110,7 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
110
110
  SOFTWARE.
111
111
  ```
112
112
 
113
- ## @modelcontextprotocol/sdk@1.30.0
113
+ ## @modelcontextprotocol/sdk@1.30.1
114
114
 
115
115
  - **Licence declared:** `MIT`
116
116
  - **Repository:** https://github.com/modelcontextprotocol/typescript-sdk
@@ -566,7 +566,7 @@ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
566
566
  SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
567
567
  ```
568
568
 
569
- ## brace-expansion@1.1.18
569
+ ## brace-expansion@1.1.21
570
570
 
571
571
  - **Licence declared:** `MIT`
572
572
  - **Repository:** git://github.com/juliangruber/brace-expansion
@@ -596,7 +596,7 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
596
596
  SOFTWARE.
597
597
  ```
598
598
 
599
- ## brace-expansion@2.1.4
599
+ ## brace-expansion@2.1.7
600
600
 
601
601
  - **Licence declared:** `MIT`
602
602
  - **Repository:** git://github.com/juliangruber/brace-expansion
@@ -1968,7 +1968,7 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
1968
1968
  SOFTWARE.
1969
1969
  ```
1970
1970
 
1971
- ## fast-uri@3.1.7
1971
+ ## fast-uri@3.1.8
1972
1972
 
1973
1973
  - **Licence declared:** `BSD-3-Clause`
1974
1974
  - **Repository:** https://github.com/fastify/fast-uri
@@ -2626,7 +2626,7 @@ OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
2626
2626
  PERFORMANCE OF THIS SOFTWARE.
2627
2627
  ```
2628
2628
 
2629
- ## ip-address@10.5.0
2629
+ ## ip-address@10.7.2
2630
2630
 
2631
2631
  - **Licence declared:** `MIT`
2632
2632
  - **Repository:** https://github.com/beaugunderson/ip-address
@@ -112,7 +112,7 @@ const USAGE = `
112
112
  --name the brand owner's legal name (required)
113
113
  --domains comma-separated email domains that resolve to this owner
114
114
  --platforms comma-separated marketplaces their searches cover
115
- omitted ⇒ the Generic default's platforms are applied and named in the output
115
+ omitted ⇒ none — searches use the general web, plus any stores chosen for each matter
116
116
  --framework their risk framework, as skills/clearance-search/<file>.md
117
117
  omitted ⇒ the Generic default is applied and named in the output
118
118
  --industry free text, shown on their profile
package/bin/onboard.mjs CHANGED
@@ -103,6 +103,8 @@ import { resolveAuthMode, CLOUD_SWITCH, CLOUD_SETTINGS, CLOUD_SECRETS, cloudsSwi
103
103
  import { isInsideCheckout } from "../shared/inside-checkout.mjs"; // — one copy of the rule, and it is testable
104
104
  import { packagedBuild as sharedPackagedBuild } from "../shared/packaged-build.mjs"; // — one reader of build-info.json, reachable from the driver
105
105
  import { processTable } from "../shared/process-table.mjs"; // — /proc is not the only box
106
+ import { hasSep } from "../shared/path-seps.mjs"; // a Windows path is written with "\"
107
+ import { npmInvocation } from "../shared/npm-cli.mjs"; // npm without a shell, which Windows needs
106
108
  import { programsFromAnotherCheckout } from "../shared/checkout-move.mjs";
107
109
  import { entrypointOf } from "../driver/systemd/install-census.mjs"; // one ExecStart parser
108
110
  import { overlayReport, renderOverlayReport } from "../shared/doctrine-overlay.mjs"; // — the doctor reports the overlay
@@ -790,7 +792,8 @@ export async function askSignIn(io, { localAccount = "user", existing = null } =
790
792
  * Was `resolveClaudeBin`. The body never had anything claude-specific in it; the NAME was the last place
791
793
  * this file still assumed one engine, and a name that lies is how the second adapter stayed invisible.
792
794
  */
793
- export function resolveEngineBin(bin, { env = process.env, wsl = null, onWindowsDrive = null, engine = null, enginesDir = undefined } = {}) {
795
+ export function resolveEngineBin(bin, { env = process.env, wsl = null, onWindowsDrive = null, engine = null, enginesDir = undefined,
796
+ platform = process.platform } = {}) {
794
797
  // A VIEW OVER THE DRIVER'S ONE RESOLVER (driver.config.mjs resolveEngineProgram), which every other
795
798
  // reader asks too: the run door, the inventory the portal reads, and both adapters. This used to be a
796
799
  // second PATH walk of its own, and it was the only one that knew to pass over a Windows copy under WSL;
@@ -803,14 +806,14 @@ export function resolveEngineBin(bin, { env = process.env, wsl = null, onWindows
803
806
  // Linux runner without root, so an arm that could only supply a PATH could never drive the skip.
804
807
  const id = engine ?? Object.keys(ENGINE_BINARIES).find((k) => ENGINE_BINARIES[k].fallback === bin) ?? DEFAULT_ENGINE_ID;
805
808
  const spec = ENGINE_BINARIES[id];
806
- const r = resolveEngineProgram(id, { env: { ...env, [spec.env]: bin }, wsl, onWindowsDrive, enginesDir });
809
+ const r = resolveEngineProgram(id, { env: { ...env, [spec.env]: bin }, wsl, onWindowsDrive, enginesDir, platform });
807
810
  const found = { source: r.source, version: r.version, rejected: r.rejected, skipped: r.skipped, windowsShim: r.windowsShim };
808
811
  // A RELATIVE path is the trap for both adapters: stage subprocesses run with cwd set to the RUN
809
812
  // DIRECTORY (driver/engine/common.mjs resolveSpawnCwd), so it resolves against a directory that did not
810
813
  // exist at setup time. Reported with its absolute form from here, which is what the wizard then uses.
811
814
  if (r.relative) { const abs = resolve(bin); return { path: abs, executable: isExec(abs), relative: true, ...found }; }
812
815
  // A PATH SOMEBODY TYPED IS NOT OVERRULED, only reported: the resolver never falls through from it.
813
- if (!r.resolved && r.explicit && bin.includes("/")) return { path: resolve(bin), executable: false, relative: false, ...found };
816
+ if (!r.resolved && r.explicit && hasSep(bin, platform)) return { path: resolve(bin), executable: false, relative: false, ...found };
814
817
  return { path: r.resolved, executable: Boolean(r.resolved), relative: false, ...found };
815
818
  }
816
819
 
@@ -927,30 +930,11 @@ export function windowsShimNote(skipped, bin) {
927
930
  const isExec = (p) => { try { accessSync(p, constants.X_OK); return statSync(p).isFile(); } catch { return false; } };
928
931
 
929
932
  /**
930
- * What doctor says about the engine on a platform the run door refuses outright, or `null` where the
931
- * ordinary binary checks apply.
933
+ * How to leave demo mode. The first line is the same everywhere; the second differs by whether the
934
+ * machine has a background form. Windows has none, so there it names closing the window and starting
935
+ * again, where elsewhere it names restarting the services and the file background services read.
932
936
  *
933
- * `platform` IS INJECTABLE, and for the reason `preflightEngineBinary` gives at its own refusal: the
934
- * population this protects is the one that cannot run this suite to find out. An arm on a Linux runner
935
- * has to be able to ask what a Windows reader is shown, or the Windows text is asserted by nobody —
936
- * and reading `process.platform` inside the caller would make that arm read source instead of driving
937
- * the answer.
938
- */
939
- export function platformEngineRefusal({ platform = process.platform } = {}) {
940
- if (platform !== "win32") return null;
941
- return "this engine does not run on native Windows — stage subprocesses are spawned with POSIX path "
942
- + "and process semantics, so the run door refuses before it reads PATH. Run it under WSL2, or in "
943
- + "the devcontainer. The demo works here as it is: it replays finished reports and needs no engine.";
944
- }
945
-
946
- /**
947
- * How to leave demo mode, which is not the same instruction everywhere.
948
- *
949
- * On the platform the run door refuses, the standard advice is a loop: install a CLI the reader may
950
- * already have, then restart a service — neither of which can change the answer, because the refusal
951
- * is about the platform rather than the program. Naming WSL2 is the only instruction that ends it.
952
- *
953
- * ELSEWHERE IT NAMES SETUP, WHICH DOES THE WORK NOW. This used to say to install the CLI by hand with
937
+ * IT NAMES SETUP, WHICH DOES THE WORK NOW. This used to say to install the CLI by hand with
954
938
  * `npm install -g`, then run `claude` to sign in, and to restart the engine service so it re-read its PATH.
955
939
  * Setup offers to install the program itself, into a folder that is not on PATH, so the bare `claude` it
956
940
  * named was a command the reader's shell does not have; and a run finds that copy without re-reading PATH.
@@ -961,16 +945,16 @@ export function platformEngineRefusal({ platform = process.platform } = {}) {
961
945
  */
962
946
  export function leaveDemoAdvice(engSpec, { platform = process.platform, command = reachableCommand("install"),
963
947
  startCommand = reachableCommand("start") } = {}) {
948
+ const first = `To leave demo: run \`${command}\`. It offers to install ${engSpec.vendor}'s CLI if this machine has none, `
949
+ + "asks how it is paid for, and proves it with one turn; if the CLI is not signed in, it names the command that signs it in.";
950
+ // WINDOWS HAS NO BACKGROUND FORM, so the second line there names the one restart it has: the window.
964
951
  if (platform === "win32") {
965
- return [`To leave demo on Windows: run the product under WSL2, or in the devcontainer. Installing `
966
- + `${engSpec.vendor}'s CLI natively will not change this — the run door refuses on the platform, `
967
- + "not on the program."];
952
+ return [first, "If Clearotron is already running, close its window and start it again: it reads the settings setup writes when it starts."];
968
953
  }
969
954
  // BACKGROUND SERVICES ARE THE EXCEPTION to "restart them": they read `~/.env`, which setup does not write,
970
955
  // and `start --background` only adds lines to it (see programDisagreement below, which says the same).
971
956
  return [
972
- `To leave demo: run \`${command}\`. It offers to install ${engSpec.vendor}'s CLI if this machine has none, `
973
- + "asks how it is paid for, and proves it with one turn; if the CLI is not signed in, it names the command that signs it in.",
957
+ first,
974
958
  "If Clearotron's services are already running, restart them afterwards: they read the settings setup writes "
975
959
  + "when they start, and the portal reports what the engine saw when it last started. Background services "
976
960
  + `read \`~/.env\` instead, which setup does not write, and \`${startCommand} --background\` adds to it only the `
@@ -1813,27 +1797,6 @@ export async function runCheck() {
1813
1797
  const seen = versionOf(bin);
1814
1798
  const copyWords = (b) => `${b.source === "installed" ? "the copy Clearotron installed"
1815
1799
  : b.source === "explicit" ? `set in ${engSpec.env}` : "on PATH"}${seen ? `, version ${seen}` : ", version not read"}`;
1816
- // ── NATIVE WINDOWS IS ANSWERED HERE, BEFORE ANY PATH IS RESOLVED OR REPORTED ──────────────────
1817
- //
1818
- // `resolveEngineBin` tests a candidate with `accessSync(X_OK)` and `isFile()`. Windows has no
1819
- // execute bit, so X_OK is satisfied by any file that exists — and an npm global install writes
1820
- // BOTH `claude`, an extensionless shell script for Git Bash, and `claude.cmd`. The POSIX test
1821
- // passes on the shell script, so this line reported the engine FOUND at a path CreateProcess
1822
- // cannot start, and the probe then failed with `spawn claude ENOENT`. Reported from a real
1823
- // Windows run, 2026-09-09.
1824
- //
1825
- // RESOLVING `claude.cmd` INSTEAD WOULD MOVE THE CONTRADICTION, NOT REMOVE IT.
1826
- // `preflightEngineBinary` refuses native Windows by name, and the run door calls it
1827
- // unconditionally — so a clearance cannot run here whatever this line finds. Making the probe
1828
- // succeed would produce a doctor that says ready, a probe that passes, and a run that refuses
1829
- // anyway: the same disagreement one step later, and more convincing for having spawned something.
1830
- //
1831
- // So doctor says what the run door says. That is the property — the two agree — and the reader
1832
- // gets one refusal they can act on instead of a found-then-failed sequence that sends them
1833
- // looking for a PATH problem they do not have. The demo is unaffected: it replays finished runs
1834
- // and needs no engine, which is why four reports published on that same Windows box.
1835
- const platformRefusal = platformEngineRefusal();
1836
- if (platformRefusal) problem(platformRefusal);
1837
1800
  // ── AND WHETHER THAT COPY IS NEW ENOUGH FOR THE MODELS IT WILL BE ASKED FOR ──────────────────
1838
1801
  //
1839
1802
  // The floor governed the copy setup INSTALLS and nothing else, and a copy already on the machine
@@ -1845,7 +1808,7 @@ export async function runCheck() {
1845
1808
  // compared. The last is said rather than passed over — doctor's own contract is that a failure to
1846
1809
  // look is not a clean result — and it is the ordinary state for a copy the machine installed by
1847
1810
  // another route, where there is no package.json to read and doctor spawns nothing to ask.
1848
- else if (bin.executable && !bin.relative && olderThanFloor(seen, engSpec.floor) === true)
1811
+ if (bin.executable && !bin.relative && olderThanFloor(seen, engSpec.floor) === true)
1849
1812
  problem(`${bin.path} — ${copyWords(bin)}. Clearotron needs ${engSpec.floor} or newer. ${belowFloorTail(engSpec, bin)}`);
1850
1813
  else if (bin.executable && !bin.relative) {
1851
1814
  ok(`${bin.path} — ${copyWords(bin)}`);
@@ -3872,26 +3835,6 @@ try {
3872
3835
  if (!pick.id) { sayNoEngine(); break; }
3873
3836
  const eng = ENGINE_BINARIES[pick.id];
3874
3837
 
3875
- // ── NATIVE WINDOWS IS ANSWERED HERE TOO, AND THE ESCAPE IS OFFERED RATHER THAN LEFT ON A MENU ──
3876
- //
3877
- // Doctor learned this and the wizard did not, and the gap is worse than it sounds: the two "no
3878
- // engine" escapes below both sit behind "no usable binary", and on Windows that is FALSE. X_OK is
3879
- // satisfied by any file that exists, so the wizard says "found C:\...\claude", walks past both
3880
- // escapes, asks which lane pays, spends a proof turn, and meets `spawn claude ENOENT`. The only
3881
- // prompt left then defaults to No and returns to the engine menu — with nothing changed between
3882
- // iterations, on either lane. Reported from a real Windows run: the menu's last row was the one
3883
- // way out and nothing on screen said so.
3884
- //
3885
- // So the refusal is stated before a candidate is resolved, and finishing with no engine is OFFERED
3886
- // rather than being a row the reader has to notice. Declining leaves them on the menu exactly as
3887
- // before, which is the branch that was already there.
3888
- const wizardRefusal = platformEngineRefusal();
3889
- if (wizardRefusal) {
3890
- problem(wizardRefusal);
3891
- if (await confirm("Finish setup with no engine configured?", true)) { sayNoEngine(); break engine; }
3892
- continue;
3893
- }
3894
-
3895
3838
  let bin = resolveEngineBin(process.env[eng.env] || eng.fallback, { engine: pick.id });
3896
3839
  // Under WSL the Windows build on the appended PATH has been passed over. Said before the install
3897
3840
  // offer below, because otherwise a reader whose Linux install is genuinely missing is asked to
@@ -3929,7 +3872,8 @@ try {
3929
3872
  say("");
3930
3873
  if (await confirm(`Run \`${engineInstallCommand(eng, dir)}\` now?`, false)) {
3931
3874
  say(` $ ${engineInstallCommand(eng, dir)}`);
3932
- const r = spawnSync("npm", engineInstallArgs(eng, dir), { stdio: "inherit" });
3875
+ const npm = npmInvocation(engineInstallArgs(eng, dir));
3876
+ const r = spawnSync(npm.command, npm.args, { stdio: "inherit" });
3933
3877
  // THE EXIT CODE IS NOT THE ANSWER, and this is the issue's own rule. An install that exits 0 may
3934
3878
  // have left the vendor's placeholder (npm told to skip install scripts), and one that exits
3935
3879
  // non-zero may still have left a usable program. So what decides is the same resolution the
package/bin/start.mjs CHANGED
@@ -87,6 +87,7 @@ import { writeSecretFile } from "../shared/secret-file.mjs"; // one atomic wri
87
87
  import { runRequiredNames, missingRequirements, CLOUD_ROUTES } from "../driver/run-requirements.mjs";
88
88
  import { CLOUD_SWITCH, cloudsSwitchedOn } from "../driver/engine/auth.mjs"; // a switch compared the way the program reads it: on or off
89
89
  import { ENGINE_BINARIES, DEFAULT_ENGINE_ID as RUN_DEFAULT_ENGINE, resolveEngineProgram } from "../driver/driver.config.mjs";
90
+ import { spawnsDetached, killWindowsTreesNow } from "../driver/engine/engine-spawn.mjs"; // Windows has no process group to stop
90
91
  import { unitEnvironment, unitValue } from "../driver/unit-environment.mjs"; // the PATH the worker unit will run with, read the way doctor reads it
91
92
 
92
93
  /**
@@ -149,7 +150,7 @@ import { addressRefusal } from "../shared/staff-domain.mjs";
149
150
  // entry is written through them, so this command cannot produce a shape of its own.
150
151
  import { withPerson, withOrganisation, withCompany } from "../shared/grants-edit.mjs";
151
152
  import { assertGrantsShape, resolvePerson } from "../shared/scope.mjs";
152
- import { backgroundManager } from "../shared/os-advice.mjs";
153
+ import { backgroundManager, backgroundRefusal } from "../shared/os-advice.mjs";
153
154
  import { recordRunning } from "../shared/running-start.mjs";
154
155
  import { frontingVariablesSet } from "../shared/install-auth.mjs"; // — one owner for what counts as a proxy in front of a door
155
156
 
@@ -713,6 +714,26 @@ export const BACKGROUND_EXCLUDED = Object.freeze({
713
714
  "profile-service.service": "the portal constructs the profile service IN-PROCESS (driver/portal-service.mjs); the standalone unit is the separate-editor deployment shape and running both double-serves the store",
714
715
  });
715
716
 
717
+ /**
718
+ * The key that signs every access token, and the one it replaces while a rotation is under way. Only the
719
+ * processes that mint or check keys hold them: the portal and the two doors. The worker holds neither,
720
+ * because it starts the AI program for every stage.
721
+ */
722
+ export const SIGNING_KEY_NAMES = Object.freeze(["TRADEMARK_MCP_TOKEN_SECRET", "TRADEMARK_MCP_TOKEN_SECRET_PREVIOUS"]);
723
+
724
+ /**
725
+ * A child's environment: this supervisor's, then the child's own block over it. The signing keys are the
726
+ * exception: a child whose own block does not carry the current key gets neither of them. This process
727
+ * sets the key on itself to mint the portal's key, so without the exception every child would inherit it,
728
+ * whatever its block said. A child that is handed the key keeps the previous one too, from this process,
729
+ * so a rotation under way still checks the keys signed before it.
730
+ */
731
+ export function childProcessEnv(parent = process.env, own = {}) {
732
+ const env = { ...parent, ...own };
733
+ if (!Object.hasOwn(own, SIGNING_KEY_NAMES[0])) for (const k of SIGNING_KEY_NAMES) delete env[k];
734
+ return env;
735
+ }
736
+
716
737
  export function childEnv({ ports, paths, user, portalSecret, tokenSecret, opsToken, host = HOST, localWorker = false, demo = false, clientFence = null, credential = null, env = process.env }) {
717
738
  // ONE AUTHOR FOR THIS EXPRESSION. The hosted install path composes the same
718
739
  // origin, and the near-miss is specific: this is an ORIGIN, the portal's client appends `/mcp`
@@ -811,8 +832,10 @@ export function childEnv({ ports, paths, user, portalSecret, tokenSecret, opsTok
811
832
  CLIENT_MCP_HTTP_PORT: String(ports.client),
812
833
  },
813
834
  // The worker needs the install's PATHS and nothing else — no ports, no secrets, no door config. It
814
- // talks to the queue and the pool, not to either listener.
815
- worker: { ...shared },
835
+ // talks to the queue and the pool, not to either listener. `shared` carries the signing key for the two
836
+ // doors and the portal, which mint and check keys; the worker does neither, and it starts the AI program
837
+ // for every stage, so the key is taken out here and withheld again at the spawn (childProcessEnv).
838
+ worker: Object.fromEntries(Object.entries(shared).filter(([k]) => !SIGNING_KEY_NAMES.includes(k))),
816
839
  // ── THE CLIENT DOOR, ON BOTH PATHS ( — F26) ──────────────────────────────
817
840
  //
818
841
  // Ruling, restated several times in session: START BOTH. It already held on the systemd path
@@ -976,6 +999,9 @@ if (isMain) {
976
999
  // exactly how to drain when they do.
977
1000
  const wantWorker = !argv.includes("--no-worker");
978
1001
  const wantBackground = argv.includes("--background");
1002
+ // ON WINDOWS THERE IS NO BACKGROUND FORM: it is systemd units. Answered here, before anything is written
1003
+ // or systemctl is called, in the owner's sentence and nothing else, so no `start:` prefix either.
1004
+ if (wantBackground && backgroundRefusal()) { err(backgroundRefusal()); process.exit(1); }
979
1005
 
980
1006
  // A flag with its value forgotten is a MISTAKE, not a request for the default. `--base` swallowing the
981
1007
  // next flag, or falling through to ~/trademark, would put an install somewhere nobody asked for.
@@ -2187,8 +2213,9 @@ if (isMain) {
2187
2213
  cwd: REPO,
2188
2214
  // Its OWN process group, so Ctrl-C reaches this supervisor alone and teardown is one ordered
2189
2215
  // sequence rather than a race between the terminal's signal and ours. It also means the group
2190
- // kill below reaches anything a child spawns, not just the child.
2191
- detached: true,
2216
+ // kill below reaches anything a child spawns, not just the child. NOT ON WINDOWS, where a detached
2217
+ // child has no console and outlives the window it was started from (engine/engine-spawn.mjs).
2218
+ detached: spawnsDetached(),
2192
2219
  // — STDERR IS TEED RATHER THAN INHERITED, AND ONLY STDERR.
2193
2220
  //
2194
2221
  // These children announce a refusal as one FATAL line on stderr and exit 1. With `inherit` the
@@ -2202,7 +2229,7 @@ if (isMain) {
2202
2229
  // progress rendering intact. Piping a stream costs the child its TTY, so only the stream that has to
2203
2230
  // be read is piped.
2204
2231
  stdio: ["ignore", "inherit", "pipe"],
2205
- env: { ...process.env, ...env },
2232
+ env: childProcessEnv(process.env, env),
2206
2233
  });
2207
2234
  // The last lines of that child's stderr, forwarded on as they arrive so nothing is delayed or
2208
2235
  // swallowed, and kept so the failure can QUOTE them rather than refer to them.
@@ -2252,7 +2279,6 @@ if (isMain) {
2252
2279
  return rec;
2253
2280
  };
2254
2281
 
2255
- const signalGroup = (rec, sig) => { try { process.kill(-rec.child.pid, sig); } catch { /* already gone */ } };
2256
2282
 
2257
2283
  async function shutdown(code) {
2258
2284
  if (stopping) return;
@@ -2260,15 +2286,14 @@ if (isMain) {
2260
2286
  const live = children.filter((c) => c.alive);
2261
2287
  if (live.length) {
2262
2288
  say(`\n stopping ${live.map((c) => c.name).join(" and ")}…`);
2263
- for (const c of live) signalGroup(c, "SIGTERM");
2289
+ stopChildren(live.map((c) => c.child), "SIGTERM");
2264
2290
  // A grace window, then the hammer. Nothing this command starts holds unflushed client state, so
2265
2291
  // the window is short; it exists so a service gets to close its listener and log its own line.
2266
2292
  const deadline = Date.now() + 5000;
2267
2293
  while (children.some((c) => c.alive) && Date.now() < deadline) await new Promise((r) => setTimeout(r, 100));
2268
- for (const c of children.filter((x) => x.alive)) {
2269
- err(` ${c.name} did not stop on SIGTERM — killing it.`);
2270
- signalGroup(c, "SIGKILL");
2271
- }
2294
+ const stuck = children.filter((x) => x.alive);
2295
+ for (const c of stuck) err(` ${c.name} did not stop on SIGTERM — killing it.`);
2296
+ stopChildren(stuck.map((c) => c.child), "SIGKILL");
2272
2297
  await new Promise((r) => setTimeout(r, 200));
2273
2298
  }
2274
2299
  // ── THE DEMO TAKES ITS FOLDER WITH IT ───────────────────────────────────────────────────────────
@@ -2589,8 +2614,7 @@ if (isMain) {
2589
2614
  // `--background` installs and enables service units. There are none on Windows, so both the offer
2590
2615
  // and the sentence naming what manages them were wrong there — a reader was told to run a flag that
2591
2616
  // cannot succeed and given a service manager that is not on the machine and cannot be put there.
2592
- // Reported from a real run. Same rule as the engine refusal above: do not name a route this platform
2593
- // does not have.
2617
+ // Reported from a real run. The rule: do not name a route this platform does not have.
2594
2618
  for (const line of backgroundOfferLines({ demo: DEMO, keep: DEMO_KEEP || READER_BASE, manager: backgroundManager(), start: invoke("start") })) say(line);
2595
2619
  say("");
2596
2620
  }
@@ -2693,6 +2717,26 @@ export function windowCloseSignals() {
2693
2717
  return ["SIGHUP"];
2694
2718
  }
2695
2719
 
2720
+ /**
2721
+ * Stop children of this command, and whatever they started.
2722
+ *
2723
+ * On Linux and macOS each child leads its own process group, so the signal goes to the group, and to the
2724
+ * child alone when the group is already gone. Windows has neither groups nor signals a Node child can
2725
+ * catch, so there every child's process tree is ended at once, from one process listing: closing the
2726
+ * window leaves only seconds, and a listing takes most of one. The second call finds nothing left.
2727
+ * `platform` and the two effects are parameters so both branches run on a Linux CI.
2728
+ */
2729
+ export function stopChildren(children, sig, { platform = process.platform, kill = (pid, s) => process.kill(pid, s),
2730
+ endTrees = killWindowsTreesNow } = {}) {
2731
+ const live = (children ?? []).filter((c) => c?.pid);
2732
+ if (platform === "win32") {
2733
+ const pids = live.filter((c) => c.exitCode === null && c.signalCode === null).map((c) => c.pid);
2734
+ if (pids.length) endTrees(pids);
2735
+ return;
2736
+ }
2737
+ for (const c of live) { try { kill(-c.pid, sig); } catch { try { c.kill(sig); } catch { /* already gone */ } } }
2738
+ }
2739
+
2696
2740
  /**
2697
2741
  * What a reader is told when a child this install cannot run without exits.
2698
2742
  *
package/bin/update.mjs CHANGED
@@ -53,6 +53,7 @@ import { fileURLToPath } from "node:url";
53
53
 
54
54
  import { config, ENGINE_BINARIES, enginesFolder, engineInstallArgs } from "../driver/driver.config.mjs";
55
55
  import { isInsideCheckout } from "../shared/inside-checkout.mjs"; // — one copy of the rule
56
+ import { npmInvocation } from "../shared/npm-cli.mjs"; // npm without a shell, which Windows needs
56
57
  import { overlayReport, renderOverlayReport, treeFiles } from "../shared/doctrine-overlay.mjs";
57
58
  import { liveRunHolds } from "../driver/deploy-live-run-guard.mjs"; // — one live-run test, shared with deploy-preflight
58
59
  import { isEntrypoint } from "../shared/is-entrypoint.mjs"; // — one entry-point test, all spellings
@@ -210,7 +211,9 @@ function refreshEngines(engines, dir = enginesFolder()) {
210
211
 
211
212
  function runInCheckout(cmd, args) {
212
213
  say(`\n $ ${cmd} ${args.join(" ")}`);
213
- const r = spawnSync(cmd, args, { cwd: REPO, stdio: "inherit" });
214
+ // npm is a batch file on Windows, which Node will not start without a shell (shared/npm-cli.mjs).
215
+ const run = cmd === "npm" ? npmInvocation(args) : { command: cmd, args };
216
+ const r = spawnSync(run.command, run.args, { cwd: REPO, stdio: "inherit" });
214
217
  if (r.error) { console.error(` could not run ${cmd}: ${r.error.message}`); return 70; }
215
218
  if (r.signal) { console.error(` ${cmd} was killed by ${r.signal}`); return 70; }
216
219
  return r.status ?? 0;
package/build-info.json CHANGED
@@ -1,4 +1,4 @@
1
1
  {
2
- "commit": "e60329390d653439053b900cf97b65e082e06622",
3
- "version": "0.4.0-beta.1"
2
+ "commit": "c8159f8575f90587b1c78d5151d9acf9235977e1",
3
+ "version": "0.4.0-beta.3"
4
4
  }
@@ -17,7 +17,7 @@
17
17
  "recipeScope": null,
18
18
  "deliveryRoute": null,
19
19
  "enqueuedVia": "mcp/start_run",
20
- "enqueuedBy": "e2e-capella",
20
+ "enqueuedBy": "demo",
21
21
  "parentRunId": null,
22
22
  "frozenAt": "2026-09-18T15:08:47.364Z"
23
23
  }
@@ -5,7 +5,7 @@
5
5
  "slug": "tmpdemo2014fullcountrysearch-venqori",
6
6
  "codename": "sample-capture",
7
7
  "date": "2026-09-18",
8
- "agent": "clawdi",
8
+ "agent": "localagent",
9
9
  "forwarder": "e2e",
10
10
  "ref": "DEMO-2014-full-country-search",
11
11
  "markName": "VENQORI",
@@ -17,7 +17,7 @@
17
17
  "recipeScope": null,
18
18
  "deliveryRoute": null,
19
19
  "enqueuedVia": "mcp/start_run",
20
- "enqueuedBy": "e2e-capella",
20
+ "enqueuedBy": "demo",
21
21
  "parentRunId": null,
22
22
  "frozenAt": "2026-09-18T12:35:08.518Z"
23
23
  }
@@ -5,7 +5,7 @@
5
5
  "slug": "tmpdemo2014globalpreliminarysearch-venqori",
6
6
  "codename": "sample-capture",
7
7
  "date": "2026-09-18",
8
- "agent": "clawdi",
8
+ "agent": "localagent",
9
9
  "forwarder": "e2e",
10
10
  "ref": "DEMO-2014-global-preliminary-search",
11
11
  "markName": "VENQORI",
@@ -17,7 +17,7 @@
17
17
  "recipeScope": null,
18
18
  "deliveryRoute": null,
19
19
  "enqueuedVia": "mcp/start_run",
20
- "enqueuedBy": "e2e-capella",
20
+ "enqueuedBy": "demo",
21
21
  "parentRunId": null,
22
22
  "frozenAt": "2026-09-18T16:18:15.945Z"
23
23
  }
@@ -5,7 +5,7 @@
5
5
  "slug": "tmpdemo2014knockoutsearch-venqori",
6
6
  "codename": "sample-capture",
7
7
  "date": "2026-09-18",
8
- "agent": "clawdi",
8
+ "agent": "localagent",
9
9
  "forwarder": "e2e",
10
10
  "ref": "DEMO-2014-knockout-search",
11
11
  "state": "delivered",
@@ -17,7 +17,7 @@
17
17
  "recipeScope": null,
18
18
  "deliveryRoute": null,
19
19
  "enqueuedVia": "mcp/start_run",
20
- "enqueuedBy": "e2e-capella",
20
+ "enqueuedBy": "demo",
21
21
  "parentRunId": null,
22
22
  "frozenAt": "2026-09-18T14:03:02.805Z"
23
23
  }
@@ -5,7 +5,7 @@
5
5
  "slug": "tmpdemo2014multicountryfocussearch-venqori",
6
6
  "codename": "sample-capture",
7
7
  "date": "2026-09-18",
8
- "agent": "clawdi",
8
+ "agent": "localagent",
9
9
  "forwarder": "e2e",
10
10
  "ref": "DEMO-2014-multi-country-focus-search",
11
11
  "markName": "VENQORI",
package/docs/README.md CHANGED
@@ -40,6 +40,7 @@ the vendor flags approximate is UNKNOWN, never a number.
40
40
  | [`E2E.md`](E2E.md) | Proving a deployment works end to end |
41
41
  | [`GLOSSARY.md`](GLOSSARY.md) | The words this codebase uses in a particular way — one line each, for a contributor meeting them for the first time |
42
42
  | [`SECURITY.md`](SECURITY.md) | The security envelope — what protects what, and where it is enforced in code. To report a vulnerability, use [`../SECURITY.md`](../SECURITY.md) |
43
+ | [`SECURITY-OWASP.md`](SECURITY-OWASP.md) | Each risk on OWASP's two lists for AI systems, what Clearotron does about it, and the file on `main` where that lives |
43
44
 
44
45
  Access control is stated once, in [`SECURITY.md`](SECURITY.md): who may see which runs, what an unset
45
46
  guest list means on each face, and which door proves identity. Issuing and rotating grants and ops