harnery 0.43.0 → 0.44.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (278) hide show
  1. package/README.md +3 -3
  2. package/dist/commands/agents.d.ts.map +1 -1
  3. package/dist/commands/agents.js +41 -48
  4. package/dist/commands/browse.d.ts.map +1 -1
  5. package/dist/commands/browse.js +38 -11
  6. package/dist/commands/diagnostics.d.ts.map +1 -1
  7. package/dist/commands/diagnostics.js +10 -0
  8. package/dist/commands/doctor.d.ts +12 -0
  9. package/dist/commands/doctor.d.ts.map +1 -1
  10. package/dist/commands/doctor.js +22 -7
  11. package/dist/commands/governor.js +22 -10
  12. package/dist/commands/inbox.d.ts +1 -1
  13. package/dist/commands/inbox.d.ts.map +1 -1
  14. package/dist/commands/inbox.js +55 -2
  15. package/dist/commands/rm.d.ts.map +1 -1
  16. package/dist/commands/rm.js +28 -4
  17. package/dist/commands/servers.d.ts.map +1 -1
  18. package/dist/commands/servers.js +16 -1
  19. package/dist/commands/supervisor.d.ts.map +1 -1
  20. package/dist/commands/supervisor.js +7 -2
  21. package/dist/commands/toc.d.ts +14 -0
  22. package/dist/commands/toc.d.ts.map +1 -1
  23. package/dist/commands/toc.js +7 -4
  24. package/dist/commands/workflow.js +24 -30
  25. package/dist/core/agents/live-authority-v3.d.ts +1 -0
  26. package/dist/core/agents/live-authority-v3.d.ts.map +1 -1
  27. package/dist/core/agents/live-authority-v3.js +11 -0
  28. package/dist/core/agents/live-lifecycle-v3.d.ts +4 -2
  29. package/dist/core/agents/live-lifecycle-v3.d.ts.map +1 -1
  30. package/dist/core/agents/live-lifecycle-v3.js +28 -7
  31. package/dist/core/agents/mailbox.d.ts +9 -8
  32. package/dist/core/agents/mailbox.d.ts.map +1 -1
  33. package/dist/core/agents/mailbox.js +24 -17
  34. package/dist/core/agents/render/prompt-context.d.ts +2 -0
  35. package/dist/core/agents/render/prompt-context.d.ts.map +1 -1
  36. package/dist/core/agents/render/prompt-context.js +8 -12
  37. package/dist/core/agents/render/session-context.d.ts +2 -8
  38. package/dist/core/agents/render/session-context.d.ts.map +1 -1
  39. package/dist/core/agents/render/session-context.js +7 -30
  40. package/dist/core/agents/rules/stop-hook.js +3 -3
  41. package/dist/core/agents/state/heartbeat-writer.d.ts.map +1 -1
  42. package/dist/core/agents/state/heartbeat-writer.js +25 -14
  43. package/dist/core/agents/state/live-coordination-writer.d.ts.map +1 -1
  44. package/dist/core/agents/state/live-coordination-writer.js +5 -0
  45. package/dist/core/agents/state/pidmap.d.ts.map +1 -1
  46. package/dist/core/agents/state/pidmap.js +5 -0
  47. package/dist/core/agents/state/proc-start.d.ts +19 -0
  48. package/dist/core/agents/state/proc-start.d.ts.map +1 -1
  49. package/dist/core/agents/state/proc-start.js +41 -0
  50. package/dist/core/agents/state/stale-sweep.d.ts.map +1 -1
  51. package/dist/core/agents/state/stale-sweep.js +180 -70
  52. package/dist/core/agents/state/unchanged-file.d.ts +44 -0
  53. package/dist/core/agents/state/unchanged-file.d.ts.map +1 -0
  54. package/dist/core/agents/state/unchanged-file.js +390 -0
  55. package/dist/core/context/index.d.ts +22 -10
  56. package/dist/core/context/index.d.ts.map +1 -1
  57. package/dist/core/context/index.js +381 -119
  58. package/dist/core/context/observation.d.ts +26 -0
  59. package/dist/core/context/observation.d.ts.map +1 -0
  60. package/dist/core/context/observation.js +11 -0
  61. package/dist/core/diagnostics/bundle.d.ts.map +1 -1
  62. package/dist/core/diagnostics/bundle.js +25 -14
  63. package/dist/core/diagnostics/path-identity.d.ts +3 -0
  64. package/dist/core/diagnostics/path-identity.d.ts.map +1 -0
  65. package/dist/core/diagnostics/path-identity.js +12 -0
  66. package/dist/core/diagnostics/sanitize.d.ts.map +1 -1
  67. package/dist/core/diagnostics/sanitize.js +4 -3
  68. package/dist/core/events/v3/authority-outbox.d.ts.map +1 -1
  69. package/dist/core/events/v3/authority-outbox.js +29 -0
  70. package/dist/core/events/v3/generation-custody.d.ts +5 -0
  71. package/dist/core/events/v3/generation-custody.d.ts.map +1 -0
  72. package/dist/core/events/v3/generation-custody.js +50 -0
  73. package/dist/core/events/v3/producers/coordination-recorder.d.ts +1 -0
  74. package/dist/core/events/v3/producers/coordination-recorder.d.ts.map +1 -1
  75. package/dist/core/events/v3/producers/coordination-recorder.js +7 -0
  76. package/dist/core/events/v3/producers/recorder.d.ts.map +1 -1
  77. package/dist/core/events/v3/producers/recorder.js +77 -64
  78. package/dist/core/governor/planning.d.ts.map +1 -1
  79. package/dist/core/governor/planning.js +14 -1
  80. package/dist/core/governor/service-read.d.ts +10 -2
  81. package/dist/core/governor/service-read.d.ts.map +1 -1
  82. package/dist/core/governor/service-read.js +105 -22
  83. package/dist/core/governor/service.d.ts +20 -2
  84. package/dist/core/governor/service.d.ts.map +1 -1
  85. package/dist/core/governor/service.js +72 -47
  86. package/dist/core/hooks/cli.js +33 -5
  87. package/dist/core/inbox/contract.d.ts +2 -1
  88. package/dist/core/inbox/contract.d.ts.map +1 -1
  89. package/dist/core/inbox/index.d.ts +2 -0
  90. package/dist/core/inbox/index.d.ts.map +1 -1
  91. package/dist/core/inbox/index.js +2 -0
  92. package/dist/core/inbox/peer-messages.d.ts +37 -0
  93. package/dist/core/inbox/peer-messages.d.ts.map +1 -0
  94. package/dist/core/inbox/peer-messages.js +72 -0
  95. package/dist/core/inbox/service.d.ts +8 -1
  96. package/dist/core/inbox/service.d.ts.map +1 -1
  97. package/dist/core/inbox/service.js +67 -21
  98. package/dist/core/inbox/targets.d.ts +22 -0
  99. package/dist/core/inbox/targets.d.ts.map +1 -0
  100. package/dist/core/inbox/targets.js +108 -0
  101. package/dist/core/inbox/watcher.js +7 -3
  102. package/dist/core/journal/index.d.ts.map +1 -1
  103. package/dist/core/journal/index.js +17 -2
  104. package/dist/core/resources/contract.d.ts +3 -0
  105. package/dist/core/resources/contract.d.ts.map +1 -1
  106. package/dist/core/resources/service-status.d.ts +14 -1
  107. package/dist/core/resources/service-status.d.ts.map +1 -1
  108. package/dist/core/resources/service-status.js +23 -13
  109. package/dist/core/resources/service.d.ts +9 -1
  110. package/dist/core/resources/service.d.ts.map +1 -1
  111. package/dist/core/resources/service.js +47 -27
  112. package/dist/core/resources/status.d.ts +2 -5
  113. package/dist/core/resources/status.d.ts.map +1 -1
  114. package/dist/core/resources/status.js +36 -7
  115. package/dist/core/servers/index.d.ts +24 -13
  116. package/dist/core/servers/index.d.ts.map +1 -1
  117. package/dist/core/servers/index.js +132 -92
  118. package/dist/core/servers/tunnels.d.ts +3 -5
  119. package/dist/core/servers/tunnels.d.ts.map +1 -1
  120. package/dist/core/servers/tunnels.js +3 -9
  121. package/dist/core/supervisor/contract.d.ts +12 -0
  122. package/dist/core/supervisor/contract.d.ts.map +1 -1
  123. package/dist/core/supervisor/explanations.d.ts.map +1 -1
  124. package/dist/core/supervisor/explanations.js +2 -1
  125. package/dist/core/supervisor/findings.d.ts.map +1 -1
  126. package/dist/core/supervisor/findings.js +54 -9
  127. package/dist/core/supervisor/service.d.ts +9 -1
  128. package/dist/core/supervisor/service.d.ts.map +1 -1
  129. package/dist/core/supervisor/service.js +59 -29
  130. package/dist/core/supervisor/services.d.ts.map +1 -1
  131. package/dist/core/supervisor/services.js +22 -1
  132. package/dist/core/supervisor/status.d.ts +17 -2
  133. package/dist/core/supervisor/status.d.ts.map +1 -1
  134. package/dist/core/supervisor/status.js +70 -12
  135. package/dist/core/supervisor/timeline.d.ts.map +1 -1
  136. package/dist/core/supervisor/timeline.js +2 -1
  137. package/dist/core/work/state.js +7 -7
  138. package/dist/core/workflow/engine.d.ts.map +1 -1
  139. package/dist/core/workflow/engine.js +105 -32
  140. package/dist/core/workflow/failure-evidence.d.ts +16 -0
  141. package/dist/core/workflow/failure-evidence.d.ts.map +1 -0
  142. package/dist/core/workflow/failure-evidence.js +114 -0
  143. package/dist/core/workflow/index.d.ts +1 -1
  144. package/dist/core/workflow/index.d.ts.map +1 -1
  145. package/dist/core/workflow/index.js +1 -1
  146. package/dist/core/workflow/live-session-v3.d.ts +6 -0
  147. package/dist/core/workflow/live-session-v3.d.ts.map +1 -1
  148. package/dist/core/workflow/live-session-v3.js +126 -5
  149. package/dist/core/workflow/proof.d.ts +5 -5
  150. package/dist/core/workflow/proof.d.ts.map +1 -1
  151. package/dist/core/workflow/proof.js +115 -20
  152. package/dist/core/workflow/run-state.d.ts +3 -1
  153. package/dist/core/workflow/run-state.d.ts.map +1 -1
  154. package/dist/core/workflow/run-state.js +31 -27
  155. package/dist/core/workflow/spawn-claude.d.ts.map +1 -1
  156. package/dist/core/workflow/spawn-claude.js +19 -0
  157. package/dist/core/workflow/spawn-codex.d.ts.map +1 -1
  158. package/dist/core/workflow/spawn-codex.js +13 -0
  159. package/dist/core/workflow/spawn-cursor.d.ts.map +1 -1
  160. package/dist/core/workflow/spawn-cursor.js +19 -0
  161. package/dist/core/workflow/spawn-opencode.d.ts.map +1 -1
  162. package/dist/core/workflow/spawn-opencode.js +14 -0
  163. package/dist/core/workflow/types.d.ts +64 -17
  164. package/dist/core/workflow/types.d.ts.map +1 -1
  165. package/dist/core/workflow/workspaces/index.d.ts +0 -1
  166. package/dist/core/workflow/workspaces/index.d.ts.map +1 -1
  167. package/dist/core/workflow/workspaces/index.js +0 -1
  168. package/dist/core/workflow/workspaces/leases.d.ts +3 -1
  169. package/dist/core/workflow/workspaces/leases.d.ts.map +1 -1
  170. package/dist/core/workflow/workspaces/leases.js +30 -16
  171. package/dist/core/workflow/workspaces/local-git.d.ts +6 -2
  172. package/dist/core/workflow/workspaces/local-git.d.ts.map +1 -1
  173. package/dist/core/workflow/workspaces/local-git.js +270 -6
  174. package/dist/core/workflow/workspaces/ownership.d.ts +11 -0
  175. package/dist/core/workflow/workspaces/ownership.d.ts.map +1 -0
  176. package/dist/core/workflow/workspaces/ownership.js +194 -0
  177. package/dist/core/workflow/workspaces/reclaim.d.ts +35 -0
  178. package/dist/core/workflow/workspaces/reclaim.d.ts.map +1 -1
  179. package/dist/core/workflow/workspaces/reclaim.js +14 -0
  180. package/dist/core/workflow/workspaces/recovery.d.ts.map +1 -1
  181. package/dist/core/workflow/workspaces/recovery.js +21 -13
  182. package/dist/core/workflow/workspaces/state.d.ts +1 -0
  183. package/dist/core/workflow/workspaces/state.d.ts.map +1 -1
  184. package/dist/core/workflow/workspaces/state.js +21 -1
  185. package/dist/lib/browser/windows-chrome.d.ts +100 -12
  186. package/dist/lib/browser/windows-chrome.d.ts.map +1 -1
  187. package/dist/lib/browser/windows-chrome.js +262 -73
  188. package/dist/lib/docs-links.d.ts.map +1 -1
  189. package/dist/lib/docs-links.js +45 -2
  190. package/dist/lib/guarded-remove.d.ts +7 -0
  191. package/dist/lib/guarded-remove.d.ts.map +1 -1
  192. package/dist/lib/guarded-remove.js +106 -12
  193. package/dist/lib/identities/assume.d.ts.map +1 -1
  194. package/dist/lib/identities/assume.js +4 -0
  195. package/dist/lib/zip/index.d.ts +41 -0
  196. package/dist/lib/zip/index.d.ts.map +1 -0
  197. package/dist/lib/zip/index.js +398 -0
  198. package/package.json +7 -1
  199. package/src/commands/agents.ts +54 -63
  200. package/src/commands/browse.ts +50 -14
  201. package/src/commands/diagnostics.ts +10 -0
  202. package/src/commands/doctor.ts +26 -7
  203. package/src/commands/governor.ts +23 -10
  204. package/src/commands/inbox.ts +52 -2
  205. package/src/commands/rm.ts +43 -4
  206. package/src/commands/servers.ts +16 -4
  207. package/src/commands/supervisor.ts +7 -2
  208. package/src/commands/toc.ts +14 -6
  209. package/src/commands/workflow.ts +30 -30
  210. package/src/core/agents/live-authority-v3.ts +17 -0
  211. package/src/core/agents/live-lifecycle-v3.ts +52 -14
  212. package/src/core/agents/mailbox.ts +26 -17
  213. package/src/core/agents/render/prompt-context.ts +10 -12
  214. package/src/core/agents/render/session-context.ts +9 -32
  215. package/src/core/agents/rules/stop-hook.ts +3 -3
  216. package/src/core/agents/state/heartbeat-writer.ts +29 -12
  217. package/src/core/agents/state/live-coordination-writer.ts +29 -0
  218. package/src/core/agents/state/pidmap.ts +13 -0
  219. package/src/core/agents/state/proc-start.ts +57 -0
  220. package/src/core/agents/state/stale-sweep.ts +247 -82
  221. package/src/core/agents/state/unchanged-file.ts +456 -0
  222. package/src/core/context/index.ts +446 -136
  223. package/src/core/context/observation.ts +29 -0
  224. package/src/core/diagnostics/bundle.ts +23 -18
  225. package/src/core/diagnostics/path-identity.ts +15 -0
  226. package/src/core/diagnostics/sanitize.ts +4 -3
  227. package/src/core/events/v3/authority-outbox.ts +56 -0
  228. package/src/core/events/v3/generation-custody.ts +59 -0
  229. package/src/core/events/v3/producers/coordination-recorder.ts +15 -0
  230. package/src/core/events/v3/producers/recorder.ts +94 -68
  231. package/src/core/governor/planning.ts +20 -8
  232. package/src/core/governor/service-read.ts +128 -20
  233. package/src/core/governor/service.ts +119 -42
  234. package/src/core/hooks/cli.ts +33 -5
  235. package/src/core/inbox/contract.ts +2 -1
  236. package/src/core/inbox/index.ts +2 -0
  237. package/src/core/inbox/peer-messages.ts +94 -0
  238. package/src/core/inbox/service.ts +88 -22
  239. package/src/core/inbox/targets.ts +148 -0
  240. package/src/core/inbox/watcher.ts +8 -9
  241. package/src/core/journal/index.ts +19 -2
  242. package/src/core/resources/contract.ts +4 -0
  243. package/src/core/resources/service-status.ts +35 -12
  244. package/src/core/resources/service.ts +71 -24
  245. package/src/core/resources/status.ts +57 -8
  246. package/src/core/servers/index.ts +177 -91
  247. package/src/core/servers/tunnels.ts +3 -11
  248. package/src/core/supervisor/contract.ts +13 -0
  249. package/src/core/supervisor/explanations.ts +4 -1
  250. package/src/core/supervisor/findings.ts +68 -10
  251. package/src/core/supervisor/service.ts +88 -26
  252. package/src/core/supervisor/services.ts +26 -8
  253. package/src/core/supervisor/status.ts +89 -12
  254. package/src/core/supervisor/timeline.ts +2 -1
  255. package/src/core/work/state.ts +7 -7
  256. package/src/core/workflow/engine.ts +131 -49
  257. package/src/core/workflow/failure-evidence.ts +141 -0
  258. package/src/core/workflow/index.ts +0 -1
  259. package/src/core/workflow/live-session-v3.ts +146 -5
  260. package/src/core/workflow/proof.ts +134 -26
  261. package/src/core/workflow/run-state.ts +31 -29
  262. package/src/core/workflow/spawn-claude.ts +18 -0
  263. package/src/core/workflow/spawn-codex.ts +13 -0
  264. package/src/core/workflow/spawn-cursor.ts +18 -0
  265. package/src/core/workflow/spawn-opencode.ts +14 -0
  266. package/src/core/workflow/types.ts +51 -17
  267. package/src/core/workflow/workspaces/index.ts +0 -1
  268. package/src/core/workflow/workspaces/leases.ts +48 -17
  269. package/src/core/workflow/workspaces/local-git.ts +315 -5
  270. package/src/core/workflow/workspaces/ownership.ts +232 -0
  271. package/src/core/workflow/workspaces/reclaim.ts +58 -0
  272. package/src/core/workflow/workspaces/recovery.ts +25 -13
  273. package/src/core/workflow/workspaces/state.ts +20 -0
  274. package/src/lib/browser/windows-chrome.ts +346 -86
  275. package/src/lib/docs-links.ts +49 -2
  276. package/src/lib/guarded-remove.ts +132 -14
  277. package/src/lib/identities/assume.ts +4 -0
  278. package/src/lib/zip/index.ts +483 -0
@@ -1,7 +1,7 @@
1
1
  import { createHash } from "node:crypto";
2
2
  import { existsSync, readFileSync, statSync } from "node:fs";
3
- import { join } from "node:path";
4
- import type { RepoSnapshot } from "../context/index.ts";
3
+ import { isAbsolute, join } from "node:path";
4
+ import { observationsComparable, type RepoSnapshot } from "../context/observation.ts";
5
5
  import { DIAGNOSTIC_ADVICE_SCHEMA_VERSION } from "../diagnostics/contract.ts";
6
6
  import type {
7
7
  NormalizedPolicy,
@@ -12,6 +12,7 @@ import type {
12
12
  import { policyDigest } from "../policy/index.ts";
13
13
  import { isCanonicalWorkflowAttemptContext } from "./attempt-context.ts";
14
14
  import { stableDigest, writeImmutableJson } from "./durable-record.ts";
15
+ import { failureEvidenceAllowsUncharged, isSpawnFailureEvidence } from "./failure-evidence.ts";
15
16
  import { readWorkflowRunManifest } from "./run-state.ts";
16
17
  import type {
17
18
  AcceptanceCriterion,
@@ -242,17 +243,25 @@ export function digestResult(value: unknown, kind?: "text" | "json"): ResultDige
242
243
  * attempt was informative about the work — charge it. This also keeps a
243
244
  * resumed run whose earlier segment did real work from being written off by
244
245
  * a later environment failure.
245
- * 3. Otherwise, among the failed agents, environment wins over upstream: a
246
- * missing binary means nothing ran at all, and it is the operator-chosen
247
- * hard stop.
246
+ * 3. Every participant must have failed with positive no-effect evidence,
247
+ * observed absent output and no refusal. A class alone is insufficient.
248
+ * Among those eligible failures, environment is the hard stop.
248
249
  * 4. Anything else is undefined ⇒ a charged work failure, exactly as today.
249
250
  */
250
251
  export function deriveRunFailureClass(
251
252
  status: "succeeded" | "failed",
252
- agents: readonly Pick<WorkflowAgentProof, "status" | "class">[],
253
+ agents: readonly Pick<WorkflowAgentProof, "status" | "class" | "failure_evidence">[],
253
254
  ): SpawnFailureClass | undefined {
254
255
  if (status !== "failed") return undefined;
255
- if (agents.some((agent) => agent.status === "succeeded" || agent.status === "cached")) {
256
+ if (
257
+ agents.length === 0 ||
258
+ agents.some(
259
+ (agent) =>
260
+ agent.status !== "failed" ||
261
+ (agent.class !== "environment" && agent.class !== "upstream") ||
262
+ !failureEvidenceAllowsUncharged(agent.failure_evidence),
263
+ )
264
+ ) {
256
265
  return undefined;
257
266
  }
258
267
  const failed = agents.filter((agent) => agent.status === "failed");
@@ -385,6 +394,13 @@ export function readWorkflowProof(coordRoot: string, runId: string): WorkflowPro
385
394
  proof.run.class !== "environment" &&
386
395
  proof.run.class !== "upstream" &&
387
396
  proof.run.class !== "decision") ||
397
+ !Array.isArray(proof.agents) ||
398
+ proof.agents.some(
399
+ (agent) =>
400
+ !agent ||
401
+ typeof agent !== "object" ||
402
+ (agent.failure_evidence !== undefined && !isSpawnFailureEvidence(agent.failure_evidence)),
403
+ ) ||
388
404
  (proof.run.work_context !== undefined &&
389
405
  (!proof.run.work_item_id ||
390
406
  proof.run.work_context.id !== proof.run.work_item_id ||
@@ -399,6 +415,22 @@ export function readWorkflowProof(coordRoot: string, runId: string): WorkflowPro
399
415
  ) {
400
416
  throw new Error(`workflow proof at ${path} has an unsupported or mismatched schema`);
401
417
  }
418
+ if (
419
+ repoComparisonProblem(proof.repository.before, proof.repository.after) !== undefined ||
420
+ proof.repository.before.dirty_paths_truncated ||
421
+ proof.repository.after.dirty_paths_truncated ||
422
+ !proof.repository.before.branch?.trim() ||
423
+ !proof.repository.after.branch?.trim() ||
424
+ !proof.repository.before.head?.trim() ||
425
+ !proof.repository.after.head?.trim()
426
+ ) {
427
+ proof.repository = buildRepoEvidence(proof.repository.before, proof.repository.after);
428
+ proof.unknowns = proof.unknowns.filter((item) => item.code !== "repository_drift_incomplete");
429
+ proof.unknowns.push({
430
+ code: "repository_drift_incomplete",
431
+ message: proof.repository.drift.note!,
432
+ });
433
+ }
402
434
  const manifestPath = join(coordRoot, ".harnery", "workflows", runId, "run.json");
403
435
  if (proof.execution === undefined && !existsSync(manifestPath)) return proof;
404
436
  const manifest = readWorkflowRunManifest(coordRoot, runId);
@@ -512,13 +544,31 @@ export function renderWorkflowProof(proof: WorkflowProof): string {
512
544
  : "admission: shadow not needed; no real child dispatch",
513
545
  );
514
546
  }
515
- const repo = proof.repository;
547
+ const comparable =
548
+ repoComparisonProblem(proof.repository.before, proof.repository.after) === undefined;
549
+ const repo =
550
+ comparable &&
551
+ !proof.repository.before.dirty_paths_truncated &&
552
+ !proof.repository.after.dirty_paths_truncated &&
553
+ proof.repository.before.branch?.trim() &&
554
+ proof.repository.after.branch?.trim() &&
555
+ proof.repository.before.head?.trim() &&
556
+ proof.repository.after.head?.trim()
557
+ ? proof.repository
558
+ : buildRepoEvidence(proof.repository.before, proof.repository.after);
516
559
  const drift = repo.drift;
517
- lines.push(
518
- `repository: branch ${repo.before.branch ?? "unknown"} -> ${repo.after.branch ?? "unknown"}; ` +
519
- `HEAD ${short(repo.before.head)} -> ${short(repo.after.head)}; ` +
520
- `${drift.dirty_paths_added.length} dirty added, ${drift.dirty_paths_cleared.length} cleared`,
521
- );
560
+ if (!comparable) {
561
+ lines.push(`repository: drift unknown. ${drift.note}`);
562
+ } else {
563
+ const paths =
564
+ drift.dirty_paths_added === null || drift.dirty_paths_cleared === null
565
+ ? "dirty-path drift unknown"
566
+ : `${drift.dirty_paths_added.length} dirty added, ${drift.dirty_paths_cleared.length} cleared`;
567
+ lines.push(
568
+ `repository: branch ${repo.before.branch ?? "unknown"} -> ${repo.after.branch ?? "unknown"}; ` +
569
+ `HEAD ${short(repo.before.head)} -> ${short(repo.after.head)}; ${paths}`,
570
+ );
571
+ }
522
572
  if (proof.unknowns.length > 0) {
523
573
  lines.push(`unknowns: ${proof.unknowns.length}`);
524
574
  for (const unknown of proof.unknowns) lines.push(` - ${unknown.message}`);
@@ -530,26 +580,56 @@ export function renderWorkflowProof(proof: WorkflowProof): string {
530
580
  function buildRepoEvidence(beforeRaw: RepoSnapshot, afterRaw: RepoSnapshot): WorkflowRepoEvidence {
531
581
  const before = normalizeRepoSnapshot(beforeRaw);
532
582
  const after = normalizeRepoSnapshot(afterRaw);
583
+ const problem = repoComparisonProblem(before, after);
584
+ if (problem !== undefined) {
585
+ return {
586
+ source: "engine",
587
+ before,
588
+ after,
589
+ drift: {
590
+ branch_changed: null,
591
+ head_changed: null,
592
+ dirty_paths_added: null,
593
+ dirty_paths_cleared: null,
594
+ dirty_paths_retained: null,
595
+ incomplete: true,
596
+ note: `Repository drift could not be verified: ${problem}. Re-check before editing.`,
597
+ },
598
+ };
599
+ }
600
+ const pathsComparable = !before.dirty_paths_truncated && !after.dirty_paths_truncated;
601
+ const branchKnown = Boolean(before.branch && after.branch);
602
+ const headKnown = Boolean(before.head && after.head);
533
603
  const beforeDirty = new Set(before.dirty_paths);
534
604
  const afterDirty = new Set(after.dirty_paths);
535
605
  const retained = after.dirty_paths.filter((path) => beforeDirty.has(path));
536
- const incomplete = Boolean(
537
- before.dirty_paths_truncated || after.dirty_paths_truncated || retained.length > 0,
538
- );
606
+ const notes = [
607
+ ...(!pathsComparable
608
+ ? ["Dirty-path drift is unknown because a snapshot list was truncated."]
609
+ : []),
610
+ ...(!branchKnown ? ["Branch drift is unknown because a snapshot has no branch."] : []),
611
+ ...(!headKnown ? ["HEAD drift is unknown because a snapshot has no HEAD."] : []),
612
+ ...(pathsComparable && retained.length > 0
613
+ ? ["Snapshots cannot prove whether retained dirty paths changed during the run."]
614
+ : []),
615
+ ];
616
+ const incomplete = notes.length > 0;
539
617
  return {
540
618
  source: "engine",
541
619
  before,
542
620
  after,
543
621
  drift: {
544
- branch_changed: before.branch !== after.branch,
545
- head_changed: before.head !== after.head,
546
- dirty_paths_added: after.dirty_paths.filter((path) => !beforeDirty.has(path)),
547
- dirty_paths_cleared: before.dirty_paths.filter((path) => !afterDirty.has(path)),
548
- dirty_paths_retained: retained,
622
+ branch_changed: branchKnown ? before.branch !== after.branch : null,
623
+ head_changed: headKnown ? before.head !== after.head : null,
624
+ dirty_paths_added: pathsComparable
625
+ ? after.dirty_paths.filter((path) => !beforeDirty.has(path))
626
+ : null,
627
+ dirty_paths_cleared: pathsComparable
628
+ ? before.dirty_paths.filter((path) => !afterDirty.has(path))
629
+ : null,
630
+ dirty_paths_retained: pathsComparable ? retained : null,
549
631
  incomplete,
550
- note: incomplete
551
- ? "Snapshots cannot prove whether retained dirty paths changed during the run, and truncated lists may omit paths."
552
- : undefined,
632
+ note: incomplete ? notes.join(" ") : undefined,
553
633
  },
554
634
  };
555
635
  }
@@ -557,14 +637,42 @@ function buildRepoEvidence(beforeRaw: RepoSnapshot, afterRaw: RepoSnapshot): Wor
557
637
  function normalizeRepoSnapshot(snapshot: RepoSnapshot): WorkflowRepoSnapshot {
558
638
  return {
559
639
  cwd: snapshot.cwd,
640
+ observation: snapshot.observation ?? "unavailable",
641
+ observation_detail: snapshot.observation
642
+ ? clippedOptional(snapshot.observation_detail, 500)
643
+ : "No repository observation was recorded.",
560
644
  root: snapshot.root,
561
- branch: snapshot.branch,
562
- head: snapshot.head,
645
+ branch: snapshot.branch?.trim() || undefined,
646
+ head: snapshot.head?.trim() || undefined,
563
647
  dirty_paths: snapshot.dirty_paths,
564
648
  dirty_paths_truncated: snapshot.dirty_paths_truncated,
565
649
  };
566
650
  }
567
651
 
652
+ function snapshotFailure(label: string, snapshot: WorkflowRepoSnapshot): string {
653
+ return `${label} snapshot is ${snapshot.observation}${snapshot.observation_detail ? ` (${snapshot.observation_detail})` : ""}`;
654
+ }
655
+
656
+ function repoComparisonProblem(
657
+ before: WorkflowRepoSnapshot,
658
+ after: WorkflowRepoSnapshot,
659
+ ): string | undefined {
660
+ if (!observationsComparable(before, after)) {
661
+ return [
662
+ ...(before.observation !== "ok" ? [snapshotFailure("before", before)] : []),
663
+ ...(after.observation !== "ok" ? [snapshotFailure("after", after)] : []),
664
+ ].join("; ");
665
+ }
666
+ const unknownRoots = [
667
+ ...(!before.root || !isAbsolute(before.root) ? ["before"] : []),
668
+ ...(!after.root || !isAbsolute(after.root) ? ["after"] : []),
669
+ ];
670
+ if (unknownRoots.length > 0)
671
+ return `${unknownRoots.join(" and ")} snapshot repository root is unknown`;
672
+ if (before.root !== after.root) return "snapshots name different repository roots";
673
+ return undefined;
674
+ }
675
+
568
676
  function buildAdapterCoverage(
569
677
  agents: WorkflowAgentProof[],
570
678
  claims: Readonly<Record<string, AdapterEvidenceCapability | undefined>> | undefined,
@@ -1,4 +1,4 @@
1
- import { createHash } from "node:crypto";
1
+ import { createHash, randomUUID } from "node:crypto";
2
2
  import {
3
3
  chmodSync,
4
4
  closeSync,
@@ -47,7 +47,6 @@ import {
47
47
  export const WORKFLOW_RUN_MANIFEST_SCHEMA_VERSION = 2 as const;
48
48
 
49
49
  const MANIFEST_LIMIT_BYTES = 256 * 1024;
50
- const FOREIGN_LEASE_STALE_MS = 24 * 60 * 60 * 1_000;
51
50
 
52
51
  export interface WorkflowRunManifest {
53
52
  schema_version: typeof WORKFLOW_RUN_MANIFEST_SCHEMA_VERSION;
@@ -144,6 +143,13 @@ export function readWorkflowRunManifest(coordRoot: string, runId: string): Workf
144
143
  ) {
145
144
  throw new Error(`workflow run manifest at ${path} has an unsupported or mismatched schema`);
146
145
  }
146
+ if (manifest.repository_before.observation === undefined) {
147
+ manifest.repository_before = {
148
+ ...manifest.repository_before,
149
+ observation: "unavailable",
150
+ observation_detail: "No repository observation was recorded.",
151
+ };
152
+ }
147
153
  assertWorkspaceManifestAuthority(manifest, readManifestWorkspaceRequest(coordRoot, runId));
148
154
  return manifest;
149
155
  }
@@ -197,18 +203,26 @@ export function workflowRunManifestPath(coordRoot: string, runId: string): strin
197
203
  return join(coordRoot, ".harnery", "workflows", runId, "run.json");
198
204
  }
199
205
 
200
- /** Acquire an exclusive, crash-recoverable lease for one resume segment. */
206
+ /** Acquire an exclusive lease shared by resume and reclaim. Existing locks
207
+ * always refuse: a stale observation cannot authorize unlinking a new owner.
208
+ * Crash recovery requires an audited ownership check outside this operation. */
201
209
  export function acquireWorkflowResumeLease(coordRoot: string, runId: string): () => void {
202
210
  assertWorkflowRunId(runId);
203
211
  const path = join(coordRoot, ".harnery", "workflows", runId, "resume.lock");
204
- const owner = { pid: process.pid, host: hostname(), created_at: new Date().toISOString() };
212
+ const owner = {
213
+ owner_id: randomUUID(),
214
+ pid: process.pid,
215
+ host: hostname(),
216
+ created_at: new Date().toISOString(),
217
+ };
218
+ const ownerBytes = `${JSON.stringify(owner)}\n`;
205
219
  mkdirSync(dirname(path), { recursive: true });
206
220
 
207
221
  const acquire = (): boolean => {
208
222
  try {
209
223
  const fd = openSync(path, "wx", stateFileMode());
210
224
  try {
211
- writeFileSync(fd, `${JSON.stringify(owner)}\n`, "utf8");
225
+ writeFileSync(fd, ownerBytes, "utf8");
212
226
  } finally {
213
227
  closeSync(fd);
214
228
  }
@@ -222,22 +236,17 @@ export function acquireWorkflowResumeLease(coordRoot: string, runId: string): ()
222
236
 
223
237
  if (!acquire()) {
224
238
  const existing = readResumeLease(path);
225
- if (existing && resumeLeaseIsLive(existing)) {
226
- throw new Error(
227
- `workflow run ${runId} is already being resumed by pid ${existing.pid} on ${existing.host}`,
228
- );
229
- }
230
- unlinkSync(path);
231
- if (!acquire())
232
- throw new Error(`workflow run ${runId} resume lease raced with another process`);
239
+ const holder = existing
240
+ ? ` by pid ${existing.pid} on ${existing.host}; its liveness has not been verified`
241
+ : "; its owner could not be verified";
242
+ throw new Error(`workflow run ${runId} resume lease is already held${holder}`);
233
243
  }
234
244
 
235
245
  return () => {
236
246
  try {
237
- const existing = readResumeLease(path);
238
- if (existing?.pid === owner.pid && existing.host === owner.host) unlinkSync(path);
247
+ if (readFileSync(path, "utf8") === ownerBytes) unlinkSync(path);
239
248
  } catch {
240
- // Best effort. A stale lease is recoverable on the next resume attempt.
249
+ // Leave an unreadable or replaced lock untouched.
241
250
  }
242
251
  };
243
252
  }
@@ -257,19 +266,6 @@ function readResumeLease(path: string): { pid: number; host: string; created_at:
257
266
  }
258
267
  }
259
268
 
260
- function resumeLeaseIsLive(lease: { pid: number; host: string; created_at: string }): boolean {
261
- if (lease.host !== hostname()) {
262
- const age = Date.now() - Date.parse(lease.created_at);
263
- return Number.isFinite(age) && age < FOREIGN_LEASE_STALE_MS;
264
- }
265
- try {
266
- process.kill(lease.pid, 0);
267
- return true;
268
- } catch (error) {
269
- return (error as NodeJS.ErrnoException).code === "EPERM";
270
- }
271
- }
272
-
273
269
  function validExecution(value: WorkflowRunManifest["execution"]): boolean {
274
270
  if (!value || typeof value !== "object") return false;
275
271
  return (
@@ -352,6 +348,12 @@ function validRepoSnapshot(value: RepoSnapshot): boolean {
352
348
  return (
353
349
  Boolean(value) &&
354
350
  typeof value === "object" &&
351
+ (value.observation === undefined ||
352
+ value.observation === "ok" ||
353
+ value.observation === "partial" ||
354
+ value.observation === "unavailable") &&
355
+ (value.observation_detail === undefined ||
356
+ (typeof value.observation_detail === "string" && value.observation_detail.length <= 500)) &&
355
357
  isAbsolute(value.cwd) &&
356
358
  (value.root === undefined || isAbsolute(value.root)) &&
357
359
  (value.branch === undefined || typeof value.branch === "string") &&
@@ -23,6 +23,7 @@ import { builtinAdapterProfile, validateAdapterEffort } from "../adapters/profil
23
23
  import type { AdapterInvocation, AdapterRawResult } from "../adapters/types.ts";
24
24
  import { notFoundError } from "./adapters.ts";
25
25
  import { buildChildEnv } from "./child-env.ts";
26
+ import { observeSpawnFailure } from "./failure-evidence.ts";
26
27
  import { resolveFullAccessArgv, resolveSandboxProjection } from "./sandbox-projection.ts";
27
28
  import { isUpstreamFailureText, vendorFailureText } from "./spawn-failure.ts";
28
29
  import type { Spawner, SpawnRequest, SpawnResult } from "./types.ts";
@@ -66,6 +67,23 @@ export function buildClaudeInvocation(req: SpawnRequest): AdapterInvocation {
66
67
  }
67
68
 
68
69
  export function normalizeClaudeResult(raw: AdapterRawResult): SpawnResult {
70
+ const result = normalizeClaudeTransportResult(raw);
71
+ if (result.ok) return result;
72
+ let text = raw.stdout.trim();
73
+ let source: "stdout" | "vendor_envelope" = "stdout";
74
+ try {
75
+ const envelope = JSON.parse(raw.stdout) as ClaudeEnvelope;
76
+ if (typeof envelope.result === "string" && envelope.result.length > 0) {
77
+ text = envelope.result;
78
+ source = "vendor_envelope";
79
+ }
80
+ } catch {
81
+ // Retain unstructured output without treating it as native authority.
82
+ }
83
+ return { ...result, text, failureEvidence: observeSpawnFailure(raw, text, source) };
84
+ }
85
+
86
+ function normalizeClaudeTransportResult(raw: AdapterRawResult): SpawnResult {
69
87
  if (raw.timedOut) {
70
88
  return {
71
89
  ok: false,
@@ -26,6 +26,7 @@ import { builtinAdapterProfile, validateAdapterEffort } from "../adapters/profil
26
26
  import type { AdapterInvocation, AdapterRawResult } from "../adapters/types.ts";
27
27
  import { notFoundError } from "./adapters.ts";
28
28
  import { buildChildEnv } from "./child-env.ts";
29
+ import { observeSpawnFailure } from "./failure-evidence.ts";
29
30
  import { resolveFullAccessArgv, resolveSandboxProjection } from "./sandbox-projection.ts";
30
31
  import { isUpstreamFailureText, vendorFailureText } from "./spawn-failure.ts";
31
32
  import type { Spawner, SpawnRequest, SpawnResult } from "./types.ts";
@@ -78,6 +79,18 @@ export function buildCodexInvocation(req: SpawnRequest, resultFile?: string): Ad
78
79
  }
79
80
 
80
81
  export function normalizeCodexResult(raw: AdapterRawResult): SpawnResult {
82
+ const result = normalizeCodexTransportResult(raw);
83
+ if (result.ok) return result;
84
+ const finalText = raw.resultFileText?.trim();
85
+ const text = finalText || raw.stdout.trim();
86
+ return {
87
+ ...result,
88
+ text,
89
+ failureEvidence: observeSpawnFailure(raw, text, finalText ? "final_message" : "stdout"),
90
+ };
91
+ }
92
+
93
+ function normalizeCodexTransportResult(raw: AdapterRawResult): SpawnResult {
81
94
  if (raw.timedOut) {
82
95
  return {
83
96
  ok: false,
@@ -24,6 +24,7 @@ import { builtinAdapterProfile, validateAdapterEffort } from "../adapters/profil
24
24
  import type { AdapterInvocation, AdapterRawResult } from "../adapters/types.ts";
25
25
  import { notFoundError } from "./adapters.ts";
26
26
  import { buildChildEnv } from "./child-env.ts";
27
+ import { observeSpawnFailure } from "./failure-evidence.ts";
27
28
  import { resolveFullAccessArgv, resolveSandboxProjection } from "./sandbox-projection.ts";
28
29
  import { isUpstreamFailureText, vendorFailureText } from "./spawn-failure.ts";
29
30
  import type { Spawner, SpawnRequest, SpawnResult } from "./types.ts";
@@ -78,6 +79,23 @@ export function buildCursorInvocation(req: SpawnRequest): AdapterInvocation {
78
79
  }
79
80
 
80
81
  export function normalizeCursorResult(raw: AdapterRawResult): SpawnResult {
82
+ const result = normalizeCursorTransportResult(raw);
83
+ if (result.ok) return result;
84
+ let text = raw.stdout;
85
+ let source: "stdout" | "vendor_envelope" = "stdout";
86
+ try {
87
+ const envelope = JSON.parse(raw.stdout) as CursorEnvelope;
88
+ if (typeof envelope.result === "string" && envelope.result.length > 0) {
89
+ text = envelope.result;
90
+ source = "vendor_envelope";
91
+ }
92
+ } catch {
93
+ // Raw output remains evidence, not an inferred refusal or effect result.
94
+ }
95
+ return { ...result, text, failureEvidence: observeSpawnFailure(raw, text, source) };
96
+ }
97
+
98
+ function normalizeCursorTransportResult(raw: AdapterRawResult): SpawnResult {
81
99
  if (raw.timedOut) {
82
100
  return {
83
101
  ok: false,
@@ -22,6 +22,7 @@ import { builtinAdapterProfile, validateAdapterEffort } from "../adapters/profil
22
22
  import type { AdapterInvocation, AdapterRawResult } from "../adapters/types.ts";
23
23
  import { notFoundError } from "./adapters.ts";
24
24
  import { buildChildEnv } from "./child-env.ts";
25
+ import { observeSpawnFailure } from "./failure-evidence.ts";
25
26
  import { resolveFullAccessArgv, resolveSandboxProjection } from "./sandbox-projection.ts";
26
27
  import { isUpstreamFailureText, vendorFailureText } from "./spawn-failure.ts";
27
28
  import type { Spawner, SpawnRequest, SpawnResult } from "./types.ts";
@@ -88,6 +89,19 @@ export function buildOpenCodeInvocation(req: SpawnRequest): AdapterInvocation {
88
89
  }
89
90
 
90
91
  export function normalizeOpenCodeResult(raw: AdapterRawResult): SpawnResult {
92
+ const result = normalizeOpenCodeTransportResult(raw);
93
+ if (result.ok) return result;
94
+ const parsed = parseOpenCodeStream(raw.stdout);
95
+ const text = parsed.text || raw.stdout.trim();
96
+ return {
97
+ ...result,
98
+ text,
99
+ sessionId: parsed.sessionId,
100
+ failureEvidence: observeSpawnFailure(raw, text, parsed.text ? "vendor_envelope" : "stdout"),
101
+ };
102
+ }
103
+
104
+ function normalizeOpenCodeTransportResult(raw: AdapterRawResult): SpawnResult {
91
105
  if (raw.timedOut) {
92
106
  return {
93
107
  ok: false,
@@ -137,11 +137,12 @@ export interface WorkflowAgentProof {
137
137
  session_id?: string;
138
138
  result?: ResultDigest;
139
139
  error?: string;
140
- /** Set on a failed agent whose spawn was classified uninformative about the
141
- * work (ADR 0046): environment (binary absent) or upstream (vendor refused).
142
- * Absent ⇒ a work failure. Recorded even when a script's parallel() swallows
143
- * the rejection, so the run-level class can still be derived from proof. */
140
+ /** Failed-spawn transport class. It is retained even when a script swallows
141
+ * the rejection; charging additionally requires validated failure evidence. */
144
142
  class?: SpawnFailureClass;
143
+ /** Failure observations carried from the adapter. Missing observations are
144
+ * unknown and cannot authorize an uncharged attempt. */
145
+ failure_evidence?: SpawnFailureEvidence;
145
146
  /** Which vendor sandbox mode this agent launched with and why (ADR 0192).
146
147
  * Absent on a cached agent, which launched nothing. */
147
148
  access?: WorkerAccessDecision;
@@ -149,6 +150,9 @@ export interface WorkflowAgentProof {
149
150
 
150
151
  export interface WorkflowRepoSnapshot {
151
152
  cwd: string;
153
+ observation: "ok" | "unavailable" | "partial";
154
+ /** Bounded explanation of an unavailable or incomplete observation. */
155
+ observation_detail?: string;
152
156
  root?: string;
153
157
  branch?: string;
154
158
  head?: string;
@@ -161,12 +165,13 @@ export interface WorkflowRepoEvidence {
161
165
  before: WorkflowRepoSnapshot;
162
166
  after: WorkflowRepoSnapshot;
163
167
  drift: {
164
- branch_changed: boolean;
165
- head_changed: boolean;
166
- dirty_paths_added: string[];
167
- dirty_paths_cleared: string[];
168
- dirty_paths_retained: string[];
169
- /** True when snapshots cannot prove whether every retained dirty path changed. */
168
+ /** Null means the snapshots do not establish this comparison. */
169
+ branch_changed: boolean | null;
170
+ head_changed: boolean | null;
171
+ dirty_paths_added: string[] | null;
172
+ dirty_paths_cleared: string[] | null;
173
+ dirty_paths_retained: string[] | null;
174
+ /** True when an observation, comparison, or retained-path change is unknown. */
170
175
  incomplete: boolean;
171
176
  note?: string;
172
177
  };
@@ -358,15 +363,16 @@ export interface WorkflowSpecialistProfile {
358
363
  }
359
364
 
360
365
  /**
361
- * Why a failed run was uninformative about the work (ADR 0046). Absent means
362
- * the attempt produced information about the work (or succeeded), which is the
363
- * default and is charged against the attempt budget exactly as before.
366
+ * The positively identified transport cause of a failed spawn (ADR 0046).
367
+ * This class alone proves neither absence of effects nor retry eligibility.
364
368
  *
365
369
  * - `environment`: the run never started — a precondition was missing (the
366
- * vendor binary was absent). Uncharged AND not retried: retrying an unchanged
370
+ * vendor binary was absent). With positive no-effect/output evidence it is
371
+ * uncharged and not retried: retrying an unchanged
367
372
  * environment cannot help, so the work item stops and names the precondition.
368
373
  * - `upstream`: the vendor was reached and refused (5xx, 429, circuit open).
369
- * Uncharged, but retry stays available (bounded by max_uncharged_attempts).
374
+ * Retry/free-attempt eligibility also requires positive no-effect evidence,
375
+ * observed absent output, and no refusal. Unsupported evidence stays unknown.
370
376
  */
371
377
  export type SpawnFailureClass = "environment" | "upstream";
372
378
 
@@ -388,6 +394,30 @@ export type SpawnFailureClass = "environment" | "upstream";
388
394
  */
389
395
  export type RunFailureClass = SpawnFailureClass | "decision";
390
396
 
397
+ /** Positive claims name the observation that supports them. A transport error
398
+ * or an empty reply is never evidence that no effects occurred. */
399
+ export type SpawnEffectEvidence =
400
+ | { state: "unknown" }
401
+ | { state: "none"; source: "spawn_errno"; reference: "ENOENT" }
402
+ | { state: "none" | "observed"; source: "adapter_evidence"; reference: string };
403
+
404
+ export type SpawnRefusalEvidence =
405
+ | { state: "unknown" }
406
+ | { state: "refused"; source: "vendor_envelope"; reference: string };
407
+
408
+ export type SpawnOutputSource = "final_message" | "vendor_envelope" | "stdout";
409
+
410
+ export type SpawnPartialOutputEvidence =
411
+ | { state: "unknown" }
412
+ | { state: "absent"; source: SpawnOutputSource }
413
+ | { state: "present"; source: SpawnOutputSource; bytes: number; sha256: string };
414
+
415
+ export interface SpawnFailureEvidence {
416
+ effect: SpawnEffectEvidence;
417
+ refusal: SpawnRefusalEvidence;
418
+ partial_output: SpawnPartialOutputEvidence;
419
+ }
420
+
391
421
  /** What a spawn adapter returns for one subagent run. */
392
422
  export interface SpawnResult {
393
423
  ok: boolean;
@@ -399,9 +429,13 @@ export interface SpawnResult {
399
429
  durationMs: number;
400
430
  /** Populated when ok=false. */
401
431
  error?: string;
402
- /** Set when ok=false and the failure was positively identified as
403
- * uninformative about the work. Absent ⇒ a work failure (charged). */
432
+ /** Transport classification when ok=false; never sufficient by itself to
433
+ * grant free work or automatic redispatch. */
404
434
  class?: SpawnFailureClass;
435
+ /** Transport classification alone grants neither a retry nor free work.
436
+ * Absent evidence is explicitly unknown. Positive adapter_evidence claims
437
+ * require a documented producer observation, never an HTTP/text heuristic. */
438
+ failureEvidence?: SpawnFailureEvidence;
405
439
  }
406
440
 
407
441
  export interface SpawnRequest {
@@ -36,7 +36,6 @@ export {
36
36
  probe as probeLocalGitWorktreeProvider,
37
37
  } from "./local-git.ts";
38
38
  export type { PrepareReclaimInput, ReclaimMode, ReclaimPreparation } from "./reclaim.ts";
39
- export { prepareReclaim } from "./reclaim.ts";
40
39
  export type {
41
40
  AuthorizedIntegrationPlan,
42
41
  AuthorizedProviderIntegrationInput,