@kya-os/cli 1.8.1 → 1.8.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/commands/agent.d.ts +35 -0
- package/dist/commands/agent.d.ts.map +1 -0
- package/dist/commands/agent.js +321 -0
- package/dist/commands/agent.js.map +1 -0
- package/dist/commands/build.js +2 -2
- package/dist/commands/build.js.map +1 -1
- package/dist/commands/check.js +3 -3
- package/dist/commands/check.js.map +1 -1
- package/dist/commands/checkpoint-init.d.ts +1 -1
- package/dist/commands/checkpoint-init.d.ts.map +1 -1
- package/dist/commands/checkpoint-init.js +8 -41
- package/dist/commands/checkpoint-init.js.map +1 -1
- package/dist/commands/claim.js +2 -2
- package/dist/commands/claim.js.map +1 -1
- package/dist/commands/dco.d.ts +59 -0
- package/dist/commands/dco.d.ts.map +1 -1
- package/dist/commands/dco.js +280 -5
- package/dist/commands/dco.js.map +1 -1
- package/dist/commands/dev.js +1 -1
- package/dist/commands/dev.js.map +1 -1
- package/dist/commands/doctor.d.ts.map +1 -1
- package/dist/commands/doctor.js +18 -24
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/env.js +6 -6
- package/dist/commands/env.js.map +1 -1
- package/dist/commands/identity.js +3 -3
- package/dist/commands/identity.js.map +1 -1
- package/dist/commands/init-stages.d.ts +141 -0
- package/dist/commands/init-stages.d.ts.map +1 -0
- package/dist/commands/init-stages.js +684 -0
- package/dist/commands/init-stages.js.map +1 -0
- package/dist/commands/init.d.ts +46 -16
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +229 -279
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/orchestrator.d.ts +18 -0
- package/dist/commands/orchestrator.d.ts.map +1 -0
- package/dist/commands/orchestrator.js +251 -0
- package/dist/commands/orchestrator.js.map +1 -0
- package/dist/commands/register.d.ts +1 -1
- package/dist/commands/register.d.ts.map +1 -1
- package/dist/commands/register.js +35 -7
- package/dist/commands/register.js.map +1 -1
- package/dist/commands/review.d.ts +48 -0
- package/dist/commands/review.d.ts.map +1 -0
- package/dist/commands/review.js +220 -0
- package/dist/commands/review.js.map +1 -0
- package/dist/commands/rotate.js +4 -4
- package/dist/commands/rotate.js.map +1 -1
- package/dist/commands/score.d.ts +15 -0
- package/dist/commands/score.d.ts.map +1 -0
- package/dist/commands/score.js +471 -0
- package/dist/commands/score.js.map +1 -0
- package/dist/commands/start.js +3 -3
- package/dist/commands/start.js.map +1 -1
- package/dist/commands/status.js +1 -1
- package/dist/commands/status.js.map +1 -1
- package/dist/commands/verify.js +6 -6
- package/dist/commands/verify.js.map +1 -1
- package/dist/dco.d.ts +4 -0
- package/dist/dco.d.ts.map +1 -0
- package/dist/dco.js +23 -0
- package/dist/dco.js.map +1 -0
- package/dist/index.js +52 -9
- package/dist/orchestrator/daemon.d.ts +137 -0
- package/dist/orchestrator/daemon.d.ts.map +1 -0
- package/dist/orchestrator/daemon.js +242 -0
- package/dist/orchestrator/daemon.js.map +1 -0
- package/dist/orchestrator/db/dispatches.d.ts +108 -0
- package/dist/orchestrator/db/dispatches.d.ts.map +1 -0
- package/dist/orchestrator/db/dispatches.js +122 -0
- package/dist/orchestrator/db/dispatches.js.map +1 -0
- package/dist/orchestrator/db/index.d.ts +50 -0
- package/dist/orchestrator/db/index.d.ts.map +1 -0
- package/dist/orchestrator/db/index.js +116 -0
- package/dist/orchestrator/db/index.js.map +1 -0
- package/dist/orchestrator/db/journal.d.ts +93 -0
- package/dist/orchestrator/db/journal.d.ts.map +1 -0
- package/dist/orchestrator/db/journal.js +91 -0
- package/dist/orchestrator/db/journal.js.map +1 -0
- package/dist/orchestrator/db/liveness.d.ts +97 -0
- package/dist/orchestrator/db/liveness.d.ts.map +1 -0
- package/dist/orchestrator/db/liveness.js +105 -0
- package/dist/orchestrator/db/liveness.js.map +1 -0
- package/dist/orchestrator/db/mission.d.ts +78 -0
- package/dist/orchestrator/db/mission.d.ts.map +1 -0
- package/dist/orchestrator/db/mission.js +99 -0
- package/dist/orchestrator/db/mission.js.map +1 -0
- package/dist/orchestrator/db/schema.d.ts +14 -0
- package/dist/orchestrator/db/schema.d.ts.map +1 -0
- package/dist/orchestrator/db/schema.js +169 -0
- package/dist/orchestrator/db/schema.js.map +1 -0
- package/dist/orchestrator/db/tickets.d.ts +147 -0
- package/dist/orchestrator/db/tickets.d.ts.map +1 -0
- package/dist/orchestrator/db/tickets.js +238 -0
- package/dist/orchestrator/db/tickets.js.map +1 -0
- package/dist/orchestrator/db/verdicts.d.ts +187 -0
- package/dist/orchestrator/db/verdicts.d.ts.map +1 -0
- package/dist/orchestrator/db/verdicts.js +197 -0
- package/dist/orchestrator/db/verdicts.js.map +1 -0
- package/dist/orchestrator/events.d.ts +69 -0
- package/dist/orchestrator/events.d.ts.map +1 -0
- package/dist/orchestrator/events.js +20 -0
- package/dist/orchestrator/events.js.map +1 -0
- package/dist/orchestrator/github-credential-anchor-sender.d.ts +159 -0
- package/dist/orchestrator/github-credential-anchor-sender.d.ts.map +1 -0
- package/dist/orchestrator/github-credential-anchor-sender.js +313 -0
- package/dist/orchestrator/github-credential-anchor-sender.js.map +1 -0
- package/dist/orchestrator/hub-dispatch.d.ts +120 -0
- package/dist/orchestrator/hub-dispatch.d.ts.map +1 -0
- package/dist/orchestrator/hub-dispatch.js +257 -0
- package/dist/orchestrator/hub-dispatch.js.map +1 -0
- package/dist/orchestrator/hub-observer.d.ts +201 -0
- package/dist/orchestrator/hub-observer.d.ts.map +1 -0
- package/dist/orchestrator/hub-observer.js +474 -0
- package/dist/orchestrator/hub-observer.js.map +1 -0
- package/dist/orchestrator/journal-writer.d.ts +80 -0
- package/dist/orchestrator/journal-writer.d.ts.map +1 -0
- package/dist/orchestrator/journal-writer.js +113 -0
- package/dist/orchestrator/journal-writer.js.map +1 -0
- package/dist/orchestrator/mcp/server.d.ts +136 -0
- package/dist/orchestrator/mcp/server.d.ts.map +1 -0
- package/dist/orchestrator/mcp/server.js +582 -0
- package/dist/orchestrator/mcp/server.js.map +1 -0
- package/dist/orchestrator/mcp/stdio.d.ts +22 -0
- package/dist/orchestrator/mcp/stdio.d.ts.map +1 -0
- package/dist/orchestrator/mcp/stdio.js +51 -0
- package/dist/orchestrator/mcp/stdio.js.map +1 -0
- package/dist/orchestrator/review.d.ts +131 -0
- package/dist/orchestrator/review.d.ts.map +1 -0
- package/dist/orchestrator/review.js +322 -0
- package/dist/orchestrator/review.js.map +1 -0
- package/dist/tui/app.d.ts +33 -0
- package/dist/tui/app.d.ts.map +1 -0
- package/dist/tui/app.js +71 -0
- package/dist/tui/app.js.map +1 -0
- package/dist/tui/components.d.ts +110 -0
- package/dist/tui/components.d.ts.map +1 -0
- package/dist/tui/components.js +118 -0
- package/dist/tui/components.js.map +1 -0
- package/dist/tui/index.d.ts +30 -0
- package/dist/tui/index.d.ts.map +1 -0
- package/dist/tui/index.js +95 -0
- package/dist/tui/index.js.map +1 -0
- package/dist/tui/keymap.d.ts +33 -0
- package/dist/tui/keymap.d.ts.map +1 -0
- package/dist/tui/keymap.js +49 -0
- package/dist/tui/keymap.js.map +1 -0
- package/dist/tui/nav.d.ts +29 -0
- package/dist/tui/nav.d.ts.map +1 -0
- package/dist/tui/nav.js +39 -0
- package/dist/tui/nav.js.map +1 -0
- package/dist/tui/sanitize.d.ts +25 -0
- package/dist/tui/sanitize.d.ts.map +1 -0
- package/dist/tui/sanitize.js +36 -0
- package/dist/tui/sanitize.js.map +1 -0
- package/dist/tui/theme.d.ts +32 -0
- package/dist/tui/theme.d.ts.map +1 -0
- package/dist/tui/theme.js +63 -0
- package/dist/tui/theme.js.map +1 -0
- package/dist/tui/views/agents.d.ts +67 -0
- package/dist/tui/views/agents.d.ts.map +1 -0
- package/dist/tui/views/agents.js +231 -0
- package/dist/tui/views/agents.js.map +1 -0
- package/dist/tui/views/index.d.ts +93 -0
- package/dist/tui/views/index.d.ts.map +1 -0
- package/dist/tui/views/index.js +150 -0
- package/dist/tui/views/index.js.map +1 -0
- package/dist/tui/views/registry.d.ts +22 -0
- package/dist/tui/views/registry.d.ts.map +1 -0
- package/dist/tui/views/registry.js +110 -0
- package/dist/tui/views/registry.js.map +1 -0
- package/dist/utils/agent/backoff.d.ts +31 -0
- package/dist/utils/agent/backoff.d.ts.map +1 -0
- package/dist/utils/agent/backoff.js +51 -0
- package/dist/utils/agent/backoff.js.map +1 -0
- package/dist/utils/agent/markers.d.ts +87 -0
- package/dist/utils/agent/markers.d.ts.map +1 -0
- package/dist/utils/agent/markers.js +147 -0
- package/dist/utils/agent/markers.js.map +1 -0
- package/dist/utils/agent/review.d.ts +67 -0
- package/dist/utils/agent/review.d.ts.map +1 -0
- package/dist/utils/agent/review.js +305 -0
- package/dist/utils/agent/review.js.map +1 -0
- package/dist/utils/agent/spawn-agent.d.ts +117 -0
- package/dist/utils/agent/spawn-agent.d.ts.map +1 -0
- package/dist/utils/agent/spawn-agent.js +177 -0
- package/dist/utils/agent/spawn-agent.js.map +1 -0
- package/dist/utils/agent/task.d.ts +101 -0
- package/dist/utils/agent/task.d.ts.map +1 -0
- package/dist/utils/agent/task.js +357 -0
- package/dist/utils/agent/task.js.map +1 -0
- package/dist/utils/agent/verify.d.ts +47 -0
- package/dist/utils/agent/verify.d.ts.map +1 -0
- package/dist/utils/agent/verify.js +144 -0
- package/dist/utils/agent/verify.js.map +1 -0
- package/dist/utils/agent-detector.d.ts +48 -0
- package/dist/utils/agent-detector.d.ts.map +1 -0
- package/dist/utils/agent-detector.js +74 -0
- package/dist/utils/agent-detector.js.map +1 -0
- package/dist/utils/agent-profile-stage.d.ts +70 -0
- package/dist/utils/agent-profile-stage.d.ts.map +1 -0
- package/dist/utils/agent-profile-stage.js +153 -0
- package/dist/utils/agent-profile-stage.js.map +1 -0
- package/dist/utils/agent-profiles.d.ts +27 -0
- package/dist/utils/agent-profiles.d.ts.map +1 -0
- package/dist/utils/agent-profiles.js +61 -0
- package/dist/utils/agent-profiles.js.map +1 -0
- package/dist/utils/checkpoint-api.d.ts +1 -1
- package/dist/utils/checkpoint-api.js +1 -1
- package/dist/utils/checkpoint-env.d.ts +1 -1
- package/dist/utils/checkpoint-env.js +2 -2
- package/dist/utils/checkpoint-env.js.map +1 -1
- package/dist/utils/checkpoint-scaffold.d.ts +1 -1
- package/dist/utils/checkpoint-scaffold.js +1 -1
- package/dist/utils/cli-kit/browser.d.ts +24 -0
- package/dist/utils/cli-kit/browser.d.ts.map +1 -0
- package/dist/utils/cli-kit/browser.js +58 -0
- package/dist/utils/cli-kit/browser.js.map +1 -0
- package/dist/utils/cli-kit/claim-polling.d.ts +18 -0
- package/dist/utils/cli-kit/claim-polling.d.ts.map +1 -0
- package/dist/utils/{claim-polling.js → cli-kit/claim-polling.js} +19 -26
- package/dist/utils/cli-kit/claim-polling.js.map +1 -0
- package/dist/utils/cli-kit/diagnostics.d.ts +9 -0
- package/dist/utils/cli-kit/diagnostics.d.ts.map +1 -0
- package/dist/utils/cli-kit/diagnostics.js +11 -0
- package/dist/utils/cli-kit/diagnostics.js.map +1 -0
- package/dist/utils/cli-kit/fs-safe.d.ts +14 -0
- package/dist/utils/cli-kit/fs-safe.d.ts.map +1 -0
- package/dist/utils/cli-kit/fs-safe.js +20 -0
- package/dist/utils/cli-kit/fs-safe.js.map +1 -0
- package/dist/utils/cli-kit/index.d.ts +15 -0
- package/dist/utils/cli-kit/index.d.ts.map +1 -0
- package/dist/utils/cli-kit/index.js +10 -0
- package/dist/utils/cli-kit/index.js.map +1 -0
- package/dist/utils/cli-kit/json-file.d.ts +21 -0
- package/dist/utils/cli-kit/json-file.d.ts.map +1 -0
- package/dist/utils/cli-kit/json-file.js +51 -0
- package/dist/utils/cli-kit/json-file.js.map +1 -0
- package/dist/utils/cli-kit/marker-block.d.ts +26 -0
- package/dist/utils/cli-kit/marker-block.d.ts.map +1 -0
- package/dist/utils/cli-kit/marker-block.js +61 -0
- package/dist/utils/cli-kit/marker-block.js.map +1 -0
- package/dist/utils/cli-kit/paths.d.ts +15 -0
- package/dist/utils/cli-kit/paths.d.ts.map +1 -0
- package/dist/utils/cli-kit/paths.js +21 -0
- package/dist/utils/cli-kit/paths.js.map +1 -0
- package/dist/utils/cli-kit/secure-storage.d.ts +14 -0
- package/dist/utils/cli-kit/secure-storage.d.ts.map +1 -0
- package/dist/utils/cli-kit/secure-storage.js +20 -0
- package/dist/utils/cli-kit/secure-storage.js.map +1 -0
- package/dist/utils/cli-kit/step-report.d.ts +16 -0
- package/dist/utils/cli-kit/step-report.d.ts.map +1 -0
- package/dist/utils/cli-kit/step-report.js +36 -0
- package/dist/utils/cli-kit/step-report.js.map +1 -0
- package/dist/utils/dco/agent-metadata.d.ts +1 -1
- package/dist/utils/dco/agent-metadata.js +1 -1
- package/dist/utils/dco/agent-metadata.js.map +1 -1
- package/dist/utils/dco/claude-config.d.ts +18 -14
- package/dist/utils/dco/claude-config.d.ts.map +1 -1
- package/dist/utils/dco/claude-config.js +31 -97
- package/dist/utils/dco/claude-config.js.map +1 -1
- package/dist/utils/dco/dispatch.d.ts +134 -0
- package/dist/utils/dco/dispatch.d.ts.map +1 -0
- package/dist/utils/dco/dispatch.js +399 -0
- package/dist/utils/dco/dispatch.js.map +1 -0
- package/dist/utils/dco/fix.d.ts +122 -0
- package/dist/utils/dco/fix.d.ts.map +1 -0
- package/dist/utils/dco/fix.js +560 -0
- package/dist/utils/dco/fix.js.map +1 -0
- package/dist/utils/dco/git-config.d.ts.map +1 -1
- package/dist/utils/dco/git-config.js +5 -3
- package/dist/utils/dco/git-config.js.map +1 -1
- package/dist/utils/dco/github.d.ts +101 -0
- package/dist/utils/dco/github.d.ts.map +1 -1
- package/dist/utils/dco/github.js +133 -0
- package/dist/utils/dco/github.js.map +1 -1
- package/dist/utils/dco/paths.d.ts +1 -8
- package/dist/utils/dco/paths.d.ts.map +1 -1
- package/dist/utils/dco/paths.js +3 -12
- package/dist/utils/dco/paths.js.map +1 -1
- package/dist/utils/dco/render.d.ts +1 -1
- package/dist/utils/dco/render.d.ts.map +1 -1
- package/dist/utils/dco/render.js +4 -21
- package/dist/utils/dco/render.js.map +1 -1
- package/dist/utils/dco/resolve-agent.d.ts +54 -0
- package/dist/utils/dco/resolve-agent.d.ts.map +1 -0
- package/dist/utils/dco/resolve-agent.js +164 -0
- package/dist/utils/dco/resolve-agent.js.map +1 -0
- package/dist/utils/dco/setup.d.ts +10 -7
- package/dist/utils/dco/setup.d.ts.map +1 -1
- package/dist/utils/dco/setup.js +264 -200
- package/dist/utils/dco/setup.js.map +1 -1
- package/dist/utils/dco/signing-key-writer.d.ts +40 -0
- package/dist/utils/dco/signing-key-writer.d.ts.map +1 -0
- package/dist/utils/dco/signing-key-writer.js +76 -0
- package/dist/utils/dco/signing-key-writer.js.map +1 -0
- package/dist/utils/dco/ssh-key.d.ts +50 -0
- package/dist/utils/dco/ssh-key.d.ts.map +1 -1
- package/dist/utils/dco/ssh-key.js +129 -8
- package/dist/utils/dco/ssh-key.js.map +1 -1
- package/dist/utils/dco/status.d.ts +1 -1
- package/dist/utils/dco/status.d.ts.map +1 -1
- package/dist/utils/dco/status.js +3 -3
- package/dist/utils/dco/status.js.map +1 -1
- package/dist/utils/dco/verify.d.ts +170 -0
- package/dist/utils/dco/verify.d.ts.map +1 -0
- package/dist/utils/dco/verify.js +550 -0
- package/dist/utils/dco/verify.js.map +1 -0
- package/dist/utils/dependency-installer.d.ts +37 -0
- package/dist/utils/dependency-installer.d.ts.map +1 -0
- package/dist/utils/dependency-installer.js +32 -0
- package/dist/utils/dependency-installer.js.map +1 -0
- package/dist/utils/github-credential-stage.d.ts +87 -0
- package/dist/utils/github-credential-stage.d.ts.map +1 -0
- package/dist/utils/github-credential-stage.js +168 -0
- package/dist/utils/github-credential-stage.js.map +1 -0
- package/dist/utils/github-credential.d.ts +160 -0
- package/dist/utils/github-credential.d.ts.map +1 -0
- package/dist/utils/github-credential.js +308 -0
- package/dist/utils/github-credential.js.map +1 -0
- package/dist/utils/gitignore.d.ts +17 -0
- package/dist/utils/gitignore.d.ts.map +1 -0
- package/dist/utils/gitignore.js +40 -0
- package/dist/utils/gitignore.js.map +1 -0
- package/dist/utils/hub-config.d.ts +21 -0
- package/dist/utils/hub-config.d.ts.map +1 -0
- package/dist/utils/hub-config.js +28 -0
- package/dist/utils/hub-config.js.map +1 -0
- package/dist/utils/identity-heal.d.ts +16 -0
- package/dist/utils/identity-heal.d.ts.map +1 -0
- package/dist/utils/identity-heal.js +37 -0
- package/dist/utils/identity-heal.js.map +1 -0
- package/dist/utils/init/pipeline.d.ts +74 -0
- package/dist/utils/init/pipeline.d.ts.map +1 -0
- package/dist/utils/init/pipeline.js +96 -0
- package/dist/utils/init/pipeline.js.map +1 -0
- package/dist/utils/poll.d.ts +15 -1
- package/dist/utils/poll.d.ts.map +1 -1
- package/dist/utils/poll.js +6 -3
- package/dist/utils/poll.js.map +1 -1
- package/dist/utils/register-agent-identity.d.ts +67 -0
- package/dist/utils/register-agent-identity.d.ts.map +1 -0
- package/dist/utils/register-agent-identity.js +34 -0
- package/dist/utils/register-agent-identity.js.map +1 -0
- package/dist/utils/review/did-web-key.d.ts +34 -0
- package/dist/utils/review/did-web-key.d.ts.map +1 -0
- package/dist/utils/review/did-web-key.js +104 -0
- package/dist/utils/review/did-web-key.js.map +1 -0
- package/dist/utils/review/load-registry.d.ts +33 -0
- package/dist/utils/review/load-registry.d.ts.map +1 -0
- package/dist/utils/review/load-registry.js +62 -0
- package/dist/utils/review/load-registry.js.map +1 -0
- package/dist/utils/review/verdict-verify.d.ts +43 -0
- package/dist/utils/review/verdict-verify.d.ts.map +1 -0
- package/dist/utils/review/verdict-verify.js +53 -0
- package/dist/utils/review/verdict-verify.js.map +1 -0
- package/dist/utils/review-registry-writer.d.ts +52 -0
- package/dist/utils/review-registry-writer.d.ts.map +1 -0
- package/dist/utils/review-registry-writer.js +79 -0
- package/dist/utils/review-registry-writer.js.map +1 -0
- package/dist/utils/should-install-dependency.d.ts +17 -0
- package/dist/utils/should-install-dependency.d.ts.map +1 -0
- package/dist/utils/should-install-dependency.js +25 -0
- package/dist/utils/should-install-dependency.js.map +1 -0
- package/dist/utils/tool-readiness.d.ts +57 -0
- package/dist/utils/tool-readiness.d.ts.map +1 -0
- package/dist/utils/tool-readiness.js +114 -0
- package/dist/utils/tool-readiness.js.map +1 -0
- package/dist/utils/which.d.ts +2 -0
- package/dist/utils/which.d.ts.map +1 -0
- package/dist/utils/which.js +22 -0
- package/dist/utils/which.js.map +1 -0
- package/dist/version.d.ts +15 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +29 -0
- package/dist/version.js.map +1 -0
- package/package.json +19 -6
- package/dist/utils/claim-polling.d.ts +0 -21
- package/dist/utils/claim-polling.d.ts.map +0 -1
- package/dist/utils/claim-polling.js.map +0 -1
- package/dist/utils/dco/fs-safe.d.ts +0 -14
- package/dist/utils/dco/fs-safe.d.ts.map +0 -1
- package/dist/utils/dco/fs-safe.js +0 -20
- package/dist/utils/dco/fs-safe.js.map +0 -1
|
@@ -0,0 +1,474 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A coordination hub, observed from outside its process.
|
|
3
|
+
*
|
|
4
|
+
* This is what makes the daemon host pluggable. `OrchestratorDaemon` takes a
|
|
5
|
+
* structural `EventSource` -- `onEvent` plus `getRecentEvents` -- rather than the
|
|
6
|
+
* concrete hub, so anything that can supply those two methods can drive it. In
|
|
7
|
+
* process, the hub itself supplies them. Here they come over a WebSocket, which
|
|
8
|
+
* lets the daemon run wherever the operator already runs things and attach to
|
|
9
|
+
* whichever hub they already point their agents at.
|
|
10
|
+
*
|
|
11
|
+
* The CLI deliberately does not depend on @kya-os/compute: compute is PID 1 in a
|
|
12
|
+
* Fly worker, this is a published CLI. So the observer protocol is spoken
|
|
13
|
+
* directly, against the wire format in @kya-os/contracts/handshake rather than
|
|
14
|
+
* a copy of it. (Not `/proof`: both a `proof.ts` and a `proof/index.ts` exist
|
|
15
|
+
* there, and Node and tsc disagree about which one that subpath means.)
|
|
16
|
+
*
|
|
17
|
+
* ## Why connect() waits for the snapshot
|
|
18
|
+
*
|
|
19
|
+
* `getRecentEvents` is synchronous, because in-process it reads an array. Over a
|
|
20
|
+
* socket the history arrives in a frame, so `connect()` does not resolve until
|
|
21
|
+
* that frame lands. A daemon started before it arrives would reconcile against
|
|
22
|
+
* an empty window and silently skip the recovery a restart exists to perform --
|
|
23
|
+
* the failure mode is a quiet no-op, which is the kind that survives review.
|
|
24
|
+
*
|
|
25
|
+
* @module cli/orchestrator/hub-observer
|
|
26
|
+
*/
|
|
27
|
+
import { readFileSync } from "node:fs";
|
|
28
|
+
import { createPublicKey } from "node:crypto";
|
|
29
|
+
import { SignJWT } from "jose";
|
|
30
|
+
import WebSocket from "ws";
|
|
31
|
+
import { DIDCOMM_POP_TYP, DIDCOMM_POP_ALG, ObserverHelloSchema, DIDCommChallengeSchema, DIDCommProofSchema, } from "@kya-os/contracts/handshake";
|
|
32
|
+
import { ed25519PrivateKeyFromSeed } from "../utils/dco/ssh-key.js";
|
|
33
|
+
import { HubEventLikeSchema } from "./events.js";
|
|
34
|
+
/**
|
|
35
|
+
* Read an identity file, returning undefined rather than throwing.
|
|
36
|
+
*
|
|
37
|
+
* A missing or malformed identity is a normal state -- the operator may not have
|
|
38
|
+
* run `dco setup` -- and it only matters if the hub demands authentication. The
|
|
39
|
+
* hub's rejection message says far more about what is wrong than a parse error
|
|
40
|
+
* raised here would.
|
|
41
|
+
*/
|
|
42
|
+
export function readObserverIdentity(path) {
|
|
43
|
+
try {
|
|
44
|
+
const raw = JSON.parse(readFileSync(path, "utf8"));
|
|
45
|
+
if (typeof raw !== "object" || raw === null)
|
|
46
|
+
return undefined;
|
|
47
|
+
const rec = raw;
|
|
48
|
+
const did = rec["did"];
|
|
49
|
+
const privateKey = rec["privateKey"];
|
|
50
|
+
if (typeof did !== "string" || did.length === 0)
|
|
51
|
+
return undefined;
|
|
52
|
+
if (typeof privateKey !== "string" || privateKey.length === 0)
|
|
53
|
+
return undefined;
|
|
54
|
+
// Check the seed HERE, not later inside a socket handler. A corrupted
|
|
55
|
+
// identity is a configuration problem, and surfacing it as "no usable
|
|
56
|
+
// identity" beats an exception thrown from an open handler where nothing is
|
|
57
|
+
// waiting to catch it.
|
|
58
|
+
if (Buffer.from(privateKey, "base64").length !== 32)
|
|
59
|
+
return undefined;
|
|
60
|
+
return { did, privateKey };
|
|
61
|
+
}
|
|
62
|
+
catch {
|
|
63
|
+
return undefined;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
/** The public JWK the hub needs to bind a did:key to the presented key. */
|
|
67
|
+
export function publicJwkFor(identity) {
|
|
68
|
+
const key = ed25519PrivateKeyFromSeed(Buffer.from(identity.privateKey, "base64"));
|
|
69
|
+
return JSON.stringify(createPublicKey(key).export({ format: "jwk" }));
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* The `observe` hello frame for an optional identity -- shared by every
|
|
73
|
+
* observer-wire client (this file's long-lived `HubObserver` and
|
|
74
|
+
* `hub-dispatch.ts`'s short-lived one-shot client), so the two cannot
|
|
75
|
+
* silently drift on what an anonymous vs. authenticated hello looks like.
|
|
76
|
+
*/
|
|
77
|
+
export function buildObserverHello(identity) {
|
|
78
|
+
return ObserverHelloSchema.parse({
|
|
79
|
+
type: "observe",
|
|
80
|
+
...(identity ? { did: identity.did, publicKey: publicJwkFor(identity) } : {}),
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
/** Sign the hub's challenge, per the wire format in contracts. */
|
|
84
|
+
export async function signChallenge(identity, nonce) {
|
|
85
|
+
const key = ed25519PrivateKeyFromSeed(Buffer.from(identity.privateKey, "base64"));
|
|
86
|
+
return new SignJWT({ nonce })
|
|
87
|
+
.setProtectedHeader({
|
|
88
|
+
alg: DIDCOMM_POP_ALG,
|
|
89
|
+
kid: `${identity.did}#key-1`,
|
|
90
|
+
typ: DIDCOMM_POP_TYP,
|
|
91
|
+
})
|
|
92
|
+
.setSubject(identity.did)
|
|
93
|
+
.sign(key);
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Validate a raw `challenge` frame and, if well-formed, answer it: sign its
|
|
97
|
+
* nonce and build the wire `proof` frame to send back. Returns `undefined`
|
|
98
|
+
* for a frame that fails `DIDCommChallengeSchema` (not this caller's to
|
|
99
|
+
* handle -- the same "ignore, don't fail" choice both callers already made
|
|
100
|
+
* before this was factored out). Every caller of the observer
|
|
101
|
+
* `observe`/`challenge`/`proof` handshake shares this exact step -- {@link
|
|
102
|
+
* HubObserver}'s long-lived `connect()` and the one-shot
|
|
103
|
+
* `github-credential-anchor-sender.ts` -- so it lives here once rather than
|
|
104
|
+
* being reimplemented per caller.
|
|
105
|
+
*/
|
|
106
|
+
export async function respondToObserverChallenge(identity, rawChallenge) {
|
|
107
|
+
const challenge = DIDCommChallengeSchema.safeParse(rawChallenge);
|
|
108
|
+
if (!challenge.success)
|
|
109
|
+
return undefined;
|
|
110
|
+
const jws = await signChallenge(identity, challenge.data.nonce);
|
|
111
|
+
return JSON.stringify(DIDCommProofSchema.parse({ type: "proof", jws }));
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Hosts that need no transport encryption because nothing off this machine
|
|
115
|
+
* can reach them. Both `::1` and `[::1]` are listed because `URL#hostname`
|
|
116
|
+
* keeps an IPv6 literal's brackets rather than stripping them.
|
|
117
|
+
*/
|
|
118
|
+
export const LOOPBACK_HOSTS = new Set(["localhost", "127.0.0.1", "::1", "[::1]"]);
|
|
119
|
+
/**
|
|
120
|
+
* Is this an unencrypted `ws://` endpoint pointed somewhere other than this
|
|
121
|
+
* machine?
|
|
122
|
+
*
|
|
123
|
+
* A loopback hub genuinely does not need TLS -- nothing but this machine can
|
|
124
|
+
* see the socket. Anything else sends the DID, the public key, and the signed
|
|
125
|
+
* proof of possession over a transport anyone on the path can read; `wss://`
|
|
126
|
+
* is the production default for exactly this reason, and a plain `ws://` to a
|
|
127
|
+
* remote host is far more likely a dev config that escaped dev than a
|
|
128
|
+
* deliberate choice. An endpoint this cannot even parse is left alone: it
|
|
129
|
+
* fails at `connect()`, with a clearer error than anything said here.
|
|
130
|
+
*/
|
|
131
|
+
export function isPlaintextNonLoopback(endpoint) {
|
|
132
|
+
let url;
|
|
133
|
+
try {
|
|
134
|
+
url = new URL(endpoint);
|
|
135
|
+
}
|
|
136
|
+
catch {
|
|
137
|
+
return false;
|
|
138
|
+
}
|
|
139
|
+
return url.protocol === "ws:" && !LOOPBACK_HOSTS.has(url.hostname.toLowerCase());
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* How many live events to retain between snapshots.
|
|
143
|
+
*
|
|
144
|
+
* The hub bounds its own ring; a client that did not would grow forever on a
|
|
145
|
+
* connection that never drops. Sized generously relative to the reconcile window
|
|
146
|
+
* the daemon actually reads.
|
|
147
|
+
*/
|
|
148
|
+
const MAX_LIVE_HISTORY = 1000;
|
|
149
|
+
/** Is this object actually shaped like a {@link HubEventLike}? */
|
|
150
|
+
function isHubEventLike(value) {
|
|
151
|
+
return HubEventLikeSchema.safeParse(value).success;
|
|
152
|
+
}
|
|
153
|
+
/** How many times to retry a replayed event whose write failed, and the gap between attempts. */
|
|
154
|
+
const REPLAY_RETRY_ATTEMPTS = 3;
|
|
155
|
+
const REPLAY_RETRY_DELAY_MS = 200;
|
|
156
|
+
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
157
|
+
export class HubObserver {
|
|
158
|
+
constructor(config) {
|
|
159
|
+
this.handlers = [];
|
|
160
|
+
this.history = [];
|
|
161
|
+
this.closedByUs = false;
|
|
162
|
+
this.backoffMs = 500;
|
|
163
|
+
/**
|
|
164
|
+
* Has a handshake ever completed?
|
|
165
|
+
*
|
|
166
|
+
* Reconnecting is only right after a connection that WORKED. Retrying a first
|
|
167
|
+
* failure means a misconfigured or unauthorised hub is retried forever in the
|
|
168
|
+
* background while the caller has already been told it failed -- a daemon that
|
|
169
|
+
* looks alive and records nothing, which is the outcome connect() rejects to
|
|
170
|
+
* avoid.
|
|
171
|
+
*/
|
|
172
|
+
this.hasConnected = false;
|
|
173
|
+
/**
|
|
174
|
+
* Serializes `handleFrame` across messages.
|
|
175
|
+
*
|
|
176
|
+
* `ws.on("message")` fires and forgets (`void this.handleFrame(...)`), which
|
|
177
|
+
* was harmless while every frame finished synchronously within one tick.
|
|
178
|
+
* Retrying a replay's failed write does not: it `await`s a `setTimeout`, and
|
|
179
|
+
* without this queue a live frame arriving during that wait ran a SECOND,
|
|
180
|
+
* concurrent `handleFrame` -- pushing onto `this.history` right before the
|
|
181
|
+
* still-in-flight snapshot handler clobbered it with `this.history = window`,
|
|
182
|
+
* and dispatching that live event ahead of the gap it was meant to follow
|
|
183
|
+
* (review finding on #686). Chaining onto this instead makes every frame
|
|
184
|
+
* fully finish before the next one starts, socket-wide.
|
|
185
|
+
*/
|
|
186
|
+
this.frameQueue = Promise.resolve();
|
|
187
|
+
this.config = config;
|
|
188
|
+
}
|
|
189
|
+
/** Queue a frame behind whatever is still being processed for this socket. */
|
|
190
|
+
enqueueFrame(text, fail, ready) {
|
|
191
|
+
this.frameQueue = this.frameQueue
|
|
192
|
+
// One frame's rejection must not wedge every frame queued behind it: a
|
|
193
|
+
// `.then` chained onto an already-rejected promise never runs its
|
|
194
|
+
// callback, so without this, `handleFrame` would silently stop being
|
|
195
|
+
// called for every later frame too.
|
|
196
|
+
.catch(() => { })
|
|
197
|
+
.then(() => this.handleFrame(text, fail, ready));
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* Connect, authenticate if asked to, and resolve once the snapshot has landed.
|
|
201
|
+
*
|
|
202
|
+
* Rejects rather than retrying: a first connection that cannot authenticate is
|
|
203
|
+
* a configuration problem, and retrying it forever would leave a daemon that
|
|
204
|
+
* looks alive and records nothing. Reconnection after a WORKING connection is
|
|
205
|
+
* a different case and is handled silently below.
|
|
206
|
+
*/
|
|
207
|
+
connect() {
|
|
208
|
+
return new Promise((resolve, reject) => {
|
|
209
|
+
const ws = new WebSocket(this.config.endpoint);
|
|
210
|
+
this.ws = ws;
|
|
211
|
+
let settled = false;
|
|
212
|
+
const finish = (err) => {
|
|
213
|
+
if (settled)
|
|
214
|
+
return;
|
|
215
|
+
settled = true;
|
|
216
|
+
clearTimeout(timer);
|
|
217
|
+
if (err)
|
|
218
|
+
reject(err);
|
|
219
|
+
else
|
|
220
|
+
resolve();
|
|
221
|
+
};
|
|
222
|
+
/**
|
|
223
|
+
* This attempt failed: report it AND drop the socket, always.
|
|
224
|
+
*
|
|
225
|
+
* These were two steps written at each call site, and one site forgot --
|
|
226
|
+
* a throw while answering the challenge rejected connect() and left the
|
|
227
|
+
* socket open. Reconnect is only scheduled from the close handler, so the
|
|
228
|
+
* observer then sat on a live socket with the handshake timer already
|
|
229
|
+
* cleared: no timeout, no retry, attached and recording nothing until the
|
|
230
|
+
* hub happened to hang up. Folding the close in makes the pair structural
|
|
231
|
+
* instead of something every future failure path has to remember.
|
|
232
|
+
*
|
|
233
|
+
* The close is NOT conditional on `settled`, because the stall is worst
|
|
234
|
+
* exactly when it is: a frame that fails after the handshake completed
|
|
235
|
+
* would otherwise leave the socket with nobody watching it at all.
|
|
236
|
+
*/
|
|
237
|
+
const fail = (err) => {
|
|
238
|
+
finish(err);
|
|
239
|
+
ws.close();
|
|
240
|
+
};
|
|
241
|
+
const timer = setTimeout(() => {
|
|
242
|
+
fail(new Error(`hub at ${this.config.endpoint} did not complete the observer handshake in time`));
|
|
243
|
+
}, this.config.handshakeTimeoutMs ?? 10000);
|
|
244
|
+
ws.on("open", () => {
|
|
245
|
+
// publicJwkFor can throw on a malformed seed, and so, now, can a hello
|
|
246
|
+
// that fails ObserverHelloSchema. Both are unhandled-inside-an-event-
|
|
247
|
+
// handler otherwise: an uncaught exception, with connect() left to hang
|
|
248
|
+
// to its timeout and report the wrong cause.
|
|
249
|
+
try {
|
|
250
|
+
ws.send(JSON.stringify(buildObserverHello(this.config.identity)));
|
|
251
|
+
}
|
|
252
|
+
catch (err) {
|
|
253
|
+
fail(err instanceof Error ? err : new Error(String(err)));
|
|
254
|
+
}
|
|
255
|
+
});
|
|
256
|
+
ws.on("message", (raw) => {
|
|
257
|
+
this.enqueueFrame(raw.toString(), fail, finish);
|
|
258
|
+
});
|
|
259
|
+
ws.on("error", (err) => fail(err instanceof Error ? err : new Error(String(err))));
|
|
260
|
+
ws.on("close", (code, reason) => {
|
|
261
|
+
// Before the handshake completes, a close IS the failure: the hub rejects
|
|
262
|
+
// an unauthenticated or unprovable observer by closing.
|
|
263
|
+
finish(new Error(`hub closed the observer connection (${code}${reason.length ? `: ${reason.toString()}` : ""})`));
|
|
264
|
+
// Only if we had a working connection to lose.
|
|
265
|
+
if (this.hasConnected)
|
|
266
|
+
this.scheduleReconnect();
|
|
267
|
+
});
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
async handleFrame(text, fail, ready) {
|
|
271
|
+
let msg;
|
|
272
|
+
try {
|
|
273
|
+
const parsed = JSON.parse(text);
|
|
274
|
+
if (typeof parsed !== "object" || parsed === null)
|
|
275
|
+
return;
|
|
276
|
+
msg = parsed;
|
|
277
|
+
}
|
|
278
|
+
catch {
|
|
279
|
+
return; // a frame we cannot parse is not a frame we can act on
|
|
280
|
+
}
|
|
281
|
+
const type = msg["type"];
|
|
282
|
+
if (type === "challenge") {
|
|
283
|
+
if (!this.config.identity)
|
|
284
|
+
return;
|
|
285
|
+
try {
|
|
286
|
+
const frame = await respondToObserverChallenge(this.config.identity, msg);
|
|
287
|
+
if (frame)
|
|
288
|
+
this.ws?.send(frame);
|
|
289
|
+
}
|
|
290
|
+
catch (err) {
|
|
291
|
+
fail(err instanceof Error ? err : new Error(String(err)));
|
|
292
|
+
}
|
|
293
|
+
return;
|
|
294
|
+
}
|
|
295
|
+
if (type === "snapshot") {
|
|
296
|
+
const events = msg["events"];
|
|
297
|
+
// A hub without the event-history addition sends agents only. Treat that as
|
|
298
|
+
// an empty window rather than an error: the daemon still works, it simply
|
|
299
|
+
// cannot recover what it slept through, which is strictly better than
|
|
300
|
+
// refusing to start. An entry that does not match HubEventLikeSchema is
|
|
301
|
+
// dropped rather than cast through: "verified protocol events" ought to
|
|
302
|
+
// mean the shape was actually checked, not merely asserted at the type
|
|
303
|
+
// level over a socket nothing on the wire guarantees conforms.
|
|
304
|
+
const window = Array.isArray(events) ? events.filter(isHubEventLike) : [];
|
|
305
|
+
if (Array.isArray(events) && window.length !== events.length) {
|
|
306
|
+
console.error(`[orchestrator] dropped ${events.length - window.length} malformed event(s) from the hub's snapshot`);
|
|
307
|
+
}
|
|
308
|
+
// REPLACE, on a reconnect as much as a first connect. The snapshot IS the
|
|
309
|
+
// hub's recent ring, and in-process `getRecentEvents` returns that same
|
|
310
|
+
// ring -- so mirroring it is what keeps this implementation faithful to the
|
|
311
|
+
// one it substitutes for. Appending instead duplicated every event the ring
|
|
312
|
+
// still held, drifted this view away from the hub's, and grew without the
|
|
313
|
+
// hub's size bound on a long-lived daemon.
|
|
314
|
+
//
|
|
315
|
+
// Events the client saw live that have since aged out of the ring are
|
|
316
|
+
// dropped, which is correct: they have aged out of the answer the question
|
|
317
|
+
// is asking.
|
|
318
|
+
if (this.hasConnected) {
|
|
319
|
+
// A RECONNECT. The daemon reconciles only at boot, so events from the
|
|
320
|
+
// gap would otherwise sit in history and never reach the ledger. Replay
|
|
321
|
+
// them through the handlers exactly as if they had arrived live.
|
|
322
|
+
//
|
|
323
|
+
// Safe to replay events already seen: journal ids are content-derived
|
|
324
|
+
// and verdicts are keyed on (repo, pr, sha, reviewer), so a duplicate
|
|
325
|
+
// collapses instead of double-counting. That idempotency is precisely
|
|
326
|
+
// what makes recovery a replay rather than a diff.
|
|
327
|
+
// Dispatch BEFORE replacing, so a handler that reads getRecentEvents
|
|
328
|
+
// sees the window it is being told about rather than the previous one.
|
|
329
|
+
//
|
|
330
|
+
// Retried, unlike a live dispatch below: a live event that a handler
|
|
331
|
+
// fails to write will still sit in the hub's ring and reach this same
|
|
332
|
+
// replay path at the NEXT reconnect, but reconcile only runs at boot,
|
|
333
|
+
// so a replay failure has no later chance -- without a retry here, the
|
|
334
|
+
// one recovery a reconnect exists to perform becomes a logged no-op.
|
|
335
|
+
for (const event of window)
|
|
336
|
+
await this.dispatchReplayed(event);
|
|
337
|
+
}
|
|
338
|
+
this.history = window;
|
|
339
|
+
this.hasConnected = true;
|
|
340
|
+
this.backoffMs = 500; // a completed handshake resets the backoff
|
|
341
|
+
ready();
|
|
342
|
+
return;
|
|
343
|
+
}
|
|
344
|
+
// Anything else with a `type` is meant to be a hub event.
|
|
345
|
+
if (typeof type === "string") {
|
|
346
|
+
if (!isHubEventLike(msg)) {
|
|
347
|
+
console.error(`[orchestrator] dropped a malformed "${type}" frame from the hub`);
|
|
348
|
+
return;
|
|
349
|
+
}
|
|
350
|
+
this.history.push(msg);
|
|
351
|
+
if (this.history.length > MAX_LIVE_HISTORY) {
|
|
352
|
+
this.history = this.history.slice(-MAX_LIVE_HISTORY);
|
|
353
|
+
}
|
|
354
|
+
this.dispatch(msg);
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
/**
|
|
358
|
+
* Hand one event to every handler, once.
|
|
359
|
+
*
|
|
360
|
+
* Shared by live delivery and reconnect replay (through {@link
|
|
361
|
+
* dispatchReplayed}) so the two cannot diverge -- a replay that skipped a
|
|
362
|
+
* handler would recover the journal and not the ticket status, which reads
|
|
363
|
+
* as the reactor having run and disagreed. Returns whether every handler
|
|
364
|
+
* succeeded, so a caller that can retry (replay) knows whether it needs to.
|
|
365
|
+
*
|
|
366
|
+
* This is now the ONLY place a handler's failure is caught. The handlers
|
|
367
|
+
* themselves (`registerJournalWriter`, `registerReviewReactor`) deliberately
|
|
368
|
+
* do not swallow their own write errors -- a handler that always reports
|
|
369
|
+
* success by catching internally makes `allOk` a lie, which is what made
|
|
370
|
+
* `dispatchReplayed`'s retry dead code against the real handlers even though
|
|
371
|
+
* it worked against a mock that threw directly.
|
|
372
|
+
*/
|
|
373
|
+
dispatch(event) {
|
|
374
|
+
let allOk = true;
|
|
375
|
+
for (const h of [...this.handlers]) {
|
|
376
|
+
try {
|
|
377
|
+
h(event);
|
|
378
|
+
}
|
|
379
|
+
catch (err) {
|
|
380
|
+
allOk = false;
|
|
381
|
+
// One bad handler must not cost the others their event, nor the socket.
|
|
382
|
+
console.error(`[orchestrator] observer handler failed on ${event.type}: ${err instanceof Error ? err.message : String(err)}`);
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
return allOk;
|
|
386
|
+
}
|
|
387
|
+
/**
|
|
388
|
+
* Dispatch one event from a reconnect's gap replay, retrying instead of
|
|
389
|
+
* letting a write failure disappear it.
|
|
390
|
+
*
|
|
391
|
+
* All handlers together, every attempt, not just the ones that failed: safe
|
|
392
|
+
* because journal ids are content-derived and verdicts are keyed on their
|
|
393
|
+
* subject, so re-dispatching an event a handler already wrote collapses
|
|
394
|
+
* rather than double-applying. SQLite's own `busy_timeout` already absorbs a
|
|
395
|
+
* same-instant lock collision between two writers; this covers whatever
|
|
396
|
+
* survives that -- a slower disk, any other transient failure -- during the
|
|
397
|
+
* one window where a dropped write has no later chance to be recovered.
|
|
398
|
+
*/
|
|
399
|
+
async dispatchReplayed(event) {
|
|
400
|
+
for (let attempt = 1; attempt <= REPLAY_RETRY_ATTEMPTS; attempt++) {
|
|
401
|
+
if (this.dispatch(event))
|
|
402
|
+
return;
|
|
403
|
+
if (attempt < REPLAY_RETRY_ATTEMPTS) {
|
|
404
|
+
await sleep(REPLAY_RETRY_DELAY_MS * attempt);
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
console.error(`[orchestrator] gave up recovering a replayed "${event.type}" event after ` +
|
|
408
|
+
`${REPLAY_RETRY_ATTEMPTS} attempts`);
|
|
409
|
+
}
|
|
410
|
+
/**
|
|
411
|
+
* Schedule the next attempt.
|
|
412
|
+
*
|
|
413
|
+
* The timer is deliberately NOT unref'd, and that is the whole point. While
|
|
414
|
+
* reconnecting there is no socket, and in `kya-os orchestrator daemon` the
|
|
415
|
+
* socket is the only handle keeping the process alive -- the liveness beat is
|
|
416
|
+
* unref'd and the command holds no stdin. An unref'd timer here therefore let
|
|
417
|
+
* the process exit during the exact window it was supposed to be recovering
|
|
418
|
+
* in: a hub restart quietly killed the daemon, with no error and no exit
|
|
419
|
+
* message. This timer stands in for the socket, so it has to hold the loop
|
|
420
|
+
* exactly as the socket does.
|
|
421
|
+
*
|
|
422
|
+
* The rule the two now share: the observer owns the daemon's liveness while
|
|
423
|
+
* it is attached or trying to be, and everything else (the beat, the signal
|
|
424
|
+
* handlers) is incidental and stays unref'd.
|
|
425
|
+
*/
|
|
426
|
+
scheduleReconnect() {
|
|
427
|
+
if (this.closedByUs || this.reconnectTimer)
|
|
428
|
+
return;
|
|
429
|
+
const delay = this.backoffMs;
|
|
430
|
+
this.backoffMs = Math.min(delay * 2, this.config.maxBackoffMs ?? 30000);
|
|
431
|
+
this.reconnectTimer = setTimeout(() => {
|
|
432
|
+
this.reconnectTimer = undefined;
|
|
433
|
+
if (this.closedByUs)
|
|
434
|
+
return;
|
|
435
|
+
// Reconnect failures are logged, not thrown: nothing is awaiting this, and
|
|
436
|
+
// an unhandled rejection would take the daemon down over a hub restart.
|
|
437
|
+
this.connect().catch((err) => {
|
|
438
|
+
console.error(`[orchestrator] hub reconnect failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
439
|
+
});
|
|
440
|
+
}, delay);
|
|
441
|
+
}
|
|
442
|
+
/**
|
|
443
|
+
* Is a reconnect scheduled -- i.e. is this observer still holding the process
|
|
444
|
+
* open on purpose? Distinguishes a cancelled timer from a merely neutered one,
|
|
445
|
+
* which is invisible from the outside and is the difference between a stopped
|
|
446
|
+
* daemon exiting and hanging.
|
|
447
|
+
*/
|
|
448
|
+
hasPendingReconnect() {
|
|
449
|
+
return this.reconnectTimer !== undefined;
|
|
450
|
+
}
|
|
451
|
+
onEvent(handler) {
|
|
452
|
+
this.handlers.push(handler);
|
|
453
|
+
return () => {
|
|
454
|
+
this.handlers = this.handlers.filter((h) => h !== handler);
|
|
455
|
+
};
|
|
456
|
+
}
|
|
457
|
+
getRecentEvents(limit) {
|
|
458
|
+
return limit === undefined ? [...this.history] : this.history.slice(-limit);
|
|
459
|
+
}
|
|
460
|
+
/** Stop observing. Idempotent, and suppresses the reconnect. */
|
|
461
|
+
close() {
|
|
462
|
+
this.closedByUs = true;
|
|
463
|
+
// CANCEL the pending reconnect, do not just make its callback do nothing.
|
|
464
|
+
// Now that the timer holds the event loop open on purpose, a neutered one
|
|
465
|
+
// still keeps a stopped daemon (and any test suite) running to its deadline.
|
|
466
|
+
if (this.reconnectTimer) {
|
|
467
|
+
clearTimeout(this.reconnectTimer);
|
|
468
|
+
this.reconnectTimer = undefined;
|
|
469
|
+
}
|
|
470
|
+
this.ws?.close();
|
|
471
|
+
this.ws = undefined;
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
//# sourceMappingURL=hub-observer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hub-observer.js","sourceRoot":"","sources":["../../src/orchestrator/hub-observer.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Journal writer (ORC-W1 PR-2).
|
|
3
|
+
*
|
|
4
|
+
* Turns hub events into ledger rows. This is the piece that makes the journal
|
|
5
|
+
* evidence rather than narration: rows land because a verified protocol event
|
|
6
|
+
* arrived, not because a model chose to record one.
|
|
7
|
+
*
|
|
8
|
+
* @module orchestrator/journal-writer
|
|
9
|
+
*/
|
|
10
|
+
import type { Ledger } from "./db/index.js";
|
|
11
|
+
import type { EventSource, HubEventLike } from "./events.js";
|
|
12
|
+
/**
|
|
13
|
+
* Pull a task id out of an event's untyped `data` bag.
|
|
14
|
+
*
|
|
15
|
+
* `data` is `Record<string, unknown>` on the wire, so this narrows rather than
|
|
16
|
+
* casting. Anything that is not a non-empty string is treated as absent, which
|
|
17
|
+
* is the honest reading: a malformed taskId must not be used to look up a
|
|
18
|
+
* ticket and silently attach an observation to the wrong one.
|
|
19
|
+
*/
|
|
20
|
+
export declare function taskIdOf(event: HubEventLike): string | null;
|
|
21
|
+
/**
|
|
22
|
+
* Persist one event.
|
|
23
|
+
*
|
|
24
|
+
* Correlates to a ticket through `dispatches` when the event names a task this
|
|
25
|
+
* orchestrator dispatched. When it does not, the row is still written with a
|
|
26
|
+
* null ticket, because an uncorrelated observation is worth more than no
|
|
27
|
+
* observation and guessing at a ticket would be worse than either.
|
|
28
|
+
*
|
|
29
|
+
* Returns true when the row was new (false means it was a replay).
|
|
30
|
+
*/
|
|
31
|
+
export declare function writeEvent(db: Ledger, event: HubEventLike): boolean;
|
|
32
|
+
/** The PR a review event is about, or null if it does not name one completely. */
|
|
33
|
+
export interface ReviewSubject {
|
|
34
|
+
repo: string;
|
|
35
|
+
prNumber: number;
|
|
36
|
+
headSha: string;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Read a review event's subject out of its untyped `data` bag.
|
|
40
|
+
*
|
|
41
|
+
* All three fields or nothing. A verdict missing part of its subject cannot be
|
|
42
|
+
* attributed, and attributing it to the wrong ticket is worse than leaving the
|
|
43
|
+
* row uncorrelated.
|
|
44
|
+
*/
|
|
45
|
+
export declare function reviewSubjectOf(event: HubEventLike): ReviewSubject | null;
|
|
46
|
+
/**
|
|
47
|
+
* Resolve the ticket an event concerns: task id first, pull request second.
|
|
48
|
+
*
|
|
49
|
+
* Task id is exact where present. PR correlation is the fallback and the ONLY
|
|
50
|
+
* option for `pr_quorum_reached`, which carries no task id -- so before this
|
|
51
|
+
* existed every quorum event journalled with a null ticket, and the Review lens
|
|
52
|
+
* could show that something reached quorum but never which ticket it was for.
|
|
53
|
+
*/
|
|
54
|
+
export declare function ticketIdForEvent(db: Ledger, event: HubEventLike): string | null;
|
|
55
|
+
/**
|
|
56
|
+
* Subscribe to the hub and journal everything it emits.
|
|
57
|
+
*
|
|
58
|
+
* Returns the unsubscribe, matching the hub's own `onEvent` contract, so the
|
|
59
|
+
* daemon can detach cleanly on shutdown instead of leaking a handler that
|
|
60
|
+
* writes to a closed database.
|
|
61
|
+
*
|
|
62
|
+
* Deliberately does NOT catch a write failure here. Every real dispatcher
|
|
63
|
+
* this attaches to -- the hub's own emit loop, `HubObserver.dispatch()` --
|
|
64
|
+
* already isolates one handler's failure from its siblings and from itself,
|
|
65
|
+
* so catching AGAIN in here just hides the failure from that dispatcher: a
|
|
66
|
+
* reconnect's replay retries a write only if it can tell the write failed,
|
|
67
|
+
* and a handler that always reports success by swallowing its own errors
|
|
68
|
+
* makes that retry permanently dead code (review finding on #686).
|
|
69
|
+
*/
|
|
70
|
+
export declare function registerJournalWriter(db: Ledger, hub: EventSource): () => void;
|
|
71
|
+
/**
|
|
72
|
+
* Replay the hub's recent-event window into the journal.
|
|
73
|
+
*
|
|
74
|
+
* Called once on boot so a daemon restart recovers what it slept through.
|
|
75
|
+
* Idempotent by construction: journal ids are derived from event content, so
|
|
76
|
+
* an event already recorded live is ignored rather than duplicated. Returns
|
|
77
|
+
* how many rows were genuinely new.
|
|
78
|
+
*/
|
|
79
|
+
export declare function reconcileRecentEvents(db: Ledger, hub: EventSource, limit?: number): number;
|
|
80
|
+
//# sourceMappingURL=journal-writer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"journal-writer.d.ts","sourceRoot":"","sources":["../../src/orchestrator/journal-writer.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,eAAe,CAAC;AAG5C,OAAO,KAAK,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAE7D;;;;;;;GAOG;AACH,wBAAgB,QAAQ,CAAC,KAAK,EAAE,YAAY,GAAG,MAAM,GAAG,IAAI,CAG3D;AAED;;;;;;;;;GASG;AACH,wBAAgB,UAAU,CAAC,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,YAAY,GAAG,OAAO,CAEnE;AAED,kFAAkF;AAClF,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,YAAY,GAAG,aAAa,GAAG,IAAI,CAWzE;AAED;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAAC,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,YAAY,GAAG,MAAM,GAAG,IAAI,CAQ/E;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,qBAAqB,CAAC,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,WAAW,GAAG,MAAM,IAAI,CAI9E;AAED;;;;;;;GAOG;AACH,wBAAgB,qBAAqB,CAAC,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,WAAW,EAAE,KAAK,SAAK,GAAG,MAAM,CAMtF"}
|