harnery 0.45.0 → 0.45.2

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.
Files changed (235) hide show
  1. package/dist/commander.js +0 -1
  2. package/dist/commands/files.d.ts +2 -2
  3. package/dist/commands/files.d.ts.map +1 -1
  4. package/dist/commands/files.js +4 -7
  5. package/dist/commands/servers.d.ts.map +1 -1
  6. package/dist/commands/servers.js +17 -11
  7. package/dist/commands/tunnel.d.ts +0 -15
  8. package/dist/commands/tunnel.d.ts.map +1 -1
  9. package/dist/commands/tunnel.js +153 -453
  10. package/dist/commands/workflow.d.ts.map +1 -1
  11. package/dist/commands/workflow.js +6 -45
  12. package/dist/core/agents/coord-client.d.ts.map +1 -1
  13. package/dist/core/agents/coord-client.js +0 -18
  14. package/dist/core/agents/live-authority-v3.d.ts +0 -1
  15. package/dist/core/agents/live-authority-v3.d.ts.map +1 -1
  16. package/dist/core/agents/live-authority-v3.js +0 -11
  17. package/dist/core/agents/live-lifecycle-v3.d.ts +2 -4
  18. package/dist/core/agents/live-lifecycle-v3.d.ts.map +1 -1
  19. package/dist/core/agents/live-lifecycle-v3.js +7 -28
  20. package/dist/core/agents/state/heartbeat-writer.d.ts.map +1 -1
  21. package/dist/core/agents/state/heartbeat-writer.js +14 -25
  22. package/dist/core/agents/state/live-coordination-writer.d.ts.map +1 -1
  23. package/dist/core/agents/state/live-coordination-writer.js +0 -5
  24. package/dist/core/agents/state/pidmap.d.ts.map +1 -1
  25. package/dist/core/agents/state/pidmap.js +2 -7
  26. package/dist/core/agents/state/proc-start.d.ts +20 -31
  27. package/dist/core/agents/state/proc-start.d.ts.map +1 -1
  28. package/dist/core/agents/state/proc-start.js +37 -70
  29. package/dist/core/agents/state/service-process.d.ts +61 -0
  30. package/dist/core/agents/state/service-process.d.ts.map +1 -0
  31. package/dist/core/agents/state/service-process.js +143 -0
  32. package/dist/core/agents/state/stale-sweep.d.ts.map +1 -1
  33. package/dist/core/agents/state/stale-sweep.js +70 -180
  34. package/dist/core/artifacts/activity.d.ts +4 -1
  35. package/dist/core/artifacts/activity.d.ts.map +1 -1
  36. package/dist/core/artifacts/activity.js +8 -2
  37. package/dist/core/artifacts/delivery-card.d.ts.map +1 -1
  38. package/dist/core/artifacts/delivery-card.js +1 -2
  39. package/dist/core/artifacts/index.d.ts.map +1 -1
  40. package/dist/core/artifacts/index.js +38 -12
  41. package/dist/core/context/index.d.ts +6 -4
  42. package/dist/core/context/index.d.ts.map +1 -1
  43. package/dist/core/context/index.js +194 -256
  44. package/dist/core/context/observation.d.ts +9 -9
  45. package/dist/core/context/observation.d.ts.map +1 -1
  46. package/dist/core/context/observation.js +37 -6
  47. package/dist/core/events/v3/authority-outbox.d.ts.map +1 -1
  48. package/dist/core/events/v3/authority-outbox.js +0 -29
  49. package/dist/core/events/v3/producers/coordination-recorder.d.ts +0 -1
  50. package/dist/core/events/v3/producers/coordination-recorder.d.ts.map +1 -1
  51. package/dist/core/events/v3/producers/coordination-recorder.js +0 -7
  52. package/dist/core/events/v3/producers/recorder.d.ts.map +1 -1
  53. package/dist/core/events/v3/producers/recorder.js +64 -77
  54. package/dist/core/events/v3/runtime-build.d.ts +14 -0
  55. package/dist/core/events/v3/runtime-build.d.ts.map +1 -1
  56. package/dist/core/events/v3/runtime-build.js +44 -0
  57. package/dist/core/governor/index.d.ts +2 -1
  58. package/dist/core/governor/index.d.ts.map +1 -1
  59. package/dist/core/governor/index.js +2 -1
  60. package/dist/core/governor/service-read.d.ts +3 -10
  61. package/dist/core/governor/service-read.d.ts.map +1 -1
  62. package/dist/core/governor/service-read.js +10 -94
  63. package/dist/core/governor/service.d.ts +3 -7
  64. package/dist/core/governor/service.d.ts.map +1 -1
  65. package/dist/core/governor/service.js +17 -182
  66. package/dist/core/hooks/cli.js +78 -48
  67. package/dist/core/hooks/resolve/owner.js +2 -2
  68. package/dist/core/resources/sampler.js +2 -2
  69. package/dist/core/resources/service-status.d.ts +2 -14
  70. package/dist/core/resources/service-status.d.ts.map +1 -1
  71. package/dist/core/resources/service-status.js +3 -23
  72. package/dist/core/resources/service.d.ts +3 -7
  73. package/dist/core/resources/service.d.ts.map +1 -1
  74. package/dist/core/resources/service.js +12 -101
  75. package/dist/core/resources/status.d.ts.map +1 -1
  76. package/dist/core/resources/status.js +3 -2
  77. package/dist/core/semantic/service.d.ts.map +1 -1
  78. package/dist/core/semantic/service.js +9 -2
  79. package/dist/core/servers/index.d.ts +11 -10
  80. package/dist/core/servers/index.d.ts.map +1 -1
  81. package/dist/core/servers/index.js +82 -192
  82. package/dist/core/servers/tunnels.d.ts +3 -5
  83. package/dist/core/servers/tunnels.d.ts.map +1 -1
  84. package/dist/core/servers/tunnels.js +2 -45
  85. package/dist/core/storage/durable-history.js +56 -22
  86. package/dist/core/storage/segments.d.ts.map +1 -1
  87. package/dist/core/storage/segments.js +73 -17
  88. package/dist/core/supervisor/service.d.ts +4 -7
  89. package/dist/core/supervisor/service.d.ts.map +1 -1
  90. package/dist/core/supervisor/service.js +23 -122
  91. package/dist/core/supervisor/services.js +2 -2
  92. package/dist/core/supervisor/status.d.ts +3 -17
  93. package/dist/core/supervisor/status.d.ts.map +1 -1
  94. package/dist/core/supervisor/status.js +7 -70
  95. package/dist/core/work/state.js +7 -7
  96. package/dist/core/workflow/engine.d.ts.map +1 -1
  97. package/dist/core/workflow/engine.js +23 -66
  98. package/dist/core/workflow/index.d.ts +3 -4
  99. package/dist/core/workflow/index.d.ts.map +1 -1
  100. package/dist/core/workflow/index.js +2 -2
  101. package/dist/core/workflow/live-session-v3.d.ts +0 -6
  102. package/dist/core/workflow/live-session-v3.d.ts.map +1 -1
  103. package/dist/core/workflow/live-session-v3.js +5 -126
  104. package/dist/core/workflow/proof.d.ts +4 -4
  105. package/dist/core/workflow/proof.d.ts.map +1 -1
  106. package/dist/core/workflow/proof.js +34 -86
  107. package/dist/core/workflow/resume-lock.d.ts +17 -0
  108. package/dist/core/workflow/resume-lock.d.ts.map +1 -0
  109. package/dist/core/workflow/resume-lock.js +158 -0
  110. package/dist/core/workflow/run-state.d.ts +0 -21
  111. package/dist/core/workflow/run-state.d.ts.map +1 -1
  112. package/dist/core/workflow/run-state.js +3 -174
  113. package/dist/core/workflow/spawn-claude.d.ts.map +1 -1
  114. package/dist/core/workflow/spawn-claude.js +0 -19
  115. package/dist/core/workflow/spawn-codex.d.ts.map +1 -1
  116. package/dist/core/workflow/spawn-codex.js +0 -13
  117. package/dist/core/workflow/spawn-cursor.d.ts.map +1 -1
  118. package/dist/core/workflow/spawn-cursor.js +0 -19
  119. package/dist/core/workflow/spawn-opencode.d.ts.map +1 -1
  120. package/dist/core/workflow/spawn-opencode.js +0 -14
  121. package/dist/core/workflow/types.d.ts +11 -54
  122. package/dist/core/workflow/types.d.ts.map +1 -1
  123. package/dist/core/workflow/workspaces/leases.d.ts +4 -0
  124. package/dist/core/workflow/workspaces/leases.d.ts.map +1 -1
  125. package/dist/core/workflow/workspaces/leases.js +1 -1
  126. package/dist/core/workflow/workspaces/local-git.js +91 -210
  127. package/dist/core/workflow/workspaces/ownership.d.ts +0 -7
  128. package/dist/core/workflow/workspaces/ownership.d.ts.map +1 -1
  129. package/dist/core/workflow/workspaces/ownership.js +3 -130
  130. package/dist/core/workflow/workspaces/reclaim.d.ts +4 -7
  131. package/dist/core/workflow/workspaces/reclaim.d.ts.map +1 -1
  132. package/dist/core/workflow/workspaces/reclaim.js +0 -4
  133. package/dist/core/workflow/workspaces/recovery.d.ts +1 -1
  134. package/dist/core/workflow/workspaces/recovery.d.ts.map +1 -1
  135. package/dist/core/workflow/workspaces/recovery.js +1 -1
  136. package/dist/core/workflow/workspaces/state.d.ts.map +1 -1
  137. package/dist/core/workflow/workspaces/state.js +1 -4
  138. package/dist/lib/identities/assume.d.ts.map +1 -1
  139. package/dist/lib/identities/assume.js +0 -4
  140. package/dist/lib/tunnel/state.d.ts +6 -13
  141. package/dist/lib/tunnel/state.d.ts.map +1 -1
  142. package/dist/lib/tunnel/state.js +12 -58
  143. package/package.json +1 -6
  144. package/src/commander.ts +0 -10
  145. package/src/commands/files.ts +5 -13
  146. package/src/commands/servers.ts +23 -12
  147. package/src/commands/tunnel.ts +166 -533
  148. package/src/commands/workflow.ts +8 -58
  149. package/src/core/agents/coord-client.ts +0 -20
  150. package/src/core/agents/live-authority-v3.ts +0 -17
  151. package/src/core/agents/live-lifecycle-v3.ts +14 -52
  152. package/src/core/agents/state/heartbeat-writer.ts +12 -29
  153. package/src/core/agents/state/live-coordination-writer.ts +0 -29
  154. package/src/core/agents/state/pidmap.ts +2 -15
  155. package/src/core/agents/state/proc-start.ts +41 -69
  156. package/src/core/agents/state/service-process.ts +217 -0
  157. package/src/core/agents/state/stale-sweep.ts +82 -247
  158. package/src/core/artifacts/activity.ts +9 -2
  159. package/src/core/artifacts/delivery-card.ts +1 -7
  160. package/src/core/artifacts/index.ts +45 -12
  161. package/src/core/context/index.ts +207 -272
  162. package/src/core/context/observation.ts +40 -9
  163. package/src/core/events/v3/authority-outbox.ts +0 -56
  164. package/src/core/events/v3/producers/coordination-recorder.ts +0 -15
  165. package/src/core/events/v3/producers/recorder.ts +68 -94
  166. package/src/core/events/v3/runtime-build.ts +62 -0
  167. package/src/core/governor/index.ts +1 -2
  168. package/src/core/governor/service-read.ts +15 -114
  169. package/src/core/governor/service.ts +36 -220
  170. package/src/core/hooks/cli.ts +85 -47
  171. package/src/core/hooks/resolve/owner.ts +2 -2
  172. package/src/core/resources/sampler.ts +3 -2
  173. package/src/core/resources/service-status.ts +6 -35
  174. package/src/core/resources/service.ts +23 -133
  175. package/src/core/resources/status.ts +3 -2
  176. package/src/core/semantic/service.ts +8 -2
  177. package/src/core/servers/index.ts +86 -228
  178. package/src/core/servers/tunnels.ts +3 -55
  179. package/src/core/storage/durable-history.ts +52 -20
  180. package/src/core/storage/segments.ts +79 -17
  181. package/src/core/supervisor/service.ts +33 -155
  182. package/src/core/supervisor/services.ts +2 -2
  183. package/src/core/supervisor/status.ts +10 -85
  184. package/src/core/work/state.ts +7 -7
  185. package/src/core/workflow/engine.ts +22 -74
  186. package/src/core/workflow/index.ts +2 -13
  187. package/src/core/workflow/live-session-v3.ts +5 -146
  188. package/src/core/workflow/proof.ts +40 -104
  189. package/src/core/workflow/resume-lock.ts +179 -0
  190. package/src/core/workflow/run-state.ts +3 -207
  191. package/src/core/workflow/spawn-claude.ts +0 -18
  192. package/src/core/workflow/spawn-codex.ts +0 -13
  193. package/src/core/workflow/spawn-cursor.ts +0 -18
  194. package/src/core/workflow/spawn-opencode.ts +0 -14
  195. package/src/core/workflow/types.ts +11 -41
  196. package/src/core/workflow/workspaces/leases.ts +1 -1
  197. package/src/core/workflow/workspaces/local-git.ts +120 -239
  198. package/src/core/workflow/workspaces/ownership.ts +3 -158
  199. package/src/core/workflow/workspaces/reclaim.ts +3 -9
  200. package/src/core/workflow/workspaces/recovery.ts +1 -1
  201. package/src/core/workflow/workspaces/state.ts +1 -5
  202. package/src/lib/identities/assume.ts +0 -4
  203. package/src/lib/tunnel/state.ts +15 -70
  204. package/dist/commands/turn-rollback.d.ts +0 -5
  205. package/dist/commands/turn-rollback.d.ts.map +0 -1
  206. package/dist/commands/turn-rollback.js +0 -76
  207. package/dist/core/agents/state/process-handle.d.ts +0 -26
  208. package/dist/core/agents/state/process-handle.d.ts.map +0 -1
  209. package/dist/core/agents/state/process-handle.js +0 -121
  210. package/dist/core/agents/state/unchanged-file.d.ts +0 -44
  211. package/dist/core/agents/state/unchanged-file.d.ts.map +0 -1
  212. package/dist/core/agents/state/unchanged-file.js +0 -390
  213. package/dist/core/events/v3/generation-custody.d.ts +0 -5
  214. package/dist/core/events/v3/generation-custody.d.ts.map +0 -1
  215. package/dist/core/events/v3/generation-custody.js +0 -50
  216. package/dist/core/turn-rollback/content.d.ts +0 -53
  217. package/dist/core/turn-rollback/content.d.ts.map +0 -1
  218. package/dist/core/turn-rollback/content.js +0 -396
  219. package/dist/core/turn-rollback/index.d.ts +0 -72
  220. package/dist/core/turn-rollback/index.d.ts.map +0 -1
  221. package/dist/core/turn-rollback/index.js +0 -443
  222. package/dist/core/workflow/failure-evidence.d.ts +0 -16
  223. package/dist/core/workflow/failure-evidence.d.ts.map +0 -1
  224. package/dist/core/workflow/failure-evidence.js +0 -114
  225. package/dist/core/workflow/recovery-audit.d.ts +0 -30
  226. package/dist/core/workflow/recovery-audit.d.ts.map +0 -1
  227. package/dist/core/workflow/recovery-audit.js +0 -116
  228. package/src/commands/turn-rollback.ts +0 -111
  229. package/src/core/agents/state/process-handle.ts +0 -165
  230. package/src/core/agents/state/unchanged-file.ts +0 -456
  231. package/src/core/events/v3/generation-custody.ts +0 -59
  232. package/src/core/turn-rollback/content.ts +0 -499
  233. package/src/core/turn-rollback/index.ts +0 -580
  234. package/src/core/workflow/failure-evidence.ts +0 -141
  235. package/src/core/workflow/recovery-audit.ts +0 -181
@@ -191,25 +191,19 @@ export async function withFamilyLease(directory, policy, operation) {
191
191
  let owner;
192
192
  while (true) {
193
193
  try {
194
- mkdirSync(lease, { mode: stateDirMode() });
195
- const leaseIdentity = captureDirectoryIdentity(lease);
196
- owner = {
197
- owner_id: randomUUID(),
198
- pid: process.pid,
199
- acquired_at: new Date().toISOString(),
200
- };
201
- writeNewFile(join(lease, "owner.json"), Buffer.from(`${JSON.stringify(owner)}\n`));
202
- assertDirectoryIdentity(leaseIdentity);
194
+ owner = claimLease(lease);
203
195
  break;
204
196
  }
205
197
  catch (error) {
206
- if (pathExists(lease) && !safeLeaseDirectory(lease)) {
198
+ if (leaseHasWrongType(lease)) {
207
199
  throw new HarneryLogLeaseError("wrong_type", `log lease is not a directory: ${lease}`);
208
200
  }
209
201
  if (recoverStaleLease(lease, policy.lease_stale_ms))
210
202
  continue;
211
203
  if (Date.now() - started >= policy.lease_timeout_ms) {
212
- throw new HarneryLogLeaseError("timeout", `timed out acquiring log lease: ${lease}`);
204
+ throw new HarneryLogLeaseError("timeout", leaseHasValidOwner(lease)
205
+ ? `timed out acquiring log lease: ${lease}`
206
+ : `timed out acquiring log lease with no valid owner; an interrupted writer left it, and it is never recovered automatically: ${lease}`);
213
207
  }
214
208
  await new Promise((resolve) => setTimeout(resolve, policy.lease_retry_ms));
215
209
  void error;
@@ -223,6 +217,42 @@ export async function withFamilyLease(directory, policy, operation) {
223
217
  releaseOwnedLease(lease, owner);
224
218
  }
225
219
  }
220
+ /**
221
+ * Publish a complete lease in one rename. The owner record is written and synced
222
+ * in a private claim directory first, so a writer stopped at any point leaves
223
+ * either no lease or one with an owner. A held lease always has an owner record,
224
+ * here and in the earlier create-then-write protocol, so a rename that replaces
225
+ * an empty lease directory never takes a held lease. A lease with any entry makes
226
+ * the rename fail, and the caller waits or recovers as before.
227
+ */
228
+ function claimLease(lease) {
229
+ const owner = {
230
+ owner_id: randomUUID(),
231
+ pid: process.pid,
232
+ acquired_at: new Date().toISOString(),
233
+ };
234
+ const content = `${JSON.stringify(owner)}\n`;
235
+ const claim = `${lease}.claim-${process.pid}-${owner.owner_id}`;
236
+ try {
237
+ mkdirSync(claim, { mode: stateDirMode() });
238
+ writeNewFile(join(claim, "owner.json"), Buffer.from(content));
239
+ renameSync(claim, lease);
240
+ }
241
+ finally {
242
+ rmSync(claim, { recursive: true, force: true });
243
+ }
244
+ try {
245
+ captureDirectoryIdentity(lease);
246
+ if (readRegularFile(join(lease, "owner.json"), 4_096).toString("utf8") !== content) {
247
+ throw new Error(`log lease owner changed during claim: ${lease}`);
248
+ }
249
+ }
250
+ catch (error) {
251
+ releaseOwnedLease(lease, owner);
252
+ throw error;
253
+ }
254
+ return owner;
255
+ }
226
256
  function shouldRotate(active, incoming, options, now) {
227
257
  const stat = regularFileStat(active);
228
258
  const incomingBytes = incoming.reduce((sum, record) => sum + record.byteLength, 0);
@@ -315,6 +345,18 @@ function atomicJson(path, value) {
315
345
  renameSync(temporary, path);
316
346
  assertRegularPath(path, captureDirectoryIdentity(dirname(path)));
317
347
  }
348
+ function leaseHasValidOwner(lease) {
349
+ try {
350
+ const owner = JSON.parse(readRegularFile(join(lease, "owner.json"), 4_096).toString("utf8"));
351
+ return (typeof owner?.owner_id === "string" &&
352
+ owner.owner_id.length > 0 &&
353
+ Number.isSafeInteger(owner.pid) &&
354
+ Number.isFinite(Date.parse(owner.acquired_at)));
355
+ }
356
+ catch {
357
+ return false;
358
+ }
359
+ }
318
360
  function recoverStaleLease(lease, maximumAgeMs) {
319
361
  let identity;
320
362
  let raw;
@@ -344,7 +386,7 @@ function recoverStaleLease(lease, maximumAgeMs) {
344
386
  assertDirectoryIdentity(identity);
345
387
  if (readRegularFile(join(lease, "owner.json"), 4_096).toString("utf8") !== raw)
346
388
  return false;
347
- rmSync(lease, { recursive: true, force: false });
389
+ retireLease(lease, randomUUID());
348
390
  return true;
349
391
  }
350
392
  catch {
@@ -356,12 +398,22 @@ function releaseOwnedLease(lease, owner) {
356
398
  const current = JSON.parse(readRegularFile(join(lease, "owner.json"), 4_096).toString("utf8"));
357
399
  if (current.owner_id !== owner.owner_id)
358
400
  return;
359
- rmSync(lease, { recursive: true, force: false });
401
+ retireLease(lease, owner.owner_id);
360
402
  }
361
403
  catch {
362
404
  // A missing or replaced lease does not belong to this operation.
363
405
  }
364
406
  }
407
+ /**
408
+ * Move a lease aside before deleting it. Its path goes from held to absent in
409
+ * one rename and is never an empty directory that a concurrent claim could
410
+ * replace while a recursive removal is still running.
411
+ */
412
+ function retireLease(lease, tag) {
413
+ const retired = `${lease}.retired-${process.pid}-${tag}`;
414
+ renameSync(lease, retired);
415
+ rmSync(retired, { recursive: true, force: true });
416
+ }
365
417
  function processIsAlive(pid) {
366
418
  try {
367
419
  process.kill(pid, 0);
@@ -519,12 +571,16 @@ function writeAllSync(fd, buffer, writer) {
519
571
  function nativeWriteSync(fd, buffer, offset, length) {
520
572
  return writeSync(fd, buffer, offset, length);
521
573
  }
522
- function safeLeaseDirectory(path) {
574
+ /** One lstat, so a lease released between checks reads as absent, not as a wrong type. */
575
+ function leaseHasWrongType(path) {
523
576
  try {
524
- return !lstatSync(path).isSymbolicLink() && lstatSync(path).isDirectory();
577
+ const stat = lstatSync(path);
578
+ return stat.isSymbolicLink() || !stat.isDirectory();
525
579
  }
526
- catch {
527
- return false;
580
+ catch (error) {
581
+ if (error.code === "ENOENT")
582
+ return false;
583
+ throw error;
528
584
  }
529
585
  }
530
586
  function pathExists(path) {
@@ -1,6 +1,5 @@
1
- import { type ProcessGenerationSignalDependencies, type ProcessGenerationVerdict } from "../agents/state/proc-start.js";
1
+ import { type ServiceProcessDependencies, type ServiceStopFields } from "../agents/state/service-process.js";
2
2
  import { type SupervisorServiceStatusRecord, type SupervisorStatus } from "./contract.js";
3
- import { type SupervisorProcessDependencies } from "./status.js";
4
3
  export declare const SUPERVISOR_DEFAULT_INTERVAL_MS = 2000;
5
4
  export declare const SUPERVISOR_DEFAULT_IDLE_EXIT_MS: number;
6
5
  export interface RunSupervisorInput {
@@ -26,10 +25,8 @@ export declare function spawnSupervisor(coordRootRaw: string, options?: {
26
25
  keepAlive?: boolean;
27
26
  }): Promise<SupervisorStatus>;
28
27
  export declare function ensureSupervisorRunning(coordRoot: string): Promise<EnsureSupervisorResult>;
29
- export interface SupervisorStopResult extends SupervisorStatus {
30
- signal_sent: boolean;
31
- signal_verdict: ProcessGenerationVerdict;
32
- }
33
- export declare function requestSupervisorStop(coordRootRaw: string, dependencies?: SupervisorProcessDependencies & ProcessGenerationSignalDependencies): SupervisorStopResult;
28
+ export type SupervisorStopResult = SupervisorStatus & ServiceStopFields;
29
+ /** Windows has no graceful SIGTERM, so the durable request alone stops it there. */
30
+ export declare function requestSupervisorStop(coordRootRaw: string, dependencies?: ServiceProcessDependencies): SupervisorStopResult;
34
31
  export declare function runSupervisor(input: RunSupervisorInput): Promise<SupervisorServiceStatusRecord>;
35
32
  //# sourceMappingURL=service.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../../../src/core/supervisor/service.ts"],"names":[],"mappings":"AAkBA,OAAO,EACL,KAAK,mCAAmC,EACxC,KAAK,wBAAwB,EAG9B,MAAM,+BAA+B,CAAC;AAevC,OAAO,EAML,KAAK,6BAA6B,EAElC,KAAK,gBAAgB,EACtB,MAAM,eAAe,CAAC;AAavB,OAAO,EAGL,KAAK,6BAA6B,EAEnC,MAAM,aAAa,CAAC;AASrB,eAAO,MAAM,8BAA8B,OAAQ,CAAC;AACpD,eAAO,MAAM,+BAA+B,QAAa,CAAC;AAe1D,MAAM,WAAW,kBAAkB;IACjC,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,GAAG,CAAC,EAAE,MAAM,IAAI,CAAC;IACjB,IAAI,CAAC,EAAE,CAAC,YAAY,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CAChD;AAED,MAAM,MAAM,sBAAsB,GAC9B;IAAE,KAAK,EAAE,SAAS,GAAG,SAAS,CAAC;IAAC,MAAM,EAAE,gBAAgB,CAAA;CAAE,GAC1D;IAAE,KAAK,EAAE,aAAa,CAAC;IAAC,MAAM,EAAE,gBAAgB,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC;AAEtE,wBAAsB,eAAe,CACnC,YAAY,EAAE,MAAM,EACpB,OAAO,GAAE;IAAE,UAAU,CAAC,EAAE,MAAM,CAAC;IAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,OAAO,CAAA;CAAO,GAC9E,OAAO,CAAC,gBAAgB,CAAC,CAyD3B;AAED,wBAAsB,uBAAuB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,sBAAsB,CAAC,CAahG;AAED,MAAM,WAAW,oBAAqB,SAAQ,gBAAgB;IAC5D,WAAW,EAAE,OAAO,CAAC;IACrB,cAAc,EAAE,wBAAwB,CAAC;CAC1C;AAED,wBAAgB,qBAAqB,CACnC,YAAY,EAAE,MAAM,EACpB,YAAY,GAAE,6BAA6B,GAAG,mCAAwC,GACrF,oBAAoB,CA8BtB;AAED,wBAAsB,aAAa,CACjC,KAAK,EAAE,kBAAkB,GACxB,OAAO,CAAC,6BAA6B,CAAC,CA+QxC"}
1
+ {"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../../../src/core/supervisor/service.ts"],"names":[],"mappings":"AAQA,OAAO,EAIL,KAAK,0BAA0B,EAC/B,KAAK,iBAAiB,EAGvB,MAAM,oCAAoC,CAAC;AAgB5C,OAAO,EAML,KAAK,6BAA6B,EAElC,KAAK,gBAAgB,EACtB,MAAM,eAAe,CAAC;AAsBvB,eAAO,MAAM,8BAA8B,OAAQ,CAAC;AACpD,eAAO,MAAM,+BAA+B,QAAa,CAAC;AAO1D,MAAM,WAAW,kBAAkB;IACjC,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,GAAG,CAAC,EAAE,MAAM,IAAI,CAAC;IACjB,IAAI,CAAC,EAAE,CAAC,YAAY,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CAChD;AAED,MAAM,MAAM,sBAAsB,GAC9B;IAAE,KAAK,EAAE,SAAS,GAAG,SAAS,CAAC;IAAC,MAAM,EAAE,gBAAgB,CAAA;CAAE,GAC1D;IAAE,KAAK,EAAE,aAAa,CAAC;IAAC,MAAM,EAAE,gBAAgB,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC;AAEtE,wBAAsB,eAAe,CACnC,YAAY,EAAE,MAAM,EACpB,OAAO,GAAE;IAAE,UAAU,CAAC,EAAE,MAAM,CAAC;IAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,OAAO,CAAA;CAAO,GAC9E,OAAO,CAAC,gBAAgB,CAAC,CAuD3B;AAED,wBAAsB,uBAAuB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,sBAAsB,CAAC,CAahG;AAED,MAAM,MAAM,oBAAoB,GAAG,gBAAgB,GAAG,iBAAiB,CAAC;AAExE,oFAAoF;AACpF,wBAAgB,qBAAqB,CACnC,YAAY,EAAE,MAAM,EACpB,YAAY,GAAE,0BAA+B,GAC5C,oBAAoB,CAStB;AAED,wBAAsB,aAAa,CACjC,KAAK,EAAE,kBAAkB,GACxB,OAAO,CAAC,6BAA6B,CAAC,CA4QxC"}
@@ -1,24 +1,25 @@
1
1
  import { spawn } from "node:child_process";
2
- import { randomUUID } from "node:crypto";
3
- import { closeSync, existsSync, mkdirSync, openSync, readdirSync, readFileSync, rmSync, unlinkSync, writeFileSync, } from "node:fs";
2
+ import { existsSync, mkdirSync, readdirSync, rmSync } from "node:fs";
4
3
  import { hostname } from "node:os";
5
4
  import { resolve } from "node:path";
6
5
  import { performance } from "node:perf_hooks";
7
6
  import { fileURLToPath } from "node:url";
8
7
  import { collectCoordinationHealthSnapshot } from "../agents/health.js";
9
- import { processStartToken, signalIfSameGeneration, } from "../agents/state/proc-start.js";
8
+ import { processStartToken } from "../agents/state/proc-start.js";
9
+ import { acquireServiceLease, requestServiceStop, serviceLeaseOwner, serviceStartRefusal, } from "../agents/state/service-process.js";
10
10
  import { resolveBinName } from "../config.js";
11
11
  import { pressureHistoryFromSupervisor } from "../diagnostics/advice.js";
12
12
  import { assessPressure } from "../diagnostics/pressure.js";
13
13
  import { readEventV3ControlState } from "../events/v3/control.js";
14
14
  import { janitorLiveDisplayV3 } from "../events/v3/live-feed.js";
15
+ import { installedHarneryBuild } from "../events/v3/runtime-build.js";
15
16
  import { sampleResources } from "../resources/sampler.js";
16
17
  import { requestResourceServiceStop } from "../resources/service.js";
17
18
  import { readResourceServiceStatus } from "../resources/service-status.js";
18
19
  import { resourcePaths } from "../resources/storage.js";
19
20
  import { writePrivateJsonAtomic } from "../storage/atomic-json.js";
20
21
  import { closeProcessLoggers, legacyLogFields, processLogger } from "../storage/logger.js";
21
- import { stateDirMode, stateFileMode } from "../storage/modes.js";
22
+ import { stateDirMode } from "../storage/modes.js";
22
23
  import { collectSupervisorActivitySnapshot } from "./activity.js";
23
24
  import { SUPERVISOR_PRESSURE_SCHEMA_VERSION, SUPERVISOR_SNAPSHOT_SCHEMA_VERSION, SUPERVISOR_STATUS_SCHEMA_VERSION, } from "./contract.js";
24
25
  import { explainSupervisorFinding } from "./explanations.js";
@@ -29,7 +30,7 @@ import { writeSupervisorHookHealth } from "./hook-health-storage.js";
29
30
  import { collectHookHealth } from "./hooks.js";
30
31
  import { SupervisorLogCollector } from "./log-feed.js";
31
32
  import { collectServiceHealth } from "./services.js";
32
- import { installedSupervisorBuild, readSupervisorStatus, supervisorProcessLiveness, } from "./status.js";
33
+ import { readSupervisorStatus } from "./status.js";
33
34
  import { readSupervisorFindings, readSupervisorHistory, readSupervisorPressure, supervisorPaths, } from "./storage.js";
34
35
  import { buildSupervisorTimeline } from "./timeline.js";
35
36
  export const SUPERVISOR_DEFAULT_INTERVAL_MS = 2_000;
@@ -42,13 +43,12 @@ const LIVE_DISPLAY_SWEEP_INTERVAL_MS = 5 * 60_000;
42
43
  export async function spawnSupervisor(coordRootRaw, options = {}) {
43
44
  const coordRoot = resolve(coordRootRaw);
44
45
  const current = readSupervisorStatus(coordRoot);
45
- if (current.liveness === "unknown")
46
- throw unknownOwnerError();
47
- if (current.liveness === "running") {
48
- throw new Error(`local supervisor is already running under pid ${current.record?.pid}`);
49
- }
46
+ if (current.liveness !== "stopped")
47
+ throw startRefusal(current.record?.pid, current.liveness);
50
48
  const paths = supervisorPaths(coordRoot);
51
- assertLeaseAvailable(paths.lease);
49
+ const lease = serviceLeaseOwner(paths.lease);
50
+ if (lease.liveness !== "stopped")
51
+ throw startRefusal(lease.lease?.pid, lease.liveness);
52
52
  await stopLegacyResourceObserver(coordRoot);
53
53
  const intervalMs = normalizeInterval(options.intervalMs);
54
54
  const idleExitMs = normalizeIdleExit(options.idleExitMs);
@@ -112,45 +112,29 @@ export async function ensureSupervisorRunning(coordRoot) {
112
112
  };
113
113
  }
114
114
  }
115
+ /** Windows has no graceful SIGTERM, so the durable request alone stops it there. */
115
116
  export function requestSupervisorStop(coordRootRaw, dependencies = {}) {
116
117
  const coordRoot = resolve(coordRootRaw);
117
- const paths = supervisorPaths(coordRoot);
118
- const status = readSupervisorStatus(coordRoot, Date.now(), dependencies);
119
- mkdirSync(paths.root, { recursive: true, mode: stateDirMode() });
120
- writePrivateJsonAtomic(paths.stop, {
121
- requested_at: new Date().toISOString(),
122
- requested_by_pid: process.pid,
123
- });
124
118
  const legacyPaths = resourcePaths(coordRoot);
125
119
  if (existsSync(legacyPaths.service) || existsSync(legacyPaths.lease))
126
120
  requestResourceServiceStop(coordRoot, dependencies);
127
- let signal = { sent: false, verdict: status.generation };
128
- if (status.liveness === "running" &&
129
- status.record?.host === hostname() &&
130
- process.platform !== "win32") {
131
- signal = signalIfSameGeneration(status.record.pid, status.record.start_token, "SIGTERM", dependencies);
132
- }
133
- return {
134
- ...readSupervisorStatus(coordRoot, Date.now(), dependencies),
135
- signal_sent: signal.sent,
136
- signal_verdict: signal.verdict,
137
- };
121
+ const read = () => readSupervisorStatus(coordRoot, Date.now(), dependencies);
122
+ return requestServiceStop(read(), read, supervisorPaths(coordRoot).stop, dependencies, {
123
+ signal: process.platform !== "win32",
124
+ });
138
125
  }
139
126
  export async function runSupervisor(input) {
140
127
  const coordRoot = resolve(input.coordRoot);
141
128
  const current = readSupervisorStatus(coordRoot);
142
- if (current.liveness === "unknown")
143
- throw unknownOwnerError();
144
- if (current.liveness === "running") {
145
- throw new Error(`local supervisor is already running under pid ${current.record?.pid}`);
146
- }
129
+ if (current.liveness !== "stopped")
130
+ throw startRefusal(current.record?.pid, current.liveness);
147
131
  const paths = supervisorPaths(coordRoot);
148
132
  const intervalMs = normalizeInterval(input.intervalMs);
149
133
  const idleExitMs = normalizeIdleExit(input.idleExitMs);
150
134
  const keepAlive = input.keepAlive ?? false;
151
135
  const now = input.now ?? (() => new Date());
152
136
  const wait = input.wait ?? delay;
153
- const lease = acquireLease(coordRoot, now());
137
+ const lease = acquireServiceLease(paths.lease, startRefusal);
154
138
  rmSync(paths.stop, { force: true });
155
139
  const startedAt = now().toISOString();
156
140
  const startToken = processStartToken(process.pid);
@@ -158,7 +142,7 @@ export async function runSupervisor(input) {
158
142
  schema_version: SUPERVISOR_STATUS_SCHEMA_VERSION,
159
143
  pid: process.pid,
160
144
  ...(startToken ? { start_token: startToken } : {}),
161
- running_build: installedSupervisorBuild(),
145
+ running_build: installedHarneryBuild(),
162
146
  host: hostname(),
163
147
  nonce: lease.nonce,
164
148
  state: "starting",
@@ -400,7 +384,7 @@ export async function runSupervisor(input) {
400
384
  await closeProcessLoggers();
401
385
  }
402
386
  finally {
403
- releaseLease(paths.lease, lease.nonce);
387
+ lease.release();
404
388
  }
405
389
  }
406
390
  return status;
@@ -440,48 +424,6 @@ function observeLedgerControlState(coordRoot, now) {
440
424
  };
441
425
  }
442
426
  }
443
- function acquireLease(coordRoot, now) {
444
- const paths = supervisorPaths(coordRoot);
445
- mkdirSync(paths.root, { recursive: true, mode: stateDirMode() });
446
- for (let attempt = 0; attempt < 2; attempt++) {
447
- const startToken = processStartToken(process.pid);
448
- const lease = {
449
- pid: process.pid,
450
- ...(startToken ? { start_token: startToken } : {}),
451
- host: hostname(),
452
- nonce: randomUUID(),
453
- created_at: now.toISOString(),
454
- };
455
- try {
456
- const fd = openSync(paths.lease, "wx", stateFileMode());
457
- try {
458
- writeFileSync(fd, `${JSON.stringify(lease)}\n`, "utf8");
459
- }
460
- finally {
461
- closeSync(fd);
462
- }
463
- return lease;
464
- }
465
- catch (error) {
466
- if (error.code !== "EEXIST")
467
- throw error;
468
- const existing = readLease(paths.lease);
469
- const liveness = existing ? leaseIsLive(existing) : "unknown";
470
- if (liveness === "unknown")
471
- throw unknownOwnerError();
472
- if (existing && liveness === "running") {
473
- throw new Error(`local supervisor lease is held by pid ${existing.pid}`);
474
- }
475
- try {
476
- unlinkSync(paths.lease);
477
- }
478
- catch {
479
- // A concurrent starter may have replaced or removed the stale lease.
480
- }
481
- }
482
- }
483
- throw new Error("local supervisor could not acquire its singleton lease");
484
- }
485
427
  function pruneProjectionDirectory(directory, retainedFindingIds) {
486
428
  for (const file of readdirSync(directory)) {
487
429
  if (!file.endsWith(".json"))
@@ -491,49 +433,8 @@ function pruneProjectionDirectory(directory, retainedFindingIds) {
491
433
  rmSync(resolve(directory, file), { force: true });
492
434
  }
493
435
  }
494
- function releaseLease(path, nonce) {
495
- const current = readLease(path);
496
- if (current?.nonce !== nonce)
497
- return;
498
- try {
499
- unlinkSync(path);
500
- }
501
- catch {
502
- // A missing lease is already released.
503
- }
504
- }
505
- function readLease(path) {
506
- try {
507
- const value = JSON.parse(readFileSync(path, "utf8"));
508
- return value &&
509
- Number.isSafeInteger(value.pid) &&
510
- value.pid > 0 &&
511
- typeof value.host === "string" &&
512
- value.host.length > 0 &&
513
- typeof value.nonce === "string" &&
514
- value.nonce.length > 0
515
- ? value
516
- : undefined;
517
- }
518
- catch {
519
- return undefined;
520
- }
521
- }
522
- function leaseIsLive(lease) {
523
- return supervisorProcessLiveness(lease).liveness;
524
- }
525
- function assertLeaseAvailable(path) {
526
- if (!existsSync(path))
527
- return;
528
- const lease = readLease(path);
529
- const liveness = lease ? leaseIsLive(lease) : "unknown";
530
- if (liveness === "unknown")
531
- throw unknownOwnerError();
532
- if (liveness === "running")
533
- throw new Error(`local supervisor lease is held by pid ${lease?.pid}`);
534
- }
535
- function unknownOwnerError() {
536
- return new Error(`local supervisor process identity is unknown; run ${resolveBinName()} supervisor stop to write the durable stop request before starting again`);
436
+ function startRefusal(pid, liveness) {
437
+ return serviceStartRefusal("local supervisor", `${resolveBinName()} supervisor stop`, pid, liveness);
537
438
  }
538
439
  async function stopLegacyResourceObserver(coordRoot) {
539
440
  const legacy = readResourceServiceStatus(coordRoot);
@@ -1,7 +1,7 @@
1
1
  import { existsSync, readdirSync, readFileSync, rmSync, statSync } from "node:fs";
2
2
  import { hostname } from "node:os";
3
3
  import { join } from "node:path";
4
- import { checkPidToken, processStartToken } from "../agents/state/proc-start.js";
4
+ import { processStartToken, verifyProcessGeneration } from "../agents/state/proc-start.js";
5
5
  import { readGovernorServiceStatus } from "../governor/service-read.js";
6
6
  import { writePrivateJsonAtomic } from "../storage/atomic-json.js";
7
7
  import { SUPERVISOR_CONSUMER_SCHEMA_VERSION, } from "./contract.js";
@@ -87,7 +87,7 @@ function consumerAlive(record) {
87
87
  if (error.code !== "EPERM")
88
88
  return false;
89
89
  }
90
- return checkPidToken(record.pid, record.start_token) !== "mismatch";
90
+ return verifyProcessGeneration(record.pid, record.start_token) !== "different";
91
91
  }
92
92
  function readDetachedService(path, id) {
93
93
  const record = readBoundedJson(path);
@@ -1,18 +1,4 @@
1
- import { type ProcessGenerationVerdict } from "../agents/state/proc-start.js";
2
- import { type SupervisorBuild, type SupervisorStatus } from "./contract.js";
3
- export interface SupervisorProcessDependencies {
4
- readToken?: (pid: number) => string | null;
5
- probePid?: (pid: number) => unknown;
6
- }
7
- export declare function installedSupervisorBuild(): SupervisorBuild;
8
- export declare function readSupervisorStatus(coordRoot: string, nowMs?: number, dependencies?: SupervisorProcessDependencies): SupervisorStatus;
9
- /** A stale heartbeat does not prove that a process has exited. */
10
- export declare function supervisorProcessLiveness(owner: {
11
- pid: number;
12
- host: string;
13
- start_token?: string;
14
- }, dependencies?: SupervisorProcessDependencies): {
15
- liveness: SupervisorStatus["liveness"];
16
- generation: ProcessGenerationVerdict;
17
- };
1
+ import { type ServiceProcessDependencies } from "../agents/state/service-process.js";
2
+ import { type SupervisorStatus } from "./contract.js";
3
+ export declare function readSupervisorStatus(coordRoot: string, nowMs?: number, dependencies?: ServiceProcessDependencies): SupervisorStatus;
18
4
  //# sourceMappingURL=status.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"status.d.ts","sourceRoot":"","sources":["../../../src/core/supervisor/status.ts"],"names":[],"mappings":"AAGA,OAAO,EACL,KAAK,wBAAwB,EAE9B,MAAM,+BAA+B,CAAC;AAEvC,OAAO,EAEL,KAAK,eAAe,EAEpB,KAAK,gBAAgB,EACtB,MAAM,eAAe,CAAC;AAKvB,MAAM,WAAW,6BAA6B;IAC5C,SAAS,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,MAAM,GAAG,IAAI,CAAC;IAC3C,QAAQ,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC;CACrC;AAED,wBAAgB,wBAAwB,IAAI,eAAe,CAW1D;AAED,wBAAgB,oBAAoB,CAClC,SAAS,EAAE,MAAM,EACjB,KAAK,SAAa,EAClB,YAAY,GAAE,6BAAkC,GAC/C,gBAAgB,CA+ClB;AAeD,kEAAkE;AAClE,wBAAgB,yBAAyB,CACvC,KAAK,EAAE;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,WAAW,CAAC,EAAE,MAAM,CAAA;CAAE,EAC1D,YAAY,GAAE,6BAAkC,GAC/C;IAAE,QAAQ,EAAE,gBAAgB,CAAC,UAAU,CAAC,CAAC;IAAC,UAAU,EAAE,wBAAwB,CAAA;CAAE,CAgBlF"}
1
+ {"version":3,"file":"status.d.ts","sourceRoot":"","sources":["../../../src/core/supervisor/status.ts"],"names":[],"mappings":"AACA,OAAO,EAEL,KAAK,0BAA0B,EAChC,MAAM,oCAAoC,CAAC;AAE5C,OAAO,EAGL,KAAK,gBAAgB,EACtB,MAAM,eAAe,CAAC;AAKvB,wBAAgB,oBAAoB,CAClC,SAAS,EAAE,MAAM,EACjB,KAAK,SAAa,EAClB,YAAY,GAAE,0BAA+B,GAC5C,gBAAgB,CA4BlB"}
@@ -1,45 +1,13 @@
1
- import { existsSync, readFileSync } from "node:fs";
2
- import { hostname } from "node:os";
3
- import { join } from "node:path";
4
- import { verifyProcessGeneration, } from "../agents/state/proc-start.js";
5
- import { currentHarneryRuntimeBuild, harneryPackageRoot } from "../events/v3/runtime-build.js";
1
+ import { existsSync } from "node:fs";
2
+ import { observeServiceProcess, } from "../agents/state/service-process.js";
3
+ import { serviceBuildStatus } from "../events/v3/runtime-build.js";
6
4
  import { SUPERVISOR_STATUS_SCHEMA_VERSION, } from "./contract.js";
7
5
  import { readSupervisorServiceRecord, supervisorPaths } from "./storage.js";
8
6
  const STALE_HEARTBEAT_MS = 15_000;
9
- export function installedSupervisorBuild() {
10
- let version = "unknown";
11
- let revision = "unknown";
12
- try {
13
- const pkg = JSON.parse(readFileSync(join(harneryPackageRoot(), "package.json"), "utf8"));
14
- if (typeof pkg.version === "string")
15
- version = pkg.version;
16
- revision = currentHarneryRuntimeBuild();
17
- }
18
- catch {
19
- // Missing build evidence must still produce an inspectable status.
20
- }
21
- return { version, revision };
22
- }
23
7
  export function readSupervisorStatus(coordRoot, nowMs = Date.now(), dependencies = {}) {
24
8
  const paths = supervisorPaths(coordRoot);
25
9
  const record = readSupervisorServiceRecord(coordRoot);
26
- const installedBuild = installedSupervisorBuild();
27
- const runningBuild = validBuild(record?.running_build) ? record.running_build : null;
28
- const restartNeeded = !runningBuild ||
29
- installedBuild.version === "unknown" ||
30
- installedBuild.revision === "unknown" ||
31
- runningBuild.version !== installedBuild.version ||
32
- runningBuild.revision !== installedBuild.revision;
33
- const build = {
34
- running_build: runningBuild,
35
- installed_build: installedBuild,
36
- restart_needed: restartNeeded,
37
- build_status: !runningBuild
38
- ? "running build unknown; restart needed"
39
- : restartNeeded
40
- ? "restart needed"
41
- : "current",
42
- };
10
+ const build = serviceBuildStatus(record?.running_build);
43
11
  if (!validRecord(record)) {
44
12
  return {
45
13
  running: false,
@@ -53,12 +21,11 @@ export function readSupervisorStatus(coordRoot, nowMs = Date.now(), dependencies
53
21
  }
54
22
  const heartbeatAt = Date.parse(record.heartbeat_at);
55
23
  const stale = !Number.isFinite(heartbeatAt) || nowMs - heartbeatAt > STALE_HEARTBEAT_MS;
56
- const owner = supervisorProcessLiveness(record, dependencies);
57
- const liveness = owner.generation === "same" && record.state === "stopped" ? "stopped" : owner.liveness;
24
+ const { generation, liveness } = observeServiceProcess(record, dependencies);
58
25
  return {
59
- running: liveness === "running" && !stale && record.state !== "stopped",
26
+ running: liveness === "running" && !stale,
60
27
  stale,
61
- generation: owner.generation,
28
+ generation,
62
29
  liveness,
63
30
  ...build,
64
31
  record,
@@ -74,33 +41,3 @@ function validRecord(value) {
74
41
  typeof value.heartbeat_at === "string" &&
75
42
  typeof value.keep_alive === "boolean");
76
43
  }
77
- /** A stale heartbeat does not prove that a process has exited. */
78
- export function supervisorProcessLiveness(owner, dependencies = {}) {
79
- if (owner.host !== hostname())
80
- return { liveness: "unknown", generation: "unknown" };
81
- try {
82
- (dependencies.probePid ?? ((pid) => process.kill(pid, 0)))(owner.pid);
83
- }
84
- catch (error) {
85
- return {
86
- liveness: error.code === "ESRCH" ? "stopped" : "unknown",
87
- generation: "unknown",
88
- };
89
- }
90
- const generation = verifyProcessGeneration(owner.pid, owner.start_token, dependencies.readToken);
91
- return {
92
- generation,
93
- liveness: generation === "same" ? "running" : generation === "different" ? "stopped" : "unknown",
94
- };
95
- }
96
- function validBuild(value) {
97
- if (!value || typeof value !== "object")
98
- return false;
99
- const build = value;
100
- return (typeof build.version === "string" &&
101
- !!build.version &&
102
- build.version !== "unknown" &&
103
- typeof build.revision === "string" &&
104
- !!build.revision &&
105
- build.revision !== "unknown");
106
- }
@@ -6,7 +6,7 @@ import { appendSegmentedJsonlFile, readSegmentedJsonlFileSync, } from "../storag
6
6
  import { stateDirMode, stateFileMode } from "../storage/modes.js";
7
7
  import { readWorkflowApproval } from "../workflow/approvals.js";
8
8
  import { stableDigest } from "../workflow/durable-record.js";
9
- import { deriveRunFailureClass, readWorkflowProof } from "../workflow/proof.js";
9
+ import { readWorkflowProof } from "../workflow/proof.js";
10
10
  import { readWorkflowRunManifest, workflowScriptDigest } from "../workflow/run-state.js";
11
11
  import { WORKFLOW_TRANSCRIPT_EVENT_BYTES, workflowTranscriptPath } from "../workflow/transcript.js";
12
12
  import { isWorkspaceBoundExecutionEvidence } from "../workflow/workspaces/validate.js";
@@ -464,13 +464,13 @@ function inspectAttempt(coordRoot, event, intent, priorRunId) {
464
464
  proof.acceptance.summary.unknown === 0
465
465
  ? "succeeded"
466
466
  : "failed";
467
- // Re-derive no-effect eligibility from every participant rather than
468
- // trusting a persisted transport class. Missing evidence stays charged.
469
- // The script's explicit decision authority remains a separate path.
467
+ // A failed attempt the run classified as uninformative about the work is
468
+ // uncharged (ADR 0046). Absent class ⇒ charged, as before. Read only for a
469
+ // failed attempt: a succeeded run never carries a class.
470
470
  if (attempt.status === "failed" &&
471
- (proof.run.class === "decision" ||
472
- ((proof.run.class === "environment" || proof.run.class === "upstream") &&
473
- deriveRunFailureClass(proof.run.status, proof.agents) === proof.run.class))) {
471
+ (proof.run.class === "environment" ||
472
+ proof.run.class === "upstream" ||
473
+ proof.run.class === "decision")) {
474
474
  attempt.uncharged = proof.run.class;
475
475
  if (proof.run.class === "decision" && typeof proof.run.decision_id === "string") {
476
476
  attempt.blocked_on = proof.run.decision_id;
@@ -1 +1 @@
1
- {"version":3,"file":"engine.d.ts","sourceRoot":"","sources":["../../../src/core/workflow/engine.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AA0DH,OAAO,KAAK,EAIV,UAAU,EAGV,SAAS,EAeV,MAAM,YAAY,CAAC;AAuDpB,qBAAa,gBAAiB,SAAQ,KAAK;IACzC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;gBAEf,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,OAAO;CAM/E;AAED,qBAAa,mBAAoB,SAAQ,KAAK;IAC5C,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;gBAEpB,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM;CAOvF;AAED;;;;6EAI6E;AAC7E,qBAAa,oBAAqB,SAAQ,KAAK;IAC7C,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;gBAEjB,MAAM,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,MAAM;CAUhD;AAED,wBAAsB,WAAW,CAAC,UAAU,EAAE,MAAM,EAAE,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,SAAS,CAAC,CA6E1F"}
1
+ {"version":3,"file":"engine.d.ts","sourceRoot":"","sources":["../../../src/core/workflow/engine.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAiDH,OAAO,KAAK,EAIV,UAAU,EAGV,SAAS,EAeV,MAAM,YAAY,CAAC;AAuDpB,qBAAa,gBAAiB,SAAQ,KAAK;IACzC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;gBAEf,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,OAAO;CAM/E;AAED,qBAAa,mBAAoB,SAAQ,KAAK;IAC5C,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;gBAEpB,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM;CAOvF;AAED;;;;6EAI6E;AAC7E,qBAAa,oBAAqB,SAAQ,KAAK;IAC7C,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;gBAEjB,MAAM,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,MAAM;CAUhD;AAED,wBAAsB,WAAW,CAAC,UAAU,EAAE,MAAM,EAAE,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,SAAS,CAAC,CA6E1F"}