@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.
- package/LICENSE +201 -104
- package/NOTICE +5 -0
- package/README.md +1 -1
- package/SKILL.md +13 -12
- package/bin/launch.js +13 -0
- package/bin/package.json +3 -0
- package/bin/reticle.js +66 -8
- package/dist/chromium-launch-options.d.ts +25 -0
- package/dist/chromium-launch-options.js +21 -1
- package/dist/command/cli/adhoc-suite.js +2 -0
- package/dist/command/cli/adhoc-verdict.d.ts +2 -43
- package/dist/command/cli/adhoc-verdict.js +50 -160
- package/dist/command/cli/answers/gate-exit.d.ts +6 -2
- package/dist/command/cli/answers/gate-exit.js +12 -2
- package/dist/command/cli/answers/mcp-connection-hint.d.ts +2 -0
- package/dist/command/cli/answers/mcp-connection-hint.js +6 -0
- package/dist/command/cli/ci-hint.d.ts +44 -0
- package/dist/command/cli/ci-hint.js +72 -0
- package/dist/command/cli/cli-doctor.d.ts +6 -1
- package/dist/command/cli/cli-doctor.js +46 -5
- package/dist/command/cli/cli-flow-commands.d.ts +7 -1
- package/dist/command/cli/cli-flow-commands.js +39 -24
- package/dist/command/cli/cli-kill.d.ts +21 -0
- package/dist/command/cli/cli-kill.js +47 -4
- package/dist/command/cli/cli-parse-compare.d.ts +22 -0
- package/dist/command/cli/cli-parse-compare.js +75 -0
- package/dist/command/cli/cli-parse-grade.d.ts +36 -0
- package/dist/command/cli/cli-parse-grade.js +75 -0
- package/dist/command/cli/cli-parse-grammar.d.ts +2 -0
- package/dist/command/cli/cli-parse-grammar.js +18 -20
- package/dist/command/cli/cli-parse-verify.d.ts +10 -0
- package/dist/command/cli/cli-parse-verify.js +63 -14
- package/dist/command/cli/cli-parse.d.ts +22 -17
- package/dist/command/cli/cli-parse.js +54 -32
- package/dist/command/cli/cli-update-commands.d.ts +1 -1
- package/dist/command/cli/cli-update-commands.js +29 -20
- package/dist/command/cli/cli-usage.js +61 -31
- package/dist/command/cli/cli-verify.d.ts +17 -3
- package/dist/command/cli/cli-verify.js +59 -21
- package/dist/command/cli/cloud-cli.js +2 -119
- package/dist/command/cli/daemon-link/daemon-client.d.ts +45 -0
- package/dist/command/cli/daemon-link/daemon-client.js +205 -0
- package/dist/command/cli/doctor/browser/chromium-hint.d.ts +6 -1
- package/dist/command/cli/doctor/browser/chromium-hint.js +11 -5
- package/dist/command/cli/doctor/doctor-daemon-line.js +6 -3
- package/dist/command/cli/doctor/doctor-fix.d.ts +44 -0
- package/dist/command/cli/doctor/doctor-fix.js +89 -0
- package/dist/command/cli/doctor/doctor-launcher-line.d.ts +23 -0
- package/dist/command/cli/doctor/doctor-launcher-line.js +60 -0
- package/dist/command/cli/doctor/doctor-rows.d.ts +4 -0
- package/dist/command/cli/doctor/doctor-rows.js +4 -0
- package/dist/command/cli/doctor-repairs.d.ts +64 -0
- package/dist/command/cli/doctor-repairs.js +214 -0
- package/dist/command/cli/drive/drive-command.js +3 -7
- package/dist/command/cli/ensure-daemon.d.ts +26 -0
- package/dist/command/cli/ensure-daemon.js +57 -0
- package/dist/command/cli/finished-run/grade-command.d.ts +23 -0
- package/dist/command/cli/finished-run/grade-command.js +180 -0
- package/dist/command/cli/first-use-checks.d.ts +41 -0
- package/dist/command/cli/first-use-checks.js +127 -0
- package/dist/command/cli/first-verdict-node.d.ts +3 -0
- package/dist/command/cli/first-verdict-node.js +189 -0
- package/dist/command/cli/first-verdict.d.ts +126 -0
- package/dist/command/cli/first-verdict.js +267 -0
- package/dist/command/cli/launch/sdk-version-match.d.ts +3 -2
- package/dist/command/cli/launch/sdk-version-match.js +3 -2
- package/dist/command/cli/lifecycle/daemon-lifecycle.d.ts +1 -1
- package/dist/command/cli/lifecycle/daemon-lifecycle.js +7 -6
- package/dist/command/cli/mcp-command.js +13 -4
- package/dist/command/cli/ports/port-holder.d.ts +11 -0
- package/dist/command/cli/ports/port-holder.js +36 -4
- package/dist/command/cli/setup-mcp-cli.d.ts +27 -1
- package/dist/command/cli/setup-mcp-cli.js +98 -23
- package/dist/command/cli/status/status-lines.d.ts +2 -0
- package/dist/command/cli/status/status-lines.js +72 -17
- package/dist/command/cli/status-fields.d.ts +5 -1
- package/dist/command/cli/status-fields.js +24 -6
- package/dist/command/cli/try-command.d.ts +7 -0
- package/dist/command/cli/try-command.js +11 -3
- package/dist/command/cli/two-builds/compare-command.d.ts +48 -0
- package/dist/command/cli/two-builds/compare-command.js +314 -0
- package/dist/command/cli/verify-against.d.ts +61 -0
- package/dist/command/cli/verify-against.js +187 -0
- package/dist/command/cli/verify-no-session.d.ts +2 -0
- package/dist/command/cli/verify-no-session.js +13 -0
- package/dist/command/cli/verify-results.d.ts +35 -2
- package/dist/command/cli/verify-results.js +76 -10
- package/dist/command/cli.js +112 -70
- package/dist/command/daemon/binding/daemon-status-probe.d.ts +3 -1
- package/dist/command/daemon/binding/daemon-status-probe.js +4 -2
- package/dist/command/daemon/daemon-argv.d.ts +9 -0
- package/dist/command/daemon/daemon-argv.js +11 -0
- package/dist/command/daemon/daemon-resolve.d.ts +8 -1
- package/dist/command/daemon/daemon-resolve.js +27 -1
- package/dist/command/daemon/daemon.d.ts +22 -4
- package/dist/command/daemon/daemon.js +52 -10
- package/dist/command/daemon/lifetime/daemon-exit.d.ts +24 -0
- package/dist/command/daemon/lifetime/daemon-exit.js +39 -0
- package/dist/command/daemon/lifetime/death-cause.d.ts +15 -0
- package/dist/command/daemon/lifetime/death-cause.js +149 -0
- package/dist/command/daemon/lifetime/heartbeat.d.ts +7 -15
- package/dist/command/daemon/lifetime/heartbeat.js +11 -15
- package/dist/command/daemon/lifetime/startup-failure.d.ts +29 -0
- package/dist/command/daemon/lifetime/startup-failure.js +62 -0
- package/dist/command/daemon/port-state.d.ts +14 -0
- package/dist/command/daemon/port-state.js +31 -0
- package/dist/command/setup/agent-configs.d.ts +2 -0
- package/dist/command/setup/agent-configs.js +42 -15
- package/dist/command/setup/agent-next-steps.d.ts +77 -0
- package/dist/command/setup/agent-next-steps.js +284 -0
- package/dist/command/setup/agent-writer.js +11 -6
- package/dist/command/setup/bringup/dev-server-files.d.ts +9 -0
- package/dist/command/setup/bringup/dev-server-files.js +17 -0
- package/dist/command/setup/bringup/static-page-server.d.ts +14 -2
- package/dist/command/setup/bringup/static-page-server.js +43 -9
- package/dist/command/setup/init/init-host.js +2 -0
- package/dist/command/setup/init/init-runtime.d.ts +6 -1
- package/dist/command/setup/init/init-runtime.js +63 -21
- package/dist/command/setup/mcp-launcher.d.ts +3 -0
- package/dist/command/setup/mcp-launcher.js +11 -0
- package/dist/command/setup/node-effects.d.ts +3 -2
- package/dist/command/setup/node-effects.js +9 -3
- package/dist/command/setup/remaining-steps.d.ts +8 -0
- package/dist/command/setup/remaining-steps.js +10 -0
- package/dist/command/setup/run-setup.d.ts +2 -0
- package/dist/command/setup/run-setup.js +9 -1
- package/dist/command/setup/setup-command.js +36 -15
- package/dist/command/setup/setup-mcp.d.ts +8 -0
- package/dist/command/setup/setup-mcp.js +47 -20
- package/dist/command/setup/start-dev-server.d.ts +8 -0
- package/dist/command/setup/start-dev-server.js +43 -0
- package/dist/command/update/updater.js +6 -0
- package/dist/command/version/identity/server-version.d.ts +2 -0
- package/dist/command/version/identity/server-version.js +2 -0
- package/dist/command/version/version-skew.d.ts +7 -0
- package/dist/command/version/version-skew.js +14 -3
- package/dist/features/compare/action-plan.d.ts +260 -0
- package/dist/features/compare/action-plan.js +543 -0
- package/dist/features/compare/behaviour-compare.d.ts +24 -0
- package/dist/features/compare/behaviour-compare.js +85 -0
- package/dist/features/compare/behaviour-diff.d.ts +97 -0
- package/dist/features/compare/behaviour-diff.js +202 -0
- package/dist/features/compare/behaviour-report.d.ts +40 -0
- package/dist/features/compare/behaviour-report.js +128 -0
- package/dist/features/compare/build-readings.d.ts +106 -0
- package/dist/features/compare/build-readings.js +250 -0
- package/dist/features/compare/compare-diff.d.ts +62 -0
- package/dist/features/compare/compare-diff.js +177 -0
- package/dist/features/compare/compare-report-html.d.ts +6 -0
- package/dist/features/compare/compare-report-html.js +102 -0
- package/dist/features/compare/compare-report.d.ts +15 -0
- package/dist/features/compare/compare-report.js +73 -0
- package/dist/features/compare/compare-run.d.ts +60 -0
- package/dist/features/compare/compare-run.js +123 -0
- package/dist/features/compare/outside/effect-lines.d.ts +26 -0
- package/dist/features/compare/outside/effect-lines.js +103 -0
- package/dist/features/compare/outside/state-sources.d.ts +76 -0
- package/dist/features/compare/outside/state-sources.js +164 -0
- package/dist/features/compare/outside/value-dictionary.d.ts +51 -0
- package/dist/features/compare/outside/value-dictionary.js +227 -0
- package/dist/features/compare/visible-content.d.ts +20 -0
- package/dist/features/compare/visible-content.js +125 -0
- package/dist/features/crawl/crawl-empty.js +2 -1
- package/dist/features/crawl/crawl-exhaustive.d.ts +55 -0
- package/dist/features/crawl/crawl-exhaustive.js +138 -0
- package/dist/features/crawl/crawl-tools.d.ts +9 -2
- package/dist/features/crawl/crawl-tools.js +28 -12
- package/dist/features/crawl/crawl.d.ts +8 -9
- package/dist/features/crawl/crawl.js +61 -65
- package/dist/features/exhaust/app-coverage.d.ts +5 -1
- package/dist/features/exhaust/app-coverage.js +3 -1
- package/dist/features/exhaust/code-coverage.js +8 -4
- package/dist/features/exhaust/edge-cases.d.ts +133 -0
- package/dist/features/exhaust/edge-cases.js +310 -0
- package/dist/features/exhaust/edges/edge-drive.d.ts +42 -0
- package/dist/features/exhaust/edges/edge-drive.js +364 -0
- package/dist/features/exhaust/edges/edge-run.d.ts +59 -0
- package/dist/features/exhaust/edges/edge-run.js +169 -0
- package/dist/features/exhaust/explorer.d.ts +87 -14
- package/dist/features/exhaust/explorer.js +159 -25
- package/dist/features/exhaust/failure-branches.d.ts +5 -0
- package/dist/features/exhaust/failure-branches.js +29 -11
- package/dist/features/exhaust/invariants.d.ts +112 -0
- package/dist/features/exhaust/invariants.js +188 -0
- package/dist/features/exhaust/ledger.d.ts +68 -0
- package/dist/features/exhaust/ledger.js +121 -13
- package/dist/features/exhaust/session-fold.d.ts +10 -0
- package/dist/features/exhaust/session-fold.js +46 -3
- package/dist/features/exhaust/session-port.d.ts +3 -1
- package/dist/features/exhaust/session-port.js +97 -28
- package/dist/features/grade/grade-answer.d.ts +99 -0
- package/dist/features/grade/grade-answer.js +112 -0
- package/dist/features/grade/grade-noise.d.ts +27 -0
- package/dist/features/grade/grade-noise.js +55 -0
- package/dist/features/grade/grade-renumber.d.ts +12 -0
- package/dist/features/grade/grade-renumber.js +86 -0
- package/dist/features/grade/grade-state.d.ts +50 -0
- package/dist/features/grade/grade-state.js +93 -0
- package/dist/features/grade/grade-suggest.d.ts +33 -0
- package/dist/features/grade/grade-suggest.js +98 -0
- package/dist/features/license/license.d.ts +11 -71
- package/dist/features/license/license.js +43 -136
- package/dist/features/visual/desktop-capture.d.ts +33 -0
- package/dist/features/visual/desktop-capture.js +94 -0
- package/dist/features/visual/step-shots.js +139 -35
- package/dist/features/visual/visual-tools.d.ts +0 -18
- package/dist/features/visual/visual-tools.js +67 -99
- package/dist/import-graph.js +5 -6
- package/dist/index.d.ts +3 -1
- package/dist/index.js +19 -7
- package/dist/judgement/capsule/causal-summary.js +9 -2
- package/dist/judgement/runs/artifact/replay-mapping.d.ts +13 -1
- package/dist/judgement/runs/artifact/replay-mapping.js +1 -1
- package/dist/judgement/runs/reticle-runner.js +16 -2
- package/dist/judgement/runs/run-ledger.d.ts +7 -5
- package/dist/judgement/runs/run-ledger.js +98 -69
- package/dist/judgement/runs/runner-port.d.ts +2 -0
- package/dist/judgement/runs/runner-port.js +23 -5
- package/dist/judgement/runs/verification-sync.d.ts +6 -3
- package/dist/judgement/runs/verification-sync.js +21 -10
- package/dist/language/flows/affected-tools.d.ts +1 -1
- package/dist/language/flows/affected-tools.js +49 -30
- package/dist/language/flows/change/git-changed.d.ts +2 -1
- package/dist/language/flows/change/git-changed.js +19 -2
- package/dist/language/flows/fields/flow-secret-field.d.ts +1 -13
- package/dist/language/flows/fields/flow-secret-field.js +77 -21
- package/dist/language/flows/flow-anchor.d.ts +60 -2
- package/dist/language/flows/flow-anchor.js +125 -20
- package/dist/language/flows/flow-classify.d.ts +20 -0
- package/dist/language/flows/flow-classify.js +90 -12
- package/dist/language/flows/flow-cross-step.d.ts +7 -1
- package/dist/language/flows/flow-cross-step.js +25 -1
- package/dist/language/flows/flow-journey.d.ts +38 -4
- package/dist/language/flows/flow-journey.js +138 -8
- package/dist/language/flows/flow-replay-run.d.ts +0 -16
- package/dist/language/flows/flow-replay-run.js +76 -14
- package/dist/language/flows/flow-replay-types.d.ts +4 -2
- package/dist/language/flows/flow-replay.d.ts +19 -1
- package/dist/language/flows/flow-replay.js +169 -52
- package/dist/language/flows/flow-replayable.js +7 -1
- package/dist/language/flows/flow-start-path.d.ts +34 -5
- package/dist/language/flows/flow-start-path.js +202 -15
- package/dist/language/flows/flow-step-runners.d.ts +14 -13
- package/dist/language/flows/flow-step-runners.js +184 -109
- package/dist/language/flows/flow-store-for-session.d.ts +26 -25
- package/dist/language/flows/flow-store-for-session.js +51 -5
- package/dist/language/flows/flow-tools.js +46 -33
- package/dist/language/flows/flows.js +176 -76
- package/dist/language/flows/recording/tape/recordings.d.ts +21 -16
- package/dist/language/flows/recording/tape/recordings.js +112 -49
- package/dist/language/flows/replay.d.ts +36 -12
- package/dist/language/flows/replay.js +193 -50
- package/dist/language/flows/steps/page-steps.d.ts +68 -0
- package/dist/language/flows/steps/page-steps.js +198 -0
- package/dist/language/flows/suite/sequential-run.d.ts +68 -0
- package/dist/language/flows/suite/sequential-run.js +96 -0
- package/dist/language/flows/verify-change-tools.js +26 -15
- package/dist/launch-chromium.d.ts +1 -0
- package/dist/launch-chromium.js +49 -12
- package/dist/memory/cloud/background-sync.d.ts +13 -0
- package/dist/memory/cloud/background-sync.js +24 -3
- package/dist/memory/cloud/sync-daemon.js +26 -27
- package/dist/memory/cloud/sync-screenshots.js +6 -1
- package/dist/memory/journal/attach-journal.js +2 -1
- package/dist/memory/journal/drive-flow.d.ts +7 -0
- package/dist/memory/journal/drive-flow.js +44 -13
- package/dist/memory/journal/journal-recorder.d.ts +4 -2
- package/dist/memory/journal/session-journal.d.ts +7 -0
- package/dist/memory/journal/session-journal.js +58 -10
- package/dist/memory/project/root-claims.d.ts +9 -0
- package/dist/memory/project/root-claims.js +9 -4
- package/dist/portal/bridge/bridge.d.ts +13 -4
- package/dist/portal/bridge/bridge.js +10 -9
- package/dist/portal/input/download-capture.d.ts +7 -0
- package/dist/portal/input/download-capture.js +92 -0
- package/dist/portal/input/network-detail.d.ts +6 -0
- package/dist/portal/input/network-detail.js +12 -1
- package/dist/portal/input/network-mock-tools.js +49 -13
- package/dist/portal/input/network-mock.d.ts +8 -0
- package/dist/portal/input/network-mock.js +30 -10
- package/dist/portal/input/page-capture.d.ts +24 -0
- package/dist/portal/input/page-capture.js +33 -0
- package/dist/portal/input/real-input.d.ts +35 -18
- package/dist/portal/input/real-input.js +99 -33
- package/dist/portal/input/viewport-tools.d.ts +8 -0
- package/dist/portal/input/viewport-tools.js +81 -21
- package/dist/portal/pool/browser-pool.d.ts +16 -30
- package/dist/portal/pool/browser-pool.js +64 -52
- package/dist/portal/pool/context-permissions.d.ts +34 -17
- package/dist/portal/pool/context-permissions.js +94 -1
- package/dist/portal/pool/csp-widen.d.ts +62 -0
- package/dist/portal/pool/csp-widen.js +132 -0
- package/dist/portal/pool/determinism.d.ts +52 -0
- package/dist/portal/pool/determinism.js +259 -0
- package/dist/portal/pool/lease-emulation.d.ts +34 -1
- package/dist/portal/pool/lease-emulation.js +18 -0
- package/dist/portal/pool/lease-marker-stamp.d.ts +6 -0
- package/dist/portal/pool/lease-marker-stamp.js +15 -1
- package/dist/portal/pool/lease-page-error.d.ts +56 -0
- package/dist/portal/pool/lease-page-error.js +160 -0
- package/dist/portal/pool/page-error-journal.d.ts +63 -0
- package/dist/portal/pool/page-error-journal.js +208 -0
- package/dist/portal/pool/playwright-launcher.js +71 -15
- package/dist/portal/pool/pool-contract.d.ts +49 -1
- package/dist/portal/pool/pool-lease.d.ts +70 -2
- package/dist/portal/pool/pool-lease.js +23 -0
- package/dist/portal/pool/zero-install.d.ts +8 -0
- package/dist/portal/pool/zero-install.js +91 -1
- package/dist/portal/session/dev-server/dev-command.d.ts +11 -2
- package/dist/portal/session/dev-server/dev-command.js +110 -16
- package/dist/portal/session/dev-server/dev-server-probe.d.ts +7 -0
- package/dist/portal/session/dev-server/dev-server-probe.js +9 -0
- package/dist/portal/session/dev-server/project-ports.d.ts +21 -0
- package/dist/portal/session/dev-server/project-ports.js +62 -0
- package/dist/portal/session/facts/departed-network.d.ts +66 -0
- package/dist/portal/session/facts/departed-network.js +142 -0
- package/dist/portal/session/no-session-diagnosis.d.ts +9 -0
- package/dist/portal/session/no-session-diagnosis.js +76 -27
- package/dist/portal/session/no-session-next-action.d.ts +14 -0
- package/dist/portal/session/no-session-next-action.js +56 -7
- package/dist/portal/session/no-session-watch.d.ts +6 -0
- package/dist/portal/session/no-session-watch.js +41 -4
- package/dist/portal/session/page-commands/undeclared-command.d.ts +9 -3
- package/dist/portal/session/page-commands/undeclared-command.js +16 -7
- package/dist/portal/session/recorded-gaps.d.ts +1 -1
- package/dist/portal/session/recorded-gaps.js +5 -2
- package/dist/portal/session/serving-directory.d.ts +10 -0
- package/dist/portal/session/serving-directory.js +39 -8
- package/dist/portal/session/session-manager.d.ts +10 -0
- package/dist/portal/session/session-manager.js +211 -81
- package/dist/portal/session/session.js +4 -3
- package/dist/portal/session/zero-install-first-call.d.ts +20 -1
- package/dist/portal/session/zero-install-first-call.js +34 -2
- package/dist/start-wiring.d.ts +30 -2
- package/dist/start-wiring.js +74 -9
- package/dist/surface/mcp/mcp-proxy.js +10 -2
- package/dist/surface/mcp/mcp.js +8 -5
- package/dist/surface/mcp/peer/agents-connected.d.ts +6 -0
- package/dist/surface/mcp/peer/agents-connected.js +58 -0
- package/dist/surface/mcp/proxy/proxy-daemon-probe.js +17 -5
- package/dist/surface/mcp/shared-params.d.ts +2 -0
- package/dist/surface/mcp/shared-params.js +31 -0
- package/dist/surface/tools/act/act-observation.d.ts +40 -2
- package/dist/surface/tools/act/act-observation.js +118 -12
- package/dist/surface/tools/act/act-preflight.js +38 -18
- package/dist/surface/tools/act/act-sequence-retry.d.ts +23 -2
- package/dist/surface/tools/act/act-sequence-retry.js +61 -5
- package/dist/surface/tools/act/act-target.js +3 -1
- package/dist/surface/tools/act/after-match.d.ts +10 -0
- package/dist/surface/tools/act/after-match.js +40 -0
- package/dist/surface/tools/act/already-true.js +1 -1
- package/dist/surface/tools/act/navigation/reload-result.d.ts +12 -1
- package/dist/surface/tools/act/navigation/reload-result.js +25 -2
- package/dist/surface/tools/act/resolve-target.d.ts +17 -1
- package/dist/surface/tools/act/resolve-target.js +59 -9
- package/dist/surface/tools/act/sequence-grade.d.ts +15 -1
- package/dist/surface/tools/act/sequence-grade.js +45 -27
- package/dist/surface/tools/act/settle-in-flight.js +27 -15
- package/dist/surface/tools/act-command.d.ts +23 -0
- package/dist/surface/tools/act-command.js +50 -0
- package/dist/surface/tools/act-sequence-tool.js +81 -50
- package/dist/surface/tools/act-tools.d.ts +1 -13
- package/dist/surface/tools/act-tools.js +83 -95
- package/dist/surface/tools/agent/build-staleness.d.ts +1 -1
- package/dist/surface/tools/agent/build-staleness.js +1 -1
- package/dist/surface/tools/assert/assert-verdict.js +18 -12
- package/dist/surface/tools/browser-tools.js +87 -13
- package/dist/surface/tools/compare-tools.d.ts +11 -0
- package/dist/surface/tools/compare-tools.js +94 -0
- package/dist/surface/tools/coverage-tools.js +1 -1
- package/dist/surface/tools/dynamic-tools.js +11 -3
- package/dist/surface/tools/empty-session-list.d.ts +2 -0
- package/dist/surface/tools/empty-session-list.js +4 -2
- package/dist/surface/tools/error-recovery.d.ts +12 -1
- package/dist/surface/tools/error-recovery.js +131 -40
- package/dist/surface/tools/explore-tools.js +1 -1
- package/dist/surface/tools/invoke-tool.js +29 -21
- package/dist/surface/tools/lease-coverage-tools.d.ts +49 -0
- package/dist/surface/tools/lease-coverage-tools.js +128 -0
- package/dist/surface/tools/lease-hint.d.ts +42 -1
- package/dist/surface/tools/lease-hint.js +52 -3
- package/dist/surface/tools/lease-readiness.d.ts +47 -0
- package/dist/surface/tools/lease-readiness.js +126 -0
- package/dist/surface/tools/lease-seed.d.ts +16 -1
- package/dist/surface/tools/lease-seed.js +37 -4
- package/dist/surface/tools/lease-tools.d.ts +3 -41
- package/dist/surface/tools/lease-tools.js +104 -124
- package/dist/surface/tools/lease-url-identity.d.ts +46 -0
- package/dist/surface/tools/lease-url-identity.js +97 -0
- package/dist/surface/tools/merge-tools.js +99 -2
- package/dist/surface/tools/merged-name-redirect.js +5 -1
- package/dist/surface/tools/observe-tools.js +15 -9
- package/dist/surface/tools/read/console-pre-attach.d.ts +19 -0
- package/dist/surface/tools/read/console-pre-attach.js +23 -0
- package/dist/surface/tools/read-tools.js +33 -30
- package/dist/surface/tools/real-input-attempt.d.ts +0 -8
- package/dist/surface/tools/real-input-attempt.js +143 -28
- package/dist/surface/tools/repeat-hint.d.ts +40 -0
- package/dist/surface/tools/repeat-hint.js +171 -0
- package/dist/surface/tools/run-report.d.ts +2 -2
- package/dist/surface/tools/run-report.js +27 -15
- package/dist/surface/tools/session-ports.d.ts +14 -0
- package/dist/surface/tools/session-ports.js +53 -0
- package/dist/surface/tools/tool-kit.d.ts +18 -0
- package/dist/surface/tools/tool-kit.js +9 -3
- package/dist/surface/tools/tool-surface.js +0 -1
- package/dist/surface/tools/tools.js +54 -18
- package/dist/surface/tools/type-spelling.d.ts +13 -0
- package/dist/surface/tools/type-spelling.js +36 -0
- package/dist/telemetry/agent-client.d.ts +6 -0
- package/dist/telemetry/agent-client.js +10 -0
- package/dist/telemetry/argument-shape.js +17 -7
- package/dist/telemetry/cli-telemetry.d.ts +10 -1
- package/dist/telemetry/cli-telemetry.js +15 -0
- package/dist/telemetry/init-telemetry.d.ts +2 -1
- package/dist/telemetry/init-telemetry.js +47 -2
- package/dist/telemetry/install-trace.js +23 -4
- package/dist/telemetry/onboarding-funnel.d.ts +19 -2
- package/dist/telemetry/onboarding-funnel.js +25 -3
- package/dist/telemetry/proxy-telemetry.d.ts +2 -1
- package/dist/telemetry/proxy-telemetry.js +2 -2
- package/dist/telemetry/session-metrics.d.ts +1 -1
- package/dist/telemetry/session-metrics.js +67 -27
- package/dist/telemetry/telemetry.d.ts +3 -1
- package/dist/telemetry/telemetry.js +60 -28
- package/dist/telemetry/tool-refused.d.ts +6 -3
- package/dist/telemetry/tool-refused.js +15 -3
- package/docs/actions.mdx +3 -3
- package/docs/agent-cheatsheet.md +1 -1
- package/docs/architecture.md +3 -6
- package/docs/capabilities.mdx +61 -1
- package/docs/cli/affected.mdx +9 -7
- package/docs/cli/cloud.mdx +3 -3
- package/docs/cli/compare.mdx +94 -0
- package/docs/cli/doctor.mdx +41 -2
- package/docs/cli/grade.mdx +97 -0
- package/docs/cli/help.mdx +4 -2
- package/docs/cli/license.mdx +13 -12
- package/docs/cli/restart.mdx +7 -5
- package/docs/cli/rollback.mdx +5 -5
- package/docs/cli/setup.mdx +13 -2
- package/docs/cli/status.mdx +4 -0
- package/docs/cli/stop.mdx +3 -3
- package/docs/cli/update.mdx +2 -2
- package/docs/cli/verify.mdx +23 -1
- package/docs/cli.mdx +34 -24
- package/docs/deploy-checks.md +25 -0
- package/docs/desktop.mdx +1 -1
- package/docs/enterprise.md +17 -22
- package/docs/environment.mdx +1 -0
- package/docs/first-drive.md +1 -1
- package/docs/flows.md +11 -1
- package/docs/frameworks.mdx +9 -9
- package/docs/getting-started.md +1 -1
- package/docs/install-manual.mdx +16 -1
- package/docs/license-activation.mdx +3 -3
- package/docs/packages/engine.mdx +1 -1
- package/docs/packages/server.mdx +1 -1
- package/docs/packages/tauri.mdx +1 -1
- package/docs/packages/test.mdx +1 -1
- package/docs/packages.mdx +1 -1
- package/docs/platform-integration.md +1 -1
- package/docs/predicates.mdx +1 -1
- package/docs/quickstart.mdx +68 -49
- package/docs/skill-setup.md +1 -1
- package/docs/skill.md +14 -13
- package/docs/telemetry.md +6 -4
- package/docs/tools/navigate.mdx +1 -1
- package/docs/tools/verify.mdx +4 -1
- package/docs/troubleshooting.mdx +1 -1
- package/docs/usage.md +17 -14
- package/package.json +18 -14
- package/LICENSE-ENTERPRISE +0 -37
- package/dist/features/ee/audit-log.d.ts +0 -14
- package/dist/features/ee/audit-log.js +0 -11
- package/dist/features/exhaust/exhaust-run.d.ts +0 -33
- 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
|
-
*
|
|
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
|
|
6
|
-
*
|
|
7
|
-
*
|
|
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
|
|
27
|
-
*
|
|
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
|
|
65
|
-
*
|
|
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
|
|
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
|
-
*
|
|
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
|
-
*
|
|
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
|
|
6
|
-
*
|
|
7
|
-
*
|
|
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
|
|
26
|
-
*
|
|
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
|
-
|
|
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
|
|
87
|
-
*
|
|
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.
|
|
143
|
-
*
|
|
144
|
-
*
|
|
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
|
|
150
|
-
*
|
|
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
|
-
*
|
|
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
|
|
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
|
-
|
|
133
|
+
contact: LICENSE_CONTACT,
|
|
191
134
|
status: LicenseActivation.INVALID,
|
|
192
|
-
detail: 'MISCONFIGURED — running in production with no issuer key baked
|
|
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
|
-
|
|
138
|
+
contact: LICENSE_CONTACT,
|
|
196
139
|
status: LicenseActivation.EVAL,
|
|
197
|
-
detail: 'evaluation mode
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
167
|
+
contact: LICENSE_CONTACT,
|
|
235
168
|
status: LicenseActivation.MISSING,
|
|
236
|
-
detail: `set ${LICENSE_KEY_ENV} to activate
|
|
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
|
-
|
|
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}
|
|
181
|
+
detail: `license expired. Renew with ${LICENSE_CONTACT}`,
|
|
250
182
|
};
|
|
251
183
|
}
|
|
252
184
|
return {
|
|
253
|
-
|
|
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)
|
|
259
|
-
* release, not an eval session
|
|
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
|
-
}
|