@revisium/orchestrator 0.1.0-alpha.9 → 0.2.0-alpha.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/README.md +210 -146
- package/control-plane/bootstrap.config.json +43 -1
- package/control-plane/default-playbook/catalog/pipelines.json +1157 -10
- package/control-plane/default-playbook/catalog/roles.json +54 -0
- package/control-plane/default-playbook/package.json +1 -1
- package/control-plane/default-playbook/prompts/developer.md +1 -0
- package/control-plane/default-playbook/prompts/triager.md +3 -0
- package/dist/api/graphql-api/graphql-ws/pubsub.module.js +0 -1
- package/dist/api/graphql-api/graphql-ws/pubsub.module.js.map +1 -1
- package/dist/api/graphql-api/graphql-ws/run-progress-subscription-poller.service.js +0 -1
- package/dist/api/graphql-api/graphql-ws/run-progress-subscription-poller.service.js.map +1 -1
- package/dist/api/graphql-api/graphql-ws/subscription-bridge.service.js +0 -1
- package/dist/api/graphql-api/graphql-ws/subscription-bridge.service.js.map +1 -1
- package/dist/api/graphql-api/graphql-ws/subscription-mappers.js +3 -0
- package/dist/api/graphql-api/graphql-ws/subscription-mappers.js.map +1 -1
- package/dist/api/graphql-api/pr/inputs/pr-readiness.input.js +6 -0
- package/dist/api/graphql-api/pr/inputs/pr-readiness.input.js.map +1 -1
- package/dist/api/graphql-api/pr/model/pr-readiness.model.js +26 -0
- package/dist/api/graphql-api/pr/model/pr-readiness.model.js.map +1 -1
- package/dist/api/graphql-api/runs/inputs/create-run.input.js +6 -0
- package/dist/api/graphql-api/runs/inputs/create-run.input.js.map +1 -1
- package/dist/api/graphql-api/runs/model/run.model.js +6 -0
- package/dist/api/graphql-api/runs/model/run.model.js.map +1 -1
- package/dist/api/graphql-api/share/model/issue-ref.model.js +53 -0
- package/dist/api/graphql-api/share/model/issue-ref.model.js.map +1 -0
- package/dist/cli/commands/doctor-report.js +0 -18
- package/dist/cli/commands/doctor-report.js.map +1 -1
- package/dist/cli/commands/lifecycle.js +3 -71
- package/dist/cli/commands/lifecycle.js.map +1 -1
- package/dist/cli/commands/mcp.js +0 -14
- package/dist/cli/commands/mcp.js.map +1 -1
- package/dist/cli/commands/process-tree.js +4 -26
- package/dist/cli/commands/process-tree.js.map +1 -1
- package/dist/cli/commands/revisium-helpers.js +0 -6
- package/dist/cli/commands/revisium-helpers.js.map +1 -1
- package/dist/cli/commands/rogue-reaper.js +0 -34
- package/dist/cli/commands/rogue-reaper.js.map +1 -1
- package/dist/cli/index.js +0 -4
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/program.js +0 -5
- package/dist/cli/program.js.map +1 -1
- package/dist/config.js +0 -25
- package/dist/config.js.map +1 -1
- package/dist/control-plane/bootstrap.js +12 -27
- package/dist/control-plane/bootstrap.js.map +1 -1
- package/dist/control-plane/change-notifications.js +0 -1
- package/dist/control-plane/change-notifications.js.map +1 -1
- package/dist/control-plane/client-transport.js +1 -12
- package/dist/control-plane/client-transport.js.map +1 -1
- package/dist/control-plane/default-playbook-policy.js +593 -0
- package/dist/control-plane/default-playbook-policy.js.map +1 -0
- package/dist/control-plane/definitions.js +8 -17
- package/dist/control-plane/definitions.js.map +1 -1
- package/dist/control-plane/inbox.js +2 -120
- package/dist/control-plane/inbox.js.map +1 -1
- package/dist/control-plane/resolve-cwd.js +0 -61
- package/dist/control-plane/resolve-cwd.js.map +1 -1
- package/dist/control-plane/schema-migration.js +0 -9
- package/dist/control-plane/schema-migration.js.map +1 -1
- package/dist/control-plane/seed-default-playbook.js +50 -46
- package/dist/control-plane/seed-default-playbook.js.map +1 -1
- package/dist/control-plane/steps.js +0 -15
- package/dist/control-plane/steps.js.map +1 -1
- package/dist/e2e/kit/agents.js +15 -21
- package/dist/e2e/kit/agents.js.map +1 -1
- package/dist/e2e/kit/assertions.js +12 -8
- package/dist/e2e/kit/assertions.js.map +1 -1
- package/dist/e2e/kit/fake-integrator.js +11 -2
- package/dist/e2e/kit/fake-integrator.js.map +1 -1
- package/dist/e2e/kit/git-target-repo.js +1 -1
- package/dist/engine/dbos.service.js +12 -179
- package/dist/engine/dbos.service.js.map +1 -1
- package/dist/engine/ensure-postgres.js +0 -48
- package/dist/engine/ensure-postgres.js.map +1 -1
- package/dist/features/pr/queries/impl/get-pr-readiness.query.js.map +1 -1
- package/dist/features/runs/commands/impl/create-run.command.js.map +1 -1
- package/dist/features/runs/queries/handlers/runs-query.handlers.js +1 -0
- package/dist/features/runs/queries/handlers/runs-query.handlers.js.map +1 -1
- package/dist/host/daemon.js +2 -21
- package/dist/host/daemon.js.map +1 -1
- package/dist/host/dbos-identity.js +0 -31
- package/dist/host/dbos-identity.js.map +1 -1
- package/dist/host/ensure-host.js +1 -37
- package/dist/host/ensure-host.js.map +1 -1
- package/dist/host/ensure-revisium.js +1 -135
- package/dist/host/ensure-revisium.js.map +1 -1
- package/dist/host/host-runtime.js +0 -28
- package/dist/host/host-runtime.js.map +1 -1
- package/dist/host/host.lifecycle.js +0 -27
- package/dist/host/host.lifecycle.js.map +1 -1
- package/dist/host/queue-ownership.js +0 -42
- package/dist/host/queue-ownership.js.map +1 -1
- package/dist/host/queue-poller-census.js +1 -28
- package/dist/host/queue-poller-census.js.map +1 -1
- package/dist/http/graphql-host.module.js +0 -3
- package/dist/http/graphql-host.module.js.map +1 -1
- package/dist/mcp/mcp-capabilities.js +23 -5
- package/dist/mcp/mcp-capabilities.js.map +1 -1
- package/dist/mcp/mcp-facade.service.js +335 -22
- package/dist/mcp/mcp-facade.service.js.map +1 -1
- package/dist/mcp/mcp-http.service.js +0 -18
- package/dist/mcp/mcp-http.service.js.map +1 -1
- package/dist/mcp/mcp-tools.js +41 -36
- package/dist/mcp/mcp-tools.js.map +1 -1
- package/dist/observability/activity-signal.js +103 -0
- package/dist/observability/activity-signal.js.map +1 -0
- package/dist/observability/agent-activity-reporter.js +0 -4
- package/dist/observability/agent-activity-reporter.js.map +1 -1
- package/dist/observability/index.js +1 -0
- package/dist/observability/index.js.map +1 -1
- package/dist/pipeline/await-human.js +0 -40
- package/dist/pipeline/await-human.js.map +1 -1
- package/dist/pipeline/data-driven-task.workflow.js +563 -251
- package/dist/pipeline/data-driven-task.workflow.js.map +1 -1
- package/dist/pipeline/data-driven-template.js +0 -25
- package/dist/pipeline/data-driven-template.js.map +1 -1
- package/dist/pipeline/pipeline.module.js +0 -12
- package/dist/pipeline/pipeline.module.js.map +1 -1
- package/dist/pipeline/pipeline.service.js +194 -189
- package/dist/pipeline/pipeline.service.js.map +1 -1
- package/dist/pipeline/route-contract.js +4 -3
- package/dist/pipeline/route-contract.js.map +1 -1
- package/dist/pipeline-core/index.js +1 -13
- package/dist/pipeline-core/index.js.map +1 -1
- package/dist/pipeline-core/interpret.js +21 -94
- package/dist/pipeline-core/interpret.js.map +1 -1
- package/dist/pipeline-core/kit/assertions.js +0 -19
- package/dist/pipeline-core/kit/assertions.js.map +1 -1
- package/dist/pipeline-core/kit/builders.js +1 -21
- package/dist/pipeline-core/kit/builders.js.map +1 -1
- package/dist/pipeline-core/kit/drive.js +1 -31
- package/dist/pipeline-core/kit/drive.js.map +1 -1
- package/dist/pipeline-core/kit/fixtures.js +78 -101
- package/dist/pipeline-core/kit/fixtures.js.map +1 -1
- package/dist/pipeline-core/kit/index.js +0 -8
- package/dist/pipeline-core/kit/index.js.map +1 -1
- package/dist/pipeline-core/types.js +13 -52
- package/dist/pipeline-core/types.js.map +1 -1
- package/dist/pipeline-core/validate-capability.js +0 -3
- package/dist/pipeline-core/validate-capability.js.map +1 -1
- package/dist/pipeline-core/validate-conflicts.js +0 -9
- package/dist/pipeline-core/validate-conflicts.js.map +1 -1
- package/dist/pipeline-core/validate-dataflow.js +0 -17
- package/dist/pipeline-core/validate-dataflow.js.map +1 -1
- package/dist/pipeline-core/validate-diff.js +3 -36
- package/dist/pipeline-core/validate-diff.js.map +1 -1
- package/dist/pipeline-core/validate-edges.js +0 -6
- package/dist/pipeline-core/validate-edges.js.map +1 -1
- package/dist/pipeline-core/validate-graph.js +0 -11
- package/dist/pipeline-core/validate-graph.js.map +1 -1
- package/dist/pipeline-core/validate-loops.js +37 -44
- package/dist/pipeline-core/validate-loops.js.map +1 -1
- package/dist/pipeline-core/validate-parallel.js +9 -15
- package/dist/pipeline-core/validate-parallel.js.map +1 -1
- package/dist/pipeline-core/validate-sink.js +0 -7
- package/dist/pipeline-core/validate-sink.js.map +1 -1
- package/dist/pipeline-core/validate-topology.js +1 -20
- package/dist/pipeline-core/validate-topology.js.map +1 -1
- package/dist/pipeline-core/validate-verdict.js +52 -7
- package/dist/pipeline-core/validate-verdict.js.map +1 -1
- package/dist/pipeline-core/validate.js +5 -29
- package/dist/pipeline-core/validate.js.map +1 -1
- package/dist/playbook/catalog-loader.js +8 -3
- package/dist/playbook/catalog-loader.js.map +1 -1
- package/dist/playbook/import-mapper.js +4 -5
- package/dist/playbook/import-mapper.js.map +1 -1
- package/dist/poller/pr-readiness-core.js +226 -29
- package/dist/poller/pr-readiness-core.js.map +1 -1
- package/dist/revisium/inbox.service.js +0 -24
- package/dist/revisium/inbox.service.js.map +1 -1
- package/dist/revisium/playbooks.service.js +15 -2
- package/dist/revisium/playbooks.service.js.map +1 -1
- package/dist/revisium/revisium.module.js +0 -12
- package/dist/revisium/revisium.module.js.map +1 -1
- package/dist/revisium/roles.service.js +0 -6
- package/dist/revisium/roles.service.js.map +1 -1
- package/dist/revisium/run.service.js +2 -75
- package/dist/revisium/run.service.js.map +1 -1
- package/dist/revisium/tokens.js +0 -4
- package/dist/revisium/tokens.js.map +1 -1
- package/dist/run/append-event.js +6 -37
- package/dist/run/append-event.js.map +1 -1
- package/dist/run/block-run.js.map +1 -1
- package/dist/run/cancel-run.js +0 -33
- package/dist/run/cancel-run.js.map +1 -1
- package/dist/run/complete-run.js.map +1 -1
- package/dist/run/create-run.js +5 -8
- package/dist/run/create-run.js.map +1 -1
- package/dist/run/fail-run.js +0 -3
- package/dist/run/fail-run.js.map +1 -1
- package/dist/run/inspect-run.js +10 -29
- package/dist/run/inspect-run.js.map +1 -1
- package/dist/run/issue-ref.js +88 -0
- package/dist/run/issue-ref.js.map +1 -0
- package/dist/run/run-outputs.js +0 -19
- package/dist/run/run-outputs.js.map +1 -1
- package/dist/run/terminal-run-status.js +0 -12
- package/dist/run/terminal-run-status.js.map +1 -1
- package/dist/runners/claude-code.service.js +2 -13
- package/dist/runners/claude-code.service.js.map +1 -1
- package/dist/runners/codex.service.js +2 -11
- package/dist/runners/codex.service.js.map +1 -1
- package/dist/runners/gh-identity.js +0 -52
- package/dist/runners/gh-identity.js.map +1 -1
- package/dist/runners/integrator-branch-naming.js +16 -19
- package/dist/runners/integrator-branch-naming.js.map +1 -1
- package/dist/runners/integrator-git.js +0 -25
- package/dist/runners/integrator-git.js.map +1 -1
- package/dist/runners/integrator-remote.js +0 -2
- package/dist/runners/integrator-remote.js.map +1 -1
- package/dist/runners/integrator-types.js +0 -7
- package/dist/runners/integrator-types.js.map +1 -1
- package/dist/runners/integrator.js +231 -230
- package/dist/runners/integrator.js.map +1 -1
- package/dist/runners/runner.module.js +0 -6
- package/dist/runners/runner.module.js.map +1 -1
- package/dist/runners/tokens.js +0 -4
- package/dist/runners/tokens.js.map +1 -1
- package/dist/runners/worktree.service.js +3 -19
- package/dist/runners/worktree.service.js.map +1 -1
- package/dist/task-control-plane/pr-readiness.service.js +15 -13
- package/dist/task-control-plane/pr-readiness.service.js.map +1 -1
- package/dist/task-control-plane/run-watch.service.js +170 -63
- package/dist/task-control-plane/run-watch.service.js.map +1 -1
- package/dist/task-control-plane/task-control-plane-api.service.js +285 -40
- package/dist/task-control-plane/task-control-plane-api.service.js.map +1 -1
- package/dist/worker/artifact-store.js +21 -3
- package/dist/worker/artifact-store.js.map +1 -1
- package/dist/worker/build-context.js +10 -10
- package/dist/worker/build-context.js.map +1 -1
- package/dist/worker/claude-code-runner.js +76 -48
- package/dist/worker/claude-code-runner.js.map +1 -1
- package/dist/worker/codex-runner.js +128 -35
- package/dist/worker/codex-runner.js.map +1 -1
- package/dist/worker/git-worktree-manager.js +20 -67
- package/dist/worker/git-worktree-manager.js.map +1 -1
- package/dist/worker/process-executor.js +144 -27
- package/dist/worker/process-executor.js.map +1 -1
- package/dist/worker/result-envelope.js +33 -49
- package/dist/worker/result-envelope.js.map +1 -1
- package/dist/worker/runner-common.js +29 -0
- package/dist/worker/runner-common.js.map +1 -1
- package/dist/worker/runner-dispatch.js +0 -3
- package/dist/worker/runner-dispatch.js.map +1 -1
- package/dist/worker/runner.js +16 -1
- package/dist/worker/runner.js.map +1 -1
- package/dist/worker/stub-runner.js +0 -7
- package/dist/worker/stub-runner.js.map +1 -1
- package/package.json +2 -2
|
@@ -1,90 +1,24 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* ensureRevisium() — shared auto-start helper (Round 3).
|
|
3
|
-
*
|
|
4
|
-
* Models three distinct states (F7):
|
|
5
|
-
* 1. HEALTHY → no-op, return proven runtime.
|
|
6
|
-
* 2. NO LIVE DAEMON (null runtime or dead pid) → fresh detached spawn + wait.
|
|
7
|
-
* 3. ALIVE BUT UNHEALTHY → bounded re-poll, then throw (never orphan a live process).
|
|
8
|
-
*
|
|
9
|
-
* Called by BOTH `revisium start` command and host bootstrap (DRY extraction, F9).
|
|
10
|
-
*
|
|
11
|
-
* TOCTOU / compare-and-delete (F16, F19, F21):
|
|
12
|
-
* In state 2 we re-classify IMMEDIATELY before any removal. decideRuntimeAction()
|
|
13
|
-
* returns a RuntimeDecision that encodes BOTH the action AND shouldRemove — so the
|
|
14
|
-
* identity rule (only remove when the recheck still matches the stale snapshot by pid
|
|
15
|
-
* AND startedAt) lives in one tested place and the caller cannot drift from it (F21).
|
|
16
|
-
*
|
|
17
|
-
* The same removeRuntimeIfMatches() helper is shared by the state-2 pre-spawn cleanup
|
|
18
|
-
* AND the spawn-timeout cleanup (F19), so neither path can accidentally delete a runtime
|
|
19
|
-
* that belongs to a concurrently-started daemon.
|
|
20
|
-
*
|
|
21
|
-
* Known limitation — concurrent cold-start (deferred per ТЗ §3.3.5, slice 0001):
|
|
22
|
-
* Two host processes starting with no live daemon in the same narrow window can still
|
|
23
|
-
* race to spawn — the compare-and-delete re-check narrows the orphan window but does
|
|
24
|
-
* NOT serialize spawns. Full cross-process file-locking is intentionally deferred to a
|
|
25
|
-
* future slice. The practical guard is the re-check plus the fact that the host path
|
|
26
|
-
* only runs for explicit dev:* invocations.
|
|
27
|
-
*/
|
|
28
1
|
import { spawn } from 'node:child_process';
|
|
29
2
|
import { closeSync, existsSync, openSync, readFileSync, writeFileSync } from 'node:fs';
|
|
30
3
|
import { createRequire } from 'node:module';
|
|
31
4
|
import { baseUrl, findFreePort, getConfig, healthUrl, isAlive, isHealthy, readRuntime, removeRuntime, } from '../cli/config.js';
|
|
32
5
|
import { killTree, parsePort, tailLines, waitForExit, waitHealthy, } from '../cli/commands/revisium-helpers.js';
|
|
33
6
|
const require = createRequire(import.meta.url);
|
|
34
|
-
/** Default cold-start timeout in ms (120 s — matches the existing waitHealthy default). */
|
|
35
7
|
const DEFAULT_TIMEOUT_MS = 120_000;
|
|
36
|
-
/** Default re-poll budget for the alive-but-unhealthy case (consensus MINOR, round 4). */
|
|
37
8
|
const DEFAULT_RECHECK_MS = 20_000;
|
|
38
|
-
/**
|
|
39
|
-
* Remove runtime.json ONLY if the current file on disk still matches the given snapshot
|
|
40
|
-
* (same pid AND startedAt). Shared by the state-2 pre-spawn path and the spawn-timeout
|
|
41
|
-
* cleanup path (F19 / F21 DRY) so neither can accidentally delete a runtime that belongs
|
|
42
|
-
* to a concurrently-started daemon.
|
|
43
|
-
*
|
|
44
|
-
* If the file has been replaced (different identity) or already removed (null), this is a
|
|
45
|
-
* safe no-op — the file either belongs to another process or was already cleaned up.
|
|
46
|
-
*
|
|
47
|
-
* Exported (@internal) for unit testing; production callers should use only the internal
|
|
48
|
-
* call sites (state-2 pre-spawn cleanup and spawn-timeout cleanup).
|
|
49
|
-
*/
|
|
50
9
|
export function removeRuntimeIfMatches(snapshot) {
|
|
51
10
|
const current = readRuntime();
|
|
52
11
|
if (current?.pid === snapshot.pid && current?.startedAt === snapshot.startedAt) {
|
|
53
12
|
removeRuntime();
|
|
54
13
|
}
|
|
55
14
|
}
|
|
56
|
-
/**
|
|
57
|
-
* Decide what action to take in state 2 ("no live daemon" from initial classification)
|
|
58
|
-
* given the stale snapshot and an immediate re-read immediately before any removal.
|
|
59
|
-
*
|
|
60
|
-
* Pure function (no side effects, no I/O) — unit-testable in isolation (F18 / F16 / F21).
|
|
61
|
-
*
|
|
62
|
-
* Compare-and-delete contract (F16, F21):
|
|
63
|
-
* - shouldRemove is true ONLY when the re-read runtime STILL matches the stale snapshot
|
|
64
|
-
* (same pid AND startedAt). This prevents deleting a live runtime written concurrently
|
|
65
|
-
* BETWEEN our initial dead-pid read and this re-check.
|
|
66
|
-
* - If the re-check shows a DIFFERENT (or new) live pid, the action routes into alive
|
|
67
|
-
* paths ('return-running' or 'repoll') and shouldRemove is always false.
|
|
68
|
-
*
|
|
69
|
-
* @param stalePidSnapshot - The RuntimeState we observed initially (dead pid), or null.
|
|
70
|
-
* @param recheck - The result of readRuntime() immediately before removal.
|
|
71
|
-
* @param recheckAlive - Whether recheck.pid is alive (ignored when recheck is null).
|
|
72
|
-
* @param recheckHealthy - Whether recheck daemon is healthy (ignored when recheck is null).
|
|
73
|
-
*/
|
|
74
15
|
export function decideRuntimeAction(stalePidSnapshot, recheck, recheckAlive, recheckHealthy) {
|
|
75
16
|
if (recheck && recheckAlive) {
|
|
76
|
-
// A concurrent process wrote a live runtime — do NOT delete or spawn over it.
|
|
77
17
|
if (recheckHealthy)
|
|
78
18
|
return { action: 'return-running', shouldRemove: false };
|
|
79
19
|
return { action: 'repoll', shouldRemove: false };
|
|
80
20
|
}
|
|
81
|
-
// No live runtime after re-read. Determine whether we may remove the stale file.
|
|
82
21
|
if (stalePidSnapshot !== null) {
|
|
83
|
-
// Compare-and-delete identity check (F21): only set shouldRemove=true when the
|
|
84
|
-
// recheck runtime STILL matches the stale snapshot (same pid AND startedAt).
|
|
85
|
-
// - recheck null → file already cleaned up by another process; skip remove but proceed.
|
|
86
|
-
// - recheck present but different identity → new stale entry from a different past run;
|
|
87
|
-
// our snapshot doesn't own it — leave it for its owner.
|
|
88
22
|
const matchesSnapshot = recheck !== null &&
|
|
89
23
|
recheck.pid === stalePidSnapshot.pid &&
|
|
90
24
|
recheck.startedAt === stalePidSnapshot.startedAt;
|
|
@@ -92,21 +26,6 @@ export function decideRuntimeAction(stalePidSnapshot, recheck, recheckAlive, rec
|
|
|
92
26
|
}
|
|
93
27
|
return { action: 'spawn', shouldRemove: false };
|
|
94
28
|
}
|
|
95
|
-
/**
|
|
96
|
-
* Classify the daemon state given a snapshot of: the runtime file, whether the
|
|
97
|
-
* recorded pid is alive, and whether the daemon responds to health checks.
|
|
98
|
-
*
|
|
99
|
-
* Pure function (no side effects, no I/O) — unit-testable in isolation (F15).
|
|
100
|
-
*
|
|
101
|
-
* - 'healthy' → pid alive AND health check passed.
|
|
102
|
-
* - 'alive-unhealthy' → pid alive BUT health check failed (never spawn a second daemon).
|
|
103
|
-
* - 'no-live-daemon' → no runtime.json OR recorded pid is dead (safe to spawn fresh).
|
|
104
|
-
*
|
|
105
|
-
* Note: the alive-unhealthy case intentionally includes a concurrent-start scenario
|
|
106
|
-
* where the runtime was written but the daemon is mid-startup (F11): returning
|
|
107
|
-
* 'alive-unhealthy' rather than 'no-live-daemon' routes callers into the bounded
|
|
108
|
-
* re-poll path instead of unconditionally spawning a second daemon.
|
|
109
|
-
*/
|
|
110
29
|
export function classifyRuntimeState(rt, alive, healthy) {
|
|
111
30
|
if (!rt || !alive)
|
|
112
31
|
return 'no-live-daemon';
|
|
@@ -114,13 +33,6 @@ export function classifyRuntimeState(rt, alive, healthy) {
|
|
|
114
33
|
return 'healthy';
|
|
115
34
|
return 'alive-unhealthy';
|
|
116
35
|
}
|
|
117
|
-
/**
|
|
118
|
-
* Handle state 3: alive-but-unhealthy daemon.
|
|
119
|
-
*
|
|
120
|
-
* Performs a bounded re-poll. Returns `{alreadyRunning:true}` if the daemon becomes
|
|
121
|
-
* healthy within `recheckMs`. Throws with an actionable message otherwise — never
|
|
122
|
-
* removes runtime.json, never spawns (would orphan a live process).
|
|
123
|
-
*/
|
|
124
36
|
async function handleAliveUnhealthy(rt, recheckMs) {
|
|
125
37
|
const becameHealthy = await waitHealthy(healthUrl(rt.httpPort), recheckMs);
|
|
126
38
|
if (becameHealthy) {
|
|
@@ -131,10 +43,6 @@ async function handleAliveUnhealthy(rt, recheckMs) {
|
|
|
131
43
|
}
|
|
132
44
|
throw new Error(`Revisium (pid ${rt.pid}) is running but unhealthy — run \`revo revisium stop\` and retry`);
|
|
133
45
|
}
|
|
134
|
-
/**
|
|
135
|
-
* Perform the "re-poll" branch of state 2: a concurrent start wrote a live-but-unhealthy
|
|
136
|
-
* runtime — poll for late-up rather than spawning a second daemon.
|
|
137
|
-
*/
|
|
138
46
|
async function recheckRepoll(rtRecheck, recheckMs) {
|
|
139
47
|
const becameHealthy = await waitHealthy(healthUrl(rtRecheck.httpPort), recheckMs);
|
|
140
48
|
if (becameHealthy) {
|
|
@@ -145,14 +53,6 @@ async function recheckRepoll(rtRecheck, recheckMs) {
|
|
|
145
53
|
}
|
|
146
54
|
throw new Error(`Revisium (pid ${rtRecheck.pid}) is running but unhealthy — run \`revo revisium stop\` and retry`);
|
|
147
55
|
}
|
|
148
|
-
/**
|
|
149
|
-
* Spawn the standalone daemon (detached + unref'd) and wait for health.
|
|
150
|
-
*
|
|
151
|
-
* Writes extended runtime.json (F8: includes dataDir). On health timeout, kills the
|
|
152
|
-
* spawned child, removes its runtime.json (compare-and-delete — F19), and throws.
|
|
153
|
-
*
|
|
154
|
-
* @returns EnsureResult from the freshly-written runtime.json (F3: pid-proven ports).
|
|
155
|
-
*/
|
|
156
56
|
async function startAndWaitForHealth(cfg, timeoutMs) {
|
|
157
57
|
const { httpPort, pgPort, dataDir, logFile, runtimeFile } = cfg;
|
|
158
58
|
const entry = require.resolve('@revisium/standalone/bin/revisium-standalone.js');
|
|
@@ -162,14 +62,12 @@ async function startAndWaitForHealth(cfg, timeoutMs) {
|
|
|
162
62
|
if (!child.pid) {
|
|
163
63
|
throw new Error('Failed to start standalone Revisium: spawn returned no pid');
|
|
164
64
|
}
|
|
165
|
-
child.unref();
|
|
166
|
-
// Capture the exact identity of the runtime WE just wrote (F19).
|
|
65
|
+
child.unref();
|
|
167
66
|
const spawnStartedAt = new Date().toISOString();
|
|
168
67
|
writeFileSync(runtimeFile, JSON.stringify({ httpPort, pgPort, pid: child.pid, startedAt: spawnStartedAt, dataDir }, null, 2));
|
|
169
68
|
const spawnSnapshot = { pid: child.pid, startedAt: spawnStartedAt };
|
|
170
69
|
const spawnHealthy = await waitHealthy(healthUrl(httpPort), timeoutMs);
|
|
171
70
|
if (!spawnHealthy) {
|
|
172
|
-
// F19: Only removeRuntime() when the file still identifies OUR child (compare-and-delete).
|
|
173
71
|
const logTail = tailLines(logFile, 20);
|
|
174
72
|
killTree(child.pid, 'SIGTERM');
|
|
175
73
|
await waitForExit(child.pid, 20_000);
|
|
@@ -179,24 +77,11 @@ async function startAndWaitForHealth(cfg, timeoutMs) {
|
|
|
179
77
|
throw new Error(`Revisium did not become healthy on ${baseUrl(httpPort)} within ${timeoutMs / 1000}s` +
|
|
180
78
|
` — see \`revo revisium logs\`\n${logTail}`);
|
|
181
79
|
}
|
|
182
|
-
// Re-read the freshly-written runtime.json (F3: pid-proven ports).
|
|
183
80
|
const written = readRuntime();
|
|
184
81
|
if (!written)
|
|
185
82
|
throw new Error('runtime.json disappeared after successful start');
|
|
186
83
|
return { runtime: written, alreadyRunning: false };
|
|
187
84
|
}
|
|
188
|
-
/**
|
|
189
|
-
* Ensure the Revisium standalone daemon is running and healthy.
|
|
190
|
-
*
|
|
191
|
-
* Three-state logic (F7):
|
|
192
|
-
* - State 1: recorded pid alive + healthy → return immediately (alreadyRunning = true).
|
|
193
|
-
* - State 2: no runtime.json OR recorded pid is dead → removeRuntime if stale, spawn fresh.
|
|
194
|
-
* - State 3: recorded pid alive but unhealthy → bounded re-poll (recheckMs), then THROW
|
|
195
|
-
* (never removeRuntime, never spawn — leaves the live process for `revo revisium stop`).
|
|
196
|
-
*
|
|
197
|
-
* @returns EnsureResult with the pid-proven runtime (fresh or existing).
|
|
198
|
-
* @throws if the daemon is alive-but-unhealthy after re-poll, or if spawn times out.
|
|
199
|
-
*/
|
|
200
85
|
export async function ensureRevisium(options = {}) {
|
|
201
86
|
const { timeoutMs = DEFAULT_TIMEOUT_MS, recheckMs = DEFAULT_RECHECK_MS } = options;
|
|
202
87
|
const config = getConfig();
|
|
@@ -204,19 +89,12 @@ export async function ensureRevisium(options = {}) {
|
|
|
204
89
|
const alive = rt ? isAlive(rt.pid) : false;
|
|
205
90
|
const healthy = rt && alive ? await isHealthy(rt.httpPort) : false;
|
|
206
91
|
const stateClass = classifyRuntimeState(rt, alive, healthy);
|
|
207
|
-
// ── State 1: healthy ──────────────────────────────────────────────────────
|
|
208
92
|
if (stateClass === 'healthy') {
|
|
209
93
|
return { runtime: rt, alreadyRunning: true };
|
|
210
94
|
}
|
|
211
|
-
// ── State 3: alive but unhealthy ─────────────────────────────────────────
|
|
212
95
|
if (stateClass === 'alive-unhealthy') {
|
|
213
96
|
return handleAliveUnhealthy(rt, recheckMs);
|
|
214
97
|
}
|
|
215
|
-
// ── State 2: no live daemon ───────────────────────────────────────────────
|
|
216
|
-
//
|
|
217
|
-
// F16 / F21 compare-and-delete: re-classify IMMEDIATELY before any removal.
|
|
218
|
-
// decideRuntimeAction() encodes both the action AND shouldRemove so the identity
|
|
219
|
-
// check lives in one tested pure function (F21).
|
|
220
98
|
const rtRecheck = readRuntime();
|
|
221
99
|
const recheckAlive = rtRecheck ? isAlive(rtRecheck.pid) : false;
|
|
222
100
|
const recheckHealthy = rtRecheck && recheckAlive ? await isHealthy(rtRecheck.httpPort) : false;
|
|
@@ -227,13 +105,9 @@ export async function ensureRevisium(options = {}) {
|
|
|
227
105
|
if (decision.action === 'repoll') {
|
|
228
106
|
return recheckRepoll(rtRecheck, recheckMs);
|
|
229
107
|
}
|
|
230
|
-
// action === 'remove-and-spawn' or 'spawn'
|
|
231
|
-
// F22: removeRuntimeIfMatches re-reads and checks identity AT DELETE TIME.
|
|
232
108
|
if (decision.shouldRemove)
|
|
233
109
|
removeRuntimeIfMatches(rtRecheck);
|
|
234
110
|
const httpPort = await findFreePort(parsePort(options.port, config.preferredPort));
|
|
235
|
-
// CR5: Two independent findFreePort calls can return the same port when their search
|
|
236
|
-
// bases overlap. If they collide, re-pick pgPort from httpPort+1 to guarantee distinct ports.
|
|
237
111
|
let pgPort = await findFreePort(parsePort(options.pgPort, config.preferredPgPort));
|
|
238
112
|
if (pgPort === httpPort) {
|
|
239
113
|
pgPort = await findFreePort(httpPort + 1);
|
|
@@ -241,21 +115,13 @@ export async function ensureRevisium(options = {}) {
|
|
|
241
115
|
const dataDir = options.data ?? config.dataDir;
|
|
242
116
|
return startAndWaitForHealth({ httpPort, pgPort, dataDir, logFile: config.logFile, runtimeFile: config.runtimeFile }, timeoutMs);
|
|
243
117
|
}
|
|
244
|
-
/** Maximum valid TCP port number (inclusive). */
|
|
245
118
|
const MAX_TCP_PORT = 65535;
|
|
246
|
-
/**
|
|
247
|
-
* Read the postmaster.pid file to cross-check the pg port from runtime.json (F3).
|
|
248
|
-
* Returns the pg port if found, or null if the file is absent (older standalone layout).
|
|
249
|
-
*
|
|
250
|
-
* CR6: Valid port range is 1–65535 (inclusive). Out-of-range values (e.g. 70000) return null.
|
|
251
|
-
*/
|
|
252
119
|
export function readPostmasterPgPort(dataDir) {
|
|
253
120
|
const postmasterPid = `${dataDir}/pgdata/postmaster.pid`;
|
|
254
121
|
if (!existsSync(postmasterPid))
|
|
255
122
|
return null;
|
|
256
123
|
try {
|
|
257
124
|
const lines = readFileSync(postmasterPid, 'utf8').split(/\r?\n/);
|
|
258
|
-
// Line 4 (index 3) is the pg port according to the PostgreSQL postmaster.pid format.
|
|
259
125
|
const portStr = lines[3]?.trim();
|
|
260
126
|
if (!portStr)
|
|
261
127
|
return null;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ensure-revisium.js","sourceRoot":"","sources":["../../src/host/ensure-revisium.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"ensure-revisium.js","sourceRoot":"","sources":["../../src/host/ensure-revisium.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,23 +1,8 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Host daemon runtime file (`host.json`) — the tracked identity of the long-lived Revo/NestJS
|
|
3
|
-
* host daemon, the single DBOS owner (ADR 0006). It lives beside the standalone `runtime.json`
|
|
4
|
-
* in the profile data dir but is a SEPARATE file: the standalone daemon and the host daemon have
|
|
5
|
-
* independent lifecycles, so conflating them would let one's stale pid mask the other's state.
|
|
6
|
-
*
|
|
7
|
-
* Shape is intentionally minimal — only what `ensureHost`/`status`/clients need: the pid to prove
|
|
8
|
-
* liveness, the GraphQL port that is the client transport, and the profile/startedAt for display
|
|
9
|
-
* and compare-and-delete identity. Standalone HTTP/pg ports stay in `runtime.json` (readRuntime).
|
|
10
|
-
*/
|
|
11
1
|
import { existsSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
|
|
12
2
|
import { join } from 'node:path';
|
|
13
3
|
import { fileURLToPath } from 'node:url';
|
|
14
4
|
import { getConfig, isAlive, profileDataDir, PROFILES } from '../config.js';
|
|
15
5
|
let cachedCodeVersion;
|
|
16
|
-
/**
|
|
17
|
-
* This build's package version (e.g. `0.1.0-alpha.7`). Recorded in host.json so `ensureHost`/`doctor`
|
|
18
|
-
* can detect a stale daemon running a DIFFERENT build than the current install (slice 139). A `0.0.0`
|
|
19
|
-
* dev checkout does not change across rebuilds, so dev relies on `revo restart`; released builds differ.
|
|
20
|
-
*/
|
|
21
6
|
export function hostCodeVersion() {
|
|
22
7
|
if (cachedCodeVersion === undefined) {
|
|
23
8
|
try {
|
|
@@ -37,7 +22,6 @@ export function hostRuntimeFile() {
|
|
|
37
22
|
export function readHostRuntime() {
|
|
38
23
|
return readHostRuntimeAt(hostRuntimeFile());
|
|
39
24
|
}
|
|
40
|
-
/** Read+parse a host.json at an ARBITRARY path (a sibling profile's). Null when absent/corrupt. */
|
|
41
25
|
export function readHostRuntimeAt(file) {
|
|
42
26
|
if (!existsSync(file))
|
|
43
27
|
return null;
|
|
@@ -48,12 +32,6 @@ export function readHostRuntimeAt(file) {
|
|
|
48
32
|
return null;
|
|
49
33
|
}
|
|
50
34
|
}
|
|
51
|
-
/**
|
|
52
|
-
* Live tracked daemon pids across ALL profiles (slice 140 Phase 2). The rogue reaper must protect
|
|
53
|
-
* EVERY profile's daemon tree, not just the active one — else `revo stop --all --profile default`
|
|
54
|
-
* would SIGKILL a live `dev` daemon (and its bridges), aborting that profile's in-flight DBOS work.
|
|
55
|
-
* Band-default dirs only (a custom REVO_DATA_DIR layout isn't enumerable; documented in profileDataDir).
|
|
56
|
-
*/
|
|
57
35
|
export function allTrackedHostPids() {
|
|
58
36
|
const pids = [];
|
|
59
37
|
for (const profile of Object.keys(PROFILES)) {
|
|
@@ -71,18 +49,12 @@ export function removeHostRuntime() {
|
|
|
71
49
|
if (existsSync(file))
|
|
72
50
|
rmSync(file);
|
|
73
51
|
}
|
|
74
|
-
/**
|
|
75
|
-
* Remove `host.json` ONLY if the file on disk still matches the snapshot (same pid AND startedAt).
|
|
76
|
-
* A replaced file (a concurrently-started daemon) or an already-removed file is a safe no-op — so
|
|
77
|
-
* cleanup can never delete a runtime that belongs to another daemon (compare-and-delete).
|
|
78
|
-
*/
|
|
79
52
|
export function removeHostRuntimeIfMatches(snapshot) {
|
|
80
53
|
const current = readHostRuntime();
|
|
81
54
|
if (current?.pid === snapshot.pid && current?.startedAt === snapshot.startedAt) {
|
|
82
55
|
removeHostRuntime();
|
|
83
56
|
}
|
|
84
57
|
}
|
|
85
|
-
/** True when `host.json` records a daemon whose pid is still alive (liveness, not health). */
|
|
86
58
|
export function isHostRunning() {
|
|
87
59
|
const runtime = readHostRuntime();
|
|
88
60
|
return runtime !== null && isAlive(runtime.pid);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"host-runtime.js","sourceRoot":"","sources":["../../src/host/host-runtime.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"host-runtime.js","sourceRoot":"","sources":["../../src/host/host-runtime.ts"],"names":[],"mappings":"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"}
|
|
@@ -10,22 +10,6 @@ var __metadata = (this && this.__metadata) || function (k, v) {
|
|
|
10
10
|
var __param = (this && this.__param) || function (paramIndex, decorator) {
|
|
11
11
|
return function (target, key) { decorator(target, key, paramIndex); }
|
|
12
12
|
};
|
|
13
|
-
/**
|
|
14
|
-
* HostLifecycle — OnApplicationBootstrap / OnApplicationShutdown.
|
|
15
|
-
*
|
|
16
|
-
* Boot order (§2 of the ТЗ):
|
|
17
|
-
* 1. ensureRevisium() — auto-start if no live daemon; three-state (F7).
|
|
18
|
-
* 2. Pid-proven pg port from the returned runtime (never resolvePorts() — F3).
|
|
19
|
-
* 3. Optional postmaster.pid cross-check (F8).
|
|
20
|
-
* 4. ensurePostgres(provenPgPort) — CREATE DATABASE dbos if absent.
|
|
21
|
-
* 5. dbosService.setConfig(url) + dbosService.launch().
|
|
22
|
-
*
|
|
23
|
-
* Shutdown:
|
|
24
|
-
* - DBOS.shutdown() only.
|
|
25
|
-
* - INTENTIONALLY does NOT stop the Revisium daemon (Round 3, human decision):
|
|
26
|
-
* the daemon is detached+unref'd and is a shared, long-lived process; subsequent
|
|
27
|
-
* `revo dev:status <id>` invocations must find the same daemon + embedded Postgres.
|
|
28
|
-
*/
|
|
29
13
|
import { Inject, Injectable, Optional } from '@nestjs/common';
|
|
30
14
|
import { DbosService } from '../engine/dbos.service.js';
|
|
31
15
|
import { ensureRevisium, readPostmasterPgPort } from './ensure-revisium.js';
|
|
@@ -35,8 +19,6 @@ function isMcpStdioHost() {
|
|
|
35
19
|
return process.env.REVO_MCP_STDIO === '1';
|
|
36
20
|
}
|
|
37
21
|
function keepControlPlaneBootstrapDependency(_api) {
|
|
38
|
-
// The constructor dependency forces the control-plane provider graph to instantiate
|
|
39
|
-
// before DBOS.launch(); pipeline registration must precede launch.
|
|
40
22
|
}
|
|
41
23
|
let HostLifecycle = class HostLifecycle {
|
|
42
24
|
dbosService;
|
|
@@ -45,11 +27,8 @@ let HostLifecycle = class HostLifecycle {
|
|
|
45
27
|
keepControlPlaneBootstrapDependency(taskControlPlaneApi);
|
|
46
28
|
}
|
|
47
29
|
async onApplicationBootstrap() {
|
|
48
|
-
// Step 1: Ensure Revisium daemon is running (auto-start if absent).
|
|
49
30
|
const { runtime } = await ensureRevisium();
|
|
50
|
-
// Step 2: Pid-proven pg port (F3 — never resolvePorts()).
|
|
51
31
|
let provenPgPort = runtime.pgPort;
|
|
52
|
-
// Optional cross-check against postmaster.pid (F8).
|
|
53
32
|
if (runtime.dataDir) {
|
|
54
33
|
const pmPort = readPostmasterPgPort(runtime.dataDir);
|
|
55
34
|
if (pmPort !== null && pmPort !== runtime.pgPort) {
|
|
@@ -60,19 +39,13 @@ let HostLifecycle = class HostLifecycle {
|
|
|
60
39
|
provenPgPort = pmPort;
|
|
61
40
|
}
|
|
62
41
|
}
|
|
63
|
-
// If runtime.dataDir is absent (older standalone), skip cross-check and rely on rt.pgPort.
|
|
64
|
-
// Step 3: Ensure the `dbos` database exists.
|
|
65
42
|
await ensurePostgres(provenPgPort);
|
|
66
|
-
// Step 4+5: Configure + launch DBOS.
|
|
67
43
|
this.dbosService.setConfig(dbosSystemDatabaseUrl(provenPgPort), {
|
|
68
44
|
logLevel: isMcpStdioHost() ? 'warn' : undefined,
|
|
69
45
|
});
|
|
70
46
|
await this.dbosService.launch();
|
|
71
47
|
}
|
|
72
48
|
async onApplicationShutdown() {
|
|
73
|
-
// Intentional: host does not own the Revisium daemon lifecycle.
|
|
74
|
-
// Only DBOS.shutdown() runs on exit.
|
|
75
|
-
// The daemon was spawned detached+unref'd and must outlive the host process.
|
|
76
49
|
await this.dbosService.shutdown();
|
|
77
50
|
}
|
|
78
51
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"host.lifecycle.js","sourceRoot":"","sources":["../../src/host/host.lifecycle.ts"],"names":[],"mappings":";;;;;;;;;;;;
|
|
1
|
+
{"version":3,"file":"host.lifecycle.js","sourceRoot":"","sources":["../../src/host/host.lifecycle.ts"],"names":[],"mappings":";;;;;;;;;;;;AAcA,OAAO,EAAE,MAAM,EAAE,UAAU,EAAiD,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAC7G,OAAO,EAAE,WAAW,EAAE,MAAM,2BAA2B,CAAC;AACxD,OAAO,EAAE,cAAc,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAC;AAC5E,OAAO,EAAE,qBAAqB,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAC;AACrF,OAAO,EAAE,0BAA0B,EAAE,MAAM,yDAAyD,CAAC;AAErG,SAAS,cAAc;IACrB,OAAO,OAAO,CAAC,GAAG,CAAC,cAAc,KAAK,GAAG,CAAC;AAC5C,CAAC;AAED,SAAS,mCAAmC,CAAC,IAA4C;AACzF,CAAC;AAGM,IAAM,aAAa,GAAnB,MAAM,aAAa;IAEgB;IADxC,YACwC,WAAwB,EAG9D,mBAAgD;QAHV,gBAAW,GAAX,WAAW,CAAa;QAK9D,mCAAmC,CAAC,mBAAmB,CAAC,CAAC;IAC3D,CAAC;IAED,KAAK,CAAC,sBAAsB;QAC1B,MAAM,EAAE,OAAO,EAAE,GAAG,MAAM,cAAc,EAAE,CAAC;QAE3C,IAAI,YAAY,GAAG,OAAO,CAAC,MAAM,CAAC;QAElC,IAAI,OAAO,CAAC,OAAO,EAAE,CAAC;YACpB,MAAM,MAAM,GAAG,oBAAoB,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;YACrD,IAAI,MAAM,KAAK,IAAI,IAAI,MAAM,KAAK,OAAO,CAAC,MAAM,EAAE,CAAC;gBACjD,MAAM,IAAI,KAAK,CACb,sCAAsC,OAAO,CAAC,MAAM,oCAAoC,MAAM,IAAI;oBAChG,6DAA6D,CAChE,CAAC;YACJ,CAAC;YACD,IAAI,MAAM,KAAK,IAAI,EAAE,CAAC;gBACpB,YAAY,GAAG,MAAM,CAAC;YACxB,CAAC;QACH,CAAC;QAED,MAAM,cAAc,CAAC,YAAY,CAAC,CAAC;QAEnC,IAAI,CAAC,WAAW,CAAC,SAAS,CAAC,qBAAqB,CAAC,YAAY,CAAC,EAAE;YAC9D,QAAQ,EAAE,cAAc,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,SAAS;SAChD,CAAC,CAAC;QACH,MAAM,IAAI,CAAC,WAAW,CAAC,MAAM,EAAE,CAAC;IAClC,CAAC;IAED,KAAK,CAAC,qBAAqB;QACzB,MAAM,IAAI,CAAC,WAAW,CAAC,QAAQ,EAAE,CAAC;IACpC,CAAC;CACF,CAAA;AAvCY,aAAa;IADzB,UAAU,EAAE;IAGR,WAAA,MAAM,CAAC,WAAW,CAAC,CAAA;IACnB,WAAA,QAAQ,EAAE,CAAA;IACV,WAAA,MAAM,CAAC,0BAA0B,CAAC,CAAA;qCAFgB,WAAW;QAGxC,0BAA0B;GALvC,aAAa,CAuCzB"}
|
|
@@ -1,47 +1,8 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Daemon singleton gate (slice 139, hardened slice 140). Exactly ONE host daemon per profile may own —
|
|
3
|
-
* and therefore poll — the `dev-tasks` WorkflowQueue. Ownership is a connection-scoped Postgres
|
|
4
|
-
* ADVISORY LOCK:
|
|
5
|
-
*
|
|
6
|
-
* - Advisory locks are DATABASE-scoped, NOT cluster-global (PG: "Advisory locks are local to each
|
|
7
|
-
* database"). The gate works only because every contending daemon connects to the SAME fixed
|
|
8
|
-
* maintenance `postgres` database, so they all contend in one shared per-database lock namespace.
|
|
9
|
-
* We use `postgres` because it exists right after `ensureRevisium` — before the per-profile `dbos`
|
|
10
|
-
* database is created — letting the daemon decide "am I the owner?" BEFORE DBOS.launch() and before
|
|
11
|
-
* the queue worker starts polling. INVARIANT: all contenders MUST take this lock on the identical
|
|
12
|
-
* database. NEVER probe this lock key from another database (e.g. `dbos`) expecting a conflict — it
|
|
13
|
-
* would silently NOT conflict and could mint a second owner.
|
|
14
|
-
* - The key is a FULL 64-bit value derived from the lock name via md5 (`('x'||substr(md5(name),1,16))
|
|
15
|
-
* ::bit(64)::bigint`), not `hashtext(...)::bigint` — `hashtext` is only 32 bits, so distinct
|
|
16
|
-
* profile names could hash-collide and wrongly block a different profile's daemon. md5's 64 bits
|
|
17
|
-
* make a cross-profile collision negligible.
|
|
18
|
-
* - The lock is held on a DEDICATED, long-lived connection for the daemon's whole life. It
|
|
19
|
-
* auto-releases when that connection ends — on graceful shutdown OR on a hard crash — so a dead
|
|
20
|
-
* owner never blocks a successor. (Verified by experiment: a second connection's
|
|
21
|
-
* `pg_try_advisory_lock` returns false while held, and true again once the holder disconnects.)
|
|
22
|
-
*
|
|
23
|
-
* A daemon that does NOT win the lock lost a concurrent cold-start race (e.g. several `revo mcp`
|
|
24
|
-
* bridges spawning at once) and exits; ensureHost then attaches to the winner via host.json. This
|
|
25
|
-
* closes the "concurrent cold-start spawns multiple daemons" hole that let stale daemons accumulate.
|
|
26
|
-
* It does NOT, however, coordinate LEGACY hosts that predate the lock — those are detected by the
|
|
27
|
-
* `pg_stat_activity` census (queue-poller-census.ts) and evicted by `revo doctor`/`stop` (slice 140).
|
|
28
|
-
*/
|
|
29
1
|
import pg from 'pg';
|
|
30
|
-
/**
|
|
31
|
-
* SQL expression that turns the text lock name (bound as `$1`) into a full 64-bit advisory-lock key.
|
|
32
|
-
* `md5` yields 128 bits of hex; the first 16 hex chars are reinterpreted as a signed bigint. Shared by
|
|
33
|
-
* lock + unlock so they always target the identical key.
|
|
34
|
-
*/
|
|
35
2
|
const LOCK_KEY_EXPR = `('x' || substr(md5($1), 1, 16))::bit(64)::bigint`;
|
|
36
|
-
/** Stable advisory-lock identity for a profile's dev-tasks queue ownership. */
|
|
37
3
|
export function ownershipLockName(profile) {
|
|
38
4
|
return `revo:dev-tasks:${profile}`;
|
|
39
5
|
}
|
|
40
|
-
/**
|
|
41
|
-
* Try to become the sole dev-tasks owner for `profile`. Returns `{owned:false}` (and closes its probe
|
|
42
|
-
* connection) when another live daemon already holds it. When `{owned:true}`, the returned connection
|
|
43
|
-
* stays open holding the lock until `release()` (or process death) frees it.
|
|
44
|
-
*/
|
|
45
6
|
export async function acquireQueueOwnership(profile, pgPort, deps = {}) {
|
|
46
7
|
const url = `postgresql://revisium:password@localhost:${pgPort}/postgres`;
|
|
47
8
|
const name = ownershipLockName(profile);
|
|
@@ -54,14 +15,11 @@ export async function acquireQueueOwnership(profile, pgPort, deps = {}) {
|
|
|
54
15
|
await client.end().catch(() => undefined);
|
|
55
16
|
return { owned: false, release: async () => undefined };
|
|
56
17
|
}
|
|
57
|
-
// Keep `client` reachable (via this closure) so its socket — and the lock — is held for the daemon's
|
|
58
|
-
// life. The OS frees the connection (and thus the lock) on crash; release() does it gracefully.
|
|
59
18
|
const release = async () => {
|
|
60
19
|
try {
|
|
61
20
|
await client.query(`SELECT pg_advisory_unlock(${LOCK_KEY_EXPR})`, [name]);
|
|
62
21
|
}
|
|
63
22
|
catch {
|
|
64
|
-
// best-effort; ending the connection releases the lock regardless
|
|
65
23
|
}
|
|
66
24
|
await client.end().catch(() => undefined);
|
|
67
25
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"queue-ownership.js","sourceRoot":"","sources":["../../src/host/queue-ownership.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"queue-ownership.js","sourceRoot":"","sources":["../../src/host/queue-ownership.ts"],"names":[],"mappings":"AA0BA,OAAO,EAAE,MAAM,IAAI,CAAC;AAKpB,MAAM,aAAa,GAAG,kDAAkD,CAAC;AAsBzE,MAAM,UAAU,iBAAiB,CAAC,OAAe;IAC/C,OAAO,kBAAkB,OAAO,EAAE,CAAC;AACrC,CAAC;AAKD,MAAM,CAAC,KAAK,UAAU,qBAAqB,CACzC,OAAe,EACf,MAAc,EACd,OAA6B,EAAE;IAE/B,MAAM,GAAG,GAAG,4CAA4C,MAAM,WAAW,CAAC;IAC1E,MAAM,IAAI,GAAG,iBAAiB,CAAC,OAAO,CAAC,CAAC;IACxC,MAAM,YAAY,GAAG,IAAI,CAAC,YAAY,IAAI,CAAC,CAAC,CAAS,EAAE,EAAE,CAAC,IAAI,EAAE,CAAC,MAAM,CAAC,CAAC,CAA+B,CAAC,CAAC;IAC1G,MAAM,MAAM,GAAG,YAAY,CAAC,GAAG,CAAC,CAAC;IACjC,MAAM,MAAM,CAAC,OAAO,EAAE,CAAC;IAEvB,MAAM,GAAG,GAAG,MAAM,MAAM,CAAC,KAAK,CAAC,+BAA+B,aAAa,YAAY,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC;IACjG,MAAM,KAAK,GAAG,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,KAAK,KAAK,IAAI,CAAC;IAE1C,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,MAAM,MAAM,CAAC,GAAG,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC;QAC1C,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,KAAK,IAAI,EAAE,CAAC,SAAS,EAAE,CAAC;IAC1D,CAAC;IAED,MAAM,OAAO,GAAG,KAAK,IAAmB,EAAE;QACxC,IAAI,CAAC;YACH,MAAM,MAAM,CAAC,KAAK,CAAC,6BAA6B,aAAa,GAAG,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC;QAC5E,CAAC;QAAC,MAAM,CAAC;QACT,CAAC;QACD,MAAM,MAAM,CAAC,GAAG,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC;IAC5C,CAAC,CAAC;IACF,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC;AAClC,CAAC"}
|
|
@@ -1,39 +1,12 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Queue-poller census (slice 140, Part A Layer 3 — detection half, pure).
|
|
3
|
-
*
|
|
4
|
-
* The advisory lock (queue-ownership.ts) only coordinates daemons that CALL it. A legacy/old-version
|
|
5
|
-
* host that predates the lock — or a `revo mcp` from before the thin-bridge era that is itself a full
|
|
6
|
-
* DBOS host — still connects to the `dbos` database and polls the `dev-tasks` queue, ignoring the lock.
|
|
7
|
-
* It has no inbound listening port, so the port-based `revo stop`/`doctor` cannot see it. The ONE
|
|
8
|
-
* control plane shared with code we do not control is the database itself: every DBOS system-DB
|
|
9
|
-
* connection is stamped `application_name = dbos_transact_<executorID>_<appVersion>` (visible in
|
|
10
|
-
* `pg_stat_activity`). A connection whose executor id is NOT this profile's pinned owner is a rogue.
|
|
11
|
-
*
|
|
12
|
-
* This module is the pure classifier (unit-tested); the live `pg_stat_activity` query lives in the IO
|
|
13
|
-
* layer (lifecycle.ts). Classification keys on the executor-id segment ONLY: `application_name` is
|
|
14
|
-
* capped at 63 bytes by Postgres, so a long suffix can truncate the appVersion — we therefore never
|
|
15
|
-
* trust the parsed version for identity (the owner's true version comes from host.json elsewhere).
|
|
16
|
-
*/
|
|
17
1
|
const STAMP_PREFIX = 'dbos_transact_';
|
|
18
|
-
/**
|
|
19
|
-
* Parse the executor id out of `dbos_transact_<executorID>_<appVersion>` — the segment between the
|
|
20
|
-
* fixed prefix and the LAST underscore (the appVersion separator). Returns null when the string is not
|
|
21
|
-
* a DBOS stamp. Robust to a truncated/empty appVersion segment (we only need the executor id); pinned
|
|
22
|
-
* revo executor ids (`revo-<profile>`) and `local` contain no underscore, so the last-underscore split
|
|
23
|
-
* is unambiguous for the identities we classify.
|
|
24
|
-
*/
|
|
25
2
|
export function parseExecutorId(applicationName) {
|
|
26
3
|
if (!applicationName.startsWith(STAMP_PREFIX))
|
|
27
4
|
return null;
|
|
28
|
-
const rest = applicationName.slice(STAMP_PREFIX.length);
|
|
5
|
+
const rest = applicationName.slice(STAMP_PREFIX.length);
|
|
29
6
|
const sep = rest.lastIndexOf('_');
|
|
30
7
|
const executorId = sep < 0 ? rest : rest.slice(0, sep);
|
|
31
8
|
return executorId.length > 0 ? executorId : null;
|
|
32
9
|
}
|
|
33
|
-
/**
|
|
34
|
-
* Foreign DBOS pollers among `backends` — any connection whose parsed executor id differs from the
|
|
35
|
-
* profile's pinned owner. Report-only data; the caller decides how to surface or evict it.
|
|
36
|
-
*/
|
|
37
10
|
export function classifyQueuePollerRogues(backends, ownerExecutorId) {
|
|
38
11
|
const rogues = [];
|
|
39
12
|
for (const b of backends) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"queue-poller-census.js","sourceRoot":"","sources":["../../src/host/queue-poller-census.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"queue-poller-census.js","sourceRoot":"","sources":["../../src/host/queue-poller-census.ts"],"names":[],"mappings":"AAeA,MAAM,YAAY,GAAG,gBAAgB,CAAC;AAwBtC,MAAM,UAAU,eAAe,CAAC,eAAuB;IACrD,IAAI,CAAC,eAAe,CAAC,UAAU,CAAC,YAAY,CAAC;QAAE,OAAO,IAAI,CAAC;IAC3D,MAAM,IAAI,GAAG,eAAe,CAAC,KAAK,CAAC,YAAY,CAAC,MAAM,CAAC,CAAC;IACxD,MAAM,GAAG,GAAG,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC;IAClC,MAAM,UAAU,GAAG,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;IACvD,OAAO,UAAU,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,IAAI,CAAC;AACnD,CAAC;AAID,MAAM,UAAU,yBAAyB,CACvC,QAAsC,EACtC,eAAuB;IAEvB,MAAM,MAAM,GAAkB,EAAE,CAAC;IACjC,KAAK,MAAM,CAAC,IAAI,QAAQ,EAAE,CAAC;QACzB,MAAM,UAAU,GAAG,eAAe,CAAC,CAAC,CAAC,eAAe,CAAC,CAAC;QACtD,IAAI,UAAU,KAAK,IAAI,IAAI,UAAU,KAAK,eAAe;YAAE,SAAS;QACpE,MAAM,CAAC,IAAI,CAAC,EAAE,GAAG,EAAE,CAAC,CAAC,GAAG,EAAE,UAAU,EAAE,eAAe,EAAE,CAAC,CAAC,eAAe,EAAE,YAAY,EAAE,CAAC,CAAC,YAAY,EAAE,CAAC,CAAC;IAC5G,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC"}
|
|
@@ -8,9 +8,6 @@ import { Module } from '@nestjs/common';
|
|
|
8
8
|
import { GraphqlApiModule } from '../api/graphql-api/graphql-api.module.js';
|
|
9
9
|
import { HostLifecycle } from '../host/host.lifecycle.js';
|
|
10
10
|
import { TaskControlPlaneModule } from '../task-control-plane/task-control-plane.module.js';
|
|
11
|
-
// The daemon serves BOTH front doors (ADR 0006): GraphQL (frontend) + MCP (agents), over one host
|
|
12
|
-
// process that is the single DBOS owner. The MCP server is built directly from TaskControlPlaneApi-
|
|
13
|
-
// Service (see daemon.ts) — no separate McpModule in the graph.
|
|
14
11
|
let GraphqlHostModule = class GraphqlHostModule {
|
|
15
12
|
};
|
|
16
13
|
GraphqlHostModule = __decorate([
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"graphql-host.module.js","sourceRoot":"","sources":["../../src/http/graphql-host.module.ts"],"names":[],"mappings":";;;;;;AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,gBAAgB,CAAC;AACxC,OAAO,EAAE,gBAAgB,EAAE,MAAM,0CAA0C,CAAC;AAC5E,OAAO,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAC;AAC1D,OAAO,EAAE,sBAAsB,EAAE,MAAM,oDAAoD,CAAC;
|
|
1
|
+
{"version":3,"file":"graphql-host.module.js","sourceRoot":"","sources":["../../src/http/graphql-host.module.ts"],"names":[],"mappings":";;;;;;AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,gBAAgB,CAAC;AACxC,OAAO,EAAE,gBAAgB,EAAE,MAAM,0CAA0C,CAAC;AAC5E,OAAO,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAC;AAC1D,OAAO,EAAE,sBAAsB,EAAE,MAAM,oDAAoD,CAAC;AAMrF,IAAM,iBAAiB,GAAvB,MAAM,iBAAiB;CAAG,CAAA;AAApB,iBAAiB;IAJ7B,MAAM,CAAC;QACN,OAAO,EAAE,CAAC,sBAAsB,EAAE,gBAAgB,CAAC;QACnD,SAAS,EAAE,CAAC,aAAa,CAAC;KAC3B,CAAC;GACW,iBAAiB,CAAG"}
|
|
@@ -8,6 +8,9 @@ export const MCP_TOOL_NAMES = [
|
|
|
8
8
|
'create_run',
|
|
9
9
|
'start_run',
|
|
10
10
|
'resume_run',
|
|
11
|
+
'get_run_attention',
|
|
12
|
+
'get_run_status',
|
|
13
|
+
'watch_run_changes',
|
|
11
14
|
'cancel_run',
|
|
12
15
|
'list_runs',
|
|
13
16
|
'get_run',
|
|
@@ -19,9 +22,6 @@ export const MCP_TOOL_NAMES = [
|
|
|
19
22
|
'tail_agent_log',
|
|
20
23
|
'read_agent_output_events',
|
|
21
24
|
'get_run_digest',
|
|
22
|
-
'wait_for_run',
|
|
23
|
-
'wait_for_any_gate',
|
|
24
|
-
'watch_runs',
|
|
25
25
|
'list_inbox',
|
|
26
26
|
'get_inbox_item',
|
|
27
27
|
'get_pending_decisions',
|
|
@@ -44,12 +44,30 @@ export const MCP_INSTRUCTIONS = `Revo is a local-first software-development task
|
|
|
44
44
|
|
|
45
45
|
Use these tools to manage tasks end-to-end from your coding agent:
|
|
46
46
|
- create and start runs;
|
|
47
|
-
-
|
|
47
|
+
- resume terminal recoverable preflight blocks with resume_run after the target repo is repaired;
|
|
48
|
+
- observe run state and attention requirements through the intent-named observation tools;
|
|
49
|
+
- inspect bounded digests, events, attempts, agent activity, and logs only when needed;
|
|
48
50
|
- resolve human inbox gates and questions;
|
|
49
51
|
- inspect installed playbooks, roles, and pipelines;
|
|
50
52
|
- inspect PR readiness and actionable review feedback before resuming work;
|
|
51
53
|
- validate repository context before starting live work.
|
|
52
54
|
|
|
55
|
+
task_monitoring_loop — default agent algorithm for monitoring a run:
|
|
56
|
+
1. Call get_run_attention(runId): answers "what currently requires attention?" with requiresAttention, nextAction, and suggestedTools.
|
|
57
|
+
2. nextAction "wait" → sleep/backoff, re-call get_run_attention.
|
|
58
|
+
3. nextAction "ask_human" → inspect and resolve the inbox item with approve_gate/reject_gate/answer_question.
|
|
59
|
+
4. nextAction "inspect_digest" → call get_run_digest once; nextAction "inspect_log" → call get_agent_log once.
|
|
60
|
+
5. nextAction "done" → stop. nextAction "start_run" → call start_run.
|
|
61
|
+
Use get_run_status for neutral dashboard or status checks that must not prescribe actions.
|
|
62
|
+
watch_run_changes is only for UI/change-stream/long-poll consumers that explicitly need cursor-based transition delivery — not for normal task monitoring.
|
|
63
|
+
|
|
64
|
+
Diagnostic tools (call only when required):
|
|
65
|
+
- get_run_digest when nextAction is "inspect_digest".
|
|
66
|
+
- get_agent_log with offsetBytes/limitBytes or tailBytes when nextAction is "inspect_log" or explicit debugging requires logs.
|
|
67
|
+
- Avoid get_run(includeEvents:true) and raw agent logs in polling loops.
|
|
68
|
+
|
|
53
69
|
This MCP server is local stdio only and does not expose generic Revisium table CRUD.
|
|
54
|
-
Use product-level tools instead of writing raw rows
|
|
70
|
+
Use product-level tools instead of writing raw rows.
|
|
71
|
+
|
|
72
|
+
create_run requires an explicit pipelineId; omit it and no run is created — you get candidatePipelines plus a best-effort wouldAutoRoute to choose from (no silent auto-routing). Use list_pipelines/simulate_route to pick.`;
|
|
55
73
|
//# sourceMappingURL=mcp-capabilities.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mcp-capabilities.js","sourceRoot":"","sources":["../../src/mcp/mcp-capabilities.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,cAAc,GAAG;IAC5B,YAAY;IACZ,QAAQ;IACR,kBAAkB;IAClB,qBAAqB;IACrB,wBAAwB;IACxB,aAAa;IACb,YAAY;IACZ,WAAW;IACX,YAAY;IACZ,YAAY;IACZ,WAAW;IACX,SAAS;IACT,gBAAgB;IAChB,aAAa;IACb,oBAAoB;IACpB,oBAAoB;IACpB,eAAe;IACf,gBAAgB;IAChB,0BAA0B;IAC1B,gBAAgB;IAChB,
|
|
1
|
+
{"version":3,"file":"mcp-capabilities.js","sourceRoot":"","sources":["../../src/mcp/mcp-capabilities.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,cAAc,GAAG;IAC5B,YAAY;IACZ,QAAQ;IACR,kBAAkB;IAClB,qBAAqB;IACrB,wBAAwB;IACxB,aAAa;IACb,YAAY;IACZ,WAAW;IACX,YAAY;IACZ,mBAAmB;IACnB,gBAAgB;IAChB,mBAAmB;IACnB,YAAY;IACZ,WAAW;IACX,SAAS;IACT,gBAAgB;IAChB,aAAa;IACb,oBAAoB;IACpB,oBAAoB;IACpB,eAAe;IACf,gBAAgB;IAChB,0BAA0B;IAC1B,gBAAgB;IAChB,YAAY;IACZ,gBAAgB;IAChB,uBAAuB;IACvB,cAAc;IACd,aAAa;IACb,iBAAiB;IACjB,oBAAoB;IACpB,qBAAqB;IACrB,kBAAkB;IAClB,gBAAgB;IAChB,YAAY;IACZ,UAAU;IACV,gBAAgB;IAChB,cAAc;IACd,gBAAgB;IAChB,kBAAkB;IAClB,kBAAkB;CACV,CAAC;AAIX,MAAM,CAAC,MAAM,gBAAgB,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;6NA6B6L,CAAC"}
|