harnery 0.38.0 → 0.40.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 (378) hide show
  1. package/dist/commands/agents.d.ts.map +1 -1
  2. package/dist/commands/agents.js +32 -8
  3. package/dist/commands/artifacts.d.ts.map +1 -1
  4. package/dist/commands/artifacts.js +97 -6
  5. package/dist/commands/backup.d.ts +16 -0
  6. package/dist/commands/backup.d.ts.map +1 -1
  7. package/dist/commands/backup.js +239 -33
  8. package/dist/commands/browse.d.ts +8 -0
  9. package/dist/commands/browse.d.ts.map +1 -1
  10. package/dist/commands/browse.js +466 -231
  11. package/dist/commands/diagnostics.d.ts.map +1 -1
  12. package/dist/commands/diagnostics.js +6 -3
  13. package/dist/commands/ledger-v3.d.ts.map +1 -1
  14. package/dist/commands/ledger-v3.js +20 -4
  15. package/dist/commands/qa-run.d.ts.map +1 -1
  16. package/dist/commands/qa-run.js +26 -3
  17. package/dist/commands/qa-status.d.ts.map +1 -1
  18. package/dist/commands/qa-status.js +7 -2
  19. package/dist/commands/resources.d.ts.map +1 -1
  20. package/dist/commands/resources.js +23 -3
  21. package/dist/commands/review-pack.d.ts.map +1 -1
  22. package/dist/commands/review-pack.js +98 -23
  23. package/dist/commands/tunnel.d.ts.map +1 -1
  24. package/dist/commands/tunnel.js +2 -2
  25. package/dist/core/adapters/profiles.d.ts +1 -1
  26. package/dist/core/agents/canonical-emit.d.ts +3 -2
  27. package/dist/core/agents/canonical-emit.d.ts.map +1 -1
  28. package/dist/core/agents/canonical-emit.js +2 -0
  29. package/dist/core/agents/cli.js +13 -6
  30. package/dist/core/agents/live-authority-v3.d.ts.map +1 -1
  31. package/dist/core/agents/live-authority-v3.js +5 -3
  32. package/dist/core/agents/live-lifecycle-v3.d.ts +2 -2
  33. package/dist/core/agents/live-lifecycle-v3.d.ts.map +1 -1
  34. package/dist/core/agents/render/prompt-context.d.ts +7 -5
  35. package/dist/core/agents/render/prompt-context.d.ts.map +1 -1
  36. package/dist/core/agents/render/prompt-context.js +63 -8
  37. package/dist/core/agents/session-finalizer-v3.d.ts +2 -2
  38. package/dist/core/agents/session-finalizer-v3.d.ts.map +1 -1
  39. package/dist/core/agents/state/live-coordination-view.d.ts +3 -3
  40. package/dist/core/agents/state/live-coordination-view.d.ts.map +1 -1
  41. package/dist/core/agents/state/live-coordination-view.js +2 -1
  42. package/dist/core/agents/state/names.d.ts +35 -2
  43. package/dist/core/agents/state/names.d.ts.map +1 -1
  44. package/dist/core/agents/state/names.js +80 -5
  45. package/dist/core/agents/state/stale-sweep.d.ts +2 -1
  46. package/dist/core/agents/state/stale-sweep.d.ts.map +1 -1
  47. package/dist/core/agents/state/stale-sweep.js +23 -6
  48. package/dist/core/agents/turn-elapsed.d.ts +51 -0
  49. package/dist/core/agents/turn-elapsed.d.ts.map +1 -0
  50. package/dist/core/agents/turn-elapsed.js +91 -0
  51. package/dist/core/artifacts/activity.d.ts +14 -0
  52. package/dist/core/artifacts/activity.d.ts.map +1 -0
  53. package/dist/core/artifacts/activity.js +52 -0
  54. package/dist/core/artifacts/delivery-card.d.ts +39 -0
  55. package/dist/core/artifacts/delivery-card.d.ts.map +1 -0
  56. package/dist/core/artifacts/delivery-card.js +282 -0
  57. package/dist/core/artifacts/index.d.ts +42 -7
  58. package/dist/core/artifacts/index.d.ts.map +1 -1
  59. package/dist/core/artifacts/index.js +288 -76
  60. package/dist/core/artifacts/mutation-lock.d.ts +2 -0
  61. package/dist/core/artifacts/mutation-lock.d.ts.map +1 -0
  62. package/dist/core/artifacts/mutation-lock.js +91 -0
  63. package/dist/core/backup/host-snapshot.d.ts +18 -0
  64. package/dist/core/backup/host-snapshot.d.ts.map +1 -0
  65. package/dist/core/backup/host-snapshot.js +64 -0
  66. package/dist/core/config.d.ts +20 -5
  67. package/dist/core/config.d.ts.map +1 -1
  68. package/dist/core/config.js +44 -10
  69. package/dist/core/diagnostics/advice.d.ts +38 -3
  70. package/dist/core/diagnostics/advice.d.ts.map +1 -1
  71. package/dist/core/diagnostics/advice.js +103 -99
  72. package/dist/core/diagnostics/bundle.d.ts.map +1 -1
  73. package/dist/core/diagnostics/bundle.js +111 -47
  74. package/dist/core/diagnostics/comparison.d.ts.map +1 -1
  75. package/dist/core/diagnostics/comparison.js +3 -2
  76. package/dist/core/diagnostics/contract.d.ts +15 -31
  77. package/dist/core/diagnostics/contract.d.ts.map +1 -1
  78. package/dist/core/diagnostics/contract.js +2 -5
  79. package/dist/core/diagnostics/index.d.ts +2 -0
  80. package/dist/core/diagnostics/index.d.ts.map +1 -1
  81. package/dist/core/diagnostics/index.js +2 -0
  82. package/dist/core/diagnostics/pressure-contract.d.ts +260 -0
  83. package/dist/core/diagnostics/pressure-contract.d.ts.map +1 -0
  84. package/dist/core/diagnostics/pressure-contract.js +70 -0
  85. package/dist/core/diagnostics/pressure.d.ts +25 -0
  86. package/dist/core/diagnostics/pressure.d.ts.map +1 -0
  87. package/dist/core/diagnostics/pressure.js +1138 -0
  88. package/dist/core/diagnostics/replay.d.ts.map +1 -1
  89. package/dist/core/diagnostics/replay.js +90 -10
  90. package/dist/core/events/v3/adapter-id.d.ts +6 -0
  91. package/dist/core/events/v3/adapter-id.d.ts.map +1 -0
  92. package/dist/core/events/v3/adapter-id.js +7 -0
  93. package/dist/core/events/v3/archive-retention.d.ts.map +1 -1
  94. package/dist/core/events/v3/archive-retention.js +3 -2
  95. package/dist/core/events/v3/bootstrap.d.ts +41 -4
  96. package/dist/core/events/v3/bootstrap.d.ts.map +1 -1
  97. package/dist/core/events/v3/bootstrap.js +120 -17
  98. package/dist/core/events/v3/capabilities.d.ts +16 -9
  99. package/dist/core/events/v3/capabilities.d.ts.map +1 -1
  100. package/dist/core/events/v3/capabilities.js +37 -2
  101. package/dist/core/events/v3/capability-drift.d.ts +2 -2
  102. package/dist/core/events/v3/capability-drift.d.ts.map +1 -1
  103. package/dist/core/events/v3/control-witness.d.ts +48 -5
  104. package/dist/core/events/v3/control-witness.d.ts.map +1 -1
  105. package/dist/core/events/v3/control-witness.js +91 -36
  106. package/dist/core/events/v3/control.d.ts.map +1 -1
  107. package/dist/core/events/v3/control.js +22 -2
  108. package/dist/core/events/v3/index.d.ts +2 -1
  109. package/dist/core/events/v3/index.d.ts.map +1 -1
  110. package/dist/core/events/v3/index.js +2 -1
  111. package/dist/core/events/v3/live-feed.d.ts +7 -1
  112. package/dist/core/events/v3/live-feed.d.ts.map +1 -1
  113. package/dist/core/events/v3/live-feed.js +21 -3
  114. package/dist/core/events/v3/live-routing.d.ts.map +1 -1
  115. package/dist/core/events/v3/live-routing.js +10 -2
  116. package/dist/core/events/v3/producers/command-recorder.d.ts +2 -2
  117. package/dist/core/events/v3/producers/command-recorder.d.ts.map +1 -1
  118. package/dist/core/events/v3/producers/command-recorder.js +2 -1
  119. package/dist/core/events/v3/producers/coordination-recorder.d.ts +2 -2
  120. package/dist/core/events/v3/producers/coordination-recorder.d.ts.map +1 -1
  121. package/dist/core/events/v3/producers/coordination-recorder.js +2 -1
  122. package/dist/core/events/v3/producers/hook-base.d.ts +2 -2
  123. package/dist/core/events/v3/producers/hook-base.d.ts.map +1 -1
  124. package/dist/core/events/v3/producers/hook-base.js +15 -5
  125. package/dist/core/events/v3/producers/hook.d.ts.map +1 -1
  126. package/dist/core/events/v3/producers/hook.js +17 -4
  127. package/dist/core/events/v3/producers/intake.d.ts +4 -4
  128. package/dist/core/events/v3/producers/intake.d.ts.map +1 -1
  129. package/dist/core/events/v3/producers/intake.js +3 -2
  130. package/dist/core/events/v3/producers/producer-state-observer.d.ts.map +1 -1
  131. package/dist/core/events/v3/producers/producer-state-observer.js +2 -1
  132. package/dist/core/events/v3/producers/recorder.d.ts +16 -5
  133. package/dist/core/events/v3/producers/recorder.d.ts.map +1 -1
  134. package/dist/core/events/v3/producers/recorder.js +31 -9
  135. package/dist/core/events/v3/reader.d.ts.map +1 -1
  136. package/dist/core/events/v3/reader.js +10 -13
  137. package/dist/core/events/v3/runtime-build.d.ts.map +1 -1
  138. package/dist/core/events/v3/runtime-build.js +3 -2
  139. package/dist/core/events/v3/runtime-telemetry-capabilities.d.ts +2 -2
  140. package/dist/core/events/v3/runtime-telemetry-capabilities.d.ts.map +1 -1
  141. package/dist/core/events/v3/turn-telemetry.d.ts +2 -2
  142. package/dist/core/events/v3/turn-telemetry.d.ts.map +1 -1
  143. package/dist/core/hooks/adapter/output.d.ts +16 -14
  144. package/dist/core/hooks/adapter/output.d.ts.map +1 -1
  145. package/dist/core/hooks/adapter/output.js +36 -38
  146. package/dist/core/hooks/backup-schedule.d.ts +34 -0
  147. package/dist/core/hooks/backup-schedule.d.ts.map +1 -0
  148. package/dist/core/hooks/backup-schedule.js +114 -0
  149. package/dist/core/hooks/cli.js +61 -18
  150. package/dist/core/hooks/codex-wsl-bridge.d.ts +2 -0
  151. package/dist/core/hooks/codex-wsl-bridge.d.ts.map +1 -1
  152. package/dist/core/hooks/codex-wsl-bridge.js +18 -4
  153. package/dist/core/hooks/index.d.ts +1 -0
  154. package/dist/core/hooks/index.d.ts.map +1 -1
  155. package/dist/core/hooks/index.js +1 -0
  156. package/dist/core/hooks/prompt-context/contract.d.ts +61 -0
  157. package/dist/core/hooks/prompt-context/contract.d.ts.map +1 -0
  158. package/dist/core/hooks/prompt-context/contract.js +102 -0
  159. package/dist/core/hooks/prompt-context/index.d.ts +5 -0
  160. package/dist/core/hooks/prompt-context/index.d.ts.map +1 -0
  161. package/dist/core/hooks/prompt-context/index.js +2 -0
  162. package/dist/core/hooks/prompt-context/runner.d.ts +24 -0
  163. package/dist/core/hooks/prompt-context/runner.d.ts.map +1 -0
  164. package/dist/core/hooks/prompt-context/runner.js +249 -0
  165. package/dist/core/qa-artifacts.d.ts +3 -0
  166. package/dist/core/qa-artifacts.d.ts.map +1 -1
  167. package/dist/core/qa-artifacts.js +25 -3
  168. package/dist/core/resources/contract.d.ts +69 -3
  169. package/dist/core/resources/contract.d.ts.map +1 -1
  170. package/dist/core/resources/contract.js +1 -1
  171. package/dist/core/resources/index.d.ts +1 -0
  172. package/dist/core/resources/index.d.ts.map +1 -1
  173. package/dist/core/resources/index.js +1 -0
  174. package/dist/core/resources/native.d.ts +36 -0
  175. package/dist/core/resources/native.d.ts.map +1 -0
  176. package/dist/core/resources/native.js +418 -0
  177. package/dist/core/resources/sampler.d.ts +2 -0
  178. package/dist/core/resources/sampler.d.ts.map +1 -1
  179. package/dist/core/resources/sampler.js +31 -1
  180. package/dist/core/resources/status.d.ts +61 -0
  181. package/dist/core/resources/status.d.ts.map +1 -0
  182. package/dist/core/resources/status.js +536 -0
  183. package/dist/core/resources/system.d.ts +27 -0
  184. package/dist/core/resources/system.d.ts.map +1 -0
  185. package/dist/core/resources/system.js +439 -0
  186. package/dist/core/semantic/contract.d.ts +13 -13
  187. package/dist/core/semantic/service.d.ts +2 -0
  188. package/dist/core/semantic/service.d.ts.map +1 -1
  189. package/dist/core/semantic/service.js +27 -4
  190. package/dist/core/storage/builtins.js +41 -0
  191. package/dist/core/supervisor/contract.d.ts +41 -3
  192. package/dist/core/supervisor/contract.d.ts.map +1 -1
  193. package/dist/core/supervisor/contract.js +3 -2
  194. package/dist/core/supervisor/findings.d.ts +3 -0
  195. package/dist/core/supervisor/findings.d.ts.map +1 -1
  196. package/dist/core/supervisor/findings.js +88 -0
  197. package/dist/core/supervisor/history.d.ts.map +1 -1
  198. package/dist/core/supervisor/history.js +14 -0
  199. package/dist/core/supervisor/ledger-state.d.ts +55 -0
  200. package/dist/core/supervisor/ledger-state.d.ts.map +1 -0
  201. package/dist/core/supervisor/ledger-state.js +0 -0
  202. package/dist/core/supervisor/service.d.ts.map +1 -1
  203. package/dist/core/supervisor/service.js +120 -7
  204. package/dist/core/supervisor/storage.d.ts +3 -1
  205. package/dist/core/supervisor/storage.d.ts.map +1 -1
  206. package/dist/core/supervisor/storage.js +4 -0
  207. package/dist/core/workflow/admission.d.ts +3 -0
  208. package/dist/core/workflow/admission.d.ts.map +1 -1
  209. package/dist/core/workflow/admission.js +28 -16
  210. package/dist/core/workflow/engine.js +2 -2
  211. package/dist/core/workflow/live-session-v3.d.ts.map +1 -1
  212. package/dist/core/workflow/live-session-v3.js +6 -1
  213. package/dist/core/workflow/proof.d.ts.map +1 -1
  214. package/dist/core/workflow/proof.js +4 -3
  215. package/dist/lib/browser/capture-fidelity.d.ts +7 -0
  216. package/dist/lib/browser/capture-fidelity.d.ts.map +1 -1
  217. package/dist/lib/browser/capture-fidelity.js +17 -0
  218. package/dist/lib/browser/client.d.ts +9 -0
  219. package/dist/lib/browser/client.d.ts.map +1 -1
  220. package/dist/lib/browser/client.js +85 -37
  221. package/dist/lib/browser/index.d.ts +3 -1
  222. package/dist/lib/browser/index.d.ts.map +1 -1
  223. package/dist/lib/browser/index.js +2 -1
  224. package/dist/lib/browser/page-review-budget.d.ts +13 -0
  225. package/dist/lib/browser/page-review-budget.d.ts.map +1 -0
  226. package/dist/lib/browser/page-review-budget.js +251 -0
  227. package/dist/lib/browser/page-review-contracts.d.ts +127 -0
  228. package/dist/lib/browser/page-review-contracts.d.ts.map +1 -0
  229. package/dist/lib/browser/page-review-contracts.js +5 -0
  230. package/dist/lib/browser/page-review-evidence.d.ts +48 -0
  231. package/dist/lib/browser/page-review-evidence.d.ts.map +1 -0
  232. package/dist/lib/browser/page-review-evidence.js +340 -0
  233. package/dist/lib/browser/page-review-judge.d.ts +1 -1
  234. package/dist/lib/browser/page-review-judge.d.ts.map +1 -1
  235. package/dist/lib/browser/page-review-judge.js +46 -38
  236. package/dist/lib/browser/page-review-pack.d.ts +44 -22
  237. package/dist/lib/browser/page-review-pack.d.ts.map +1 -1
  238. package/dist/lib/browser/page-review-pack.js +116 -36
  239. package/dist/lib/browser/page-review-source.d.ts +12 -0
  240. package/dist/lib/browser/page-review-source.d.ts.map +1 -0
  241. package/dist/lib/browser/page-review-source.js +22 -0
  242. package/dist/lib/browser/qa-run-contracts.d.ts +35 -9
  243. package/dist/lib/browser/qa-run-contracts.d.ts.map +1 -1
  244. package/dist/lib/browser/qa-run-contracts.js +48 -14
  245. package/dist/lib/browser/qa-run.d.ts.map +1 -1
  246. package/dist/lib/browser/qa-run.js +215 -68
  247. package/dist/lib/browser/qa-snapshot.d.ts +6 -0
  248. package/dist/lib/browser/qa-snapshot.d.ts.map +1 -1
  249. package/dist/lib/browser/qa-snapshot.js +259 -48
  250. package/dist/lib/browser/review-capture-transaction.d.ts +13 -0
  251. package/dist/lib/browser/review-capture-transaction.d.ts.map +1 -0
  252. package/dist/lib/browser/review-capture-transaction.js +79 -0
  253. package/dist/lib/browser/standalone-html.d.ts.map +1 -1
  254. package/dist/lib/browser/standalone-html.js +4 -2
  255. package/dist/lib/browser/thumbnail-association.d.ts +34 -0
  256. package/dist/lib/browser/thumbnail-association.d.ts.map +1 -0
  257. package/dist/lib/browser/thumbnail-association.js +158 -0
  258. package/dist/lib/browser/tiling.d.ts +30 -0
  259. package/dist/lib/browser/tiling.d.ts.map +1 -1
  260. package/dist/lib/browser/tiling.js +38 -0
  261. package/dist/lib/docs-metadata-managed.d.ts.map +1 -1
  262. package/dist/lib/docs-metadata-managed.js +4 -1
  263. package/dist/lib/identities/assume.d.ts.map +1 -1
  264. package/dist/lib/identities/assume.js +7 -1
  265. package/dist/lib/instructions/templates.d.ts.map +1 -1
  266. package/dist/lib/instructions/templates.js +1 -0
  267. package/dist/lib/local-file-url.d.ts +12 -0
  268. package/dist/lib/local-file-url.d.ts.map +1 -1
  269. package/dist/lib/local-file-url.js +16 -2
  270. package/dist/lib/tunnel/state.d.ts +19 -4
  271. package/dist/lib/tunnel/state.d.ts.map +1 -1
  272. package/dist/lib/tunnel/state.js +53 -13
  273. package/package.json +7 -4
  274. package/schemas/config.schema.json +69 -2
  275. package/src/commands/agents.ts +42 -9
  276. package/src/commands/artifacts.ts +130 -6
  277. package/src/commands/backup.ts +287 -34
  278. package/src/commands/browse.ts +564 -267
  279. package/src/commands/diagnostics.ts +6 -3
  280. package/src/commands/ledger-v3.ts +20 -3
  281. package/src/commands/qa-run.ts +37 -3
  282. package/src/commands/qa-status.ts +11 -2
  283. package/src/commands/resources.ts +23 -3
  284. package/src/commands/review-pack.ts +165 -62
  285. package/src/commands/tunnel.ts +2 -1
  286. package/src/core/agents/canonical-emit.ts +4 -2
  287. package/src/core/agents/cli.ts +14 -6
  288. package/src/core/agents/live-authority-v3.ts +7 -3
  289. package/src/core/agents/live-lifecycle-v3.ts +2 -2
  290. package/src/core/agents/render/prompt-context.ts +68 -14
  291. package/src/core/agents/session-finalizer-v3.ts +2 -2
  292. package/src/core/agents/state/live-coordination-view.ts +4 -4
  293. package/src/core/agents/state/names.ts +92 -7
  294. package/src/core/agents/state/stale-sweep.ts +22 -8
  295. package/src/core/agents/turn-elapsed.ts +135 -0
  296. package/src/core/artifacts/activity.ts +71 -0
  297. package/src/core/artifacts/delivery-card.ts +375 -0
  298. package/src/core/artifacts/index.ts +385 -91
  299. package/src/core/artifacts/mutation-lock.ts +106 -0
  300. package/src/core/backup/host-snapshot.ts +76 -0
  301. package/src/core/config.ts +66 -13
  302. package/src/core/diagnostics/advice.ts +148 -120
  303. package/src/core/diagnostics/bundle.ts +138 -52
  304. package/src/core/diagnostics/comparison.ts +6 -2
  305. package/src/core/diagnostics/contract.ts +39 -42
  306. package/src/core/diagnostics/index.ts +2 -0
  307. package/src/core/diagnostics/pressure-contract.ts +294 -0
  308. package/src/core/diagnostics/pressure.ts +1361 -0
  309. package/src/core/diagnostics/replay.ts +116 -11
  310. package/src/core/events/v3/adapter-id.ts +12 -0
  311. package/src/core/events/v3/archive-retention.ts +3 -6
  312. package/src/core/events/v3/bootstrap.ts +163 -16
  313. package/src/core/events/v3/capabilities.ts +75 -25
  314. package/src/core/events/v3/capability-drift.ts +2 -2
  315. package/src/core/events/v3/control-witness.ts +172 -41
  316. package/src/core/events/v3/control.ts +34 -2
  317. package/src/core/events/v3/index.ts +3 -0
  318. package/src/core/events/v3/live-feed.ts +23 -5
  319. package/src/core/events/v3/live-routing.ts +14 -2
  320. package/src/core/events/v3/producers/command-recorder.ts +4 -4
  321. package/src/core/events/v3/producers/coordination-recorder.ts +4 -4
  322. package/src/core/events/v3/producers/hook-base.ts +21 -16
  323. package/src/core/events/v3/producers/hook.ts +21 -4
  324. package/src/core/events/v3/producers/intake.ts +6 -6
  325. package/src/core/events/v3/producers/producer-state-observer.ts +2 -1
  326. package/src/core/events/v3/producers/recorder.ts +50 -21
  327. package/src/core/events/v3/reader.ts +14 -13
  328. package/src/core/events/v3/runtime-build.ts +3 -2
  329. package/src/core/events/v3/runtime-telemetry-capabilities.ts +3 -3
  330. package/src/core/events/v3/turn-telemetry.ts +4 -4
  331. package/src/core/hooks/adapter/output.ts +50 -36
  332. package/src/core/hooks/backup-schedule.ts +147 -0
  333. package/src/core/hooks/cli.ts +65 -14
  334. package/src/core/hooks/codex-wsl-bridge.ts +21 -5
  335. package/src/core/hooks/index.ts +1 -0
  336. package/src/core/hooks/prompt-context/contract.ts +177 -0
  337. package/src/core/hooks/prompt-context/index.ts +22 -0
  338. package/src/core/hooks/prompt-context/runner.ts +303 -0
  339. package/src/core/qa-artifacts.ts +29 -3
  340. package/src/core/resources/contract.ts +76 -3
  341. package/src/core/resources/index.ts +1 -0
  342. package/src/core/resources/native.ts +481 -0
  343. package/src/core/resources/sampler.ts +46 -1
  344. package/src/core/resources/status.ts +717 -0
  345. package/src/core/resources/system.ts +530 -0
  346. package/src/core/semantic/service.ts +37 -3
  347. package/src/core/storage/builtins.ts +41 -0
  348. package/src/core/supervisor/contract.ts +45 -3
  349. package/src/core/supervisor/findings.ts +159 -0
  350. package/src/core/supervisor/history.ts +15 -0
  351. package/src/core/supervisor/ledger-state.ts +0 -0
  352. package/src/core/supervisor/service.ts +133 -5
  353. package/src/core/supervisor/storage.ts +7 -0
  354. package/src/core/workflow/admission.ts +33 -17
  355. package/src/core/workflow/engine.ts +2 -2
  356. package/src/core/workflow/live-session-v3.ts +6 -8
  357. package/src/core/workflow/proof.ts +4 -3
  358. package/src/lib/browser/capture-fidelity.ts +23 -0
  359. package/src/lib/browser/client.ts +96 -44
  360. package/src/lib/browser/index.ts +15 -1
  361. package/src/lib/browser/page-review-budget.ts +309 -0
  362. package/src/lib/browser/page-review-contracts.ts +129 -0
  363. package/src/lib/browser/page-review-evidence.ts +465 -0
  364. package/src/lib/browser/page-review-judge.ts +56 -40
  365. package/src/lib/browser/page-review-pack.ts +181 -43
  366. package/src/lib/browser/page-review-source.ts +30 -0
  367. package/src/lib/browser/qa-run-contracts.ts +62 -18
  368. package/src/lib/browser/qa-run.ts +259 -71
  369. package/src/lib/browser/qa-snapshot.ts +289 -48
  370. package/src/lib/browser/review-capture-transaction.ts +106 -0
  371. package/src/lib/browser/standalone-html.ts +4 -2
  372. package/src/lib/browser/thumbnail-association.ts +190 -0
  373. package/src/lib/browser/tiling.ts +63 -0
  374. package/src/lib/docs-metadata-managed.ts +4 -1
  375. package/src/lib/identities/assume.ts +12 -4
  376. package/src/lib/instructions/templates.ts +1 -0
  377. package/src/lib/local-file-url.ts +17 -2
  378. package/src/lib/tunnel/state.ts +70 -13
@@ -6,11 +6,13 @@ import type {
6
6
  ObservedHookHealth,
7
7
  ObservedServiceHealth,
8
8
  SupervisorActivitySnapshot,
9
+ SupervisorCapability,
9
10
  SupervisorFinding,
10
11
  SupervisorFindingExplanation,
11
12
  SupervisorFindings,
12
13
  SupervisorHistory,
13
14
  SupervisorLogFeed,
15
+ SupervisorPressureRecord,
14
16
  SupervisorSnapshot,
15
17
  SupervisorTimeline,
16
18
  } from "../supervisor/contract.ts";
@@ -23,14 +25,24 @@ import { explainSupervisorFinding } from "../supervisor/explanations.ts";
23
25
  import { updateSupervisorFindings } from "../supervisor/findings.ts";
24
26
  import { projectHookHealth } from "../supervisor/hook-health.ts";
25
27
  import { buildSupervisorTimeline } from "../supervisor/timeline.ts";
26
- import { buildDiagnosticAdvice } from "./advice.ts";
28
+ import {
29
+ buildDiagnosticAdvice,
30
+ pressureHistoryFromSupervisor,
31
+ unknownPressureAssessment,
32
+ } from "./advice.ts";
27
33
  import {
28
34
  DIAGNOSTIC_EXPECTED_SCHEMA_VERSION,
35
+ type DiagnosticAdvice,
29
36
  type DiagnosticExpected,
30
37
  type DiagnosticObservations,
31
38
  type DiagnosticSelection,
32
39
  type DiagnosticThresholds,
33
40
  } from "./contract.ts";
41
+ import { assessPressure } from "./pressure.ts";
42
+ import type { PressureFindingInput } from "./pressure-contract.ts";
43
+
44
+ const PRESSURE_SOURCE = "supervisor.pressure";
45
+ const FINDINGS_SOURCE = "supervisor.findings";
34
46
 
35
47
  export function canonicalJson(value: unknown): string {
36
48
  return JSON.stringify(sortValue(value));
@@ -60,7 +72,7 @@ export function deriveCapturedExpected(
60
72
  findings,
61
73
  findings.map((finding) => diagnosticTimeline(finding, relatedSources)),
62
74
  findings.map(explainSupervisorFinding),
63
- observations,
75
+ capturedAdvice(observations, findings),
64
76
  );
65
77
  }
66
78
 
@@ -75,8 +87,15 @@ export function replayDiagnosticInputs(
75
87
  const supervisor =
76
88
  optionalSource<SupervisorSnapshot>(observations, "supervisor.snapshot") ??
77
89
  neutralSupervisor(observations.captured_at);
90
+ // A captured history is untrusted JSON from a bundle that may predate the
91
+ // current point shape. Reading it unchecked would let an older capture reach
92
+ // the trend projection with no pressure readings on its points. The live
93
+ // observer and the dashboard validate the same way.
94
+ const capturedHistory = optionalSource<SupervisorHistory>(observations, "supervisor.history");
78
95
  const history =
79
- optionalSource<SupervisorHistory>(observations, "supervisor.history") ?? neutralHistory();
96
+ capturedHistory?.schema_version === SUPERVISOR_HISTORY_SCHEMA_VERSION
97
+ ? capturedHistory
98
+ : neutralHistory();
80
99
  const logFeed =
81
100
  optionalSource<SupervisorLogFeed>(observations, "supervisor.log-feed") ??
82
101
  neutralLogFeed(observations.captured_at);
@@ -115,10 +134,100 @@ export function replayDiagnosticInputs(
115
134
  findings,
116
135
  findings.map((finding) => diagnosticTimeline(finding, relatedSources)),
117
136
  findings.map(explainSupervisorFinding),
118
- observations,
137
+ replayedAdvice(observations, resource, history, evaluated),
119
138
  );
120
139
  }
121
140
 
141
+ /**
142
+ * The live assessment, exactly as the observer published it. Nothing is
143
+ * recomputed here, because `expected.json` freezes what the machine actually
144
+ * reported at capture time.
145
+ */
146
+ function capturedAdvice(
147
+ observations: DiagnosticObservations,
148
+ findings: readonly SupervisorFinding[],
149
+ ): DiagnosticAdvice {
150
+ const record = optionalSource<SupervisorPressureRecord>(observations, PRESSURE_SOURCE);
151
+ const capability = capturedSourceCapability(observations, PRESSURE_SOURCE);
152
+ if (!record?.assessment) {
153
+ return unavailableAdvice(observations.captured_at, capability);
154
+ }
155
+ return buildDiagnosticAdvice({
156
+ assessment: record.assessment,
157
+ priorHysteresis: record.prior_hysteresis ?? null,
158
+ sourceCapability: capability,
159
+ activeFindingCount: openedCount(findings),
160
+ evaluatedAt: observations.captured_at,
161
+ });
162
+ }
163
+
164
+ /**
165
+ * Recompute the assessment from frozen inputs. `prior_hysteresis` comes from
166
+ * the captured record, so a transition that depended on the previous sample is
167
+ * reproduced instead of restarting from a cold state.
168
+ */
169
+ function replayedAdvice(
170
+ observations: DiagnosticObservations,
171
+ resource: ResourceSnapshot,
172
+ history: SupervisorHistory,
173
+ evaluated: SupervisorFindings,
174
+ ): DiagnosticAdvice {
175
+ const record = optionalSource<SupervisorPressureRecord>(observations, PRESSURE_SOURCE);
176
+ const capability = capturedSourceCapability(observations, PRESSURE_SOURCE);
177
+ if (!record?.assessment) {
178
+ return unavailableAdvice(observations.captured_at, capability);
179
+ }
180
+ const snapshotAvailable = optionalSource<ResourceSnapshot>(observations, "resources.snapshot");
181
+ const findingsCapability = capturedSourceCapability(observations, FINDINGS_SOURCE);
182
+ const active = [...evaluated.active, ...evaluated.transitions].filter(
183
+ (finding) => finding.state === "opened",
184
+ );
185
+ const assessment = assessPressure({
186
+ snapshot: snapshotAvailable ? resource : null,
187
+ snapshot_reason: snapshotAvailable ? null : "captured_source_unavailable",
188
+ history: pressureHistoryFromSupervisor(history.points),
189
+ findings: active as readonly PressureFindingInput[],
190
+ findings_capability: {
191
+ source_kind: findingsCapability.source_kind,
192
+ state: findingsCapability.state,
193
+ ...(findingsCapability.reason_code ? { reason_code: findingsCapability.reason_code } : {}),
194
+ },
195
+ prior: record.prior_hysteresis ?? null,
196
+ observer_generation: record.observer_generation,
197
+ // Recover the observer's exact clock. `observed_at` is the sample instant
198
+ // and `sample_age_ms` is how far past it the observer read, so the sum is
199
+ // the original `now`, which staleness and the OOM hold both depend on.
200
+ now_ms: Date.parse(record.assessment.observed_at) + (record.assessment.sample_age_ms ?? 0),
201
+ });
202
+ return buildDiagnosticAdvice({
203
+ assessment,
204
+ priorHysteresis: record.prior_hysteresis ?? null,
205
+ sourceCapability: capability,
206
+ activeFindingCount: openedCount(
207
+ filterFindings(findingsProjection(evaluated), observations.selection),
208
+ ),
209
+ evaluatedAt: observations.captured_at,
210
+ });
211
+ }
212
+
213
+ function unavailableAdvice(capturedAt: string, capability: SupervisorCapability): DiagnosticAdvice {
214
+ return buildDiagnosticAdvice({
215
+ assessment: unknownPressureAssessment({
216
+ observedAt: capturedAt,
217
+ reasonCode: "evidence_unavailable",
218
+ summary: `Local resource pressure cannot be determined because the published assessment is ${capability.state}${capability.reason_code ? ` (${capability.reason_code})` : ""}.`,
219
+ }),
220
+ priorHysteresis: null,
221
+ sourceCapability: capability,
222
+ activeFindingCount: 0,
223
+ evaluatedAt: capturedAt,
224
+ });
225
+ }
226
+
227
+ function openedCount(findings: readonly SupervisorFinding[]): number {
228
+ return findings.filter((finding) => finding.state === "opened").length;
229
+ }
230
+
122
231
  function diagnosticTimeline(
123
232
  finding: SupervisorFinding,
124
233
  relatedSources: readonly CoordinationHealthSnapshot["recent_events"][number][],
@@ -138,7 +247,7 @@ function expected(
138
247
  findings: readonly SupervisorFinding[],
139
248
  timelines: readonly SupervisorTimeline[],
140
249
  explanations: readonly SupervisorFindingExplanation[],
141
- observations: DiagnosticObservations,
250
+ advice: DiagnosticAdvice,
142
251
  ): DiagnosticExpected {
143
252
  return {
144
253
  schema_version: DIAGNOSTIC_EXPECTED_SCHEMA_VERSION,
@@ -151,18 +260,14 @@ function expected(
151
260
  explanations: [...explanations].sort((left, right) =>
152
261
  left.finding_id.localeCompare(right.finding_id),
153
262
  ),
154
- advice: buildDiagnosticAdvice({
155
- findings,
156
- sourceCapability: capturedSourceCapability(observations, "supervisor.findings"),
157
- evaluatedAt: observations.captured_at,
158
- }),
263
+ advice,
159
264
  };
160
265
  }
161
266
 
162
267
  function capturedSourceCapability(
163
268
  observations: DiagnosticObservations,
164
269
  sourceKind: string,
165
- ): import("../supervisor/contract.ts").SupervisorCapability {
270
+ ): SupervisorCapability {
166
271
  const source = observations.sources.find((candidate) => candidate.source_kind === sourceKind);
167
272
  return source
168
273
  ? {
@@ -0,0 +1,12 @@
1
+ import type { Adapter } from "../../adapter.ts";
2
+
3
+ /** Harness identities that may produce or describe Event Ledger V3 evidence. */
4
+ export type EventAdapterIdV3 = Adapter | "openclaw";
5
+
6
+ /** Canonical event-adapter census. Workflow launch adapters remain in core/adapter.ts. */
7
+ export const EVENT_ADAPTER_IDS_V3 = [
8
+ "claude-code",
9
+ "codex",
10
+ "cursor",
11
+ "openclaw",
12
+ ] as const satisfies readonly EventAdapterIdV3[];
@@ -10,11 +10,7 @@ import {
10
10
  writeFileSync,
11
11
  } from "node:fs";
12
12
  import { basename, join, relative, resolve } from "node:path";
13
- import {
14
- artifactAutoCleanIntervalHours,
15
- type EventLedgerArchivePolicy,
16
- eventLedgerArchivePolicy,
17
- } from "../../config.ts";
13
+ import { type EventLedgerArchivePolicy, eventLedgerArchivePolicy } from "../../config.ts";
18
14
 
19
15
  export type EventV3ArchiveClassification =
20
16
  | "retained"
@@ -151,7 +147,8 @@ export function autoCleanEventV3Archives(
151
147
  const policy = eventLedgerArchivePolicy(repoRoot);
152
148
  if (!policy.autoClean) return { ran: false, reason: "disabled", deleted: 0, bytes: 0 };
153
149
  const stampPath = join(resolve(repoRoot), AUTO_CLEAN_STAMP);
154
- const intervalMs = artifactAutoCleanIntervalHours() * 60 * 60 * 1000;
150
+ // Archive cleanup remains daily, independent of short-lived working files.
151
+ const intervalMs = 24 * 60 * 60 * 1000;
155
152
  try {
156
153
  const stamp = JSON.parse(readFileSync(stampPath, "utf8")) as { last_run_at?: string };
157
154
  const last = Date.parse(stamp.last_run_at ?? "");
@@ -10,15 +10,18 @@ import {
10
10
  readFileSync,
11
11
  renameSync,
12
12
  statSync,
13
+ unlinkSync,
13
14
  writeFileSync,
14
15
  } from "node:fs";
15
16
  import { hostname } from "node:os";
16
17
  import { dirname, join, resolve } from "node:path";
18
+ import { fileURLToPath } from "node:url";
17
19
  import { fsyncParentDirectory } from "../../workflow/durable-record.ts";
18
20
  import { acquireNoClobberLease } from "../../workflow/workspaces/leases.ts";
19
21
  import { canonicalJsonV3, sha256V3 } from "./canonical.ts";
20
22
  import { ADAPTER_CAPABILITY_PROFILES_V3 } from "./capabilities.ts";
21
23
  import {
24
+ type ActivationManifestV3,
22
25
  buildActivationManifestV3,
23
26
  buildCandidateGenesisManifestV3,
24
27
  type CandidateGenesisManifestV3,
@@ -42,6 +45,16 @@ export interface InitializeEventLedgerV3Input {
42
45
  configDigest: `sha256:${string}`;
43
46
  approvalRecordId: string;
44
47
  forceNewEpoch?: boolean;
48
+ /**
49
+ * Activate a candidate epoch that was already created for this root.
50
+ *
51
+ * Unset, this activates a candidate that carries this initializer's producer
52
+ * identity and refuses every other one. Creation and activation are one
53
+ * locked step here, so such a candidate is the residue of a crash or a
54
+ * failed activation, never work in progress; a candidate from another
55
+ * producer is a deliberate cutover state. Set it explicitly to force either
56
+ * answer.
57
+ */
45
58
  resumeCandidate?: boolean;
46
59
  now?: () => Date;
47
60
  }
@@ -59,6 +72,17 @@ export interface RefreshIncompatibleEventLedgerV3Result {
59
72
  control: Extract<EventV3ControlState, { state: "active" }>;
60
73
  }
61
74
 
75
+ /**
76
+ * The control producer every epoch created by this initializer carries.
77
+ *
78
+ * It separates a stranded epoch from a deliberate one. A candidate minted here
79
+ * was always meant to be activated in the same locked step, so finishing it is
80
+ * a repair. A candidate published by any other producer (an operator cutover or
81
+ * rollback rehearsal) is a state someone chose, and automatic activation would
82
+ * overrule them.
83
+ */
84
+ const BOOTSTRAP_CONTROL_PRODUCER_ID = "prd_harnery-init" as const;
85
+
62
86
  const BOOTSTRAP_LEASE_RETRIES = 12;
63
87
  const BOOTSTRAP_LEASE_RETRY_MS = 25;
64
88
  const BOOTSTRAP_LEASE_STALE_MS = 10_000;
@@ -141,6 +165,74 @@ export function refreshIncompatibleEventLedgerV3(
141
165
  });
142
166
  }
143
167
 
168
+ export interface RepairStrandedEventLedgerV3CandidateResult {
169
+ state: "repaired" | "not_stranded" | "unavailable";
170
+ reason?: string;
171
+ control: EventV3ControlState;
172
+ }
173
+
174
+ /** The approval record every automatic stranded-candidate repair is bound to. */
175
+ export const EVENT_V3_STRANDED_CANDIDATE_APPROVAL_RECORD_ID =
176
+ "harnery-runtime-v3-stranded-candidate" as const;
177
+
178
+ /**
179
+ * Complete an epoch that was created but never activated.
180
+ *
181
+ * This initializer creates a candidate and activates it inside one lease, so a
182
+ * candidate carrying its producer identity is residue: some process published
183
+ * the genesis and died or failed before publishing the activation, and every
184
+ * hook afterwards served that half-built epoch with a candidate-only write
185
+ * gate. Finishing it is a repair, not a decision, and it is exactly as safe as
186
+ * the creation it completes: the same lease serializes it, the activation is
187
+ * derived from the immutable candidate packet, and no ledger row is edited or
188
+ * synthesized.
189
+ *
190
+ * A candidate from any other producer is a deliberate cutover state and is
191
+ * left untouched. A live bootstrap lease makes this a no-op rather than a
192
+ * failure, so the next boundary retries.
193
+ */
194
+ export function repairStrandedEventLedgerV3Candidate(
195
+ coordRoot: string,
196
+ ): RepairStrandedEventLedgerV3CandidateResult {
197
+ const root = resolve(coordRoot);
198
+ const observed = readEventV3ControlState(root);
199
+ const deliberateCandidate = observed.state === "candidate";
200
+ if (!strandedBootstrapCandidate(observed)) {
201
+ return {
202
+ state: "not_stranded",
203
+ control: observed,
204
+ ...(deliberateCandidate ? { reason: "candidate_not_bootstrap_created" } : {}),
205
+ };
206
+ }
207
+ try {
208
+ return withBootstrapLease(root, () => {
209
+ const current = readEventV3ControlState(root);
210
+ if (!strandedBootstrapCandidate(current)) return { state: "not_stranded", control: current };
211
+ const activated = activateCandidateEpoch(
212
+ root,
213
+ current.genesis,
214
+ EVENT_V3_STRANDED_CANDIDATE_APPROVAL_RECORD_ID,
215
+ );
216
+ return { state: "repaired", control: activated.control };
217
+ });
218
+ } catch (error) {
219
+ return {
220
+ state: "unavailable",
221
+ reason: error instanceof Error ? error.message : String(error),
222
+ control: observed,
223
+ };
224
+ }
225
+ }
226
+
227
+ function strandedBootstrapCandidate(
228
+ control: EventV3ControlState,
229
+ ): control is Extract<EventV3ControlState, { state: "candidate" }> {
230
+ return (
231
+ control.state === "candidate" &&
232
+ control.genesis.event.producer.producer_id === BOOTSTRAP_CONTROL_PRODUCER_ID
233
+ );
234
+ }
235
+
144
236
  export interface RotateOversizedEventLedgerV3Result {
145
237
  state: "rotated" | "not_oversized" | "not_active" | "disabled";
146
238
  active_bytes: number;
@@ -158,10 +250,13 @@ export interface RotateOversizedEventLedgerV3Result {
158
250
  * (events, spool, producer states, manifests) moves whole into the V3
159
251
  * archive, live sessions re-onboard into the new epoch on their next signal,
160
252
  * and the writer's epoch fence keeps in-flight producers of the old epoch
161
- * from ever committing into the new one. Only a currently valid, active
162
- * ledger rotates; integrity failures stay closed for the explicit recovery
163
- * command. The threshold comes from the isolated Event V3 config resolver
164
- * unless the caller pins one; a non-positive threshold disables rotation.
253
+ * from ever committing into the new one. A candidate epoch rotates on the same
254
+ * terms as an active one: its control pair is valid, every reader still
255
+ * validates all of its history, and refusing to bound it is how a stranded
256
+ * epoch grew to 100 MB. Integrity failures stay closed for the explicit
257
+ * recovery command. The threshold comes from the isolated Event V3 config
258
+ * resolver unless the caller pins one; a non-positive threshold disables
259
+ * rotation.
165
260
  */
166
261
  export function rotateOversizedEventLedgerV3(
167
262
  coordRoot: string,
@@ -189,7 +284,7 @@ export function rotateOversizedEventLedgerV3(
189
284
  return { state: "not_oversized", active_bytes: activeBytes, threshold_bytes: threshold };
190
285
  }
191
286
  const current = readEventV3ControlState(root);
192
- if (current.state !== "active") {
287
+ if (current.state !== "active" && current.state !== "candidate") {
193
288
  return { state: "not_active", active_bytes: activeBytes, threshold_bytes: threshold };
194
289
  }
195
290
  // Flush durable ready rows into the epoch they were produced for, so the
@@ -235,13 +330,14 @@ function initializeEventLedgerV3Locked(
235
330
  ): InitializeEventLedgerV3Result {
236
331
  const root = resolve(input.coordRoot);
237
332
  let current = readEventV3ControlState(root);
238
- if (input.resumeCandidate && current.state === "repairable") {
333
+ const resumeCandidate = input.resumeCandidate ?? strandedBootstrapCandidate(current);
334
+ if (resumeCandidate && current.state === "repairable") {
239
335
  current = repairEventV3ControlPair(root);
240
336
  }
241
337
  if (!input.forceNewEpoch && current.state === "active") {
242
338
  return { state: "active", initialized: false, control: current };
243
339
  }
244
- if (!input.forceNewEpoch && input.resumeCandidate && current.state === "candidate") {
340
+ if (!input.forceNewEpoch && resumeCandidate && current.state === "candidate") {
245
341
  return activateCandidateEpoch(root, current.genesis, input.approvalRecordId);
246
342
  }
247
343
  if (!input.forceNewEpoch && current.state === "candidate") {
@@ -250,11 +346,10 @@ function initializeEventLedgerV3Locked(
250
346
 
251
347
  const now = input.now ?? (() => new Date());
252
348
  const createdAt = now().toISOString();
253
- const archivedEpoch = archiveCurrentEpoch(root, createdAt);
254
349
  const keys = loadOrCreateFingerprintKeyStoreV3(root, now);
255
350
  const harneryBuild = safeBuild(input.harneryBuild);
256
351
  const hostBuild = safeBuild(input.hostBuild);
257
- const producerId = "prd_harnery-init" as const;
352
+ const producerId = BOOTSTRAP_CONTROL_PRODUCER_ID;
258
353
  const bootId =
259
354
  `boot_${createHash("sha256").update(`${root}\0${createdAt}`).digest("hex")}` as const;
260
355
  const buildId = liveEventV3BuildId(harneryBuild);
@@ -288,18 +383,42 @@ function initializeEventLedgerV3Locked(
288
383
  platform: livePlatformV3(),
289
384
  },
290
385
  });
386
+ // Mint the complete control pair before any epoch bytes move. Every approval
387
+ // and validation failure then happens while the current epoch is still
388
+ // whole, so it can never leave a replaced epoch stranded in candidate state.
389
+ const activation = buildActivationManifestV3(activationInput(candidate, input.approvalRecordId));
390
+
391
+ const archivedEpoch = archiveCurrentEpoch(root, createdAt);
291
392
  publishControlFile(join(root, EVENT_V3_GENESIS_MANIFEST), candidate);
292
- const candidateState = repairEventV3ControlPair(root);
293
- if (candidateState.state !== "candidate") {
294
- throw new Error(`event_v3_candidate_initialization_failed:${candidateState.state}`);
393
+ let activated: InitializeEventLedgerV3Result;
394
+ try {
395
+ activated = completeCandidateActivation(root, activation);
396
+ } catch (error) {
397
+ // The epoch has already been replaced, so returning here would strand it.
398
+ // Retry the completion inside the same lease and, failing that, name the
399
+ // stranded epoch rather than reporting a successful initialization.
400
+ activated = completeStrandedCandidateActivation(root, activation, error);
295
401
  }
296
- const activated = activateCandidateEpoch(root, candidate, input.approvalRecordId);
297
402
  return {
298
403
  ...activated,
299
404
  ...(archivedEpoch ? { archived_epoch: archivedEpoch } : {}),
300
405
  };
301
406
  }
302
407
 
408
+ function completeStrandedCandidateActivation(
409
+ root: string,
410
+ activation: ActivationManifestV3,
411
+ cause: unknown,
412
+ ): InitializeEventLedgerV3Result {
413
+ try {
414
+ return completeCandidateActivation(root, activation);
415
+ } catch {
416
+ throw new Error(
417
+ `event_v3_epoch_stranded_before_activation:${cause instanceof Error ? cause.message : String(cause)}`,
418
+ );
419
+ }
420
+ }
421
+
303
422
  function runtimeCapabilityProfileCurrent(
304
423
  control: Extract<EventV3ControlState, { state: "candidate" | "active" }>,
305
424
  ): boolean {
@@ -355,8 +474,16 @@ function activateCandidateEpoch(
355
474
  candidate: CandidateGenesisManifestV3,
356
475
  approvalRecordId: string,
357
476
  ): InitializeEventLedgerV3Result {
477
+ return completeCandidateActivation(
478
+ root,
479
+ buildActivationManifestV3(activationInput(candidate, approvalRecordId)),
480
+ );
481
+ }
482
+
483
+ /** The activation packet one exact candidate deserves, derived and pure. */
484
+ function activationInput(candidate: CandidateGenesisManifestV3, approvalRecordId: string) {
358
485
  const producer = candidate.event.producer;
359
- const activation = buildActivationManifestV3({
486
+ return {
360
487
  candidate,
361
488
  approval_record_id: approvalRecordId,
362
489
  activation_approved_at: candidate.profile.candidate_created_at,
@@ -368,7 +495,23 @@ function activateCandidateEpoch(
368
495
  platform: producer.platform,
369
496
  ...(producer.bridge ? { bridge: producer.bridge } : {}),
370
497
  },
371
- });
498
+ };
499
+ }
500
+
501
+ /**
502
+ * Bring a published candidate to active: append the pre-minted genesis event
503
+ * if a crash left it missing, publish the activation manifest, then append its
504
+ * pre-minted event. Every step is idempotent, so the caller may retry it, and
505
+ * anything short of `active` throws with the state that blocked it.
506
+ */
507
+ function completeCandidateActivation(
508
+ root: string,
509
+ activation: ActivationManifestV3,
510
+ ): InitializeEventLedgerV3Result {
511
+ const candidateState = repairEventV3ControlPair(root);
512
+ if (candidateState.state !== "candidate") {
513
+ throw new Error(`event_v3_candidate_initialization_failed:${candidateState.state}`);
514
+ }
372
515
  publishControlFile(join(root, EVENT_V3_ACTIVATION_MANIFEST), activation);
373
516
  const active = repairEventV3ControlPair(root);
374
517
  if (active.state !== "active") {
@@ -394,6 +537,10 @@ function archiveCurrentEpoch(root: string, createdAt: string): string | undefine
394
537
  function publishControlFile(path: string, value: unknown): void {
395
538
  mkdirSync(dirname(path), { recursive: true, mode: 0o700 });
396
539
  const temp = `${path}.tmp.${process.pid}`;
540
+ // A prior failed publish by this same pid would otherwise make every retry
541
+ // fail on the exclusive create, which is exactly how one transient error
542
+ // turns into a permanently stranded epoch.
543
+ if (existsSync(temp)) unlinkSync(temp);
397
544
  const fd = openSync(temp, "wx", 0o600);
398
545
  try {
399
546
  writeFileSync(fd, `${canonicalJsonV3(value)}\n`, "utf8");
@@ -415,7 +562,7 @@ function safeBuild(value: string): string {
415
562
  }
416
563
 
417
564
  function rootOfHarnery(): string {
418
- return resolve(import.meta.dir, "../../../..");
565
+ return resolve(dirname(fileURLToPath(import.meta.url)), "../../../..");
419
566
  }
420
567
 
421
568
  function repositoryBuild(root: string): string {