harnery 0.45.1 → 0.46.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/dist/commander.d.ts +13 -0
- package/dist/commander.d.ts.map +1 -1
- package/dist/commands/admission.d.ts.map +1 -1
- package/dist/commands/admission.js +214 -27
- package/dist/commands/agents.d.ts +4 -0
- package/dist/commands/agents.d.ts.map +1 -1
- package/dist/commands/agents.js +102 -25
- package/dist/commands/browse.d.ts +3 -0
- package/dist/commands/browse.d.ts.map +1 -1
- package/dist/commands/browse.js +172 -8
- package/dist/commands/fetch.js +1 -0
- package/dist/commands/ledger-v3.d.ts.map +1 -1
- package/dist/commands/ledger-v3.js +8 -2
- package/dist/commands/presence.d.ts.map +1 -1
- package/dist/commands/presence.js +40 -0
- package/dist/commands/qa-run.d.ts.map +1 -1
- package/dist/commands/qa-run.js +4 -3
- package/dist/commands/review-pack.d.ts.map +1 -1
- package/dist/commands/review-pack.js +33 -6
- package/dist/commands/workflow.d.ts.map +1 -1
- package/dist/commands/workflow.js +6 -45
- package/dist/core/agents/coord-client.d.ts.map +1 -1
- package/dist/core/agents/coord-client.js +0 -18
- package/dist/core/agents/index.d.ts +1 -0
- package/dist/core/agents/index.d.ts.map +1 -1
- package/dist/core/agents/index.js +1 -0
- package/dist/core/agents/live-authority-v3.d.ts.map +1 -1
- package/dist/core/agents/live-authority-v3.js +11 -4
- package/dist/core/agents/render/session-context.d.ts +0 -6
- package/dist/core/agents/render/session-context.d.ts.map +1 -1
- package/dist/core/agents/render/session-context.js +1 -6
- package/dist/core/agents/session-events.d.ts.map +1 -1
- package/dist/core/agents/session-events.js +12 -4
- package/dist/core/agents/state/heartbeat-reader.d.ts +3 -0
- package/dist/core/agents/state/heartbeat-reader.d.ts.map +1 -1
- package/dist/core/agents/state/live-coordination-view.d.ts +3 -1
- package/dist/core/agents/state/live-coordination-view.d.ts.map +1 -1
- package/dist/core/agents/state/live-coordination-view.js +6 -2
- package/dist/core/agents/state/parent-reference-contract.d.ts +16 -0
- package/dist/core/agents/state/parent-reference-contract.d.ts.map +1 -0
- package/dist/core/agents/state/parent-reference-contract.js +1 -0
- package/dist/core/agents/state/parent-reference.d.ts +18 -0
- package/dist/core/agents/state/parent-reference.d.ts.map +1 -0
- package/dist/core/agents/state/parent-reference.js +119 -0
- package/dist/core/agents/state/pidmap.js +2 -2
- package/dist/core/agents/state/proc-start.d.ts +20 -31
- package/dist/core/agents/state/proc-start.d.ts.map +1 -1
- package/dist/core/agents/state/proc-start.js +37 -70
- package/dist/core/agents/state/service-process.d.ts +61 -0
- package/dist/core/agents/state/service-process.d.ts.map +1 -0
- package/dist/core/agents/state/service-process.js +143 -0
- package/dist/core/config.d.ts +39 -0
- package/dist/core/config.d.ts.map +1 -1
- package/dist/core/config.js +63 -0
- package/dist/core/context/index.d.ts +6 -4
- package/dist/core/context/index.d.ts.map +1 -1
- package/dist/core/context/index.js +194 -256
- package/dist/core/context/observation.d.ts +9 -9
- package/dist/core/context/observation.d.ts.map +1 -1
- package/dist/core/context/observation.js +37 -6
- package/dist/core/diagnostics/path-identity.d.ts +1 -1
- package/dist/core/diagnostics/path-identity.d.ts.map +1 -1
- package/dist/core/diagnostics/path-identity.js +51 -6
- package/dist/core/events/v3/canonical.js +43 -14
- package/dist/core/events/v3/index.d.ts +1 -1
- package/dist/core/events/v3/index.d.ts.map +1 -1
- package/dist/core/events/v3/index.js +1 -1
- package/dist/core/events/v3/producers/recorder.d.ts +34 -0
- package/dist/core/events/v3/producers/recorder.d.ts.map +1 -1
- package/dist/core/events/v3/producers/recorder.js +117 -13
- package/dist/core/events/v3/reader.d.ts.map +1 -1
- package/dist/core/events/v3/reader.js +53 -13
- package/dist/core/events/v3/runtime-build.d.ts +14 -0
- package/dist/core/events/v3/runtime-build.d.ts.map +1 -1
- package/dist/core/events/v3/runtime-build.js +44 -0
- package/dist/core/events/v3/validate.d.ts.map +1 -1
- package/dist/core/events/v3/validate.js +36 -1
- package/dist/core/governor/index.d.ts +2 -1
- package/dist/core/governor/index.d.ts.map +1 -1
- package/dist/core/governor/index.js +2 -1
- package/dist/core/governor/service-read.d.ts +3 -10
- package/dist/core/governor/service-read.d.ts.map +1 -1
- package/dist/core/governor/service-read.js +10 -94
- package/dist/core/governor/service.d.ts +3 -7
- package/dist/core/governor/service.d.ts.map +1 -1
- package/dist/core/governor/service.js +17 -182
- package/dist/core/hooks/adapter/native-parent.d.ts +23 -0
- package/dist/core/hooks/adapter/native-parent.d.ts.map +1 -0
- package/dist/core/hooks/adapter/native-parent.js +91 -0
- package/dist/core/hooks/cli.js +79 -4
- package/dist/core/hooks/resolve/owner.d.ts +0 -11
- package/dist/core/hooks/resolve/owner.d.ts.map +1 -1
- package/dist/core/hooks/resolve/owner.js +2 -25
- package/dist/core/hooks/shell-admission.d.ts +84 -0
- package/dist/core/hooks/shell-admission.d.ts.map +1 -0
- package/dist/core/hooks/shell-admission.js +622 -0
- package/dist/core/hooks/shell-commit-tree.d.ts +2 -0
- package/dist/core/hooks/shell-commit-tree.d.ts.map +1 -0
- package/dist/core/hooks/shell-commit-tree.js +104 -0
- package/dist/core/inbox/contract.d.ts +12 -0
- package/dist/core/inbox/contract.d.ts.map +1 -1
- package/dist/core/inbox/contract.js +16 -0
- package/dist/core/inbox/peer-messages.d.ts +41 -16
- package/dist/core/inbox/peer-messages.d.ts.map +1 -1
- package/dist/core/inbox/peer-messages.js +372 -15
- 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 +83 -3
- package/dist/core/inbox/targets.d.ts +17 -3
- package/dist/core/inbox/targets.d.ts.map +1 -1
- package/dist/core/inbox/targets.js +77 -2
- package/dist/core/presence/git.d.ts +18 -12
- package/dist/core/presence/git.d.ts.map +1 -1
- package/dist/core/presence/git.js +32 -38
- package/dist/core/presence/index.d.ts +2 -2
- package/dist/core/presence/index.d.ts.map +1 -1
- package/dist/core/presence/index.js +8 -6
- package/dist/core/presence/mail.d.ts +133 -0
- package/dist/core/presence/mail.d.ts.map +1 -0
- package/dist/core/presence/mail.js +464 -0
- package/dist/core/resources/sampler.js +2 -2
- package/dist/core/resources/service-status.d.ts +2 -14
- package/dist/core/resources/service-status.d.ts.map +1 -1
- package/dist/core/resources/service-status.js +3 -23
- package/dist/core/resources/service.d.ts +3 -7
- package/dist/core/resources/service.d.ts.map +1 -1
- package/dist/core/resources/service.js +12 -101
- package/dist/core/resources/status.d.ts.map +1 -1
- package/dist/core/resources/status.js +3 -2
- package/dist/core/semantic/contract.d.ts +5 -5
- package/dist/core/semantic/service.d.ts.map +1 -1
- package/dist/core/semantic/service.js +9 -2
- package/dist/core/servers/index.d.ts +0 -2
- package/dist/core/servers/index.d.ts.map +1 -1
- package/dist/core/servers/index.js +3 -18
- package/dist/core/storage/modes.d.ts +4 -0
- package/dist/core/storage/modes.d.ts.map +1 -1
- package/dist/core/storage/modes.js +4 -0
- package/dist/core/storage/segments.d.ts.map +1 -1
- package/dist/core/storage/segments.js +5 -4
- package/dist/core/supervisor/service.d.ts +4 -7
- package/dist/core/supervisor/service.d.ts.map +1 -1
- package/dist/core/supervisor/service.js +23 -122
- package/dist/core/supervisor/services.js +2 -2
- package/dist/core/supervisor/status.d.ts +3 -17
- package/dist/core/supervisor/status.d.ts.map +1 -1
- package/dist/core/supervisor/status.js +7 -70
- package/dist/core/workflow/engine.js +2 -1
- package/dist/core/workflow/index.d.ts +3 -4
- package/dist/core/workflow/index.d.ts.map +1 -1
- package/dist/core/workflow/index.js +2 -2
- package/dist/core/workflow/proof.d.ts.map +1 -1
- package/dist/core/workflow/proof.js +30 -74
- package/dist/core/workflow/resume-lock.d.ts +17 -0
- package/dist/core/workflow/resume-lock.d.ts.map +1 -0
- package/dist/core/workflow/resume-lock.js +158 -0
- package/dist/core/workflow/run-state.d.ts +0 -21
- package/dist/core/workflow/run-state.d.ts.map +1 -1
- package/dist/core/workflow/run-state.js +3 -174
- package/dist/core/workflow/workspaces/leases.d.ts +4 -0
- package/dist/core/workflow/workspaces/leases.d.ts.map +1 -1
- package/dist/core/workflow/workspaces/leases.js +1 -1
- package/dist/core/workflow/workspaces/local-git.js +91 -210
- package/dist/core/workflow/workspaces/ownership.d.ts +0 -7
- package/dist/core/workflow/workspaces/ownership.d.ts.map +1 -1
- package/dist/core/workflow/workspaces/ownership.js +3 -130
- package/dist/core/workflow/workspaces/reclaim.d.ts +4 -7
- package/dist/core/workflow/workspaces/reclaim.d.ts.map +1 -1
- package/dist/core/workflow/workspaces/reclaim.js +0 -4
- package/dist/core/workflow/workspaces/recovery.d.ts +1 -1
- package/dist/core/workflow/workspaces/recovery.d.ts.map +1 -1
- package/dist/core/workflow/workspaces/recovery.js +1 -1
- package/dist/core/workflow/workspaces/state.d.ts.map +1 -1
- package/dist/core/workflow/workspaces/state.js +1 -4
- package/dist/lib/admission-history.d.ts +96 -0
- package/dist/lib/admission-history.d.ts.map +1 -0
- package/dist/lib/admission-history.js +196 -0
- package/dist/lib/admission.d.ts +56 -7
- package/dist/lib/admission.d.ts.map +1 -1
- package/dist/lib/admission.js +161 -19
- package/dist/lib/browser/client.d.ts +18 -0
- package/dist/lib/browser/client.d.ts.map +1 -1
- package/dist/lib/browser/client.js +100 -12
- package/dist/lib/browser/content-checks.d.ts +3 -1
- package/dist/lib/browser/content-checks.d.ts.map +1 -1
- package/dist/lib/browser/content-checks.js +10 -2
- package/dist/lib/browser/critique.d.ts +32 -0
- package/dist/lib/browser/critique.d.ts.map +1 -1
- package/dist/lib/browser/critique.js +72 -3
- package/dist/lib/browser/geometry.d.ts +2 -0
- package/dist/lib/browser/geometry.d.ts.map +1 -1
- package/dist/lib/browser/geometry.js +61 -15
- package/dist/lib/browser/index.d.ts +1 -1
- package/dist/lib/browser/index.d.ts.map +1 -1
- package/dist/lib/browser/index.js +1 -1
- package/dist/lib/browser/page-review-contracts.d.ts +6 -1
- package/dist/lib/browser/page-review-contracts.d.ts.map +1 -1
- package/dist/lib/browser/page-review-contracts.js +6 -1
- package/dist/lib/browser/page-review-judge.d.ts +3 -1
- package/dist/lib/browser/page-review-judge.d.ts.map +1 -1
- package/dist/lib/browser/page-review-judge.js +41 -11
- package/dist/lib/browser/page-review-pack.d.ts +3 -0
- package/dist/lib/browser/page-review-pack.d.ts.map +1 -1
- package/dist/lib/browser/page-review-pack.js +5 -2
- package/dist/lib/browser/qa-run-contracts.d.ts +11 -0
- package/dist/lib/browser/qa-run-contracts.d.ts.map +1 -1
- package/dist/lib/browser/qa-run-contracts.js +11 -4
- package/dist/lib/browser/qa-run.d.ts.map +1 -1
- package/dist/lib/browser/qa-run.js +73 -16
- package/dist/lib/browser/runts.d.ts +4 -2
- package/dist/lib/browser/runts.d.ts.map +1 -1
- package/dist/lib/browser/runts.js +84 -33
- package/dist/lib/browser/windows-chrome.d.ts +14 -0
- package/dist/lib/browser/windows-chrome.d.ts.map +1 -1
- package/dist/lib/browser/windows-chrome.js +42 -0
- package/dist/lib/browser/windows-login-sync.d.ts +110 -0
- package/dist/lib/browser/windows-login-sync.d.ts.map +1 -0
- package/dist/lib/browser/windows-login-sync.js +171 -0
- package/dist/lib/cookies/client.d.ts +30 -5
- package/dist/lib/cookies/client.d.ts.map +1 -1
- package/dist/lib/cookies/client.js +55 -10
- package/dist/lib/cookies/index.d.ts +1 -1
- package/dist/lib/cookies/index.d.ts.map +1 -1
- package/dist/lib/cookies/index.js +1 -1
- package/dist/lib/docs-metadata-sync.d.ts.map +1 -1
- package/dist/lib/docs-metadata-sync.js +41 -2
- package/dist/lib/durable-job.d.ts +8 -0
- package/dist/lib/durable-job.d.ts.map +1 -1
- package/dist/lib/durable-job.js +10 -0
- package/dist/lib/http/client.d.ts +9 -8
- package/dist/lib/http/client.d.ts.map +1 -1
- package/dist/lib/http/client.js +44 -2
- package/dist/lib/instructions/templates.js +4 -4
- package/package.json +2 -2
- package/src/commander.ts +13 -0
- package/src/commands/admission.ts +272 -29
- package/src/commands/agents.ts +132 -27
- package/src/commands/browse.ts +208 -6
- package/src/commands/fetch.ts +1 -0
- package/src/commands/ledger-v3.ts +8 -2
- package/src/commands/presence.ts +49 -0
- package/src/commands/qa-run.ts +4 -3
- package/src/commands/review-pack.ts +41 -5
- package/src/commands/workflow.ts +8 -58
- package/src/core/agents/coord-client.ts +0 -20
- package/src/core/agents/index.ts +6 -0
- package/src/core/agents/live-authority-v3.ts +11 -4
- package/src/core/agents/render/session-context.ts +1 -7
- package/src/core/agents/session-events.ts +18 -3
- package/src/core/agents/state/heartbeat-reader.ts +3 -0
- package/src/core/agents/state/live-coordination-view.ts +13 -6
- package/src/core/agents/state/parent-reference-contract.ts +14 -0
- package/src/core/agents/state/parent-reference.ts +179 -0
- package/src/core/agents/state/pidmap.ts +2 -2
- package/src/core/agents/state/proc-start.ts +41 -69
- package/src/core/agents/state/service-process.ts +217 -0
- package/src/core/config.ts +85 -0
- package/src/core/context/index.ts +207 -272
- package/src/core/context/observation.ts +40 -9
- package/src/core/diagnostics/path-identity.ts +52 -6
- package/src/core/events/v3/canonical.ts +46 -13
- package/src/core/events/v3/index.ts +3 -0
- package/src/core/events/v3/producers/recorder.ts +151 -13
- package/src/core/events/v3/reader.ts +55 -11
- package/src/core/events/v3/runtime-build.ts +62 -0
- package/src/core/events/v3/validate.ts +42 -1
- package/src/core/governor/index.ts +1 -2
- package/src/core/governor/service-read.ts +15 -114
- package/src/core/governor/service.ts +36 -220
- package/src/core/hooks/adapter/native-parent.ts +109 -0
- package/src/core/hooks/cli.ts +83 -4
- package/src/core/hooks/resolve/owner.ts +2 -25
- package/src/core/hooks/shell-admission.ts +634 -0
- package/src/core/hooks/shell-commit-tree.ts +102 -0
- package/src/core/inbox/contract.ts +18 -0
- package/src/core/inbox/peer-messages.ts +501 -15
- package/src/core/inbox/service.ts +108 -3
- package/src/core/inbox/targets.ts +94 -3
- package/src/core/presence/git.ts +45 -37
- package/src/core/presence/index.ts +16 -8
- package/src/core/presence/mail.ts +620 -0
- package/src/core/resources/sampler.ts +3 -2
- package/src/core/resources/service-status.ts +6 -35
- package/src/core/resources/service.ts +23 -133
- package/src/core/resources/status.ts +3 -2
- package/src/core/semantic/service.ts +8 -2
- package/src/core/servers/index.ts +3 -23
- package/src/core/storage/modes.ts +6 -0
- package/src/core/storage/segments.ts +5 -4
- package/src/core/supervisor/service.ts +33 -155
- package/src/core/supervisor/services.ts +2 -2
- package/src/core/supervisor/status.ts +10 -85
- package/src/core/workflow/engine.ts +1 -1
- package/src/core/workflow/index.ts +2 -13
- package/src/core/workflow/proof.ts +35 -83
- package/src/core/workflow/resume-lock.ts +179 -0
- package/src/core/workflow/run-state.ts +3 -207
- package/src/core/workflow/workspaces/leases.ts +1 -1
- package/src/core/workflow/workspaces/local-git.ts +120 -239
- package/src/core/workflow/workspaces/ownership.ts +3 -158
- package/src/core/workflow/workspaces/reclaim.ts +3 -9
- package/src/core/workflow/workspaces/recovery.ts +1 -1
- package/src/core/workflow/workspaces/state.ts +1 -5
- package/src/lib/admission-history.ts +267 -0
- package/src/lib/admission.ts +217 -19
- package/src/lib/browser/client.ts +147 -35
- package/src/lib/browser/content-checks.ts +14 -3
- package/src/lib/browser/critique.ts +113 -3
- package/src/lib/browser/geometry.ts +63 -14
- package/src/lib/browser/index.ts +5 -0
- package/src/lib/browser/page-review-contracts.ts +6 -1
- package/src/lib/browser/page-review-judge.ts +58 -13
- package/src/lib/browser/page-review-pack.ts +13 -2
- package/src/lib/browser/qa-run-contracts.ts +30 -4
- package/src/lib/browser/qa-run.ts +78 -17
- package/src/lib/browser/runts.ts +85 -37
- package/src/lib/browser/windows-chrome.ts +50 -0
- package/src/lib/browser/windows-login-sync.ts +275 -0
- package/src/lib/cookies/client.ts +62 -10
- package/src/lib/cookies/index.ts +3 -0
- package/src/lib/docs-metadata-sync.ts +44 -2
- package/src/lib/durable-job.ts +15 -0
- package/src/lib/http/client.ts +63 -1
- package/src/lib/instructions/templates.ts +4 -4
- package/dist/core/agents/state/process-handle.d.ts +0 -26
- package/dist/core/agents/state/process-handle.d.ts.map +0 -1
- package/dist/core/agents/state/process-handle.js +0 -121
- package/dist/core/workflow/recovery-audit.d.ts +0 -30
- package/dist/core/workflow/recovery-audit.d.ts.map +0 -1
- package/dist/core/workflow/recovery-audit.js +0 -116
- package/src/core/agents/state/process-handle.ts +0 -165
- package/src/core/workflow/recovery-audit.ts +0 -181
|
@@ -45,8 +45,49 @@ interface RecoveryShape {
|
|
|
45
45
|
elapsed_upper_bound_ms?: ObservationShape;
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
+
type EventVariantSchema = (typeof EventV3Schema.anyOf)[number];
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Every union member requires its own literal `event_type`, so only the member
|
|
52
|
+
* named by a value's `event_type` can accept it. Checking that one member gives
|
|
53
|
+
* the union's verdict without trying the other variants, which more than halves
|
|
54
|
+
* a full-ledger read. Returns null, falling back to the whole union, if a member
|
|
55
|
+
* ever stops carrying a required unique literal.
|
|
56
|
+
*/
|
|
57
|
+
let eventVariantsByType: Map<string, EventVariantSchema> | null | undefined;
|
|
58
|
+
|
|
59
|
+
function eventVariantsByTypeV3(): Map<string, EventVariantSchema> | null {
|
|
60
|
+
if (eventVariantsByType !== undefined) return eventVariantsByType;
|
|
61
|
+
const variants = new Map<string, EventVariantSchema>();
|
|
62
|
+
for (const variant of EventV3Schema.anyOf) {
|
|
63
|
+
const literal = (variant.properties as Record<string, { const?: unknown }>).event_type?.const;
|
|
64
|
+
if (
|
|
65
|
+
typeof literal !== "string" ||
|
|
66
|
+
!(variant.required as readonly string[] | undefined)?.includes("event_type") ||
|
|
67
|
+
variants.has(literal)
|
|
68
|
+
) {
|
|
69
|
+
eventVariantsByType = null;
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
variants.set(literal, variant);
|
|
73
|
+
}
|
|
74
|
+
eventVariantsByType = variants;
|
|
75
|
+
return variants;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function checkEventSchemaV3(value: unknown): boolean {
|
|
79
|
+
const variants = eventVariantsByTypeV3();
|
|
80
|
+
if (!variants) return Value.Check(EventV3Schema, value);
|
|
81
|
+
const eventType =
|
|
82
|
+
typeof value === "object" && value !== null
|
|
83
|
+
? (value as { event_type?: unknown }).event_type
|
|
84
|
+
: undefined;
|
|
85
|
+
const variant = typeof eventType === "string" ? variants.get(eventType) : undefined;
|
|
86
|
+
return variant !== undefined && Value.Check(variant, value);
|
|
87
|
+
}
|
|
88
|
+
|
|
48
89
|
export function validateEventV3(value: unknown): EventV3ValidationResult {
|
|
49
|
-
if (!
|
|
90
|
+
if (!checkEventSchemaV3(value)) {
|
|
50
91
|
return {
|
|
51
92
|
ok: false,
|
|
52
93
|
issues: [...Value.Errors(EventV3Schema, value)]
|
|
@@ -63,13 +63,12 @@ export {
|
|
|
63
63
|
type RunGovernorServiceDaemonInput,
|
|
64
64
|
type RunGovernorServiceSweepInput,
|
|
65
65
|
readGovernorServiceConfig,
|
|
66
|
-
readGovernorServiceRuntime,
|
|
67
|
-
readGovernorServiceStatus,
|
|
68
66
|
requestGovernorServiceStop,
|
|
69
67
|
runGovernorServiceDaemon,
|
|
70
68
|
runGovernorServiceSweep,
|
|
71
69
|
spawnGovernorService,
|
|
72
70
|
} from "./service.ts";
|
|
71
|
+
export { readGovernorServiceRuntime, readGovernorServiceStatus } from "./service-read.ts";
|
|
73
72
|
export {
|
|
74
73
|
assertGovernorId,
|
|
75
74
|
type CreateGovernorInput,
|
|
@@ -1,21 +1,20 @@
|
|
|
1
1
|
import { closeSync, existsSync, openSync, readFileSync, readSync, statSync } from "node:fs";
|
|
2
|
-
import { hostname } from "node:os";
|
|
3
2
|
import { join, resolve } from "node:path";
|
|
4
3
|
import { gunzipSync } from "node:zlib";
|
|
5
4
|
import { coordEnv } from "../../lib/env.ts";
|
|
5
|
+
import type { ProcessGenerationVerdict } from "../agents/state/proc-start.ts";
|
|
6
6
|
import {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
7
|
+
observeServiceProcess,
|
|
8
|
+
type ServiceLiveness,
|
|
9
|
+
type ServiceProcessDependencies,
|
|
10
|
+
} from "../agents/state/service-process.ts";
|
|
11
|
+
import { serviceBuildStatus } from "../events/v3/runtime-build.ts";
|
|
11
12
|
import { createStorageCatalog } from "../storage/catalog.ts";
|
|
12
13
|
import type { HarneryLogRecordV1 } from "../storage/jsonl.ts";
|
|
13
14
|
import { parseLogRecord } from "../storage/jsonl.ts";
|
|
14
15
|
import { familyLogDirectory, readSegmentManifest } from "../storage/segments.ts";
|
|
15
16
|
import type {
|
|
16
|
-
GovernorServiceBuild,
|
|
17
17
|
GovernorServiceConfig,
|
|
18
|
-
GovernorServiceProcessDependencies,
|
|
19
18
|
GovernorServiceRuntime,
|
|
20
19
|
GovernorServiceStatus,
|
|
21
20
|
GovernorServiceStatusRecord,
|
|
@@ -25,106 +24,6 @@ const MAX_FILE_BYTES = 512 * 1024;
|
|
|
25
24
|
const MAX_LOG_RECORDS = 10_000;
|
|
26
25
|
const MAX_GOALS = 100;
|
|
27
26
|
|
|
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
|
-
}
|
|
127
|
-
|
|
128
27
|
export function readGovernorServiceConfig(coordRootRaw: string): GovernorServiceConfig {
|
|
129
28
|
const coordRoot = resolve(coordRootRaw);
|
|
130
29
|
const config = readBoundedJson<GovernorServiceConfig>(
|
|
@@ -175,7 +74,7 @@ export function readGovernorServiceRuntime(
|
|
|
175
74
|
|
|
176
75
|
export function readGovernorServiceStatus(
|
|
177
76
|
coordRootRaw: string,
|
|
178
|
-
dependencies:
|
|
77
|
+
dependencies: ServiceProcessDependencies = {},
|
|
179
78
|
): GovernorServiceStatus {
|
|
180
79
|
const coordRoot = resolve(coordRootRaw);
|
|
181
80
|
const record = readStatusRecord(coordRoot);
|
|
@@ -191,15 +90,17 @@ export function readGovernorServiceStatus(
|
|
|
191
90
|
} catch {
|
|
192
91
|
// Runtime is recoverable and must not make the dashboard unreadable.
|
|
193
92
|
}
|
|
194
|
-
const observation =
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
93
|
+
const observation: { generation: ProcessGenerationVerdict; liveness: ServiceLiveness } = record
|
|
94
|
+
? observeServiceProcess(record, dependencies)
|
|
95
|
+
: {
|
|
96
|
+
generation: "unknown",
|
|
97
|
+
liveness: existsSync(join(serviceDir(coordRoot), "status.json")) ? "unknown" : "stopped",
|
|
98
|
+
};
|
|
198
99
|
return {
|
|
199
|
-
running: observation.liveness === "running"
|
|
100
|
+
running: observation.liveness === "running",
|
|
200
101
|
stale: observation.liveness === "stopped" && record !== undefined && record.state !== "stopped",
|
|
201
102
|
...observation,
|
|
202
|
-
...
|
|
103
|
+
...serviceBuildStatus(record?.running_build),
|
|
203
104
|
record,
|
|
204
105
|
config,
|
|
205
106
|
runtime,
|
|
@@ -10,30 +10,28 @@ import {
|
|
|
10
10
|
renameSync,
|
|
11
11
|
rmSync,
|
|
12
12
|
statSync,
|
|
13
|
-
unlinkSync,
|
|
14
13
|
writeFileSync,
|
|
15
14
|
} from "node:fs";
|
|
16
15
|
import { hostname } from "node:os";
|
|
17
16
|
import { dirname, join, resolve } from "node:path";
|
|
18
17
|
import { coordEnv } from "../../lib/env.ts";
|
|
18
|
+
import { type ProcessGenerationVerdict, processStartToken } from "../agents/state/proc-start.ts";
|
|
19
19
|
import {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
20
|
+
acquireServiceLease,
|
|
21
|
+
requestServiceStop,
|
|
22
|
+
type ServiceLiveness,
|
|
23
|
+
type ServiceProcessDependencies,
|
|
24
|
+
serviceStartRefusal,
|
|
25
|
+
} from "../agents/state/service-process.ts";
|
|
25
26
|
import { resolveBinName } from "../config.ts";
|
|
27
|
+
import { installedHarneryBuild } from "../events/v3/runtime-build.ts";
|
|
26
28
|
import { type NormalizedPolicy, normalizePolicy, type PolicyIsolation } from "../policy/index.ts";
|
|
27
29
|
import { closeProcessLoggers, legacyLogFields, processLogger } from "../storage/logger.ts";
|
|
28
30
|
import { stateDirMode, stateFileMode } from "../storage/modes.ts";
|
|
29
31
|
import { RotatingTextSink } from "../storage/process-log.ts";
|
|
30
32
|
import type { RunWorkItemInput } from "../work/index.ts";
|
|
31
33
|
import { type GovernorRunReport, type GovernorStopReason, runGovernor } from "./runner.ts";
|
|
32
|
-
import {
|
|
33
|
-
currentGovernorServiceBuild,
|
|
34
|
-
governorServiceBuildStatus,
|
|
35
|
-
observeGovernorServiceProcess,
|
|
36
|
-
} from "./service-read.ts";
|
|
34
|
+
import { readGovernorServiceRuntime, readGovernorServiceStatus } from "./service-read.ts";
|
|
37
35
|
import { type GovernorRecord, readGovernor } from "./state.ts";
|
|
38
36
|
|
|
39
37
|
export const GOVERNOR_SERVICE_CONFIG_SCHEMA_VERSION = 1 as const;
|
|
@@ -101,10 +99,6 @@ export interface GovernorServiceBuild {
|
|
|
101
99
|
revision: string;
|
|
102
100
|
}
|
|
103
101
|
|
|
104
|
-
export interface GovernorServiceProcessDependencies extends ProcessGenerationSignalDependencies {
|
|
105
|
-
probePid?: (pid: number) => boolean;
|
|
106
|
-
}
|
|
107
|
-
|
|
108
102
|
export interface GovernorServiceStatusRecord {
|
|
109
103
|
schema_version: typeof GOVERNOR_SERVICE_STATUS_SCHEMA_VERSION;
|
|
110
104
|
pid: number;
|
|
@@ -173,27 +167,13 @@ export interface RunGovernorServiceDaemonInput {
|
|
|
173
167
|
onLog?: (line: string) => void;
|
|
174
168
|
}
|
|
175
169
|
|
|
176
|
-
interface ServiceLease {
|
|
177
|
-
pid: number;
|
|
178
|
-
start_token?: string;
|
|
179
|
-
running_build?: GovernorServiceBuild;
|
|
180
|
-
host: string;
|
|
181
|
-
nonce: string;
|
|
182
|
-
created_at: string;
|
|
183
|
-
}
|
|
184
|
-
|
|
185
170
|
export function configureGovernorService(
|
|
186
171
|
input: ConfigureGovernorServiceInput,
|
|
187
172
|
): GovernorServiceConfig {
|
|
188
173
|
const coordRoot = resolve(input.coordRoot);
|
|
189
|
-
const
|
|
190
|
-
if (
|
|
191
|
-
throw
|
|
192
|
-
}
|
|
193
|
-
if (active) {
|
|
194
|
-
const liveness = statusOwnerIsLive(active);
|
|
195
|
-
if (liveness !== "stopped") throw serviceStartRefusal(coordRoot, active.pid, liveness);
|
|
196
|
-
}
|
|
174
|
+
const current = readGovernorServiceStatus(coordRoot);
|
|
175
|
+
if (current.liveness !== "stopped")
|
|
176
|
+
throw startRefusal(coordRoot, current.record?.pid, current.liveness);
|
|
197
177
|
const goalIds = unique(input.goalIds.map((value) => value.trim()).filter(Boolean));
|
|
198
178
|
if (goalIds.length < 1 || goalIds.length > MAX_GOALS) {
|
|
199
179
|
throw new Error(`governor service requires from 1 to ${MAX_GOALS} unique goal ids`);
|
|
@@ -249,65 +229,12 @@ export function readGovernorServiceConfig(coordRootRaw: string): GovernorService
|
|
|
249
229
|
return config;
|
|
250
230
|
}
|
|
251
231
|
|
|
252
|
-
export function readGovernorServiceRuntime(
|
|
253
|
-
coordRootRaw: string,
|
|
254
|
-
): GovernorServiceRuntime | undefined {
|
|
255
|
-
const coordRoot = resolve(coordRootRaw);
|
|
256
|
-
if (!existsSync(serviceRuntimePath(coordRoot))) return undefined;
|
|
257
|
-
const runtime = readBoundedJson<GovernorServiceRuntime>(
|
|
258
|
-
serviceRuntimePath(coordRoot),
|
|
259
|
-
"governor service runtime",
|
|
260
|
-
);
|
|
261
|
-
if (
|
|
262
|
-
runtime.schema_version !== GOVERNOR_SERVICE_RUNTIME_SCHEMA_VERSION ||
|
|
263
|
-
!validTimestamp(runtime.config_created_at) ||
|
|
264
|
-
!validTimestamp(runtime.updated_at) ||
|
|
265
|
-
!runtime.goals ||
|
|
266
|
-
typeof runtime.goals !== "object" ||
|
|
267
|
-
Array.isArray(runtime.goals)
|
|
268
|
-
) {
|
|
269
|
-
throw new Error("governor service runtime has an unsupported schema");
|
|
270
|
-
}
|
|
271
|
-
return runtime;
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
export function readGovernorServiceStatus(
|
|
275
|
-
coordRootRaw: string,
|
|
276
|
-
dependencies: GovernorServiceProcessDependencies = {},
|
|
277
|
-
): GovernorServiceStatus {
|
|
278
|
-
const coordRoot = resolve(coordRootRaw);
|
|
279
|
-
const record = readStatusRecord(coordRoot);
|
|
280
|
-
let config: GovernorServiceConfig | undefined;
|
|
281
|
-
let runtime: GovernorServiceRuntime | undefined;
|
|
282
|
-
try {
|
|
283
|
-
config = readGovernorServiceConfig(coordRoot);
|
|
284
|
-
} catch {
|
|
285
|
-
// A service has no usable configuration until explicitly configured.
|
|
286
|
-
}
|
|
287
|
-
try {
|
|
288
|
-
runtime = readGovernorServiceRuntime(coordRoot);
|
|
289
|
-
} catch {
|
|
290
|
-
// Status remains readable when recoverable runtime state is corrupt.
|
|
291
|
-
}
|
|
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
|
-
};
|
|
303
|
-
}
|
|
304
|
-
|
|
305
232
|
export async function spawnGovernorService(coordRootRaw: string): Promise<GovernorServiceStatus> {
|
|
306
233
|
const coordRoot = resolve(coordRootRaw);
|
|
307
234
|
readGovernorServiceConfig(coordRoot);
|
|
308
235
|
const current = readGovernorServiceStatus(coordRoot);
|
|
309
236
|
if (current.liveness !== "stopped") {
|
|
310
|
-
throw
|
|
237
|
+
throw startRefusal(coordRoot, current.record?.pid, current.liveness);
|
|
311
238
|
}
|
|
312
239
|
mkdirSync(serviceDir(coordRoot), { recursive: true, mode: stateDirMode() });
|
|
313
240
|
const sharedLogs = coordEnv("SHARED_LOGS") !== "0";
|
|
@@ -347,31 +274,19 @@ export async function spawnGovernorService(coordRootRaw: string): Promise<Govern
|
|
|
347
274
|
|
|
348
275
|
export function requestGovernorServiceStop(
|
|
349
276
|
coordRootRaw: string,
|
|
350
|
-
dependencies:
|
|
277
|
+
dependencies: ServiceProcessDependencies = {},
|
|
351
278
|
): GovernorServiceStatus {
|
|
352
279
|
const coordRoot = resolve(coordRootRaw);
|
|
353
280
|
const status = readGovernorServiceStatus(coordRoot, dependencies);
|
|
354
281
|
if ((status.record?.state === "stopped" || !status.record) && status.liveness === "stopped") {
|
|
355
282
|
return { ...status, signal_sent: false, signal_verdict: status.generation };
|
|
356
283
|
}
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
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
|
-
};
|
|
284
|
+
return requestServiceStop(
|
|
285
|
+
status,
|
|
286
|
+
() => readGovernorServiceStatus(coordRoot, dependencies),
|
|
287
|
+
serviceStopPath(coordRoot),
|
|
288
|
+
dependencies,
|
|
289
|
+
);
|
|
375
290
|
}
|
|
376
291
|
|
|
377
292
|
export async function runGovernorServiceSweep(
|
|
@@ -519,22 +434,21 @@ export async function runGovernorServiceDaemon(
|
|
|
519
434
|
): Promise<GovernorServiceStatusRecord> {
|
|
520
435
|
const coordRoot = resolve(input.coordRoot);
|
|
521
436
|
const config = readGovernorServiceConfig(coordRoot);
|
|
522
|
-
const
|
|
523
|
-
if (
|
|
524
|
-
throw
|
|
525
|
-
}
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
const release = acquireServiceLease(coordRoot);
|
|
437
|
+
const current = readGovernorServiceStatus(coordRoot);
|
|
438
|
+
if (current.liveness !== "stopped")
|
|
439
|
+
throw startRefusal(coordRoot, current.record?.pid, current.liveness);
|
|
440
|
+
const { release } = acquireServiceLease(
|
|
441
|
+
serviceLeasePath(coordRoot),
|
|
442
|
+
(pid, liveness) => startRefusal(coordRoot, pid, liveness),
|
|
443
|
+
{ running_build: installedHarneryBuild() },
|
|
444
|
+
);
|
|
531
445
|
rmSync(serviceStopPath(coordRoot), { force: true });
|
|
532
446
|
const now = new Date().toISOString();
|
|
533
447
|
const status: GovernorServiceStatusRecord = {
|
|
534
448
|
schema_version: GOVERNOR_SERVICE_STATUS_SCHEMA_VERSION,
|
|
535
449
|
pid: process.pid,
|
|
536
450
|
start_token: processStartToken(process.pid) ?? undefined,
|
|
537
|
-
running_build:
|
|
451
|
+
running_build: installedHarneryBuild(),
|
|
538
452
|
host: hostname(),
|
|
539
453
|
nonce: randomUUID(),
|
|
540
454
|
state: "starting",
|
|
@@ -781,114 +695,16 @@ function normalizeIsolation(value: unknown): PolicyIsolation | undefined {
|
|
|
781
695
|
throw new Error("service isolation must be shared, worktree, sandbox, or remote");
|
|
782
696
|
}
|
|
783
697
|
|
|
784
|
-
function
|
|
785
|
-
const path = serviceLeasePath(coordRoot);
|
|
786
|
-
mkdirSync(dirname(path), { recursive: true, mode: stateDirMode() });
|
|
787
|
-
const owner: ServiceLease = {
|
|
788
|
-
pid: process.pid,
|
|
789
|
-
start_token: processStartToken(process.pid) ?? undefined,
|
|
790
|
-
running_build: currentGovernorServiceBuild(),
|
|
791
|
-
host: hostname(),
|
|
792
|
-
nonce: randomUUID(),
|
|
793
|
-
created_at: new Date().toISOString(),
|
|
794
|
-
};
|
|
795
|
-
const acquire = (): boolean => {
|
|
796
|
-
try {
|
|
797
|
-
const fd = openSync(path, "wx", stateFileMode());
|
|
798
|
-
try {
|
|
799
|
-
writeFileSync(fd, `${JSON.stringify(owner)}\n`, "utf8");
|
|
800
|
-
} finally {
|
|
801
|
-
closeSync(fd);
|
|
802
|
-
}
|
|
803
|
-
return true;
|
|
804
|
-
} catch (error) {
|
|
805
|
-
if ((error as NodeJS.ErrnoException).code !== "EEXIST") throw error;
|
|
806
|
-
return false;
|
|
807
|
-
}
|
|
808
|
-
};
|
|
809
|
-
if (!acquire()) {
|
|
810
|
-
const existing = readLease(path);
|
|
811
|
-
const liveness = existing ? leaseOwnerIsLive(existing) : "unknown";
|
|
812
|
-
if (liveness !== "stopped") {
|
|
813
|
-
throw serviceStartRefusal(coordRoot, existing?.pid, liveness);
|
|
814
|
-
}
|
|
815
|
-
unlinkSync(path);
|
|
816
|
-
if (!acquire()) throw new Error("governor service lease raced with another process");
|
|
817
|
-
}
|
|
818
|
-
return () => {
|
|
819
|
-
try {
|
|
820
|
-
const existing = readLease(path);
|
|
821
|
-
if (existing?.nonce === owner.nonce) unlinkSync(path);
|
|
822
|
-
} catch {
|
|
823
|
-
// A stale lease is recoverable by the next explicit service start.
|
|
824
|
-
}
|
|
825
|
-
};
|
|
826
|
-
}
|
|
827
|
-
|
|
828
|
-
function readLease(path: string): ServiceLease | null {
|
|
829
|
-
try {
|
|
830
|
-
const value = JSON.parse(readFileSync(path, "utf8")) as Partial<ServiceLease>;
|
|
831
|
-
return Number.isSafeInteger(value.pid) &&
|
|
832
|
-
(value.pid ?? 0) > 0 &&
|
|
833
|
-
typeof value.host === "string" &&
|
|
834
|
-
typeof value.nonce === "string" &&
|
|
835
|
-
typeof value.created_at === "string"
|
|
836
|
-
? ({
|
|
837
|
-
...value,
|
|
838
|
-
start_token: typeof value.start_token === "string" ? value.start_token : undefined,
|
|
839
|
-
} as ServiceLease)
|
|
840
|
-
: null;
|
|
841
|
-
} catch {
|
|
842
|
-
return null;
|
|
843
|
-
}
|
|
844
|
-
}
|
|
845
|
-
|
|
846
|
-
function leaseOwnerIsLive(lease: ServiceLease): GovernorServiceStatus["liveness"] {
|
|
847
|
-
return observeGovernorServiceProcess({ ...lease, state: "running" }).liveness;
|
|
848
|
-
}
|
|
849
|
-
|
|
850
|
-
function readStatusRecord(coordRoot: string): GovernorServiceStatusRecord | undefined {
|
|
851
|
-
if (!existsSync(serviceStatusPath(coordRoot))) return undefined;
|
|
852
|
-
try {
|
|
853
|
-
const value = readBoundedJson<GovernorServiceStatusRecord>(
|
|
854
|
-
serviceStatusPath(coordRoot),
|
|
855
|
-
"governor service status",
|
|
856
|
-
);
|
|
857
|
-
if (
|
|
858
|
-
value.schema_version !== GOVERNOR_SERVICE_STATUS_SCHEMA_VERSION ||
|
|
859
|
-
!Number.isSafeInteger(value.pid) ||
|
|
860
|
-
value.pid < 1 ||
|
|
861
|
-
typeof value.host !== "string" ||
|
|
862
|
-
typeof value.nonce !== "string" ||
|
|
863
|
-
!validTimestamp(value.started_at) ||
|
|
864
|
-
!validTimestamp(value.heartbeat_at)
|
|
865
|
-
) {
|
|
866
|
-
return undefined;
|
|
867
|
-
}
|
|
868
|
-
return {
|
|
869
|
-
...value,
|
|
870
|
-
start_token: typeof value.start_token === "string" ? value.start_token : undefined,
|
|
871
|
-
};
|
|
872
|
-
} catch {
|
|
873
|
-
return undefined;
|
|
874
|
-
}
|
|
875
|
-
}
|
|
876
|
-
|
|
877
|
-
function statusOwnerIsLive(record: GovernorServiceStatusRecord): GovernorServiceStatus["liveness"] {
|
|
878
|
-
return observeGovernorServiceProcess(record).liveness;
|
|
879
|
-
}
|
|
880
|
-
|
|
881
|
-
function serviceStartRefusal(
|
|
698
|
+
function startRefusal(
|
|
882
699
|
coordRoot: string,
|
|
883
700
|
pid: number | undefined,
|
|
884
|
-
liveness:
|
|
701
|
+
liveness: ServiceLiveness,
|
|
885
702
|
): Error {
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
`${reason}; run \`${resolveBinName(coordRoot)} governor service stop\` to write a durable stop request before starting or reconfiguring it`,
|
|
703
|
+
return serviceStartRefusal(
|
|
704
|
+
"governor service",
|
|
705
|
+
`${resolveBinName(coordRoot)} governor service stop`,
|
|
706
|
+
pid,
|
|
707
|
+
liveness,
|
|
892
708
|
);
|
|
893
709
|
}
|
|
894
710
|
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { closeSync, constants, fstatSync, lstatSync, openSync, readSync } from "node:fs";
|
|
3
|
+
import { discoverCodexSessionTranscript } from "./runtime-telemetry.ts";
|
|
4
|
+
|
|
5
|
+
export type NativeParentObservation =
|
|
6
|
+
| { state: "observed"; parent_session_id: string; source_witness: string }
|
|
7
|
+
| { state: "none"; reason: string; source_witness: string }
|
|
8
|
+
| { state: "unknown"; reason: string };
|
|
9
|
+
|
|
10
|
+
export interface NativeParentRequest {
|
|
11
|
+
adapter: string;
|
|
12
|
+
native_session_id?: string;
|
|
13
|
+
transcript_path?: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface NativeParentReaderOptions {
|
|
17
|
+
discoverTranscript?: (sessionId: string, transcriptPath?: string) => string | undefined;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const HEADER_LIMIT = 1024 * 1024;
|
|
21
|
+
const NATIVE_ID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
22
|
+
const ORDINARY_SOURCES = new Set(["cli", "exec", "vscode", "app-server"]);
|
|
23
|
+
|
|
24
|
+
function object(value: unknown): Record<string, unknown> | undefined {
|
|
25
|
+
return value !== null && typeof value === "object" && !Array.isArray(value)
|
|
26
|
+
? (value as Record<string, unknown>)
|
|
27
|
+
: undefined;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** Native relationship evidence only; no prompt bodies, state writes or recipient authority. */
|
|
31
|
+
export function readNativeParentReference(
|
|
32
|
+
request: NativeParentRequest,
|
|
33
|
+
options: NativeParentReaderOptions = {},
|
|
34
|
+
): NativeParentObservation {
|
|
35
|
+
if (request.adapter !== "codex") return unknown("native_parent_adapter_unsupported");
|
|
36
|
+
const child = request.native_session_id;
|
|
37
|
+
if (!child || !NATIVE_ID.test(child)) return unknown("native_child_identity_unavailable");
|
|
38
|
+
let fd: number | undefined;
|
|
39
|
+
try {
|
|
40
|
+
const path = (options.discoverTranscript ?? discoverCodexSessionTranscript)(
|
|
41
|
+
child,
|
|
42
|
+
request.transcript_path,
|
|
43
|
+
);
|
|
44
|
+
if (!path) return unknown("native_parent_transcript_unavailable");
|
|
45
|
+
const entry = lstatSync(path);
|
|
46
|
+
if (!entry.isFile() || entry.isSymbolicLink()) return unknown("native_parent_not_regular");
|
|
47
|
+
fd = openSync(path, constants.O_RDONLY | (constants.O_NOFOLLOW ?? 0));
|
|
48
|
+
const before = fstatSync(fd);
|
|
49
|
+
if (!before.isFile() || before.dev !== entry.dev || before.ino !== entry.ino) {
|
|
50
|
+
return unknown("native_parent_file_changed");
|
|
51
|
+
}
|
|
52
|
+
const header = readHeader(fd);
|
|
53
|
+
if (!header) return unknown("native_parent_header_incomplete");
|
|
54
|
+
// Appending later rows is normal. The same header must remain unchanged.
|
|
55
|
+
const checked = readHeader(fd);
|
|
56
|
+
const after = lstatSync(path);
|
|
57
|
+
if (
|
|
58
|
+
!checked?.equals(header) ||
|
|
59
|
+
!after.isFile() ||
|
|
60
|
+
after.isSymbolicLink() ||
|
|
61
|
+
before.dev !== after.dev ||
|
|
62
|
+
before.ino !== after.ino
|
|
63
|
+
) {
|
|
64
|
+
return unknown("native_parent_file_changed");
|
|
65
|
+
}
|
|
66
|
+
const row = object(JSON.parse(header.toString("utf8")));
|
|
67
|
+
const payload = object(row?.payload);
|
|
68
|
+
if (row?.type !== "session_meta" || payload?.id !== child) {
|
|
69
|
+
return unknown("native_parent_child_mismatch");
|
|
70
|
+
}
|
|
71
|
+
const source_witness = createHash("sha256").update(header).digest("hex");
|
|
72
|
+
if (typeof payload.source === "string" && ORDINARY_SOURCES.has(payload.source)) {
|
|
73
|
+
return { state: "none", reason: "native_ordinary_session", source_witness };
|
|
74
|
+
}
|
|
75
|
+
const source = object(payload.source);
|
|
76
|
+
const spawn = object(object(source?.subagent)?.thread_spawn);
|
|
77
|
+
const parent = spawn?.parent_thread_id;
|
|
78
|
+
if (typeof parent !== "string" || !NATIVE_ID.test(parent)) {
|
|
79
|
+
return unknown("native_parent_not_reported");
|
|
80
|
+
}
|
|
81
|
+
if (parent.toLowerCase() === child.toLowerCase())
|
|
82
|
+
return unknown("native_parent_self_reference");
|
|
83
|
+
return { state: "observed", parent_session_id: parent, source_witness };
|
|
84
|
+
} catch {
|
|
85
|
+
return unknown("native_parent_metadata_unreadable");
|
|
86
|
+
} finally {
|
|
87
|
+
if (fd !== undefined) closeSync(fd);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function readHeader(fd: number): Buffer | undefined {
|
|
92
|
+
const chunks: Buffer[] = [];
|
|
93
|
+
let offset = 0;
|
|
94
|
+
while (offset < HEADER_LIMIT) {
|
|
95
|
+
const buffer = Buffer.alloc(Math.min(16 * 1024, HEADER_LIMIT - offset));
|
|
96
|
+
const bytes = readSync(fd, buffer, 0, buffer.length, offset);
|
|
97
|
+
if (bytes === 0) return undefined;
|
|
98
|
+
const data = buffer.subarray(0, bytes);
|
|
99
|
+
const newline = data.indexOf(10);
|
|
100
|
+
if (newline >= 0) return Buffer.concat([...chunks, data.subarray(0, newline)]);
|
|
101
|
+
chunks.push(data);
|
|
102
|
+
offset += bytes;
|
|
103
|
+
}
|
|
104
|
+
return undefined;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function unknown(reason: string): NativeParentObservation {
|
|
108
|
+
return { state: "unknown", reason };
|
|
109
|
+
}
|