@reticlehq/server 3.8.2 → 3.10.0

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 (477) hide show
  1. package/LICENSE +201 -104
  2. package/NOTICE +5 -0
  3. package/README.md +1 -1
  4. package/SKILL.md +13 -12
  5. package/bin/launch.js +13 -0
  6. package/bin/package.json +3 -0
  7. package/bin/reticle.js +66 -8
  8. package/dist/chromium-launch-options.d.ts +25 -0
  9. package/dist/chromium-launch-options.js +21 -1
  10. package/dist/command/cli/adhoc-suite.js +2 -0
  11. package/dist/command/cli/adhoc-verdict.d.ts +2 -43
  12. package/dist/command/cli/adhoc-verdict.js +50 -160
  13. package/dist/command/cli/answers/gate-exit.d.ts +6 -2
  14. package/dist/command/cli/answers/gate-exit.js +12 -2
  15. package/dist/command/cli/answers/mcp-connection-hint.d.ts +2 -0
  16. package/dist/command/cli/answers/mcp-connection-hint.js +6 -0
  17. package/dist/command/cli/ci-hint.d.ts +44 -0
  18. package/dist/command/cli/ci-hint.js +72 -0
  19. package/dist/command/cli/cli-doctor.d.ts +6 -1
  20. package/dist/command/cli/cli-doctor.js +46 -5
  21. package/dist/command/cli/cli-flow-commands.d.ts +7 -1
  22. package/dist/command/cli/cli-flow-commands.js +39 -24
  23. package/dist/command/cli/cli-kill.d.ts +21 -0
  24. package/dist/command/cli/cli-kill.js +47 -4
  25. package/dist/command/cli/cli-parse-compare.d.ts +22 -0
  26. package/dist/command/cli/cli-parse-compare.js +75 -0
  27. package/dist/command/cli/cli-parse-grade.d.ts +36 -0
  28. package/dist/command/cli/cli-parse-grade.js +75 -0
  29. package/dist/command/cli/cli-parse-grammar.d.ts +2 -0
  30. package/dist/command/cli/cli-parse-grammar.js +18 -20
  31. package/dist/command/cli/cli-parse-verify.d.ts +10 -0
  32. package/dist/command/cli/cli-parse-verify.js +63 -14
  33. package/dist/command/cli/cli-parse.d.ts +22 -17
  34. package/dist/command/cli/cli-parse.js +54 -32
  35. package/dist/command/cli/cli-update-commands.d.ts +1 -1
  36. package/dist/command/cli/cli-update-commands.js +29 -20
  37. package/dist/command/cli/cli-usage.js +61 -31
  38. package/dist/command/cli/cli-verify.d.ts +17 -3
  39. package/dist/command/cli/cli-verify.js +59 -21
  40. package/dist/command/cli/cloud-cli.js +2 -119
  41. package/dist/command/cli/daemon-link/daemon-client.d.ts +45 -0
  42. package/dist/command/cli/daemon-link/daemon-client.js +205 -0
  43. package/dist/command/cli/doctor/browser/chromium-hint.d.ts +6 -1
  44. package/dist/command/cli/doctor/browser/chromium-hint.js +11 -5
  45. package/dist/command/cli/doctor/doctor-daemon-line.js +6 -3
  46. package/dist/command/cli/doctor/doctor-fix.d.ts +44 -0
  47. package/dist/command/cli/doctor/doctor-fix.js +89 -0
  48. package/dist/command/cli/doctor/doctor-launcher-line.d.ts +23 -0
  49. package/dist/command/cli/doctor/doctor-launcher-line.js +60 -0
  50. package/dist/command/cli/doctor/doctor-rows.d.ts +4 -0
  51. package/dist/command/cli/doctor/doctor-rows.js +4 -0
  52. package/dist/command/cli/doctor-repairs.d.ts +64 -0
  53. package/dist/command/cli/doctor-repairs.js +214 -0
  54. package/dist/command/cli/drive/drive-command.js +3 -7
  55. package/dist/command/cli/ensure-daemon.d.ts +26 -0
  56. package/dist/command/cli/ensure-daemon.js +57 -0
  57. package/dist/command/cli/finished-run/grade-command.d.ts +23 -0
  58. package/dist/command/cli/finished-run/grade-command.js +180 -0
  59. package/dist/command/cli/first-use-checks.d.ts +41 -0
  60. package/dist/command/cli/first-use-checks.js +127 -0
  61. package/dist/command/cli/first-verdict-node.d.ts +3 -0
  62. package/dist/command/cli/first-verdict-node.js +189 -0
  63. package/dist/command/cli/first-verdict.d.ts +126 -0
  64. package/dist/command/cli/first-verdict.js +267 -0
  65. package/dist/command/cli/launch/sdk-version-match.d.ts +3 -2
  66. package/dist/command/cli/launch/sdk-version-match.js +3 -2
  67. package/dist/command/cli/lifecycle/daemon-lifecycle.d.ts +1 -1
  68. package/dist/command/cli/lifecycle/daemon-lifecycle.js +7 -6
  69. package/dist/command/cli/mcp-command.js +13 -4
  70. package/dist/command/cli/ports/port-holder.d.ts +11 -0
  71. package/dist/command/cli/ports/port-holder.js +36 -4
  72. package/dist/command/cli/setup-mcp-cli.d.ts +27 -1
  73. package/dist/command/cli/setup-mcp-cli.js +98 -23
  74. package/dist/command/cli/status/status-lines.d.ts +2 -0
  75. package/dist/command/cli/status/status-lines.js +72 -17
  76. package/dist/command/cli/status-fields.d.ts +5 -1
  77. package/dist/command/cli/status-fields.js +24 -6
  78. package/dist/command/cli/try-command.d.ts +7 -0
  79. package/dist/command/cli/try-command.js +11 -3
  80. package/dist/command/cli/two-builds/compare-command.d.ts +48 -0
  81. package/dist/command/cli/two-builds/compare-command.js +314 -0
  82. package/dist/command/cli/verify-against.d.ts +61 -0
  83. package/dist/command/cli/verify-against.js +187 -0
  84. package/dist/command/cli/verify-no-session.d.ts +2 -0
  85. package/dist/command/cli/verify-no-session.js +13 -0
  86. package/dist/command/cli/verify-results.d.ts +35 -2
  87. package/dist/command/cli/verify-results.js +76 -10
  88. package/dist/command/cli.js +112 -70
  89. package/dist/command/daemon/binding/daemon-status-probe.d.ts +3 -1
  90. package/dist/command/daemon/binding/daemon-status-probe.js +4 -2
  91. package/dist/command/daemon/daemon-argv.d.ts +9 -0
  92. package/dist/command/daemon/daemon-argv.js +11 -0
  93. package/dist/command/daemon/daemon-resolve.d.ts +8 -1
  94. package/dist/command/daemon/daemon-resolve.js +27 -1
  95. package/dist/command/daemon/daemon.d.ts +22 -4
  96. package/dist/command/daemon/daemon.js +52 -10
  97. package/dist/command/daemon/lifetime/daemon-exit.d.ts +24 -0
  98. package/dist/command/daemon/lifetime/daemon-exit.js +39 -0
  99. package/dist/command/daemon/lifetime/death-cause.d.ts +15 -0
  100. package/dist/command/daemon/lifetime/death-cause.js +149 -0
  101. package/dist/command/daemon/lifetime/heartbeat.d.ts +7 -15
  102. package/dist/command/daemon/lifetime/heartbeat.js +11 -15
  103. package/dist/command/daemon/lifetime/startup-failure.d.ts +29 -0
  104. package/dist/command/daemon/lifetime/startup-failure.js +62 -0
  105. package/dist/command/daemon/port-state.d.ts +14 -0
  106. package/dist/command/daemon/port-state.js +31 -0
  107. package/dist/command/setup/agent-configs.d.ts +2 -0
  108. package/dist/command/setup/agent-configs.js +42 -15
  109. package/dist/command/setup/agent-next-steps.d.ts +77 -0
  110. package/dist/command/setup/agent-next-steps.js +284 -0
  111. package/dist/command/setup/agent-writer.js +11 -6
  112. package/dist/command/setup/bringup/dev-server-files.d.ts +9 -0
  113. package/dist/command/setup/bringup/dev-server-files.js +17 -0
  114. package/dist/command/setup/bringup/static-page-server.d.ts +14 -2
  115. package/dist/command/setup/bringup/static-page-server.js +43 -9
  116. package/dist/command/setup/init/init-host.js +2 -0
  117. package/dist/command/setup/init/init-runtime.d.ts +6 -1
  118. package/dist/command/setup/init/init-runtime.js +63 -21
  119. package/dist/command/setup/mcp-launcher.d.ts +3 -0
  120. package/dist/command/setup/mcp-launcher.js +11 -0
  121. package/dist/command/setup/node-effects.d.ts +3 -2
  122. package/dist/command/setup/node-effects.js +9 -3
  123. package/dist/command/setup/remaining-steps.d.ts +8 -0
  124. package/dist/command/setup/remaining-steps.js +10 -0
  125. package/dist/command/setup/run-setup.d.ts +2 -0
  126. package/dist/command/setup/run-setup.js +9 -1
  127. package/dist/command/setup/setup-command.js +36 -15
  128. package/dist/command/setup/setup-mcp.d.ts +8 -0
  129. package/dist/command/setup/setup-mcp.js +47 -20
  130. package/dist/command/setup/start-dev-server.d.ts +8 -0
  131. package/dist/command/setup/start-dev-server.js +43 -0
  132. package/dist/command/update/updater.js +6 -0
  133. package/dist/command/version/identity/server-version.d.ts +2 -0
  134. package/dist/command/version/identity/server-version.js +2 -0
  135. package/dist/command/version/version-skew.d.ts +7 -0
  136. package/dist/command/version/version-skew.js +14 -3
  137. package/dist/features/compare/action-plan.d.ts +260 -0
  138. package/dist/features/compare/action-plan.js +543 -0
  139. package/dist/features/compare/behaviour-compare.d.ts +24 -0
  140. package/dist/features/compare/behaviour-compare.js +85 -0
  141. package/dist/features/compare/behaviour-diff.d.ts +97 -0
  142. package/dist/features/compare/behaviour-diff.js +202 -0
  143. package/dist/features/compare/behaviour-report.d.ts +40 -0
  144. package/dist/features/compare/behaviour-report.js +128 -0
  145. package/dist/features/compare/build-readings.d.ts +106 -0
  146. package/dist/features/compare/build-readings.js +250 -0
  147. package/dist/features/compare/compare-diff.d.ts +62 -0
  148. package/dist/features/compare/compare-diff.js +177 -0
  149. package/dist/features/compare/compare-report-html.d.ts +6 -0
  150. package/dist/features/compare/compare-report-html.js +102 -0
  151. package/dist/features/compare/compare-report.d.ts +15 -0
  152. package/dist/features/compare/compare-report.js +73 -0
  153. package/dist/features/compare/compare-run.d.ts +60 -0
  154. package/dist/features/compare/compare-run.js +123 -0
  155. package/dist/features/compare/outside/effect-lines.d.ts +26 -0
  156. package/dist/features/compare/outside/effect-lines.js +103 -0
  157. package/dist/features/compare/outside/state-sources.d.ts +76 -0
  158. package/dist/features/compare/outside/state-sources.js +164 -0
  159. package/dist/features/compare/outside/value-dictionary.d.ts +51 -0
  160. package/dist/features/compare/outside/value-dictionary.js +227 -0
  161. package/dist/features/compare/visible-content.d.ts +20 -0
  162. package/dist/features/compare/visible-content.js +125 -0
  163. package/dist/features/crawl/crawl-empty.js +2 -1
  164. package/dist/features/crawl/crawl-exhaustive.d.ts +55 -0
  165. package/dist/features/crawl/crawl-exhaustive.js +138 -0
  166. package/dist/features/crawl/crawl-tools.d.ts +9 -2
  167. package/dist/features/crawl/crawl-tools.js +28 -12
  168. package/dist/features/crawl/crawl.d.ts +8 -9
  169. package/dist/features/crawl/crawl.js +61 -65
  170. package/dist/features/exhaust/app-coverage.d.ts +5 -1
  171. package/dist/features/exhaust/app-coverage.js +3 -1
  172. package/dist/features/exhaust/code-coverage.js +8 -4
  173. package/dist/features/exhaust/edge-cases.d.ts +133 -0
  174. package/dist/features/exhaust/edge-cases.js +310 -0
  175. package/dist/features/exhaust/edges/edge-drive.d.ts +42 -0
  176. package/dist/features/exhaust/edges/edge-drive.js +364 -0
  177. package/dist/features/exhaust/edges/edge-run.d.ts +59 -0
  178. package/dist/features/exhaust/edges/edge-run.js +169 -0
  179. package/dist/features/exhaust/explorer.d.ts +87 -14
  180. package/dist/features/exhaust/explorer.js +159 -25
  181. package/dist/features/exhaust/failure-branches.d.ts +5 -0
  182. package/dist/features/exhaust/failure-branches.js +29 -11
  183. package/dist/features/exhaust/invariants.d.ts +112 -0
  184. package/dist/features/exhaust/invariants.js +188 -0
  185. package/dist/features/exhaust/ledger.d.ts +68 -0
  186. package/dist/features/exhaust/ledger.js +121 -13
  187. package/dist/features/exhaust/session-fold.d.ts +10 -0
  188. package/dist/features/exhaust/session-fold.js +46 -3
  189. package/dist/features/exhaust/session-port.d.ts +3 -1
  190. package/dist/features/exhaust/session-port.js +97 -28
  191. package/dist/features/grade/grade-answer.d.ts +99 -0
  192. package/dist/features/grade/grade-answer.js +112 -0
  193. package/dist/features/grade/grade-noise.d.ts +27 -0
  194. package/dist/features/grade/grade-noise.js +55 -0
  195. package/dist/features/grade/grade-renumber.d.ts +12 -0
  196. package/dist/features/grade/grade-renumber.js +86 -0
  197. package/dist/features/grade/grade-state.d.ts +50 -0
  198. package/dist/features/grade/grade-state.js +93 -0
  199. package/dist/features/grade/grade-suggest.d.ts +33 -0
  200. package/dist/features/grade/grade-suggest.js +98 -0
  201. package/dist/features/license/license.d.ts +11 -71
  202. package/dist/features/license/license.js +43 -136
  203. package/dist/features/visual/desktop-capture.d.ts +33 -0
  204. package/dist/features/visual/desktop-capture.js +94 -0
  205. package/dist/features/visual/step-shots.js +139 -35
  206. package/dist/features/visual/visual-tools.d.ts +0 -18
  207. package/dist/features/visual/visual-tools.js +67 -99
  208. package/dist/import-graph.js +5 -6
  209. package/dist/index.d.ts +3 -1
  210. package/dist/index.js +19 -7
  211. package/dist/judgement/capsule/causal-summary.js +9 -2
  212. package/dist/judgement/runs/artifact/replay-mapping.d.ts +13 -1
  213. package/dist/judgement/runs/artifact/replay-mapping.js +1 -1
  214. package/dist/judgement/runs/reticle-runner.js +16 -2
  215. package/dist/judgement/runs/run-ledger.d.ts +7 -5
  216. package/dist/judgement/runs/run-ledger.js +98 -69
  217. package/dist/judgement/runs/runner-port.d.ts +2 -0
  218. package/dist/judgement/runs/runner-port.js +23 -5
  219. package/dist/judgement/runs/verification-sync.d.ts +6 -3
  220. package/dist/judgement/runs/verification-sync.js +21 -10
  221. package/dist/language/flows/affected-tools.d.ts +1 -1
  222. package/dist/language/flows/affected-tools.js +49 -30
  223. package/dist/language/flows/change/git-changed.d.ts +2 -1
  224. package/dist/language/flows/change/git-changed.js +19 -2
  225. package/dist/language/flows/fields/flow-secret-field.d.ts +1 -13
  226. package/dist/language/flows/fields/flow-secret-field.js +77 -21
  227. package/dist/language/flows/flow-anchor.d.ts +60 -2
  228. package/dist/language/flows/flow-anchor.js +125 -20
  229. package/dist/language/flows/flow-classify.d.ts +20 -0
  230. package/dist/language/flows/flow-classify.js +90 -12
  231. package/dist/language/flows/flow-cross-step.d.ts +7 -1
  232. package/dist/language/flows/flow-cross-step.js +25 -1
  233. package/dist/language/flows/flow-journey.d.ts +38 -4
  234. package/dist/language/flows/flow-journey.js +138 -8
  235. package/dist/language/flows/flow-replay-run.d.ts +0 -16
  236. package/dist/language/flows/flow-replay-run.js +76 -14
  237. package/dist/language/flows/flow-replay-types.d.ts +4 -2
  238. package/dist/language/flows/flow-replay.d.ts +19 -1
  239. package/dist/language/flows/flow-replay.js +169 -52
  240. package/dist/language/flows/flow-replayable.js +7 -1
  241. package/dist/language/flows/flow-start-path.d.ts +34 -5
  242. package/dist/language/flows/flow-start-path.js +202 -15
  243. package/dist/language/flows/flow-step-runners.d.ts +14 -13
  244. package/dist/language/flows/flow-step-runners.js +184 -109
  245. package/dist/language/flows/flow-store-for-session.d.ts +26 -25
  246. package/dist/language/flows/flow-store-for-session.js +51 -5
  247. package/dist/language/flows/flow-tools.js +46 -33
  248. package/dist/language/flows/flows.js +176 -76
  249. package/dist/language/flows/recording/tape/recordings.d.ts +21 -16
  250. package/dist/language/flows/recording/tape/recordings.js +112 -49
  251. package/dist/language/flows/replay.d.ts +36 -12
  252. package/dist/language/flows/replay.js +193 -50
  253. package/dist/language/flows/steps/page-steps.d.ts +68 -0
  254. package/dist/language/flows/steps/page-steps.js +198 -0
  255. package/dist/language/flows/suite/sequential-run.d.ts +68 -0
  256. package/dist/language/flows/suite/sequential-run.js +96 -0
  257. package/dist/language/flows/verify-change-tools.js +26 -15
  258. package/dist/launch-chromium.d.ts +1 -0
  259. package/dist/launch-chromium.js +49 -12
  260. package/dist/memory/cloud/background-sync.d.ts +13 -0
  261. package/dist/memory/cloud/background-sync.js +24 -3
  262. package/dist/memory/cloud/sync-daemon.js +26 -27
  263. package/dist/memory/cloud/sync-screenshots.js +6 -1
  264. package/dist/memory/journal/attach-journal.js +2 -1
  265. package/dist/memory/journal/drive-flow.d.ts +7 -0
  266. package/dist/memory/journal/drive-flow.js +44 -13
  267. package/dist/memory/journal/journal-recorder.d.ts +4 -2
  268. package/dist/memory/journal/session-journal.d.ts +7 -0
  269. package/dist/memory/journal/session-journal.js +58 -10
  270. package/dist/memory/project/root-claims.d.ts +9 -0
  271. package/dist/memory/project/root-claims.js +9 -4
  272. package/dist/portal/bridge/bridge.d.ts +13 -4
  273. package/dist/portal/bridge/bridge.js +10 -9
  274. package/dist/portal/input/download-capture.d.ts +7 -0
  275. package/dist/portal/input/download-capture.js +92 -0
  276. package/dist/portal/input/network-detail.d.ts +6 -0
  277. package/dist/portal/input/network-detail.js +12 -1
  278. package/dist/portal/input/network-mock-tools.js +49 -13
  279. package/dist/portal/input/network-mock.d.ts +8 -0
  280. package/dist/portal/input/network-mock.js +30 -10
  281. package/dist/portal/input/page-capture.d.ts +24 -0
  282. package/dist/portal/input/page-capture.js +33 -0
  283. package/dist/portal/input/real-input.d.ts +35 -18
  284. package/dist/portal/input/real-input.js +99 -33
  285. package/dist/portal/input/viewport-tools.d.ts +8 -0
  286. package/dist/portal/input/viewport-tools.js +81 -21
  287. package/dist/portal/pool/browser-pool.d.ts +16 -30
  288. package/dist/portal/pool/browser-pool.js +64 -52
  289. package/dist/portal/pool/context-permissions.d.ts +34 -17
  290. package/dist/portal/pool/context-permissions.js +94 -1
  291. package/dist/portal/pool/csp-widen.d.ts +62 -0
  292. package/dist/portal/pool/csp-widen.js +132 -0
  293. package/dist/portal/pool/determinism.d.ts +52 -0
  294. package/dist/portal/pool/determinism.js +259 -0
  295. package/dist/portal/pool/lease-emulation.d.ts +34 -1
  296. package/dist/portal/pool/lease-emulation.js +18 -0
  297. package/dist/portal/pool/lease-marker-stamp.d.ts +6 -0
  298. package/dist/portal/pool/lease-marker-stamp.js +15 -1
  299. package/dist/portal/pool/lease-page-error.d.ts +56 -0
  300. package/dist/portal/pool/lease-page-error.js +160 -0
  301. package/dist/portal/pool/page-error-journal.d.ts +63 -0
  302. package/dist/portal/pool/page-error-journal.js +208 -0
  303. package/dist/portal/pool/playwright-launcher.js +71 -15
  304. package/dist/portal/pool/pool-contract.d.ts +49 -1
  305. package/dist/portal/pool/pool-lease.d.ts +70 -2
  306. package/dist/portal/pool/pool-lease.js +23 -0
  307. package/dist/portal/pool/zero-install.d.ts +8 -0
  308. package/dist/portal/pool/zero-install.js +91 -1
  309. package/dist/portal/session/dev-server/dev-command.d.ts +11 -2
  310. package/dist/portal/session/dev-server/dev-command.js +110 -16
  311. package/dist/portal/session/dev-server/dev-server-probe.d.ts +7 -0
  312. package/dist/portal/session/dev-server/dev-server-probe.js +9 -0
  313. package/dist/portal/session/dev-server/project-ports.d.ts +21 -0
  314. package/dist/portal/session/dev-server/project-ports.js +62 -0
  315. package/dist/portal/session/facts/departed-network.d.ts +66 -0
  316. package/dist/portal/session/facts/departed-network.js +142 -0
  317. package/dist/portal/session/no-session-diagnosis.d.ts +9 -0
  318. package/dist/portal/session/no-session-diagnosis.js +76 -27
  319. package/dist/portal/session/no-session-next-action.d.ts +14 -0
  320. package/dist/portal/session/no-session-next-action.js +56 -7
  321. package/dist/portal/session/no-session-watch.d.ts +6 -0
  322. package/dist/portal/session/no-session-watch.js +41 -4
  323. package/dist/portal/session/page-commands/undeclared-command.d.ts +9 -3
  324. package/dist/portal/session/page-commands/undeclared-command.js +16 -7
  325. package/dist/portal/session/recorded-gaps.d.ts +1 -1
  326. package/dist/portal/session/recorded-gaps.js +5 -2
  327. package/dist/portal/session/serving-directory.d.ts +10 -0
  328. package/dist/portal/session/serving-directory.js +39 -8
  329. package/dist/portal/session/session-manager.d.ts +10 -0
  330. package/dist/portal/session/session-manager.js +211 -81
  331. package/dist/portal/session/session.js +4 -3
  332. package/dist/portal/session/zero-install-first-call.d.ts +20 -1
  333. package/dist/portal/session/zero-install-first-call.js +34 -2
  334. package/dist/start-wiring.d.ts +30 -2
  335. package/dist/start-wiring.js +74 -9
  336. package/dist/surface/mcp/mcp-proxy.js +10 -2
  337. package/dist/surface/mcp/mcp.js +8 -5
  338. package/dist/surface/mcp/peer/agents-connected.d.ts +6 -0
  339. package/dist/surface/mcp/peer/agents-connected.js +58 -0
  340. package/dist/surface/mcp/proxy/proxy-daemon-probe.js +17 -5
  341. package/dist/surface/mcp/shared-params.d.ts +2 -0
  342. package/dist/surface/mcp/shared-params.js +31 -0
  343. package/dist/surface/tools/act/act-observation.d.ts +40 -2
  344. package/dist/surface/tools/act/act-observation.js +118 -12
  345. package/dist/surface/tools/act/act-preflight.js +38 -18
  346. package/dist/surface/tools/act/act-sequence-retry.d.ts +23 -2
  347. package/dist/surface/tools/act/act-sequence-retry.js +61 -5
  348. package/dist/surface/tools/act/act-target.js +3 -1
  349. package/dist/surface/tools/act/after-match.d.ts +10 -0
  350. package/dist/surface/tools/act/after-match.js +40 -0
  351. package/dist/surface/tools/act/already-true.js +1 -1
  352. package/dist/surface/tools/act/navigation/reload-result.d.ts +12 -1
  353. package/dist/surface/tools/act/navigation/reload-result.js +25 -2
  354. package/dist/surface/tools/act/resolve-target.d.ts +17 -1
  355. package/dist/surface/tools/act/resolve-target.js +59 -9
  356. package/dist/surface/tools/act/sequence-grade.d.ts +15 -1
  357. package/dist/surface/tools/act/sequence-grade.js +45 -27
  358. package/dist/surface/tools/act/settle-in-flight.js +27 -15
  359. package/dist/surface/tools/act-command.d.ts +23 -0
  360. package/dist/surface/tools/act-command.js +50 -0
  361. package/dist/surface/tools/act-sequence-tool.js +81 -50
  362. package/dist/surface/tools/act-tools.d.ts +1 -13
  363. package/dist/surface/tools/act-tools.js +83 -95
  364. package/dist/surface/tools/agent/build-staleness.d.ts +1 -1
  365. package/dist/surface/tools/agent/build-staleness.js +1 -1
  366. package/dist/surface/tools/assert/assert-verdict.js +18 -12
  367. package/dist/surface/tools/browser-tools.js +87 -13
  368. package/dist/surface/tools/compare-tools.d.ts +11 -0
  369. package/dist/surface/tools/compare-tools.js +94 -0
  370. package/dist/surface/tools/coverage-tools.js +1 -1
  371. package/dist/surface/tools/dynamic-tools.js +11 -3
  372. package/dist/surface/tools/empty-session-list.d.ts +2 -0
  373. package/dist/surface/tools/empty-session-list.js +4 -2
  374. package/dist/surface/tools/error-recovery.d.ts +12 -1
  375. package/dist/surface/tools/error-recovery.js +131 -40
  376. package/dist/surface/tools/explore-tools.js +1 -1
  377. package/dist/surface/tools/invoke-tool.js +29 -21
  378. package/dist/surface/tools/lease-coverage-tools.d.ts +49 -0
  379. package/dist/surface/tools/lease-coverage-tools.js +128 -0
  380. package/dist/surface/tools/lease-hint.d.ts +42 -1
  381. package/dist/surface/tools/lease-hint.js +52 -3
  382. package/dist/surface/tools/lease-readiness.d.ts +47 -0
  383. package/dist/surface/tools/lease-readiness.js +126 -0
  384. package/dist/surface/tools/lease-seed.d.ts +16 -1
  385. package/dist/surface/tools/lease-seed.js +37 -4
  386. package/dist/surface/tools/lease-tools.d.ts +3 -41
  387. package/dist/surface/tools/lease-tools.js +104 -124
  388. package/dist/surface/tools/lease-url-identity.d.ts +46 -0
  389. package/dist/surface/tools/lease-url-identity.js +97 -0
  390. package/dist/surface/tools/merge-tools.js +99 -2
  391. package/dist/surface/tools/merged-name-redirect.js +5 -1
  392. package/dist/surface/tools/observe-tools.js +15 -9
  393. package/dist/surface/tools/read/console-pre-attach.d.ts +19 -0
  394. package/dist/surface/tools/read/console-pre-attach.js +23 -0
  395. package/dist/surface/tools/read-tools.js +33 -30
  396. package/dist/surface/tools/real-input-attempt.d.ts +0 -8
  397. package/dist/surface/tools/real-input-attempt.js +143 -28
  398. package/dist/surface/tools/repeat-hint.d.ts +40 -0
  399. package/dist/surface/tools/repeat-hint.js +171 -0
  400. package/dist/surface/tools/run-report.d.ts +2 -2
  401. package/dist/surface/tools/run-report.js +27 -15
  402. package/dist/surface/tools/session-ports.d.ts +14 -0
  403. package/dist/surface/tools/session-ports.js +53 -0
  404. package/dist/surface/tools/tool-kit.d.ts +18 -0
  405. package/dist/surface/tools/tool-kit.js +9 -3
  406. package/dist/surface/tools/tool-surface.js +0 -1
  407. package/dist/surface/tools/tools.js +54 -18
  408. package/dist/surface/tools/type-spelling.d.ts +13 -0
  409. package/dist/surface/tools/type-spelling.js +36 -0
  410. package/dist/telemetry/agent-client.d.ts +6 -0
  411. package/dist/telemetry/agent-client.js +10 -0
  412. package/dist/telemetry/argument-shape.js +17 -7
  413. package/dist/telemetry/cli-telemetry.d.ts +10 -1
  414. package/dist/telemetry/cli-telemetry.js +15 -0
  415. package/dist/telemetry/init-telemetry.d.ts +2 -1
  416. package/dist/telemetry/init-telemetry.js +47 -2
  417. package/dist/telemetry/install-trace.js +23 -4
  418. package/dist/telemetry/onboarding-funnel.d.ts +19 -2
  419. package/dist/telemetry/onboarding-funnel.js +25 -3
  420. package/dist/telemetry/proxy-telemetry.d.ts +2 -1
  421. package/dist/telemetry/proxy-telemetry.js +2 -2
  422. package/dist/telemetry/session-metrics.d.ts +1 -1
  423. package/dist/telemetry/session-metrics.js +67 -27
  424. package/dist/telemetry/telemetry.d.ts +3 -1
  425. package/dist/telemetry/telemetry.js +60 -28
  426. package/dist/telemetry/tool-refused.d.ts +6 -3
  427. package/dist/telemetry/tool-refused.js +15 -3
  428. package/docs/actions.mdx +3 -3
  429. package/docs/agent-cheatsheet.md +1 -1
  430. package/docs/architecture.md +3 -6
  431. package/docs/capabilities.mdx +61 -1
  432. package/docs/cli/affected.mdx +9 -7
  433. package/docs/cli/cloud.mdx +3 -3
  434. package/docs/cli/compare.mdx +94 -0
  435. package/docs/cli/doctor.mdx +41 -2
  436. package/docs/cli/grade.mdx +97 -0
  437. package/docs/cli/help.mdx +4 -2
  438. package/docs/cli/license.mdx +13 -12
  439. package/docs/cli/restart.mdx +7 -5
  440. package/docs/cli/rollback.mdx +5 -5
  441. package/docs/cli/setup.mdx +13 -2
  442. package/docs/cli/status.mdx +4 -0
  443. package/docs/cli/stop.mdx +3 -3
  444. package/docs/cli/update.mdx +2 -2
  445. package/docs/cli/verify.mdx +23 -1
  446. package/docs/cli.mdx +34 -24
  447. package/docs/deploy-checks.md +25 -0
  448. package/docs/desktop.mdx +1 -1
  449. package/docs/enterprise.md +17 -22
  450. package/docs/environment.mdx +1 -0
  451. package/docs/first-drive.md +1 -1
  452. package/docs/flows.md +11 -1
  453. package/docs/frameworks.mdx +9 -9
  454. package/docs/getting-started.md +1 -1
  455. package/docs/install-manual.mdx +16 -1
  456. package/docs/license-activation.mdx +3 -3
  457. package/docs/packages/engine.mdx +1 -1
  458. package/docs/packages/server.mdx +1 -1
  459. package/docs/packages/tauri.mdx +1 -1
  460. package/docs/packages/test.mdx +1 -1
  461. package/docs/packages.mdx +1 -1
  462. package/docs/platform-integration.md +1 -1
  463. package/docs/predicates.mdx +1 -1
  464. package/docs/quickstart.mdx +68 -49
  465. package/docs/skill-setup.md +1 -1
  466. package/docs/skill.md +14 -13
  467. package/docs/telemetry.md +6 -4
  468. package/docs/tools/navigate.mdx +1 -1
  469. package/docs/tools/verify.mdx +4 -1
  470. package/docs/troubleshooting.mdx +1 -1
  471. package/docs/usage.md +17 -14
  472. package/package.json +18 -14
  473. package/LICENSE-ENTERPRISE +0 -37
  474. package/dist/features/ee/audit-log.d.ts +0 -14
  475. package/dist/features/ee/audit-log.js +0 -11
  476. package/dist/features/exhaust/exhaust-run.d.ts +0 -33
  477. package/dist/features/exhaust/exhaust-run.js +0 -82
@@ -0,0 +1,93 @@
1
+ /**
2
+ * Grade a finished run of an app from snapshots of its stored data. Pure.
3
+ *
4
+ * A snapshot is the state lines of `readStateSources`, and optionally a `url` line for the page the
5
+ * run ended on. Three snapshots go in: the reference (the app after the correct steps), again (the
6
+ * app after the same correct steps, from the same start), and the candidate (the app after the steps
7
+ * to grade). A line that differs between the two reference snapshots is noise and is ignored, and so
8
+ * is a change to a cell of a row that the two references wrote differently (see `grade-noise.ts`). Any
9
+ * other line that differs between the reference and the candidate is a difference, and the grade
10
+ * fails. A source that could not be read makes the grade unverified, never a pass.
11
+ *
12
+ * A row the candidate deleted and wrote again with a new id and the same values is not a difference
13
+ * (see `grade-renumber.ts`). The grade counts these rows.
14
+ *
15
+ * State lines are sorted rows, so the diff is a count of each line, not an ordered diff. That keeps
16
+ * the cost linear in the number of rows, and a source of many thousand rows is cheap.
17
+ */
18
+ import { z } from 'zod';
19
+ import { MSG_READ_FAILED } from '../compare/outside/state-sources.js';
20
+ import { noiseCells, withoutNoiseCells } from './grade-noise.js';
21
+ import { renumber } from './grade-renumber.js';
22
+ export const GradeVerdict = {
23
+ PASS: 'pass',
24
+ FAIL: 'fail',
25
+ /** The grade proves nothing: a source could not be read, or the answer could not be checked. */
26
+ UNVERIFIED: 'unverified',
27
+ };
28
+ export const SnapshotSchema = z.strictObject({
29
+ lines: z.array(z.string()),
30
+ });
31
+ /** The line that holds the page a run ended on. */
32
+ export const URL_PREFIX = 'url';
33
+ /**
34
+ * The page a run ended on, as one line: the origin and path without a final slash, and the query
35
+ * values sorted by name. The fragment is removed. Text that is not a URL is kept as it is.
36
+ */
37
+ export function urlLine(url) {
38
+ let parsed;
39
+ try {
40
+ parsed = new URL(url);
41
+ }
42
+ catch {
43
+ return `${URL_PREFIX} ${url.trim()}`;
44
+ }
45
+ parsed.searchParams.sort();
46
+ const path = parsed.pathname.replace(/\/+$/, '');
47
+ const query = parsed.searchParams.toString();
48
+ return `${URL_PREFIX} ${parsed.origin}${path}${'' === query ? '' : `?${query}`}`;
49
+ }
50
+ const counts = (lines) => {
51
+ const out = new Map();
52
+ for (const line of lines) {
53
+ out.set(line, (out.get(line) ?? 0) + 1);
54
+ }
55
+ return out;
56
+ };
57
+ /** The lines `a` has more times than `b`, once for each extra time. */
58
+ function surplus(a, b) {
59
+ const left = counts(b);
60
+ const out = [];
61
+ for (const line of a) {
62
+ const n = left.get(line) ?? 0;
63
+ if (n > 0) {
64
+ left.set(line, n - 1);
65
+ }
66
+ else {
67
+ out.push(line);
68
+ }
69
+ }
70
+ return out;
71
+ }
72
+ export function gradeState(reference, again, candidate) {
73
+ const unread = [reference, again, candidate].flatMap((s) => s.lines.filter((line) => line.startsWith(MSG_READ_FAILED)));
74
+ const referenceOnly = surplus(reference.lines, again.lines);
75
+ const againOnly = surplus(again.lines, reference.lines);
76
+ const noise = new Set([...referenceOnly, ...againOnly]);
77
+ const all = {
78
+ removed: surplus(reference.lines, candidate.lines),
79
+ added: surplus(candidate.lines, reference.lines),
80
+ };
81
+ const removedKept = all.removed.filter((line) => !noise.has(line));
82
+ const byCell = withoutNoiseCells(all.added.filter((line) => !noise.has(line)), noiseCells(referenceOnly, againOnly));
83
+ const { removed, added, renumbered } = renumber(removedKept, byCell.added, reference.lines, candidate.lines);
84
+ const ignored = all.removed.length + all.added.length - removedKept.length - byCell.added.length;
85
+ let verdict = GradeVerdict.PASS;
86
+ if (unread.length > 0) {
87
+ verdict = GradeVerdict.UNVERIFIED;
88
+ }
89
+ else if (removed.length > 0 || added.length > 0) {
90
+ verdict = GradeVerdict.FAIL;
91
+ }
92
+ return { verdict, removed, added, ignored, unread, renumbered };
93
+ }
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Write the state declaration for an app, so nobody writes one by hand. Pure.
3
+ *
4
+ * Two steps:
5
+ * 1. `tableSources`: one `command` source for each table of a database, from a list of table names
6
+ * and a command template with `{table}` in it. `skip` holds name prefixes. It removes the tables that hold no task data,
7
+ * such as a message queue that a background worker empties at its own time.
8
+ * 2. `quietSources`: keep the sources that read the same on two runs of the same correct steps.
9
+ * A source that changes between those two runs (a session table, a log) would make every
10
+ * candidate fail, because the candidate's own new rows are lines that neither reference has. Such
11
+ * a source is kept with a time pattern removed when that makes it read the same, else dropped.
12
+ * Each dropped source is reported with the reason.
13
+ *
14
+ * A source the reference does not change is kept too: a change to it by the candidate is a
15
+ * change to a record the task did not name.
16
+ */
17
+ import { type StateSource } from '../compare/outside/state-sources.js';
18
+ /** The word in a command template that the table name replaces. */
19
+ export declare const TABLE_SLOT = "{table}";
20
+ /** A date and time, with optional fractions of a second: the most common noise in a row. */
21
+ export declare const TIME_PATTERN = "\\d{4}-\\d\\d-\\d\\d[ T]\\d\\d:\\d\\d:\\d\\d(?:\\.\\d+)?";
22
+ export interface Skipped {
23
+ label: string;
24
+ reason: string;
25
+ }
26
+ export declare function tableSources(names: readonly string[], template: string, container?: string, skip?: readonly string[]): {
27
+ sources: StateSource[];
28
+ skipped: Skipped[];
29
+ };
30
+ export declare function quietSources(sources: readonly StateSource[], reference: readonly string[], again: readonly string[]): {
31
+ sources: StateSource[];
32
+ dropped: Skipped[];
33
+ };
@@ -0,0 +1,98 @@
1
+ /**
2
+ * Write the state declaration for an app, so nobody writes one by hand. Pure.
3
+ *
4
+ * Two steps:
5
+ * 1. `tableSources`: one `command` source for each table of a database, from a list of table names
6
+ * and a command template with `{table}` in it. `skip` holds name prefixes. It removes the tables that hold no task data,
7
+ * such as a message queue that a background worker empties at its own time.
8
+ * 2. `quietSources`: keep the sources that read the same on two runs of the same correct steps.
9
+ * A source that changes between those two runs (a session table, a log) would make every
10
+ * candidate fail, because the candidate's own new rows are lines that neither reference has. Such
11
+ * a source is kept with a time pattern removed when that makes it read the same, else dropped.
12
+ * Each dropped source is reported with the reason.
13
+ *
14
+ * A source the reference does not change is kept too: a change to it by the candidate is a
15
+ * change to a record the task did not name.
16
+ */
17
+ import { MSG_READ_FAILED, MSG_ROWS_CUT, STATE_PREFIX, StateSourceKind, } from '../compare/outside/state-sources.js';
18
+ /** The word in a command template that the table name replaces. */
19
+ export const TABLE_SLOT = '{table}';
20
+ /** A table name that is safe in a shell command and in SQL without quotes. */
21
+ const TABLE_NAME = /^[A-Za-z0-9_$]+$/;
22
+ /** A date and time, with optional fractions of a second: the most common noise in a row. */
23
+ export const TIME_PATTERN = '\\d{4}-\\d\\d-\\d\\d[ T]\\d\\d:\\d\\d:\\d\\d(?:\\.\\d+)?';
24
+ export function tableSources(names, template, container, skip = []) {
25
+ const sources = [];
26
+ const skipped = [];
27
+ for (const raw of names) {
28
+ const name = raw.trim();
29
+ if ('' === name) {
30
+ continue;
31
+ }
32
+ if (skip.some((prefix) => name.startsWith(prefix))) {
33
+ skipped.push({ label: name, reason: 'the name starts with a --skip prefix' });
34
+ continue;
35
+ }
36
+ if (!TABLE_NAME.test(name)) {
37
+ skipped.push({ label: name, reason: 'the name has a character other than A-Z, 0-9, _ or $' });
38
+ continue;
39
+ }
40
+ sources.push({
41
+ kind: StateSourceKind.COMMAND,
42
+ label: name,
43
+ command: template.replaceAll(TABLE_SLOT, name),
44
+ ...(container === undefined ? {} : { container }),
45
+ });
46
+ }
47
+ return { sources, skipped };
48
+ }
49
+ /** The rows of one source in a snapshot, and the read failure when there is one. */
50
+ function rowsOf(lines, label) {
51
+ const row = `${STATE_PREFIX} ${label} `;
52
+ const cut = `${MSG_ROWS_CUT} ${label}: `;
53
+ const failed = lines.find((line) => line.startsWith(`${MSG_READ_FAILED} ${label}: `));
54
+ const rows = lines.filter((line) => line.startsWith(row) || line.startsWith(cut));
55
+ return failed === undefined ? { rows } : { rows, failed };
56
+ }
57
+ function sameRows(a, b) {
58
+ if (a.length !== b.length) {
59
+ return false;
60
+ }
61
+ const x = [...a].sort();
62
+ const y = [...b].sort();
63
+ return x.every((line, i) => line === y[i]);
64
+ }
65
+ /** The first row that only one of the two runs has, to show why a source was dropped. */
66
+ function firstDifference(a, b) {
67
+ const inA = new Set(a);
68
+ const inB = new Set(b);
69
+ return a.find((line) => !inB.has(line)) ?? b.find((line) => !inA.has(line)) ?? '';
70
+ }
71
+ export function quietSources(sources, reference, again) {
72
+ const kept = [];
73
+ const dropped = [];
74
+ const time = new RegExp(TIME_PATTERN, 'g');
75
+ for (const source of sources) {
76
+ const a = rowsOf(reference, source.label);
77
+ const b = rowsOf(again, source.label);
78
+ const failed = a.failed ?? b.failed;
79
+ if (failed !== undefined) {
80
+ dropped.push({ label: source.label, reason: failed });
81
+ continue;
82
+ }
83
+ if (sameRows(a.rows, b.rows)) {
84
+ kept.push(source);
85
+ continue;
86
+ }
87
+ const untimed = (rows) => rows.map((r) => r.replace(time, ''));
88
+ if (StateSourceKind.COMMAND === source.kind && sameRows(untimed(a.rows), untimed(b.rows))) {
89
+ kept.push({ ...source, ignore: [...(source.ignore ?? []), TIME_PATTERN] });
90
+ continue;
91
+ }
92
+ dropped.push({
93
+ label: source.label,
94
+ reason: `it changes between two runs of the same steps: ${firstDifference(a.rows, b.rows)}`,
95
+ });
96
+ }
97
+ return { sources: kept, dropped };
98
+ }
@@ -1,14 +1,9 @@
1
1
  /**
2
- * Enterprise license-key verification + the assertEnterprise gate. ENTERPRISE CODE (Reticle Enterprise
3
- * License — see./LICENSE), not the FSL that covers the rest of the server.
2
+ * License-key verification for `reticle license` and the telemetry `licenseStatus` field.
4
3
  *
5
- * Offline + no phone-home (preserves the no-telemetry brand): a key is a signed payload Reticle issues
6
- * with its private key; this verifies it against the issuer's PUBLIC key with Ed25519. Dev/eval is a
7
- * no-op (requireLicense:false) so the gate never gets in a contributor's way; only a production-mode
8
- * caller with requireLicense:true must present a valid, unexpired key. The clock is injected (rule 7).
9
- *
10
- * Rules for this directory: the free server must never import from ee/; ee/ depends only on protocol +
11
- * node + zod. This file imports neither server core nor any sibling outside ee/.
4
+ * Offline, with no phone-home: a key is a signed payload that Reticle issues with its private key.
5
+ * This file verifies it against the issuer's PUBLIC key with Ed25519. Nothing in this package is
6
+ * gated on a key. The clock is injected (rule 7).
12
7
  */
13
8
  import { type KeyObject } from 'node:crypto';
14
9
  import { z } from 'zod';
@@ -23,8 +18,8 @@ export declare const LicenseStatus: {
23
18
  };
24
19
  export type LicenseStatus = (typeof LicenseStatus)[keyof typeof LicenseStatus];
25
20
  /**
26
- * The signed claims inside a key. `exp` is epoch ms; `features` (optional) scopes which ee features
27
- * unlock.
21
+ * The signed claims inside a key. `exp` is epoch ms; `features` (optional) scopes the features that
22
+ * the key covers.
28
23
  *
29
24
  * `lid` is the stable licence id and the only safe identity: `org` is a display name a human typed at
30
25
  * signing time, so two organisations with the same name collapse into one and a rename splits one into
@@ -61,48 +56,14 @@ export declare function signLicenseKey(payload: LicensePayload, privateKey: KeyO
61
56
  /** Verify a key against the issuer public key. Never throws — returns a structured status. */
62
57
  export declare function verifyLicenseKey(key: string | undefined, publicKey: KeyObject, now: number): LicenseCheck;
63
58
  /**
64
- * Where to get a key. A named constant because it appears in the gate's error AND in what
65
- * `reticle license` prints, and a contact address that drifts between the two is a support request
66
- * that reaches nobody.
59
+ * Where to get a key. A named constant because `reticle license` prints it on every branch, and a
60
+ * contact address that drifts is a support request that reaches nobody.
67
61
  */
68
62
  export declare const LICENSE_CONTACT = "hey@reticle.sh";
69
- /**
70
- * The features a licence gates in THIS build. The single source: `assertEnterprise` callers name a
71
- * member rather than a string literal, and `reticle license` reports the list, so what the CLI claims
72
- * is gated is derived from what is actually gated rather than a second list somebody maintains.
73
- *
74
- * It is deliberately short. Reticle's roadmap for enterprise is longer (SSO, SCIM, RBAC, multi-org,
75
- * policy gates), and none of it is here yet — printing a roadmap as though it were shipped is how a
76
- * buyer discovers on day two that the thing they paid for does not exist. What is listed is what the
77
- * running build will actually refuse without a key.
78
- */
79
- export declare const EnterpriseFeature: {
80
- /** Recording an audit event. */
81
- readonly AUDIT_LOG: "audit-log";
82
- };
83
- export type EnterpriseFeature = (typeof EnterpriseFeature)[keyof typeof EnterpriseFeature];
84
- /** Thrown when a production-mode enterprise feature is used without a valid license. */
85
- export declare class EnterpriseLicenseError extends Error {
86
- readonly feature: string;
87
- readonly reason: string;
88
- constructor(feature: string, reason: string);
89
- }
90
- /** Context for the gate. requireLicense:false ⇒ dev/eval no-op. publicKey injectable for tests. */
91
- export interface GateContext {
92
- requireLicense: boolean;
93
- now: () => number;
94
- key?: string;
95
- publicKey?: KeyObject;
96
- }
97
- /**
98
- * Gate an enterprise feature. No-op in dev/eval (requireLicense:false). In production it throws
99
- * EnterpriseLicenseError unless a valid, unexpired key that covers `feature` is present.
100
- */
101
- export declare function assertEnterprise(feature: string, ctx: GateContext, env?: NodeJS.ProcessEnv, baked?: string): void;
102
63
  /** Env names that carry the activation: the operator's key, and the issuer public key baked at release. */
103
64
  export declare const LICENSE_KEY_ENV = "RETICLE_LICENSE_KEY";
104
65
  export declare const LICENSE_PUBLIC_KEY_ENV = "RETICLE_LICENSE_PUBLIC_KEY";
105
- /** The human-facing state of enterprise activation on this machine (what `reticle license status` shows). */
66
+ /** The human-facing state of license activation on this machine (what `reticle license` shows). */
106
67
  interface LicenseReport {
107
68
  /**
108
69
  * Typed against core's closed list rather than re-listing the strings here: this status is reported
@@ -112,18 +73,7 @@ interface LicenseReport {
112
73
  status: LicenseActivation;
113
74
  /** The stable license id — what usage is attributed to. Present only when `status` is `active`. */
114
75
  licenseId?: string;
115
- /**
116
- * What a licence unlocks in this build, reported alongside where to get one — being told to set a
117
- * variable with no idea what it unlocks is a dead end at the moment somebody is asking. Omitted
118
- * once a licence is `active`, where the reader already has it.
119
- */
120
- gated?: readonly string[];
121
76
  contact: string;
122
- /**
123
- * The key is valid, and scoped to features this build does not gate, so it unlocks nothing here.
124
- * Present only when true: a `false` on every healthy licence is noise on the common case.
125
- */
126
- coversNothingHere?: boolean;
127
77
  org?: string;
128
78
  plan?: string;
129
79
  expiresAt?: number;
@@ -132,18 +82,8 @@ interface LicenseReport {
132
82
  }
133
83
  /**
134
84
  * Resolve activation entirely from the environment — the install mechanism: the release bakes the
135
- * issuer public key, the operator sets RETICLE_LICENSE_KEY. No public key configured ⇒ evaluation mode
136
- * (enterprise features run free, dev/test only). Offline, no phone-home.
85
+ * issuer public key, the operator sets RETICLE_LICENSE_KEY. No public key configured ⇒ evaluation mode.
86
+ * Offline, no phone-home.
137
87
  */
138
88
  export declare function describeLicense(now: number, env?: NodeJS.ProcessEnv, baked?: string): LicenseReport;
139
- /**
140
- * Gate an enterprise feature using env-resolved activation. Enforcement is ON when the issuer public key
141
- * is configured (a real release). Without it, behaviour splits on runtime:
142
- * - dev/eval (NODE_ENV ≠ production): features run FREE — never blocks a contributor or CI.
143
- * - production (NODE_ENV = production): FAIL CLOSED — a release that reaches production with no
144
- * resolvable issuer key was mis-built (the baked key wasn't stamped in, BAKED_ISSUER_PUBLIC_KEY_PEM
145
- * is still ''), and silently unlocking every ee feature with no key and no warning is the worst
146
- * outcome. Deny instead, with a distinct reason so the misconfiguration is obvious.
147
- */
148
- export declare function assertEnterpriseFromEnv(feature: string, now: number, env?: NodeJS.ProcessEnv, baked?: string): void;
149
89
  export {};
@@ -1,14 +1,9 @@
1
1
  /**
2
- * Enterprise license-key verification + the assertEnterprise gate. ENTERPRISE CODE (Reticle Enterprise
3
- * License — see./LICENSE), not the FSL that covers the rest of the server.
2
+ * License-key verification for `reticle license` and the telemetry `licenseStatus` field.
4
3
  *
5
- * Offline + no phone-home (preserves the no-telemetry brand): a key is a signed payload Reticle issues
6
- * with its private key; this verifies it against the issuer's PUBLIC key with Ed25519. Dev/eval is a
7
- * no-op (requireLicense:false) so the gate never gets in a contributor's way; only a production-mode
8
- * caller with requireLicense:true must present a valid, unexpired key. The clock is injected (rule 7).
9
- *
10
- * Rules for this directory: the free server must never import from ee/; ee/ depends only on protocol +
11
- * node + zod. This file imports neither server core nor any sibling outside ee/.
4
+ * Offline, with no phone-home: a key is a signed payload that Reticle issues with its private key.
5
+ * This file verifies it against the issuer's PUBLIC key with Ed25519. Nothing in this package is
6
+ * gated on a key. The clock is injected (rule 7).
12
7
  */
13
8
  import { createPublicKey, sign as edSign, verify as edVerify } from 'node:crypto';
14
9
  import { z } from 'zod';
@@ -22,8 +17,8 @@ export const LicenseStatus = {
22
17
  EXPIRED: 'expired',
23
18
  };
24
19
  /**
25
- * The signed claims inside a key. `exp` is epoch ms; `features` (optional) scopes which ee features
26
- * unlock.
20
+ * The signed claims inside a key. `exp` is epoch ms; `features` (optional) scopes the features that
21
+ * the key covers.
27
22
  *
28
23
  * `lid` is the stable licence id and the only safe identity: `org` is a display name a human typed at
29
24
  * signing time, so two organisations with the same name collapse into one and a rename splits one into
@@ -48,8 +43,9 @@ export function signLicenseKey(payload, privateKey) {
48
43
  }
49
44
  /** Verify a key against the issuer public key. Never throws — returns a structured status. */
50
45
  export function verifyLicenseKey(key, publicKey, now) {
51
- if (key === undefined || 0 === key.length)
46
+ if (key === undefined || 0 === key.length) {
52
47
  return { status: LicenseStatus.MISSING };
48
+ }
53
49
  const parts = key.split(KEY_SEP);
54
50
  if (parts.length !== 2 ||
55
51
  parts[0] === undefined ||
@@ -62,8 +58,9 @@ export function verifyLicenseKey(key, publicKey, now) {
62
58
  try {
63
59
  json = Buffer.from(parts[0], 'base64url').toString('utf8');
64
60
  const parsed = LicensePayloadSchema.safeParse(JSON.parse(json));
65
- if (!parsed.success)
61
+ if (!parsed.success) {
66
62
  return { status: LicenseStatus.MALFORMED };
63
+ }
67
64
  payload = parsed.data;
68
65
  }
69
66
  catch {
@@ -76,98 +73,45 @@ export function verifyLicenseKey(key, publicKey, now) {
76
73
  catch {
77
74
  return { status: LicenseStatus.BAD_SIGNATURE };
78
75
  }
79
- if (!signatureOk)
76
+ if (!signatureOk) {
80
77
  return { status: LicenseStatus.BAD_SIGNATURE };
81
- if (payload.exp <= now)
78
+ }
79
+ if (payload.exp <= now) {
82
80
  return { status: LicenseStatus.EXPIRED, payload };
81
+ }
83
82
  return { status: LicenseStatus.VALID, payload };
84
83
  }
85
84
  /**
86
- * Where to get a key. A named constant because it appears in the gate's error AND in what
87
- * `reticle license` prints, and a contact address that drifts between the two is a support request
88
- * that reaches nobody.
85
+ * Where to get a key. A named constant because `reticle license` prints it on every branch, and a
86
+ * contact address that drifts is a support request that reaches nobody.
89
87
  */
90
88
  export const LICENSE_CONTACT = 'hey@reticle.sh';
91
- /**
92
- * The features a licence gates in THIS build. The single source: `assertEnterprise` callers name a
93
- * member rather than a string literal, and `reticle license` reports the list, so what the CLI claims
94
- * is gated is derived from what is actually gated rather than a second list somebody maintains.
95
- *
96
- * It is deliberately short. Reticle's roadmap for enterprise is longer (SSO, SCIM, RBAC, multi-org,
97
- * policy gates), and none of it is here yet — printing a roadmap as though it were shipped is how a
98
- * buyer discovers on day two that the thing they paid for does not exist. What is listed is what the
99
- * running build will actually refuse without a key.
100
- */
101
- export const EnterpriseFeature = {
102
- /** Recording an audit event. */
103
- AUDIT_LOG: 'audit-log',
104
- };
105
- /** Thrown when a production-mode enterprise feature is used without a valid license. */
106
- export class EnterpriseLicenseError extends Error {
107
- feature;
108
- reason;
109
- constructor(feature, reason) {
110
- super(`Reticle Enterprise feature "${feature}" requires a valid license (${reason}). Contact ${LICENSE_CONTACT}.`);
111
- this.name = 'EnterpriseLicenseError';
112
- this.feature = feature;
113
- this.reason = reason;
114
- }
115
- }
116
- /**
117
- * Gate an enterprise feature. No-op in dev/eval (requireLicense:false). In production it throws
118
- * EnterpriseLicenseError unless a valid, unexpired key that covers `feature` is present.
119
- */
120
- export function assertEnterprise(feature, ctx, env = process.env, baked) {
121
- if (!ctx.requireLicense)
122
- return;
123
- // Resolved exactly as the env-entry point resolves it: BAKED first, env only as the dev/test
124
- // hatch. Reading the environment alone denies every valid licence on a real release, where the key
125
- // is baked and no env var is set. Two gates, one truth.
126
- const publicKey = ctx.publicKey ?? loadPublicKey(resolveIssuerPublicKeyPem(env, baked));
127
- if (publicKey === undefined)
128
- throw new EnterpriseLicenseError(feature, 'no-issuer-key');
129
- const check = verifyLicenseKey(ctx.key, publicKey, ctx.now());
130
- if (check.status !== LicenseStatus.VALID)
131
- throw new EnterpriseLicenseError(feature, check.status);
132
- const { features } = check.payload;
133
- if (features !== undefined && !features.includes(feature)) {
134
- throw new EnterpriseLicenseError(feature, 'feature-not-licensed');
135
- }
136
- }
137
89
  /** Env names that carry the activation: the operator's key, and the issuer public key baked at release. */
138
90
  export const LICENSE_KEY_ENV = 'RETICLE_LICENSE_KEY';
139
91
  export const LICENSE_PUBLIC_KEY_ENV = 'RETICLE_LICENSE_PUBLIC_KEY';
140
92
  /**
141
93
  * Issuer public key COMPILED INTO the build. Empty in the source tree; the release pipeline replaces
142
- * this literal with the real spki PEM. Baking it (rather than only reading env) is what makes the
143
- * enterprise gate fail closed: a self-hosted operator can no longer disable enforcement by simply
144
- * never setting RETICLE_LICENSE_PUBLIC_KEY, because the baked key takes precedence. The public key is
145
- * safe to ship openly — it can only verify licenses, never mint them (that needs the private key).
94
+ * this literal with the real spki PEM. The baked key takes precedence over RETICLE_LICENSE_PUBLIC_KEY,
95
+ * so an operator cannot swap in a key of their own. The public key is safe to ship openly: it can
96
+ * only verify licenses, never mint them (that needs the private key).
146
97
  */
147
98
  const BAKED_ISSUER_PUBLIC_KEY_PEM = "-----BEGIN PUBLIC KEY-----\nMCowBQYDK2VwAyEAcg+u61Fs5pwDHcYxFGhaxVaQoUbAdyl+RUdSViZyxDY=\n-----END PUBLIC KEY-----";
148
99
  /**
149
- * Resolve the issuer public-key PEM enforcement uses: the baked-in release key wins, so it can't be
150
- * turned off from the environment. Only when nothing is baked (dev/repo) does the env var apply — that
100
+ * Resolve the issuer public-key PEM: the baked-in release key wins, so the environment cannot
101
+ * replace it. Only when nothing is baked (dev/repo) does the env var apply — that
151
102
  * env path is the test/self-eval escape hatch, not the production switch.
152
103
  */
153
104
  function resolveIssuerPublicKeyPem(env, baked = BAKED_ISSUER_PUBLIC_KEY_PEM) {
154
- if (baked.length > 0)
105
+ if (baked.length > 0) {
155
106
  return baked;
107
+ }
156
108
  const envPem = env[LICENSE_PUBLIC_KEY_ENV];
157
109
  return envPem !== undefined && envPem.length > 0 ? envPem : undefined;
158
110
  }
159
- /**
160
- * The two fields every report carries, spread into each branch. One object rather than a field on each
161
- * return, because the value that must never go missing is the one on the branch nobody was thinking
162
- * about — and the branch an unlicensed reader lands on is exactly that branch.
163
- */
164
- const LICENSE_OFFER = {
165
- gated: Object.values(EnterpriseFeature),
166
- contact: LICENSE_CONTACT,
167
- };
168
111
  function loadPublicKey(pem) {
169
- if (pem === undefined || 0 === pem.length)
112
+ if (pem === undefined || 0 === pem.length) {
170
113
  return undefined;
114
+ }
171
115
  try {
172
116
  return createPublicKey(pem);
173
117
  }
@@ -177,111 +121,74 @@ function loadPublicKey(pem) {
177
121
  }
178
122
  /**
179
123
  * Resolve activation entirely from the environment — the install mechanism: the release bakes the
180
- * issuer public key, the operator sets RETICLE_LICENSE_KEY. No public key configured ⇒ evaluation mode
181
- * (enterprise features run free, dev/test only). Offline, no phone-home.
124
+ * issuer public key, the operator sets RETICLE_LICENSE_KEY. No public key configured ⇒ evaluation mode.
125
+ * Offline, no phone-home.
182
126
  */
183
127
  export function describeLicense(now, env = process.env, baked = BAKED_ISSUER_PUBLIC_KEY_PEM) {
184
128
  const pem = resolveIssuerPublicKeyPem(env, baked);
185
129
  if (pem === undefined || 0 === pem.length) {
186
- // In a production runtime this is NOT a benign eval session — it is a mis-built release whose gate
187
- // is off, and assertEnterpriseFromEnv now denies rather than unlocking. Say so loudly here too.
130
+ // In a production runtime this is NOT a benign eval session: it is a mis-built release. Say so.
188
131
  return isProductionEnv(env)
189
132
  ? {
190
- ...LICENSE_OFFER,
133
+ contact: LICENSE_CONTACT,
191
134
  status: LicenseActivation.INVALID,
192
- detail: 'MISCONFIGURED — running in production with no issuer key baked; enterprise features are DENIED (rebuild with BAKED_ISSUER_PUBLIC_KEY_PEM stamped in)',
135
+ detail: 'MISCONFIGURED — running in production with no issuer key baked (rebuild with BAKED_ISSUER_PUBLIC_KEY_PEM stamped in)',
193
136
  }
194
137
  : {
195
- ...LICENSE_OFFER,
138
+ contact: LICENSE_CONTACT,
196
139
  status: LicenseActivation.EVAL,
197
- detail: 'evaluation mode: enterprise features run free (no issuer key configured)',
140
+ detail: 'evaluation mode (no issuer key configured)',
198
141
  };
199
142
  }
200
143
  const publicKey = loadPublicKey(pem);
201
- if (publicKey === undefined)
144
+ if (publicKey === undefined) {
202
145
  return {
203
- ...LICENSE_OFFER,
146
+ contact: LICENSE_CONTACT,
204
147
  status: LicenseActivation.INVALID,
205
148
  detail: `${LICENSE_PUBLIC_KEY_ENV} is not a valid public key`,
206
149
  };
150
+ }
207
151
  const check = verifyLicenseKey(env[LICENSE_KEY_ENV], publicKey, now);
208
152
  if (check.status === LicenseStatus.VALID) {
209
153
  const { lid, org, plan, exp, features } = check.payload;
210
- // A key scoped to features this build does not gate is `active` and unlocks nothing. Both halves
211
- // were already on screen (`features` against `gated`) and nothing joined them, so the one word a
212
- // customer actually reads told them they were fine while every gated call refused them.
213
- const coversNothingHere = features !== undefined && !features.some((f) => LICENSE_OFFER.gated.includes(f));
214
- const detail = coversNothingHere
215
- ? `licensed to ${org} (${plan}), expires ${new Date(exp).toISOString()}. This key covers ${features?.join(', ')}, and nothing this build gates (${LICENSE_OFFER.gated.join(', ')}) is included. Contact ${LICENSE_CONTACT}`
216
- : `licensed to ${org} (${plan}), expires ${new Date(exp).toISOString()}`;
217
154
  return {
218
155
  contact: LICENSE_CONTACT,
219
- // The one active case that still needs the list: a key covering nothing this build gates is
220
- // reported here, and the sentence explaining it is unreadable without both sides named.
221
- ...(coversNothingHere ? { gated: LICENSE_OFFER.gated } : {}),
222
156
  status: LicenseActivation.ACTIVE,
223
157
  licenseId: lid,
224
158
  org,
225
159
  plan,
226
160
  expiresAt: exp,
227
161
  ...(features !== undefined ? { features } : {}),
228
- ...(coversNothingHere ? { coversNothingHere } : {}),
229
- detail,
162
+ detail: `licensed to ${org} (${plan}), expires ${new Date(exp).toISOString()}`,
230
163
  };
231
164
  }
232
165
  if (check.status === LicenseStatus.MISSING) {
233
166
  return {
234
- ...LICENSE_OFFER,
167
+ contact: LICENSE_CONTACT,
235
168
  status: LicenseActivation.MISSING,
236
- detail: `set ${LICENSE_KEY_ENV} to activate enterprise features in production. ` +
237
- `Request a key from ${LICENSE_CONTACT}`,
169
+ detail: `set ${LICENSE_KEY_ENV} to activate a license. Request a key from ${LICENSE_CONTACT}`,
238
170
  };
239
171
  }
240
172
  if (check.status === LicenseStatus.EXPIRED) {
241
173
  // The id rides an expired key so the lapse can be identified; without it an expiry is
242
174
  // indistinguishable from any other.
243
175
  return {
244
- ...LICENSE_OFFER,
176
+ contact: LICENSE_CONTACT,
245
177
  status: LicenseActivation.EXPIRED,
246
178
  licenseId: check.payload.lid,
247
179
  org: check.payload.org,
248
180
  expiresAt: check.payload.exp,
249
- detail: `license expired. Renew with ${LICENSE_CONTACT} to keep using enterprise features`,
181
+ detail: `license expired. Renew with ${LICENSE_CONTACT}`,
250
182
  };
251
183
  }
252
184
  return {
253
- ...LICENSE_OFFER,
185
+ contact: LICENSE_CONTACT,
254
186
  status: LicenseActivation.INVALID,
255
187
  detail: `license key rejected (${check.status}). ${LICENSE_CONTACT} can re-issue it`,
256
188
  };
257
189
  }
258
- /** True in a production runtime (NODE_ENV=production) — where a missing issuer key is a mis-built
259
- * release, not an eval session, so the gate must fail CLOSED rather than run features free. */
190
+ /** True in a production runtime (NODE_ENV=production), where a missing issuer key is a mis-built
191
+ * release, not an eval session. */
260
192
  function isProductionEnv(env) {
261
193
  return 'production' === env['NODE_ENV'];
262
194
  }
263
- /**
264
- * Gate an enterprise feature using env-resolved activation. Enforcement is ON when the issuer public key
265
- * is configured (a real release). Without it, behaviour splits on runtime:
266
- * - dev/eval (NODE_ENV ≠ production): features run FREE — never blocks a contributor or CI.
267
- * - production (NODE_ENV = production): FAIL CLOSED — a release that reaches production with no
268
- * resolvable issuer key was mis-built (the baked key wasn't stamped in, BAKED_ISSUER_PUBLIC_KEY_PEM
269
- * is still ''), and silently unlocking every ee feature with no key and no warning is the worst
270
- * outcome. Deny instead, with a distinct reason so the misconfiguration is obvious.
271
- */
272
- export function assertEnterpriseFromEnv(feature, now, env = process.env, baked = BAKED_ISSUER_PUBLIC_KEY_PEM) {
273
- const publicKey = loadPublicKey(resolveIssuerPublicKeyPem(env, baked));
274
- if (publicKey === undefined) {
275
- if (isProductionEnv(env)) {
276
- throw new EnterpriseLicenseError(feature, 'enterprise-gate-unconfigured');
277
- }
278
- return; // dev/eval — free
279
- }
280
- const key = env[LICENSE_KEY_ENV];
281
- assertEnterprise(feature, {
282
- requireLicense: true,
283
- now: () => now,
284
- ...(key !== undefined ? { key } : {}),
285
- publicKey,
286
- });
287
- }