harnery 0.42.0 → 0.44.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 +3 -3
- package/dist/commander.d.ts +16 -0
- package/dist/commander.d.ts.map +1 -1
- package/dist/commander.js +18 -0
- package/dist/commands/agents.d.ts.map +1 -1
- package/dist/commands/agents.js +95 -52
- package/dist/commands/browse.d.ts.map +1 -1
- package/dist/commands/browse.js +38 -11
- package/dist/commands/diagnostics.d.ts.map +1 -1
- package/dist/commands/diagnostics.js +10 -0
- package/dist/commands/doctor.d.ts +12 -0
- package/dist/commands/doctor.d.ts.map +1 -1
- package/dist/commands/doctor.js +22 -7
- package/dist/commands/governor.js +22 -10
- package/dist/commands/inbox.d.ts +1 -1
- package/dist/commands/inbox.d.ts.map +1 -1
- package/dist/commands/inbox.js +55 -2
- package/dist/commands/rm.d.ts.map +1 -1
- package/dist/commands/rm.js +28 -4
- package/dist/commands/servers.d.ts.map +1 -1
- package/dist/commands/servers.js +16 -1
- package/dist/commands/supervisor.d.ts.map +1 -1
- package/dist/commands/supervisor.js +7 -2
- package/dist/commands/toc.d.ts +14 -0
- package/dist/commands/toc.d.ts.map +1 -1
- package/dist/commands/toc.js +7 -4
- package/dist/commands/tunnel.d.ts +65 -0
- package/dist/commands/tunnel.d.ts.map +1 -1
- package/dist/commands/tunnel.js +290 -101
- package/dist/commands/web-fetch.d.ts +3 -2
- package/dist/commands/web-fetch.d.ts.map +1 -1
- package/dist/commands/web-fetch.js +25 -19
- package/dist/commands/web.d.ts +7 -1
- package/dist/commands/web.d.ts.map +1 -1
- package/dist/commands/web.js +9 -3
- package/dist/commands/workflow.js +24 -30
- package/dist/core/agents/live-authority-v3.d.ts +1 -0
- package/dist/core/agents/live-authority-v3.d.ts.map +1 -1
- package/dist/core/agents/live-authority-v3.js +11 -0
- package/dist/core/agents/live-lifecycle-v3.d.ts +4 -2
- package/dist/core/agents/live-lifecycle-v3.d.ts.map +1 -1
- package/dist/core/agents/live-lifecycle-v3.js +28 -7
- package/dist/core/agents/mailbox.d.ts +9 -8
- package/dist/core/agents/mailbox.d.ts.map +1 -1
- package/dist/core/agents/mailbox.js +24 -17
- package/dist/core/agents/render/prompt-context.d.ts +2 -0
- package/dist/core/agents/render/prompt-context.d.ts.map +1 -1
- package/dist/core/agents/render/prompt-context.js +19 -17
- package/dist/core/agents/render/session-context.d.ts +2 -8
- package/dist/core/agents/render/session-context.d.ts.map +1 -1
- package/dist/core/agents/render/session-context.js +64 -43
- package/dist/core/agents/rules/stop-hook.js +3 -3
- package/dist/core/agents/state/heartbeat-writer.d.ts.map +1 -1
- package/dist/core/agents/state/heartbeat-writer.js +25 -14
- package/dist/core/agents/state/live-coordination-writer.d.ts.map +1 -1
- package/dist/core/agents/state/live-coordination-writer.js +5 -0
- package/dist/core/agents/state/names.d.ts +43 -3
- package/dist/core/agents/state/names.d.ts.map +1 -1
- package/dist/core/agents/state/names.js +87 -3
- package/dist/core/agents/state/pidmap.d.ts.map +1 -1
- package/dist/core/agents/state/pidmap.js +5 -0
- package/dist/core/agents/state/proc-start.d.ts +19 -0
- package/dist/core/agents/state/proc-start.d.ts.map +1 -1
- package/dist/core/agents/state/proc-start.js +41 -0
- package/dist/core/agents/state/stale-sweep.d.ts.map +1 -1
- package/dist/core/agents/state/stale-sweep.js +180 -70
- package/dist/core/agents/state/unchanged-file.d.ts +44 -0
- package/dist/core/agents/state/unchanged-file.d.ts.map +1 -0
- package/dist/core/agents/state/unchanged-file.js +390 -0
- package/dist/core/context/index.d.ts +22 -10
- package/dist/core/context/index.d.ts.map +1 -1
- package/dist/core/context/index.js +381 -119
- package/dist/core/context/observation.d.ts +26 -0
- package/dist/core/context/observation.d.ts.map +1 -0
- package/dist/core/context/observation.js +11 -0
- package/dist/core/diagnostics/bundle.d.ts.map +1 -1
- package/dist/core/diagnostics/bundle.js +25 -14
- package/dist/core/diagnostics/path-identity.d.ts +3 -0
- package/dist/core/diagnostics/path-identity.d.ts.map +1 -0
- package/dist/core/diagnostics/path-identity.js +12 -0
- package/dist/core/diagnostics/sanitize.d.ts.map +1 -1
- package/dist/core/diagnostics/sanitize.js +4 -3
- package/dist/core/events/v3/authority-outbox.d.ts.map +1 -1
- package/dist/core/events/v3/authority-outbox.js +29 -0
- package/dist/core/events/v3/generation-custody.d.ts +5 -0
- package/dist/core/events/v3/generation-custody.d.ts.map +1 -0
- package/dist/core/events/v3/generation-custody.js +50 -0
- package/dist/core/events/v3/producers/coordination-recorder.d.ts +1 -0
- package/dist/core/events/v3/producers/coordination-recorder.d.ts.map +1 -1
- package/dist/core/events/v3/producers/coordination-recorder.js +7 -0
- package/dist/core/events/v3/producers/recorder.d.ts.map +1 -1
- package/dist/core/events/v3/producers/recorder.js +77 -64
- package/dist/core/events/v3/support-storage/pack-reader.d.ts.map +1 -1
- package/dist/core/events/v3/support-storage/pack-reader.js +42 -16
- package/dist/core/governor/planning.d.ts.map +1 -1
- package/dist/core/governor/planning.js +14 -1
- package/dist/core/governor/service-read.d.ts +10 -2
- package/dist/core/governor/service-read.d.ts.map +1 -1
- package/dist/core/governor/service-read.js +105 -22
- package/dist/core/governor/service.d.ts +20 -2
- package/dist/core/governor/service.d.ts.map +1 -1
- package/dist/core/governor/service.js +72 -47
- package/dist/core/hooks/cli.js +120 -23
- package/dist/core/hooks/fork-lineage.d.ts +40 -0
- package/dist/core/hooks/fork-lineage.d.ts.map +1 -0
- package/dist/core/hooks/fork-lineage.js +62 -0
- package/dist/core/hooks/resolve/transcript.d.ts +16 -0
- package/dist/core/hooks/resolve/transcript.d.ts.map +1 -1
- package/dist/core/hooks/resolve/transcript.js +84 -0
- package/dist/core/hooks/shell-rm.d.ts +2 -0
- package/dist/core/hooks/shell-rm.d.ts.map +1 -0
- package/dist/core/hooks/shell-rm.js +105 -0
- package/dist/core/hooks/shell-tokens.d.ts +12 -0
- package/dist/core/hooks/shell-tokens.d.ts.map +1 -0
- package/dist/core/hooks/shell-tokens.js +97 -0
- package/dist/core/hooks/shell-waiter.d.ts.map +1 -1
- package/dist/core/hooks/shell-waiter.js +3 -93
- package/dist/core/inbox/contract.d.ts +2 -1
- package/dist/core/inbox/contract.d.ts.map +1 -1
- package/dist/core/inbox/index.d.ts +2 -0
- package/dist/core/inbox/index.d.ts.map +1 -1
- package/dist/core/inbox/index.js +2 -0
- package/dist/core/inbox/peer-messages.d.ts +37 -0
- package/dist/core/inbox/peer-messages.d.ts.map +1 -0
- package/dist/core/inbox/peer-messages.js +72 -0
- package/dist/core/inbox/service.d.ts +8 -1
- package/dist/core/inbox/service.d.ts.map +1 -1
- package/dist/core/inbox/service.js +67 -21
- package/dist/core/inbox/targets.d.ts +22 -0
- package/dist/core/inbox/targets.d.ts.map +1 -0
- package/dist/core/inbox/targets.js +108 -0
- package/dist/core/inbox/watcher.js +7 -3
- package/dist/core/journal/index.d.ts.map +1 -1
- package/dist/core/journal/index.js +17 -2
- package/dist/core/resources/contract.d.ts +3 -0
- package/dist/core/resources/contract.d.ts.map +1 -1
- package/dist/core/resources/service-status.d.ts +14 -1
- package/dist/core/resources/service-status.d.ts.map +1 -1
- package/dist/core/resources/service-status.js +23 -13
- package/dist/core/resources/service.d.ts +9 -1
- package/dist/core/resources/service.d.ts.map +1 -1
- package/dist/core/resources/service.js +47 -27
- package/dist/core/resources/status.d.ts +2 -5
- package/dist/core/resources/status.d.ts.map +1 -1
- package/dist/core/resources/status.js +36 -7
- package/dist/core/semantic/contract.d.ts +5 -5
- package/dist/core/servers/index.d.ts +24 -13
- package/dist/core/servers/index.d.ts.map +1 -1
- package/dist/core/servers/index.js +132 -92
- package/dist/core/servers/tunnels.d.ts +3 -5
- package/dist/core/servers/tunnels.d.ts.map +1 -1
- package/dist/core/servers/tunnels.js +3 -9
- package/dist/core/storage/process-log.d.ts +12 -0
- package/dist/core/storage/process-log.d.ts.map +1 -1
- package/dist/core/storage/process-log.js +10 -2
- package/dist/core/supervisor/contract.d.ts +12 -0
- package/dist/core/supervisor/contract.d.ts.map +1 -1
- package/dist/core/supervisor/explanations.d.ts.map +1 -1
- package/dist/core/supervisor/explanations.js +2 -1
- package/dist/core/supervisor/findings.d.ts.map +1 -1
- package/dist/core/supervisor/findings.js +54 -9
- package/dist/core/supervisor/service.d.ts +9 -1
- package/dist/core/supervisor/service.d.ts.map +1 -1
- package/dist/core/supervisor/service.js +59 -29
- package/dist/core/supervisor/services.d.ts.map +1 -1
- package/dist/core/supervisor/services.js +22 -1
- package/dist/core/supervisor/status.d.ts +17 -2
- package/dist/core/supervisor/status.d.ts.map +1 -1
- package/dist/core/supervisor/status.js +70 -12
- package/dist/core/supervisor/timeline.d.ts.map +1 -1
- package/dist/core/supervisor/timeline.js +2 -1
- package/dist/core/work/state.js +7 -7
- package/dist/core/workflow/engine.d.ts.map +1 -1
- package/dist/core/workflow/engine.js +105 -32
- package/dist/core/workflow/failure-evidence.d.ts +16 -0
- package/dist/core/workflow/failure-evidence.d.ts.map +1 -0
- package/dist/core/workflow/failure-evidence.js +114 -0
- package/dist/core/workflow/index.d.ts +1 -1
- package/dist/core/workflow/index.d.ts.map +1 -1
- package/dist/core/workflow/index.js +1 -1
- package/dist/core/workflow/live-session-v3.d.ts +6 -0
- package/dist/core/workflow/live-session-v3.d.ts.map +1 -1
- package/dist/core/workflow/live-session-v3.js +126 -5
- package/dist/core/workflow/proof.d.ts +5 -5
- package/dist/core/workflow/proof.d.ts.map +1 -1
- package/dist/core/workflow/proof.js +115 -20
- package/dist/core/workflow/run-state.d.ts +3 -1
- package/dist/core/workflow/run-state.d.ts.map +1 -1
- package/dist/core/workflow/run-state.js +31 -27
- package/dist/core/workflow/spawn-claude.d.ts.map +1 -1
- package/dist/core/workflow/spawn-claude.js +19 -0
- package/dist/core/workflow/spawn-codex.d.ts.map +1 -1
- package/dist/core/workflow/spawn-codex.js +13 -0
- package/dist/core/workflow/spawn-cursor.d.ts.map +1 -1
- package/dist/core/workflow/spawn-cursor.js +19 -0
- package/dist/core/workflow/spawn-opencode.d.ts.map +1 -1
- package/dist/core/workflow/spawn-opencode.js +14 -0
- package/dist/core/workflow/types.d.ts +64 -17
- package/dist/core/workflow/types.d.ts.map +1 -1
- package/dist/core/workflow/workspaces/index.d.ts +0 -1
- package/dist/core/workflow/workspaces/index.d.ts.map +1 -1
- package/dist/core/workflow/workspaces/index.js +0 -1
- package/dist/core/workflow/workspaces/leases.d.ts +3 -1
- package/dist/core/workflow/workspaces/leases.d.ts.map +1 -1
- package/dist/core/workflow/workspaces/leases.js +30 -16
- package/dist/core/workflow/workspaces/local-git.d.ts +6 -2
- package/dist/core/workflow/workspaces/local-git.d.ts.map +1 -1
- package/dist/core/workflow/workspaces/local-git.js +270 -6
- package/dist/core/workflow/workspaces/ownership.d.ts +11 -0
- package/dist/core/workflow/workspaces/ownership.d.ts.map +1 -0
- package/dist/core/workflow/workspaces/ownership.js +194 -0
- package/dist/core/workflow/workspaces/reclaim.d.ts +35 -0
- package/dist/core/workflow/workspaces/reclaim.d.ts.map +1 -1
- package/dist/core/workflow/workspaces/reclaim.js +14 -0
- package/dist/core/workflow/workspaces/recovery.d.ts.map +1 -1
- package/dist/core/workflow/workspaces/recovery.js +21 -13
- package/dist/core/workflow/workspaces/state.d.ts +1 -0
- package/dist/core/workflow/workspaces/state.d.ts.map +1 -1
- package/dist/core/workflow/workspaces/state.js +21 -1
- package/dist/lib/browser/windows-chrome.d.ts +100 -12
- package/dist/lib/browser/windows-chrome.d.ts.map +1 -1
- package/dist/lib/browser/windows-chrome.js +262 -73
- package/dist/lib/docs-links.d.ts.map +1 -1
- package/dist/lib/docs-links.js +45 -2
- package/dist/lib/guarded-remove.d.ts +7 -0
- package/dist/lib/guarded-remove.d.ts.map +1 -1
- package/dist/lib/guarded-remove.js +106 -12
- package/dist/lib/identities/assume.d.ts.map +1 -1
- package/dist/lib/identities/assume.js +4 -0
- package/dist/lib/tunnel/allowlist.d.ts +44 -0
- package/dist/lib/tunnel/allowlist.d.ts.map +1 -0
- package/dist/lib/tunnel/allowlist.js +204 -0
- package/dist/lib/tunnel/current-address.d.ts +41 -0
- package/dist/lib/tunnel/current-address.d.ts.map +1 -0
- package/dist/lib/tunnel/current-address.js +123 -0
- package/dist/lib/tunnel/gate.js +13 -6
- package/dist/lib/tunnel/processes.d.ts +39 -0
- package/dist/lib/tunnel/processes.d.ts.map +1 -0
- package/dist/lib/tunnel/processes.js +121 -0
- package/dist/lib/tunnel/state.d.ts +28 -3
- package/dist/lib/tunnel/state.d.ts.map +1 -1
- package/dist/lib/tunnel/state.js +98 -27
- package/dist/lib/zip/index.d.ts +41 -0
- package/dist/lib/zip/index.d.ts.map +1 -0
- package/dist/lib/zip/index.js +398 -0
- package/package.json +8 -1
- package/src/commander.ts +32 -0
- package/src/commands/agents.ts +151 -74
- package/src/commands/browse.ts +50 -14
- package/src/commands/diagnostics.ts +10 -0
- package/src/commands/doctor.ts +26 -7
- package/src/commands/governor.ts +23 -10
- package/src/commands/inbox.ts +52 -2
- package/src/commands/rm.ts +43 -4
- package/src/commands/servers.ts +16 -4
- package/src/commands/supervisor.ts +7 -2
- package/src/commands/toc.ts +14 -6
- package/src/commands/tunnel.ts +394 -116
- package/src/commands/web-fetch.ts +30 -19
- package/src/commands/web.ts +9 -3
- package/src/commands/workflow.ts +30 -30
- package/src/core/agents/live-authority-v3.ts +17 -0
- package/src/core/agents/live-lifecycle-v3.ts +52 -14
- package/src/core/agents/mailbox.ts +26 -17
- package/src/core/agents/render/prompt-context.ts +35 -20
- package/src/core/agents/render/session-context.ts +92 -48
- package/src/core/agents/rules/stop-hook.ts +3 -3
- package/src/core/agents/state/heartbeat-writer.ts +29 -12
- package/src/core/agents/state/live-coordination-writer.ts +29 -0
- package/src/core/agents/state/names.ts +117 -6
- package/src/core/agents/state/pidmap.ts +13 -0
- package/src/core/agents/state/proc-start.ts +57 -0
- package/src/core/agents/state/stale-sweep.ts +247 -82
- package/src/core/agents/state/unchanged-file.ts +456 -0
- package/src/core/context/index.ts +446 -136
- package/src/core/context/observation.ts +29 -0
- package/src/core/diagnostics/bundle.ts +23 -18
- package/src/core/diagnostics/path-identity.ts +15 -0
- package/src/core/diagnostics/sanitize.ts +4 -3
- package/src/core/events/v3/authority-outbox.ts +56 -0
- package/src/core/events/v3/generation-custody.ts +59 -0
- package/src/core/events/v3/producers/coordination-recorder.ts +15 -0
- package/src/core/events/v3/producers/recorder.ts +94 -68
- package/src/core/events/v3/support-storage/pack-reader.ts +45 -19
- package/src/core/governor/planning.ts +20 -8
- package/src/core/governor/service-read.ts +128 -20
- package/src/core/governor/service.ts +119 -42
- package/src/core/hooks/cli.ts +139 -22
- package/src/core/hooks/fork-lineage.ts +82 -0
- package/src/core/hooks/resolve/transcript.ts +88 -0
- package/src/core/hooks/shell-rm.ts +103 -0
- package/src/core/hooks/shell-tokens.ts +96 -0
- package/src/core/hooks/shell-waiter.ts +4 -91
- package/src/core/inbox/contract.ts +2 -1
- package/src/core/inbox/index.ts +2 -0
- package/src/core/inbox/peer-messages.ts +94 -0
- package/src/core/inbox/service.ts +88 -22
- package/src/core/inbox/targets.ts +148 -0
- package/src/core/inbox/watcher.ts +8 -9
- package/src/core/journal/index.ts +19 -2
- package/src/core/resources/contract.ts +4 -0
- package/src/core/resources/service-status.ts +35 -12
- package/src/core/resources/service.ts +71 -24
- package/src/core/resources/status.ts +57 -8
- package/src/core/servers/index.ts +177 -91
- package/src/core/servers/tunnels.ts +3 -11
- package/src/core/storage/process-log.ts +17 -2
- package/src/core/supervisor/contract.ts +13 -0
- package/src/core/supervisor/explanations.ts +4 -1
- package/src/core/supervisor/findings.ts +68 -10
- package/src/core/supervisor/service.ts +88 -26
- package/src/core/supervisor/services.ts +26 -8
- package/src/core/supervisor/status.ts +89 -12
- package/src/core/supervisor/timeline.ts +2 -1
- package/src/core/work/state.ts +7 -7
- package/src/core/workflow/engine.ts +131 -49
- package/src/core/workflow/failure-evidence.ts +141 -0
- package/src/core/workflow/index.ts +0 -1
- package/src/core/workflow/live-session-v3.ts +146 -5
- package/src/core/workflow/proof.ts +134 -26
- package/src/core/workflow/run-state.ts +31 -29
- package/src/core/workflow/spawn-claude.ts +18 -0
- package/src/core/workflow/spawn-codex.ts +13 -0
- package/src/core/workflow/spawn-cursor.ts +18 -0
- package/src/core/workflow/spawn-opencode.ts +14 -0
- package/src/core/workflow/types.ts +51 -17
- package/src/core/workflow/workspaces/index.ts +0 -1
- package/src/core/workflow/workspaces/leases.ts +48 -17
- package/src/core/workflow/workspaces/local-git.ts +315 -5
- package/src/core/workflow/workspaces/ownership.ts +232 -0
- package/src/core/workflow/workspaces/reclaim.ts +58 -0
- package/src/core/workflow/workspaces/recovery.ts +25 -13
- package/src/core/workflow/workspaces/state.ts +20 -0
- package/src/lib/browser/windows-chrome.ts +346 -86
- package/src/lib/docs-links.ts +49 -2
- package/src/lib/guarded-remove.ts +132 -14
- package/src/lib/identities/assume.ts +4 -0
- package/src/lib/tunnel/allowlist.ts +208 -0
- package/src/lib/tunnel/current-address.ts +158 -0
- package/src/lib/tunnel/gate.ts +16 -8
- package/src/lib/tunnel/processes.ts +149 -0
- package/src/lib/tunnel/state.ts +123 -27
- package/src/lib/zip/index.ts +483 -0
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
|
-
import { constants
|
|
2
|
+
import { constants } from "node:fs";
|
|
3
3
|
import { lstat, mkdir, open, realpath, rm, writeFile } from "node:fs/promises";
|
|
4
4
|
import { dirname, isAbsolute, relative, resolve } from "node:path";
|
|
5
|
+
import { Readable } from "node:stream";
|
|
6
|
+
import { finished } from "node:stream/promises";
|
|
5
7
|
import { StringDecoder } from "node:string_decoder";
|
|
6
8
|
import { createGunzip, createInflateRaw } from "node:zlib";
|
|
7
9
|
import { stateDirMode, stateFileMode } from "../../../storage/modes.ts";
|
|
@@ -89,24 +91,17 @@ async function* streamEventV3SupportPackRecordsForManifest(
|
|
|
89
91
|
throw new Error("event_v3_support_payload_length_mismatch");
|
|
90
92
|
if (payloadStat.size > bounded.max_payload_bytes)
|
|
91
93
|
throw new Error("event_v3_support_payload_too_large");
|
|
92
|
-
if ((await hashHandle(
|
|
94
|
+
if ((await hashHandle(payloadHandle)) !== manifest.payload.digest) {
|
|
93
95
|
throw new Error("event_v3_support_payload_digest_mismatch");
|
|
94
96
|
}
|
|
95
97
|
limits.fault?.("after_payload_digest", payloadPath);
|
|
96
98
|
await assertCanonicalSingleGzipMember(
|
|
97
|
-
payloadPath,
|
|
98
99
|
payloadHandle,
|
|
99
100
|
payloadStat.size,
|
|
100
101
|
bounded.max_decompressed_bytes,
|
|
101
102
|
);
|
|
102
103
|
|
|
103
104
|
const gunzip = createGunzip();
|
|
104
|
-
const source = createReadStream(payloadPath, {
|
|
105
|
-
fd: payloadHandle.fd,
|
|
106
|
-
autoClose: false,
|
|
107
|
-
start: 0,
|
|
108
|
-
});
|
|
109
|
-
source.pipe(gunzip);
|
|
110
105
|
const decoder = new StringDecoder("utf8");
|
|
111
106
|
let pending = "";
|
|
112
107
|
let decompressedBytes = 0;
|
|
@@ -116,7 +111,7 @@ async function* streamEventV3SupportPackRecordsForManifest(
|
|
|
116
111
|
const entries: EventV3LogicalAuthorityEntry[] = [];
|
|
117
112
|
const seen = new Set<string>();
|
|
118
113
|
|
|
119
|
-
for await (const current of gunzip) {
|
|
114
|
+
for await (const current of decompressHandle(payloadHandle, 0, gunzip)) {
|
|
120
115
|
const chunk = current as Buffer;
|
|
121
116
|
decompressedBytes += chunk.length;
|
|
122
117
|
if (decompressedBytes > bounded.max_decompressed_bytes) {
|
|
@@ -247,16 +242,49 @@ export async function unpackEventV3SupportPack(
|
|
|
247
242
|
}
|
|
248
243
|
}
|
|
249
244
|
|
|
250
|
-
async function hashHandle(
|
|
251
|
-
path: string,
|
|
252
|
-
handle: Awaited<ReturnType<typeof open>>,
|
|
253
|
-
): Promise<`sha256:${string}`> {
|
|
245
|
+
async function hashHandle(handle: Awaited<ReturnType<typeof open>>): Promise<`sha256:${string}`> {
|
|
254
246
|
const hash = createHash("sha256");
|
|
255
|
-
|
|
256
|
-
for await (const current of stream) hash.update(current as Buffer);
|
|
247
|
+
for await (const current of handleChunks(handle, 0)) hash.update(current);
|
|
257
248
|
return `sha256:${hash.digest("hex")}`;
|
|
258
249
|
}
|
|
259
250
|
|
|
251
|
+
async function* handleChunks(handle: Awaited<ReturnType<typeof open>>, position: number) {
|
|
252
|
+
while (true) {
|
|
253
|
+
const buffer = Buffer.alloc(64 * 1024);
|
|
254
|
+
const { bytesRead } = await handle.read(buffer, 0, buffer.length, position);
|
|
255
|
+
if (bytesRead === 0) return;
|
|
256
|
+
position += bytesRead;
|
|
257
|
+
yield buffer.subarray(0, bytesRead);
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
async function* decompressHandle(
|
|
262
|
+
handle: Awaited<ReturnType<typeof open>>,
|
|
263
|
+
start: number,
|
|
264
|
+
decoder: ReturnType<typeof createGunzip>,
|
|
265
|
+
): AsyncGenerator<Buffer> {
|
|
266
|
+
const source = Readable.from(handleChunks(handle, start));
|
|
267
|
+
// Observe failures immediately, then settle source reads before the caller
|
|
268
|
+
// can close and reuse the verified handle, including on early rejection.
|
|
269
|
+
const completed = finished(source, { cleanup: true }).then(
|
|
270
|
+
() => undefined,
|
|
271
|
+
(error: unknown) => error,
|
|
272
|
+
);
|
|
273
|
+
source.once("error", (error) => decoder.destroy(error));
|
|
274
|
+
source.pipe(decoder);
|
|
275
|
+
try {
|
|
276
|
+
for await (const chunk of decoder) yield chunk as Buffer;
|
|
277
|
+
source.unpipe(decoder);
|
|
278
|
+
source.resume();
|
|
279
|
+
const error = await completed;
|
|
280
|
+
if (error) throw error;
|
|
281
|
+
} finally {
|
|
282
|
+
source.destroy();
|
|
283
|
+
decoder.destroy();
|
|
284
|
+
await completed;
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
|
|
260
288
|
async function realDirectory(path: string): Promise<string> {
|
|
261
289
|
const stat = await lstat(path);
|
|
262
290
|
if (!stat.isDirectory() || stat.isSymbolicLink())
|
|
@@ -265,7 +293,6 @@ async function realDirectory(path: string): Promise<string> {
|
|
|
265
293
|
}
|
|
266
294
|
|
|
267
295
|
async function assertCanonicalSingleGzipMember(
|
|
268
|
-
path: string,
|
|
269
296
|
handle: Awaited<ReturnType<typeof open>>,
|
|
270
297
|
payloadBytes: number,
|
|
271
298
|
maximumDecompressedBytes: number,
|
|
@@ -284,9 +311,8 @@ async function assertCanonicalSingleGzipMember(
|
|
|
284
311
|
throw new Error("event_v3_support_gzip_header_noncanonical");
|
|
285
312
|
}
|
|
286
313
|
const inflate = createInflateRaw();
|
|
287
|
-
createReadStream(path, { fd: handle.fd, autoClose: false, start: 10 }).pipe(inflate);
|
|
288
314
|
let decompressed = 0;
|
|
289
|
-
for await (const current of inflate) {
|
|
315
|
+
for await (const current of decompressHandle(handle, 10, inflate)) {
|
|
290
316
|
decompressed += (current as Buffer).length;
|
|
291
317
|
if (decompressed > maximumDecompressedBytes) {
|
|
292
318
|
inflate.destroy();
|
|
@@ -124,6 +124,7 @@ export async function runGovernorPlanner(
|
|
|
124
124
|
if (recovered) return recovered;
|
|
125
125
|
}
|
|
126
126
|
let proposalWritten = false;
|
|
127
|
+
let invalidProposal = false;
|
|
127
128
|
try {
|
|
128
129
|
const reviewRunId = reviewWorkflowRunId(request.id);
|
|
129
130
|
const resumingReview = Boolean(
|
|
@@ -153,14 +154,22 @@ export async function runGovernorPlanner(
|
|
|
153
154
|
onLog: input.onLog,
|
|
154
155
|
})
|
|
155
156
|
).result;
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
157
|
+
let proposal: GovernorPlanProposal;
|
|
158
|
+
try {
|
|
159
|
+
proposal = normalizeProposal(
|
|
160
|
+
request.id,
|
|
161
|
+
rawProposal,
|
|
162
|
+
input.record,
|
|
163
|
+
history.materialized_work_ids.length,
|
|
164
|
+
request.trigger ?? "recovery",
|
|
165
|
+
reviewInput ? persistedProposalTimestamp(reviewInput.candidate) : undefined,
|
|
166
|
+
);
|
|
167
|
+
} catch (error) {
|
|
168
|
+
// A fresh invalid proposal can consume a replan without aborting the run.
|
|
169
|
+
// Corrupt persisted review input still requires investigation.
|
|
170
|
+
invalidProposal = !reviewInput;
|
|
171
|
+
throw error;
|
|
172
|
+
}
|
|
164
173
|
if (policy.review) {
|
|
165
174
|
if (proposal.decision === "attention") {
|
|
166
175
|
appendPlanEvent(coordRoot, input.record.intent.id, request.id, {
|
|
@@ -258,6 +267,9 @@ export async function runGovernorPlanner(
|
|
|
258
267
|
reason: bounded((error as Error).message, "plan failure", MAX_REASON),
|
|
259
268
|
...(failureClass ? { class: failureClass } : {}),
|
|
260
269
|
});
|
|
270
|
+
if (invalidProposal) {
|
|
271
|
+
return outcome(readGovernorPlan(coordRoot, input.record.intent.id, request.id));
|
|
272
|
+
}
|
|
261
273
|
throw error;
|
|
262
274
|
}
|
|
263
275
|
}
|
|
@@ -3,12 +3,19 @@ import { hostname } from "node:os";
|
|
|
3
3
|
import { join, resolve } from "node:path";
|
|
4
4
|
import { gunzipSync } from "node:zlib";
|
|
5
5
|
import { coordEnv } from "../../lib/env.ts";
|
|
6
|
+
import {
|
|
7
|
+
type ProcessGenerationVerdict,
|
|
8
|
+
verifyProcessGeneration,
|
|
9
|
+
} from "../agents/state/proc-start.ts";
|
|
10
|
+
import { currentHarneryRuntimeBuild } from "../events/v3/runtime-build.ts";
|
|
6
11
|
import { createStorageCatalog } from "../storage/catalog.ts";
|
|
7
12
|
import type { HarneryLogRecordV1 } from "../storage/jsonl.ts";
|
|
8
13
|
import { parseLogRecord } from "../storage/jsonl.ts";
|
|
9
14
|
import { familyLogDirectory, readSegmentManifest } from "../storage/segments.ts";
|
|
10
15
|
import type {
|
|
16
|
+
GovernorServiceBuild,
|
|
11
17
|
GovernorServiceConfig,
|
|
18
|
+
GovernorServiceProcessDependencies,
|
|
12
19
|
GovernorServiceRuntime,
|
|
13
20
|
GovernorServiceStatus,
|
|
14
21
|
GovernorServiceStatusRecord,
|
|
@@ -17,7 +24,106 @@ import type {
|
|
|
17
24
|
const MAX_FILE_BYTES = 512 * 1024;
|
|
18
25
|
const MAX_LOG_RECORDS = 10_000;
|
|
19
26
|
const MAX_GOALS = 100;
|
|
20
|
-
|
|
27
|
+
|
|
28
|
+
export function currentGovernorServiceBuild(): GovernorServiceBuild {
|
|
29
|
+
let version = "unknown";
|
|
30
|
+
let revision = "unknown";
|
|
31
|
+
try {
|
|
32
|
+
const manifest = JSON.parse(
|
|
33
|
+
readFileSync(new URL("../../../package.json", import.meta.url), "utf8"),
|
|
34
|
+
) as {
|
|
35
|
+
version?: unknown;
|
|
36
|
+
};
|
|
37
|
+
if (typeof manifest.version === "string" && manifest.version.length <= 100) {
|
|
38
|
+
version = manifest.version;
|
|
39
|
+
}
|
|
40
|
+
} catch {
|
|
41
|
+
// Missing package metadata cannot establish that the running build is current.
|
|
42
|
+
}
|
|
43
|
+
try {
|
|
44
|
+
revision = currentHarneryRuntimeBuild();
|
|
45
|
+
} catch {
|
|
46
|
+
// Status still reports the unavailable build instead of guessing a match.
|
|
47
|
+
}
|
|
48
|
+
return { version, revision };
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function governorServiceBuildStatus(
|
|
52
|
+
record: GovernorServiceStatusRecord | undefined,
|
|
53
|
+
): Pick<
|
|
54
|
+
GovernorServiceStatus,
|
|
55
|
+
"running_build" | "installed_build" | "restart_needed" | "build_status"
|
|
56
|
+
> {
|
|
57
|
+
const installed_build = currentGovernorServiceBuild();
|
|
58
|
+
const candidate = record?.running_build;
|
|
59
|
+
const running_build =
|
|
60
|
+
candidate &&
|
|
61
|
+
typeof candidate.version === "string" &&
|
|
62
|
+
candidate.version.length > 0 &&
|
|
63
|
+
candidate.version.length <= 100 &&
|
|
64
|
+
typeof candidate.revision === "string" &&
|
|
65
|
+
candidate.revision.length > 0 &&
|
|
66
|
+
candidate.revision.length <= 100 &&
|
|
67
|
+
candidate.version !== "unknown" &&
|
|
68
|
+
candidate.revision !== "unknown"
|
|
69
|
+
? candidate
|
|
70
|
+
: null;
|
|
71
|
+
const restart_needed =
|
|
72
|
+
running_build === null ||
|
|
73
|
+
installed_build.version === "unknown" ||
|
|
74
|
+
installed_build.revision === "unknown" ||
|
|
75
|
+
running_build.version !== installed_build.version ||
|
|
76
|
+
running_build.revision !== installed_build.revision;
|
|
77
|
+
return {
|
|
78
|
+
running_build,
|
|
79
|
+
installed_build,
|
|
80
|
+
restart_needed,
|
|
81
|
+
build_status:
|
|
82
|
+
running_build === null
|
|
83
|
+
? "running build unknown; restart needed"
|
|
84
|
+
: restart_needed
|
|
85
|
+
? "restart needed"
|
|
86
|
+
: "current",
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/** An unknown token or process probe cannot establish a running or stopped owner. */
|
|
91
|
+
export function observeGovernorServiceProcess(
|
|
92
|
+
record: Pick<GovernorServiceStatusRecord, "pid" | "host" | "state" | "start_token"> | undefined,
|
|
93
|
+
dependencies: GovernorServiceProcessDependencies = {},
|
|
94
|
+
): { generation: ProcessGenerationVerdict; liveness: GovernorServiceStatus["liveness"] } {
|
|
95
|
+
if (!record) return { generation: "unknown", liveness: "stopped" };
|
|
96
|
+
const generation =
|
|
97
|
+
record.host === hostname()
|
|
98
|
+
? verifyProcessGeneration(record.pid, record.start_token, dependencies.readToken)
|
|
99
|
+
: "unknown";
|
|
100
|
+
if (record.host !== hostname()) return { generation, liveness: "unknown" };
|
|
101
|
+
try {
|
|
102
|
+
const present = (dependencies.probePid ?? probePid)(record.pid);
|
|
103
|
+
if (!present) return { generation, liveness: "stopped" };
|
|
104
|
+
} catch (error) {
|
|
105
|
+
return {
|
|
106
|
+
generation,
|
|
107
|
+
liveness: (error as NodeJS.ErrnoException).code === "ESRCH" ? "stopped" : "unknown",
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
return {
|
|
111
|
+
generation,
|
|
112
|
+
liveness:
|
|
113
|
+
generation === "same"
|
|
114
|
+
? record.state === "stopped"
|
|
115
|
+
? "stopped"
|
|
116
|
+
: "running"
|
|
117
|
+
: generation === "different"
|
|
118
|
+
? "stopped"
|
|
119
|
+
: "unknown",
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function probePid(pid: number): boolean {
|
|
124
|
+
process.kill(pid, 0);
|
|
125
|
+
return true;
|
|
126
|
+
}
|
|
21
127
|
|
|
22
128
|
export function readGovernorServiceConfig(coordRootRaw: string): GovernorServiceConfig {
|
|
23
129
|
const coordRoot = resolve(coordRootRaw);
|
|
@@ -67,7 +173,10 @@ export function readGovernorServiceRuntime(
|
|
|
67
173
|
return runtime;
|
|
68
174
|
}
|
|
69
175
|
|
|
70
|
-
export function readGovernorServiceStatus(
|
|
176
|
+
export function readGovernorServiceStatus(
|
|
177
|
+
coordRootRaw: string,
|
|
178
|
+
dependencies: GovernorServiceProcessDependencies = {},
|
|
179
|
+
): GovernorServiceStatus {
|
|
71
180
|
const coordRoot = resolve(coordRootRaw);
|
|
72
181
|
const record = readStatusRecord(coordRoot);
|
|
73
182
|
let config: GovernorServiceConfig | undefined;
|
|
@@ -82,9 +191,19 @@ export function readGovernorServiceStatus(coordRootRaw: string): GovernorService
|
|
|
82
191
|
} catch {
|
|
83
192
|
// Runtime is recoverable and must not make the dashboard unreadable.
|
|
84
193
|
}
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
194
|
+
const observation = observeGovernorServiceProcess(record, dependencies);
|
|
195
|
+
if (!record && existsSync(join(serviceDir(coordRoot), "status.json"))) {
|
|
196
|
+
observation.liveness = "unknown";
|
|
197
|
+
}
|
|
198
|
+
return {
|
|
199
|
+
running: observation.liveness === "running" && record?.state !== "stopped",
|
|
200
|
+
stale: observation.liveness === "stopped" && record !== undefined && record.state !== "stopped",
|
|
201
|
+
...observation,
|
|
202
|
+
...governorServiceBuildStatus(record),
|
|
203
|
+
record,
|
|
204
|
+
config,
|
|
205
|
+
runtime,
|
|
206
|
+
};
|
|
88
207
|
}
|
|
89
208
|
|
|
90
209
|
export function governorServiceLogPath(coordRoot: string): string {
|
|
@@ -418,26 +537,15 @@ function readStatusRecord(coordRoot: string): GovernorServiceStatusRecord | unde
|
|
|
418
537
|
) {
|
|
419
538
|
return undefined;
|
|
420
539
|
}
|
|
421
|
-
return
|
|
540
|
+
return {
|
|
541
|
+
...value,
|
|
542
|
+
start_token: typeof value.start_token === "string" ? value.start_token : undefined,
|
|
543
|
+
};
|
|
422
544
|
} catch {
|
|
423
545
|
return undefined;
|
|
424
546
|
}
|
|
425
547
|
}
|
|
426
548
|
|
|
427
|
-
function statusOwnerIsLive(record: GovernorServiceStatusRecord): boolean {
|
|
428
|
-
if (record.state === "stopped") return false;
|
|
429
|
-
if (record.host === hostname()) {
|
|
430
|
-
try {
|
|
431
|
-
process.kill(record.pid, 0);
|
|
432
|
-
return true;
|
|
433
|
-
} catch (error) {
|
|
434
|
-
return (error as NodeJS.ErrnoException).code === "EPERM";
|
|
435
|
-
}
|
|
436
|
-
}
|
|
437
|
-
const age = Date.now() - Date.parse(record.heartbeat_at);
|
|
438
|
-
return Number.isFinite(age) && age < FOREIGN_STATUS_STALE_MS;
|
|
439
|
-
}
|
|
440
|
-
|
|
441
549
|
function readBoundedJson<T>(path: string, label: string): T {
|
|
442
550
|
if (!existsSync(path)) throw new Error(`${label} does not exist`);
|
|
443
551
|
const size = statSync(path).size;
|
|
@@ -16,12 +16,24 @@ import {
|
|
|
16
16
|
import { hostname } from "node:os";
|
|
17
17
|
import { dirname, join, resolve } from "node:path";
|
|
18
18
|
import { coordEnv } from "../../lib/env.ts";
|
|
19
|
+
import {
|
|
20
|
+
type ProcessGenerationSignalDependencies,
|
|
21
|
+
type ProcessGenerationVerdict,
|
|
22
|
+
processStartToken,
|
|
23
|
+
signalIfSameGeneration,
|
|
24
|
+
} from "../agents/state/proc-start.ts";
|
|
25
|
+
import { resolveBinName } from "../config.ts";
|
|
19
26
|
import { type NormalizedPolicy, normalizePolicy, type PolicyIsolation } from "../policy/index.ts";
|
|
20
27
|
import { closeProcessLoggers, legacyLogFields, processLogger } from "../storage/logger.ts";
|
|
21
28
|
import { stateDirMode, stateFileMode } from "../storage/modes.ts";
|
|
22
29
|
import { RotatingTextSink } from "../storage/process-log.ts";
|
|
23
30
|
import type { RunWorkItemInput } from "../work/index.ts";
|
|
24
31
|
import { type GovernorRunReport, type GovernorStopReason, runGovernor } from "./runner.ts";
|
|
32
|
+
import {
|
|
33
|
+
currentGovernorServiceBuild,
|
|
34
|
+
governorServiceBuildStatus,
|
|
35
|
+
observeGovernorServiceProcess,
|
|
36
|
+
} from "./service-read.ts";
|
|
25
37
|
import { type GovernorRecord, readGovernor } from "./state.ts";
|
|
26
38
|
|
|
27
39
|
export const GOVERNOR_SERVICE_CONFIG_SCHEMA_VERSION = 1 as const;
|
|
@@ -31,7 +43,6 @@ export const GOVERNOR_SERVICE_STATUS_SCHEMA_VERSION = 1 as const;
|
|
|
31
43
|
const MAX_FILE_BYTES = 512 * 1024;
|
|
32
44
|
const MAX_GOALS = 100;
|
|
33
45
|
const MAX_ERROR = 2_000;
|
|
34
|
-
const FOREIGN_STATUS_STALE_MS = 30_000;
|
|
35
46
|
|
|
36
47
|
export interface GovernorServiceEngineConfig {
|
|
37
48
|
default_adapter?: string;
|
|
@@ -85,9 +96,20 @@ export interface GovernorServiceRuntime {
|
|
|
85
96
|
|
|
86
97
|
export type GovernorServiceProcessState = "starting" | "running" | "stopping" | "stopped";
|
|
87
98
|
|
|
99
|
+
export interface GovernorServiceBuild {
|
|
100
|
+
version: string;
|
|
101
|
+
revision: string;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export interface GovernorServiceProcessDependencies extends ProcessGenerationSignalDependencies {
|
|
105
|
+
probePid?: (pid: number) => boolean;
|
|
106
|
+
}
|
|
107
|
+
|
|
88
108
|
export interface GovernorServiceStatusRecord {
|
|
89
109
|
schema_version: typeof GOVERNOR_SERVICE_STATUS_SCHEMA_VERSION;
|
|
90
110
|
pid: number;
|
|
111
|
+
start_token?: string;
|
|
112
|
+
running_build?: GovernorServiceBuild;
|
|
91
113
|
host: string;
|
|
92
114
|
nonce: string;
|
|
93
115
|
state: GovernorServiceProcessState;
|
|
@@ -105,6 +127,14 @@ export interface GovernorServiceStatusRecord {
|
|
|
105
127
|
export interface GovernorServiceStatus {
|
|
106
128
|
running: boolean;
|
|
107
129
|
stale: boolean;
|
|
130
|
+
generation: ProcessGenerationVerdict;
|
|
131
|
+
liveness: "running" | "stopped" | "unknown";
|
|
132
|
+
running_build: GovernorServiceBuild | null;
|
|
133
|
+
installed_build: GovernorServiceBuild;
|
|
134
|
+
restart_needed: boolean;
|
|
135
|
+
build_status: string;
|
|
136
|
+
signal_sent?: boolean;
|
|
137
|
+
signal_verdict?: ProcessGenerationVerdict;
|
|
108
138
|
record?: GovernorServiceStatusRecord;
|
|
109
139
|
config?: GovernorServiceConfig;
|
|
110
140
|
runtime?: GovernorServiceRuntime;
|
|
@@ -145,6 +175,8 @@ export interface RunGovernorServiceDaemonInput {
|
|
|
145
175
|
|
|
146
176
|
interface ServiceLease {
|
|
147
177
|
pid: number;
|
|
178
|
+
start_token?: string;
|
|
179
|
+
running_build?: GovernorServiceBuild;
|
|
148
180
|
host: string;
|
|
149
181
|
nonce: string;
|
|
150
182
|
created_at: string;
|
|
@@ -155,8 +187,12 @@ export function configureGovernorService(
|
|
|
155
187
|
): GovernorServiceConfig {
|
|
156
188
|
const coordRoot = resolve(input.coordRoot);
|
|
157
189
|
const active = readStatusRecord(coordRoot);
|
|
158
|
-
if (active &&
|
|
159
|
-
throw
|
|
190
|
+
if (!active && existsSync(serviceStatusPath(coordRoot))) {
|
|
191
|
+
throw serviceStartRefusal(coordRoot, undefined, "unknown");
|
|
192
|
+
}
|
|
193
|
+
if (active) {
|
|
194
|
+
const liveness = statusOwnerIsLive(active);
|
|
195
|
+
if (liveness !== "stopped") throw serviceStartRefusal(coordRoot, active.pid, liveness);
|
|
160
196
|
}
|
|
161
197
|
const goalIds = unique(input.goalIds.map((value) => value.trim()).filter(Boolean));
|
|
162
198
|
if (goalIds.length < 1 || goalIds.length > MAX_GOALS) {
|
|
@@ -235,7 +271,10 @@ export function readGovernorServiceRuntime(
|
|
|
235
271
|
return runtime;
|
|
236
272
|
}
|
|
237
273
|
|
|
238
|
-
export function readGovernorServiceStatus(
|
|
274
|
+
export function readGovernorServiceStatus(
|
|
275
|
+
coordRootRaw: string,
|
|
276
|
+
dependencies: GovernorServiceProcessDependencies = {},
|
|
277
|
+
): GovernorServiceStatus {
|
|
239
278
|
const coordRoot = resolve(coordRootRaw);
|
|
240
279
|
const record = readStatusRecord(coordRoot);
|
|
241
280
|
let config: GovernorServiceConfig | undefined;
|
|
@@ -250,17 +289,25 @@ export function readGovernorServiceStatus(coordRootRaw: string): GovernorService
|
|
|
250
289
|
} catch {
|
|
251
290
|
// Status remains readable when recoverable runtime state is corrupt.
|
|
252
291
|
}
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
return {
|
|
292
|
+
const observation = observeGovernorServiceProcess(record, dependencies);
|
|
293
|
+
if (!record && existsSync(serviceStatusPath(coordRoot))) observation.liveness = "unknown";
|
|
294
|
+
return {
|
|
295
|
+
running: observation.liveness === "running" && record?.state !== "stopped",
|
|
296
|
+
stale: observation.liveness === "stopped" && record !== undefined && record.state !== "stopped",
|
|
297
|
+
...observation,
|
|
298
|
+
...governorServiceBuildStatus(record),
|
|
299
|
+
record,
|
|
300
|
+
config,
|
|
301
|
+
runtime,
|
|
302
|
+
};
|
|
256
303
|
}
|
|
257
304
|
|
|
258
305
|
export async function spawnGovernorService(coordRootRaw: string): Promise<GovernorServiceStatus> {
|
|
259
306
|
const coordRoot = resolve(coordRootRaw);
|
|
260
307
|
readGovernorServiceConfig(coordRoot);
|
|
261
308
|
const current = readGovernorServiceStatus(coordRoot);
|
|
262
|
-
if (current.
|
|
263
|
-
throw
|
|
309
|
+
if (current.liveness !== "stopped") {
|
|
310
|
+
throw serviceStartRefusal(coordRoot, current.record?.pid, current.liveness);
|
|
264
311
|
}
|
|
265
312
|
mkdirSync(serviceDir(coordRoot), { recursive: true, mode: stateDirMode() });
|
|
266
313
|
const sharedLogs = coordEnv("SHARED_LOGS") !== "0";
|
|
@@ -298,22 +345,33 @@ export async function spawnGovernorService(coordRootRaw: string): Promise<Govern
|
|
|
298
345
|
throw new Error(`governor service failed to start; inspect ${governorServiceLogPath(coordRoot)}`);
|
|
299
346
|
}
|
|
300
347
|
|
|
301
|
-
export function requestGovernorServiceStop(
|
|
348
|
+
export function requestGovernorServiceStop(
|
|
349
|
+
coordRootRaw: string,
|
|
350
|
+
dependencies: GovernorServiceProcessDependencies = {},
|
|
351
|
+
): GovernorServiceStatus {
|
|
302
352
|
const coordRoot = resolve(coordRootRaw);
|
|
303
|
-
const status = readGovernorServiceStatus(coordRoot);
|
|
304
|
-
if (
|
|
353
|
+
const status = readGovernorServiceStatus(coordRoot, dependencies);
|
|
354
|
+
if ((status.record?.state === "stopped" || !status.record) && status.liveness === "stopped") {
|
|
355
|
+
return { ...status, signal_sent: false, signal_verdict: status.generation };
|
|
356
|
+
}
|
|
305
357
|
writePrivateJsonAtomic(serviceStopPath(coordRoot), {
|
|
306
358
|
requested_at: new Date().toISOString(),
|
|
307
359
|
requested_by_pid: process.pid,
|
|
308
360
|
});
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
361
|
+
const signal =
|
|
362
|
+
status.record?.host === hostname() && status.record.state !== "stopped"
|
|
363
|
+
? signalIfSameGeneration(
|
|
364
|
+
status.record.pid,
|
|
365
|
+
status.record.start_token,
|
|
366
|
+
"SIGTERM",
|
|
367
|
+
dependencies,
|
|
368
|
+
)
|
|
369
|
+
: { sent: false, verdict: status.generation };
|
|
370
|
+
return {
|
|
371
|
+
...readGovernorServiceStatus(coordRoot, dependencies),
|
|
372
|
+
signal_sent: signal.sent,
|
|
373
|
+
signal_verdict: signal.verdict,
|
|
374
|
+
};
|
|
317
375
|
}
|
|
318
376
|
|
|
319
377
|
export async function runGovernorServiceSweep(
|
|
@@ -461,12 +519,22 @@ export async function runGovernorServiceDaemon(
|
|
|
461
519
|
): Promise<GovernorServiceStatusRecord> {
|
|
462
520
|
const coordRoot = resolve(input.coordRoot);
|
|
463
521
|
const config = readGovernorServiceConfig(coordRoot);
|
|
522
|
+
const existing = readStatusRecord(coordRoot);
|
|
523
|
+
if (!existing && existsSync(serviceStatusPath(coordRoot))) {
|
|
524
|
+
throw serviceStartRefusal(coordRoot, undefined, "unknown");
|
|
525
|
+
}
|
|
526
|
+
if (existing) {
|
|
527
|
+
const liveness = statusOwnerIsLive(existing);
|
|
528
|
+
if (liveness !== "stopped") throw serviceStartRefusal(coordRoot, existing.pid, liveness);
|
|
529
|
+
}
|
|
464
530
|
const release = acquireServiceLease(coordRoot);
|
|
465
531
|
rmSync(serviceStopPath(coordRoot), { force: true });
|
|
466
532
|
const now = new Date().toISOString();
|
|
467
533
|
const status: GovernorServiceStatusRecord = {
|
|
468
534
|
schema_version: GOVERNOR_SERVICE_STATUS_SCHEMA_VERSION,
|
|
469
535
|
pid: process.pid,
|
|
536
|
+
start_token: processStartToken(process.pid) ?? undefined,
|
|
537
|
+
running_build: currentGovernorServiceBuild(),
|
|
470
538
|
host: hostname(),
|
|
471
539
|
nonce: randomUUID(),
|
|
472
540
|
state: "starting",
|
|
@@ -493,6 +561,8 @@ export async function runGovernorServiceDaemon(
|
|
|
493
561
|
appendGovernorServiceDiagnostic(coordRoot, {
|
|
494
562
|
event: "service.started",
|
|
495
563
|
pid: process.pid,
|
|
564
|
+
start_token: status.start_token,
|
|
565
|
+
running_build: status.running_build,
|
|
496
566
|
goals: config.goal_ids,
|
|
497
567
|
config_created_at: config.created_at,
|
|
498
568
|
});
|
|
@@ -716,6 +786,8 @@ function acquireServiceLease(coordRoot: string): () => void {
|
|
|
716
786
|
mkdirSync(dirname(path), { recursive: true, mode: stateDirMode() });
|
|
717
787
|
const owner: ServiceLease = {
|
|
718
788
|
pid: process.pid,
|
|
789
|
+
start_token: processStartToken(process.pid) ?? undefined,
|
|
790
|
+
running_build: currentGovernorServiceBuild(),
|
|
719
791
|
host: hostname(),
|
|
720
792
|
nonce: randomUUID(),
|
|
721
793
|
created_at: new Date().toISOString(),
|
|
@@ -736,8 +808,9 @@ function acquireServiceLease(coordRoot: string): () => void {
|
|
|
736
808
|
};
|
|
737
809
|
if (!acquire()) {
|
|
738
810
|
const existing = readLease(path);
|
|
739
|
-
|
|
740
|
-
|
|
811
|
+
const liveness = existing ? leaseOwnerIsLive(existing) : "unknown";
|
|
812
|
+
if (liveness !== "stopped") {
|
|
813
|
+
throw serviceStartRefusal(coordRoot, existing?.pid, liveness);
|
|
741
814
|
}
|
|
742
815
|
unlinkSync(path);
|
|
743
816
|
if (!acquire()) throw new Error("governor service lease raced with another process");
|
|
@@ -760,19 +833,18 @@ function readLease(path: string): ServiceLease | null {
|
|
|
760
833
|
typeof value.host === "string" &&
|
|
761
834
|
typeof value.nonce === "string" &&
|
|
762
835
|
typeof value.created_at === "string"
|
|
763
|
-
? (
|
|
836
|
+
? ({
|
|
837
|
+
...value,
|
|
838
|
+
start_token: typeof value.start_token === "string" ? value.start_token : undefined,
|
|
839
|
+
} as ServiceLease)
|
|
764
840
|
: null;
|
|
765
841
|
} catch {
|
|
766
842
|
return null;
|
|
767
843
|
}
|
|
768
844
|
}
|
|
769
845
|
|
|
770
|
-
function leaseOwnerIsLive(lease: ServiceLease):
|
|
771
|
-
|
|
772
|
-
const age = Date.now() - Date.parse(lease.created_at);
|
|
773
|
-
return Number.isFinite(age) && age < FOREIGN_STATUS_STALE_MS;
|
|
774
|
-
}
|
|
775
|
-
return pidAlive(lease.pid);
|
|
846
|
+
function leaseOwnerIsLive(lease: ServiceLease): GovernorServiceStatus["liveness"] {
|
|
847
|
+
return observeGovernorServiceProcess({ ...lease, state: "running" }).liveness;
|
|
776
848
|
}
|
|
777
849
|
|
|
778
850
|
function readStatusRecord(coordRoot: string): GovernorServiceStatusRecord | undefined {
|
|
@@ -793,26 +865,31 @@ function readStatusRecord(coordRoot: string): GovernorServiceStatusRecord | unde
|
|
|
793
865
|
) {
|
|
794
866
|
return undefined;
|
|
795
867
|
}
|
|
796
|
-
return
|
|
868
|
+
return {
|
|
869
|
+
...value,
|
|
870
|
+
start_token: typeof value.start_token === "string" ? value.start_token : undefined,
|
|
871
|
+
};
|
|
797
872
|
} catch {
|
|
798
873
|
return undefined;
|
|
799
874
|
}
|
|
800
875
|
}
|
|
801
876
|
|
|
802
|
-
function statusOwnerIsLive(record: GovernorServiceStatusRecord):
|
|
803
|
-
|
|
804
|
-
if (record.host === hostname()) return pidAlive(record.pid);
|
|
805
|
-
const age = Date.now() - Date.parse(record.heartbeat_at);
|
|
806
|
-
return Number.isFinite(age) && age < FOREIGN_STATUS_STALE_MS;
|
|
877
|
+
function statusOwnerIsLive(record: GovernorServiceStatusRecord): GovernorServiceStatus["liveness"] {
|
|
878
|
+
return observeGovernorServiceProcess(record).liveness;
|
|
807
879
|
}
|
|
808
880
|
|
|
809
|
-
function
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
881
|
+
function serviceStartRefusal(
|
|
882
|
+
coordRoot: string,
|
|
883
|
+
pid: number | undefined,
|
|
884
|
+
liveness: GovernorServiceStatus["liveness"],
|
|
885
|
+
): Error {
|
|
886
|
+
const reason =
|
|
887
|
+
liveness === "unknown"
|
|
888
|
+
? `governor service process generation is unknown${pid ? ` for pid ${pid}` : ""}`
|
|
889
|
+
: `governor service process is still running under pid ${pid}`;
|
|
890
|
+
return new Error(
|
|
891
|
+
`${reason}; run \`${resolveBinName(coordRoot)} governor service stop\` to write a durable stop request before starting or reconfiguring it`,
|
|
892
|
+
);
|
|
816
893
|
}
|
|
817
894
|
|
|
818
895
|
export function appendGovernorServiceDiagnostic(
|