harnery 0.35.0 → 0.36.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 (616) hide show
  1. package/dist/commander.d.ts +30 -5
  2. package/dist/commander.d.ts.map +1 -1
  3. package/dist/commander.js +124 -96
  4. package/dist/commands/agents.d.ts +23 -3
  5. package/dist/commands/agents.d.ts.map +1 -1
  6. package/dist/commands/agents.js +379 -198
  7. package/dist/commands/backup.d.ts.map +1 -1
  8. package/dist/commands/backup.js +10 -10
  9. package/dist/commands/browse-session.d.ts.map +1 -1
  10. package/dist/commands/browse-session.js +12 -0
  11. package/dist/commands/browse.d.ts +1 -1
  12. package/dist/commands/browse.d.ts.map +1 -1
  13. package/dist/commands/browse.js +5 -4
  14. package/dist/commands/conversations.d.ts +10 -0
  15. package/dist/commands/conversations.d.ts.map +1 -0
  16. package/dist/commands/conversations.js +201 -0
  17. package/dist/commands/deinit.d.ts.map +1 -1
  18. package/dist/commands/deinit.js +5 -0
  19. package/dist/commands/docs.d.ts.map +1 -1
  20. package/dist/commands/docs.js +13 -2
  21. package/dist/commands/doctor.d.ts +5 -0
  22. package/dist/commands/doctor.d.ts.map +1 -1
  23. package/dist/commands/doctor.js +51 -7
  24. package/dist/commands/fetch.js +6 -5
  25. package/dist/commands/files.d.ts +22 -0
  26. package/dist/commands/files.d.ts.map +1 -0
  27. package/dist/commands/files.js +113 -0
  28. package/dist/commands/governor.js +14 -12
  29. package/dist/commands/inbox.d.ts +5 -0
  30. package/dist/commands/inbox.d.ts.map +1 -0
  31. package/dist/commands/inbox.js +57 -0
  32. package/dist/commands/init.d.ts +4 -5
  33. package/dist/commands/init.d.ts.map +1 -1
  34. package/dist/commands/init.js +63 -13
  35. package/dist/commands/ledger-v3.d.ts.map +1 -1
  36. package/dist/commands/ledger-v3.js +347 -1
  37. package/dist/commands/logs.d.ts +4 -0
  38. package/dist/commands/logs.d.ts.map +1 -0
  39. package/dist/commands/logs.js +169 -0
  40. package/dist/commands/messages.d.ts +5 -0
  41. package/dist/commands/messages.d.ts.map +1 -0
  42. package/dist/commands/messages.js +45 -0
  43. package/dist/commands/outline.d.ts +1 -1
  44. package/dist/commands/outline.d.ts.map +1 -1
  45. package/dist/commands/outline.js +2 -2
  46. package/dist/commands/read.d.ts +1 -1
  47. package/dist/commands/read.d.ts.map +1 -1
  48. package/dist/commands/read.js +2 -2
  49. package/dist/commands/semantic.d.ts.map +1 -1
  50. package/dist/commands/semantic.js +54 -2
  51. package/dist/commands/storage.d.ts +4 -0
  52. package/dist/commands/storage.d.ts.map +1 -0
  53. package/dist/commands/storage.js +238 -0
  54. package/dist/commands/tunnel.d.ts +5 -1
  55. package/dist/commands/tunnel.d.ts.map +1 -1
  56. package/dist/commands/tunnel.js +44 -31
  57. package/dist/commands/web.d.ts +3 -0
  58. package/dist/commands/web.d.ts.map +1 -1
  59. package/dist/commands/web.js +67 -7
  60. package/dist/commands/work.js +3 -3
  61. package/dist/commands/workflow.js +13 -13
  62. package/dist/core/adapters/profiles.d.ts +1 -1
  63. package/dist/core/agents/cli.js +209 -39
  64. package/dist/core/agents/live-authority-v3.d.ts +47 -1
  65. package/dist/core/agents/live-authority-v3.d.ts.map +1 -1
  66. package/dist/core/agents/live-authority-v3.js +244 -10
  67. package/dist/core/agents/live-lifecycle-v3.js +6 -1
  68. package/dist/core/agents/live-observation-v3.d.ts.map +1 -1
  69. package/dist/core/agents/live-observation-v3.js +6 -1
  70. package/dist/core/agents/render/prompt-context.d.ts +9 -3
  71. package/dist/core/agents/render/prompt-context.d.ts.map +1 -1
  72. package/dist/core/agents/render/prompt-context.js +24 -7
  73. package/dist/core/agents/rules/stop-hook.d.ts +2 -0
  74. package/dist/core/agents/rules/stop-hook.d.ts.map +1 -1
  75. package/dist/core/agents/rules/stop-hook.js +94 -13
  76. package/dist/core/agents/rules/verdict.d.ts +1 -0
  77. package/dist/core/agents/rules/verdict.d.ts.map +1 -1
  78. package/dist/core/agents/session-events.d.ts.map +1 -1
  79. package/dist/core/agents/session-events.js +4 -0
  80. package/dist/core/agents/session-finalization-state-v3.js +1 -1
  81. package/dist/core/agents/session-finalizer-v3.js +5 -1
  82. package/dist/core/agents/state/activity-log.d.ts +3 -0
  83. package/dist/core/agents/state/activity-log.d.ts.map +1 -1
  84. package/dist/core/agents/state/activity-log.js +6 -3
  85. package/dist/core/agents/state/heartbeat-writer.d.ts +6 -0
  86. package/dist/core/agents/state/heartbeat-writer.d.ts.map +1 -1
  87. package/dist/core/agents/state/heartbeat-writer.js +18 -0
  88. package/dist/core/agents/state/live-coordination-view.d.ts +5 -7
  89. package/dist/core/agents/state/live-coordination-view.d.ts.map +1 -1
  90. package/dist/core/agents/state/live-coordination-view.js +15 -90
  91. package/dist/core/agents/state/live-coordination-writer.d.ts +12 -0
  92. package/dist/core/agents/state/live-coordination-writer.d.ts.map +1 -0
  93. package/dist/core/agents/state/live-coordination-writer.js +92 -0
  94. package/dist/core/artifacts/index.d.ts +21 -0
  95. package/dist/core/artifacts/index.d.ts.map +1 -1
  96. package/dist/core/artifacts/index.js +46 -1
  97. package/dist/core/config.d.ts +62 -4
  98. package/dist/core/config.d.ts.map +1 -1
  99. package/dist/core/config.js +98 -4
  100. package/dist/core/conversations/archive.d.ts +49 -0
  101. package/dist/core/conversations/archive.d.ts.map +1 -0
  102. package/dist/core/conversations/archive.js +136 -0
  103. package/dist/core/conversations/catalog.d.ts +10 -0
  104. package/dist/core/conversations/catalog.d.ts.map +1 -0
  105. package/dist/core/conversations/catalog.js +72 -0
  106. package/dist/core/conversations/context-pack.d.ts +6 -0
  107. package/dist/core/conversations/context-pack.d.ts.map +1 -0
  108. package/dist/core/conversations/context-pack.js +60 -0
  109. package/dist/core/conversations/contract.d.ts +155 -0
  110. package/dist/core/conversations/contract.d.ts.map +1 -0
  111. package/dist/core/conversations/contract.js +1 -0
  112. package/dist/core/conversations/evaluation.d.ts +16 -0
  113. package/dist/core/conversations/evaluation.d.ts.map +1 -0
  114. package/dist/core/conversations/evaluation.js +9 -0
  115. package/dist/core/conversations/index.d.ts +10 -0
  116. package/dist/core/conversations/index.d.ts.map +1 -0
  117. package/dist/core/conversations/index.js +9 -0
  118. package/dist/core/conversations/lifecycle.d.ts +28 -0
  119. package/dist/core/conversations/lifecycle.d.ts.map +1 -0
  120. package/dist/core/conversations/lifecycle.js +29 -0
  121. package/dist/core/conversations/normalize.d.ts +14 -0
  122. package/dist/core/conversations/normalize.d.ts.map +1 -0
  123. package/dist/core/conversations/normalize.js +82 -0
  124. package/dist/core/conversations/query.d.ts +8 -0
  125. package/dist/core/conversations/query.d.ts.map +1 -0
  126. package/dist/core/conversations/query.js +280 -0
  127. package/dist/core/conversations/search-index.d.ts +9 -0
  128. package/dist/core/conversations/search-index.d.ts.map +1 -0
  129. package/dist/core/conversations/search-index.js +29 -0
  130. package/dist/core/events/legacy-storage/canary.d.ts +18 -0
  131. package/dist/core/events/legacy-storage/canary.d.ts.map +1 -0
  132. package/dist/core/events/legacy-storage/canary.js +150 -0
  133. package/dist/core/events/legacy-storage/fence.d.ts +15 -0
  134. package/dist/core/events/legacy-storage/fence.d.ts.map +1 -0
  135. package/dist/core/events/legacy-storage/fence.js +115 -0
  136. package/dist/core/events/legacy-storage/index.d.ts +6 -0
  137. package/dist/core/events/legacy-storage/index.d.ts.map +1 -0
  138. package/dist/core/events/legacy-storage/index.js +5 -0
  139. package/dist/core/events/legacy-storage/inventory.d.ts +10 -0
  140. package/dist/core/events/legacy-storage/inventory.d.ts.map +1 -0
  141. package/dist/core/events/legacy-storage/inventory.js +40 -0
  142. package/dist/core/events/legacy-storage/manifest.d.ts +23 -0
  143. package/dist/core/events/legacy-storage/manifest.d.ts.map +1 -0
  144. package/dist/core/events/legacy-storage/manifest.js +108 -0
  145. package/dist/core/events/legacy-storage/reader.d.ts +15 -0
  146. package/dist/core/events/legacy-storage/reader.d.ts.map +1 -0
  147. package/dist/core/events/legacy-storage/reader.js +154 -0
  148. package/dist/core/events/v3/authority-outbox.d.ts +7 -2
  149. package/dist/core/events/v3/authority-outbox.d.ts.map +1 -1
  150. package/dist/core/events/v3/authority-outbox.js +16 -3
  151. package/dist/core/events/v3/base-contract.d.ts +81 -41
  152. package/dist/core/events/v3/base-contract.d.ts.map +1 -1
  153. package/dist/core/events/v3/base-contract.js +17 -7
  154. package/dist/core/events/v3/bootstrap.d.ts +39 -0
  155. package/dist/core/events/v3/bootstrap.d.ts.map +1 -1
  156. package/dist/core/events/v3/bootstrap.js +167 -3
  157. package/dist/core/events/v3/capabilities.js +3 -3
  158. package/dist/core/events/v3/control-witness.d.ts +44 -0
  159. package/dist/core/events/v3/control-witness.d.ts.map +1 -0
  160. package/dist/core/events/v3/control-witness.js +233 -0
  161. package/dist/core/events/v3/control-writer.d.ts +4 -0
  162. package/dist/core/events/v3/control-writer.d.ts.map +1 -0
  163. package/dist/core/events/v3/control-writer.js +16 -0
  164. package/dist/core/events/v3/control.d.ts +8 -2
  165. package/dist/core/events/v3/control.d.ts.map +1 -1
  166. package/dist/core/events/v3/control.js +46 -18
  167. package/dist/core/events/v3/coordination-transaction-recovery.d.ts +42 -0
  168. package/dist/core/events/v3/coordination-transaction-recovery.d.ts.map +1 -0
  169. package/dist/core/events/v3/coordination-transaction-recovery.js +342 -0
  170. package/dist/core/events/v3/coordination-view.d.ts.map +1 -1
  171. package/dist/core/events/v3/coordination-view.js +16 -1
  172. package/dist/core/events/v3/generated.d.ts +1 -1
  173. package/dist/core/events/v3/generated.js +1 -1
  174. package/dist/core/events/v3/index.d.ts +4 -2
  175. package/dist/core/events/v3/index.d.ts.map +1 -1
  176. package/dist/core/events/v3/index.js +3 -1
  177. package/dist/core/events/v3/live-route-observer.d.ts +31 -0
  178. package/dist/core/events/v3/live-route-observer.d.ts.map +1 -0
  179. package/dist/core/events/v3/live-route-observer.js +58 -0
  180. package/dist/core/events/v3/live-routing.d.ts +13 -21
  181. package/dist/core/events/v3/live-routing.d.ts.map +1 -1
  182. package/dist/core/events/v3/live-routing.js +45 -41
  183. package/dist/core/events/v3/producers/coordination-recorder.d.ts +11 -1
  184. package/dist/core/events/v3/producers/coordination-recorder.d.ts.map +1 -1
  185. package/dist/core/events/v3/producers/coordination-recorder.js +76 -10
  186. package/dist/core/events/v3/producers/diagnostic-summaries.d.ts +129 -0
  187. package/dist/core/events/v3/producers/diagnostic-summaries.d.ts.map +1 -0
  188. package/dist/core/events/v3/producers/diagnostic-summaries.js +499 -0
  189. package/dist/core/events/v3/producers/hook-base.js +1 -1
  190. package/dist/core/events/v3/producers/intake.d.ts +7 -0
  191. package/dist/core/events/v3/producers/intake.d.ts.map +1 -1
  192. package/dist/core/events/v3/producers/intake.js +25 -2
  193. package/dist/core/events/v3/producers/producer-state-observer.d.ts +8 -0
  194. package/dist/core/events/v3/producers/producer-state-observer.d.ts.map +1 -0
  195. package/dist/core/events/v3/producers/producer-state-observer.js +45 -0
  196. package/dist/core/events/v3/producers/recorder.d.ts +55 -1
  197. package/dist/core/events/v3/producers/recorder.d.ts.map +1 -1
  198. package/dist/core/events/v3/producers/recorder.js +353 -24
  199. package/dist/core/events/v3/reader.d.ts +51 -0
  200. package/dist/core/events/v3/reader.d.ts.map +1 -1
  201. package/dist/core/events/v3/reader.js +181 -1
  202. package/dist/core/events/v3/rotation-config.d.ts +9 -0
  203. package/dist/core/events/v3/rotation-config.d.ts.map +1 -0
  204. package/dist/core/events/v3/rotation-config.js +91 -0
  205. package/dist/core/events/v3/runtime-build.d.ts +11 -0
  206. package/dist/core/events/v3/runtime-build.d.ts.map +1 -0
  207. package/dist/core/events/v3/runtime-build.js +39 -0
  208. package/dist/core/events/v3/runtime-identity.d.ts +5 -0
  209. package/dist/core/events/v3/runtime-identity.d.ts.map +1 -0
  210. package/dist/core/events/v3/runtime-identity.js +18 -0
  211. package/dist/core/events/v3/runtime-telemetry-capabilities.d.ts +6 -5
  212. package/dist/core/events/v3/runtime-telemetry-capabilities.d.ts.map +1 -1
  213. package/dist/core/events/v3/runtime-telemetry-capabilities.js +1 -1
  214. package/dist/core/events/v3/support-storage/classify.d.ts +46 -0
  215. package/dist/core/events/v3/support-storage/classify.d.ts.map +1 -0
  216. package/dist/core/events/v3/support-storage/classify.js +149 -0
  217. package/dist/core/events/v3/support-storage/eligibility.d.ts +29 -0
  218. package/dist/core/events/v3/support-storage/eligibility.d.ts.map +1 -0
  219. package/dist/core/events/v3/support-storage/eligibility.js +35 -0
  220. package/dist/core/events/v3/support-storage/index.d.ts +10 -0
  221. package/dist/core/events/v3/support-storage/index.d.ts.map +1 -0
  222. package/dist/core/events/v3/support-storage/index.js +9 -0
  223. package/dist/core/events/v3/support-storage/inventory.d.ts +30 -0
  224. package/dist/core/events/v3/support-storage/inventory.d.ts.map +1 -0
  225. package/dist/core/events/v3/support-storage/inventory.js +94 -0
  226. package/dist/core/events/v3/support-storage/logical-authority.d.ts +14 -0
  227. package/dist/core/events/v3/support-storage/logical-authority.d.ts.map +1 -0
  228. package/dist/core/events/v3/support-storage/logical-authority.js +104 -0
  229. package/dist/core/events/v3/support-storage/maintenance.d.ts +98 -0
  230. package/dist/core/events/v3/support-storage/maintenance.d.ts.map +1 -0
  231. package/dist/core/events/v3/support-storage/maintenance.js +352 -0
  232. package/dist/core/events/v3/support-storage/pack-contract.d.ts +59 -0
  233. package/dist/core/events/v3/support-storage/pack-contract.d.ts.map +1 -0
  234. package/dist/core/events/v3/support-storage/pack-contract.js +249 -0
  235. package/dist/core/events/v3/support-storage/pack-reader.d.ts +34 -0
  236. package/dist/core/events/v3/support-storage/pack-reader.d.ts.map +1 -0
  237. package/dist/core/events/v3/support-storage/pack-reader.js +258 -0
  238. package/dist/core/events/v3/support-storage/pack-writer.d.ts +30 -0
  239. package/dist/core/events/v3/support-storage/pack-writer.d.ts.map +1 -0
  240. package/dist/core/events/v3/support-storage/pack-writer.js +268 -0
  241. package/dist/core/events/v3/support-storage/recovery.d.ts +15 -0
  242. package/dist/core/events/v3/support-storage/recovery.d.ts.map +1 -0
  243. package/dist/core/events/v3/support-storage/recovery.js +22 -0
  244. package/dist/core/events/v3/turn-telemetry.d.ts +14 -8
  245. package/dist/core/events/v3/turn-telemetry.d.ts.map +1 -1
  246. package/dist/core/events/v3/turn-telemetry.js +32 -2
  247. package/dist/core/events/v3/writer.d.ts +13 -1
  248. package/dist/core/events/v3/writer.d.ts.map +1 -1
  249. package/dist/core/events/v3/writer.js +126 -12
  250. package/dist/core/governor/plan-read.d.ts.map +1 -1
  251. package/dist/core/governor/plan-read.js +5 -21
  252. package/dist/core/governor/planning.js +6 -8
  253. package/dist/core/governor/service-read.d.ts +12 -0
  254. package/dist/core/governor/service-read.d.ts.map +1 -1
  255. package/dist/core/governor/service-read.js +266 -2
  256. package/dist/core/governor/service.d.ts +1 -0
  257. package/dist/core/governor/service.d.ts.map +1 -1
  258. package/dist/core/governor/service.js +53 -18
  259. package/dist/core/guard/evidence-v3.d.ts.map +1 -1
  260. package/dist/core/guard/evidence-v3.js +2 -0
  261. package/dist/core/hooks/adapter/events.d.ts.map +1 -1
  262. package/dist/core/hooks/adapter/events.js +1 -0
  263. package/dist/core/hooks/adapter/output.d.ts +4 -2
  264. package/dist/core/hooks/adapter/output.d.ts.map +1 -1
  265. package/dist/core/hooks/adapter/output.js +13 -5
  266. package/dist/core/hooks/adapter/runtime-telemetry.d.ts +27 -4
  267. package/dist/core/hooks/adapter/runtime-telemetry.d.ts.map +1 -1
  268. package/dist/core/hooks/adapter/runtime-telemetry.js +307 -6
  269. package/dist/core/hooks/cli.js +212 -30
  270. package/dist/core/hooks/effects/image-capture.d.ts +1 -1
  271. package/dist/core/hooks/effects/image-capture.d.ts.map +1 -1
  272. package/dist/core/hooks/effects/image-capture.js +4 -2
  273. package/dist/core/hooks/remediation-cap.d.ts +8 -3
  274. package/dist/core/hooks/remediation-cap.d.ts.map +1 -1
  275. package/dist/core/hooks/remediation-cap.js +3 -11
  276. package/dist/core/hooks/resolve/coord-root.d.ts +22 -9
  277. package/dist/core/hooks/resolve/coord-root.d.ts.map +1 -1
  278. package/dist/core/hooks/resolve/coord-root.js +92 -11
  279. package/dist/core/hooks/resolve/owner.d.ts.map +1 -1
  280. package/dist/core/hooks/resolve/owner.js +5 -0
  281. package/dist/core/hooks/resolve/transcript.d.ts +8 -0
  282. package/dist/core/hooks/resolve/transcript.d.ts.map +1 -1
  283. package/dist/core/hooks/resolve/transcript.js +11 -0
  284. package/dist/core/inbox/contract.d.ts +64 -0
  285. package/dist/core/inbox/contract.d.ts.map +1 -0
  286. package/dist/core/inbox/contract.js +13 -0
  287. package/dist/core/inbox/index.d.ts +5 -0
  288. package/dist/core/inbox/index.d.ts.map +1 -0
  289. package/dist/core/inbox/index.js +4 -0
  290. package/dist/core/inbox/lifecycle.d.ts +22 -0
  291. package/dist/core/inbox/lifecycle.d.ts.map +1 -0
  292. package/dist/core/inbox/lifecycle.js +41 -0
  293. package/dist/core/inbox/service.d.ts +36 -0
  294. package/dist/core/inbox/service.d.ts.map +1 -0
  295. package/dist/core/inbox/service.js +210 -0
  296. package/dist/core/inbox/watcher.d.ts +7 -0
  297. package/dist/core/inbox/watcher.d.ts.map +1 -0
  298. package/dist/core/inbox/watcher.js +26 -0
  299. package/dist/core/presence/codec-digest.d.ts.map +1 -1
  300. package/dist/core/presence/codec-digest.js +14 -3
  301. package/dist/core/presence/relay-client.d.ts +1 -0
  302. package/dist/core/presence/relay-client.d.ts.map +1 -1
  303. package/dist/core/presence/relay-client.js +52 -8
  304. package/dist/core/semantic/adapters.d.ts +11 -8
  305. package/dist/core/semantic/adapters.d.ts.map +1 -1
  306. package/dist/core/semantic/adapters.js +169 -31
  307. package/dist/core/semantic/contract.d.ts +532 -48
  308. package/dist/core/semantic/contract.d.ts.map +1 -1
  309. package/dist/core/semantic/contract.js +104 -3
  310. package/dist/core/semantic/index.d.ts +7 -3
  311. package/dist/core/semantic/index.d.ts.map +1 -1
  312. package/dist/core/semantic/index.js +7 -3
  313. package/dist/core/semantic/once.d.ts +6 -1
  314. package/dist/core/semantic/once.d.ts.map +1 -1
  315. package/dist/core/semantic/once.js +80 -9
  316. package/dist/core/semantic/prompt.d.ts.map +1 -1
  317. package/dist/core/semantic/prompt.js +14 -1
  318. package/dist/core/semantic/review.d.ts +144 -0
  319. package/dist/core/semantic/review.d.ts.map +1 -0
  320. package/dist/core/semantic/review.js +544 -0
  321. package/dist/core/semantic/routes.d.ts +10 -0
  322. package/dist/core/semantic/routes.d.ts.map +1 -0
  323. package/dist/core/semantic/routes.js +21 -0
  324. package/dist/core/semantic/scheduler.d.ts +14 -3
  325. package/dist/core/semantic/scheduler.d.ts.map +1 -1
  326. package/dist/core/semantic/scheduler.js +30 -6
  327. package/dist/core/semantic/service-status.d.ts +20 -0
  328. package/dist/core/semantic/service-status.d.ts.map +1 -1
  329. package/dist/core/semantic/service-status.js +60 -4
  330. package/dist/core/semantic/service.d.ts +1 -0
  331. package/dist/core/semantic/service.d.ts.map +1 -1
  332. package/dist/core/semantic/service.js +104 -15
  333. package/dist/core/semantic/soak.d.ts +75 -0
  334. package/dist/core/semantic/soak.d.ts.map +1 -0
  335. package/dist/core/semantic/soak.js +476 -0
  336. package/dist/core/semantic/storage.d.ts +11 -1
  337. package/dist/core/semantic/storage.d.ts.map +1 -1
  338. package/dist/core/semantic/storage.js +4 -3
  339. package/dist/core/semantic/usage.d.ts +43 -0
  340. package/dist/core/semantic/usage.d.ts.map +1 -0
  341. package/dist/core/semantic/usage.js +193 -0
  342. package/dist/core/semantic/validate.d.ts +3 -1
  343. package/dist/core/semantic/validate.d.ts.map +1 -1
  344. package/dist/core/semantic/validate.js +34 -1
  345. package/dist/core/storage/buffer.d.ts +28 -0
  346. package/dist/core/storage/buffer.d.ts.map +1 -0
  347. package/dist/core/storage/buffer.js +101 -0
  348. package/dist/core/storage/builtins.d.ts +3 -0
  349. package/dist/core/storage/builtins.d.ts.map +1 -0
  350. package/dist/core/storage/builtins.js +683 -0
  351. package/dist/core/storage/catalog.d.ts +19 -0
  352. package/dist/core/storage/catalog.d.ts.map +1 -0
  353. package/dist/core/storage/catalog.js +365 -0
  354. package/dist/core/storage/config.d.ts +16 -0
  355. package/dist/core/storage/config.d.ts.map +1 -0
  356. package/dist/core/storage/config.js +311 -0
  357. package/dist/core/storage/contract.d.ts +275 -0
  358. package/dist/core/storage/contract.d.ts.map +1 -0
  359. package/dist/core/storage/contract.js +3 -0
  360. package/dist/core/storage/durable-history.d.ts +36 -0
  361. package/dist/core/storage/durable-history.d.ts.map +1 -0
  362. package/dist/core/storage/durable-history.js +606 -0
  363. package/dist/core/storage/health.d.ts +4 -0
  364. package/dist/core/storage/health.d.ts.map +1 -0
  365. package/dist/core/storage/health.js +65 -0
  366. package/dist/core/storage/index.d.ts +16 -0
  367. package/dist/core/storage/index.d.ts.map +1 -0
  368. package/dist/core/storage/index.js +15 -0
  369. package/dist/core/storage/inventory.d.ts +25 -0
  370. package/dist/core/storage/inventory.d.ts.map +1 -0
  371. package/dist/core/storage/inventory.js +666 -0
  372. package/dist/core/storage/jsonl.d.ts +52 -0
  373. package/dist/core/storage/jsonl.d.ts.map +1 -0
  374. package/dist/core/storage/jsonl.js +156 -0
  375. package/dist/core/storage/log-retention.d.ts +52 -0
  376. package/dist/core/storage/log-retention.d.ts.map +1 -0
  377. package/dist/core/storage/log-retention.js +419 -0
  378. package/dist/core/storage/logger.d.ts +82 -0
  379. package/dist/core/storage/logger.d.ts.map +1 -0
  380. package/dist/core/storage/logger.js +411 -0
  381. package/dist/core/storage/maintenance-providers.d.ts +26 -0
  382. package/dist/core/storage/maintenance-providers.d.ts.map +1 -0
  383. package/dist/core/storage/maintenance-providers.js +141 -0
  384. package/dist/core/storage/maintenance.d.ts +125 -0
  385. package/dist/core/storage/maintenance.d.ts.map +1 -0
  386. package/dist/core/storage/maintenance.js +609 -0
  387. package/dist/core/storage/metrics.d.ts +60 -0
  388. package/dist/core/storage/metrics.d.ts.map +1 -0
  389. package/dist/core/storage/metrics.js +134 -0
  390. package/dist/core/storage/policy.d.ts +7 -0
  391. package/dist/core/storage/policy.d.ts.map +1 -0
  392. package/dist/core/storage/policy.js +110 -0
  393. package/dist/core/storage/process-log.d.ts +38 -0
  394. package/dist/core/storage/process-log.d.ts.map +1 -0
  395. package/dist/core/storage/process-log.js +165 -0
  396. package/dist/core/storage/query.d.ts +34 -0
  397. package/dist/core/storage/query.d.ts.map +1 -0
  398. package/dist/core/storage/query.js +295 -0
  399. package/dist/core/storage/receipts.d.ts +23 -0
  400. package/dist/core/storage/receipts.d.ts.map +1 -0
  401. package/dist/core/storage/receipts.js +117 -0
  402. package/dist/core/storage/segments.d.ts +84 -0
  403. package/dist/core/storage/segments.d.ts.map +1 -0
  404. package/dist/core/storage/segments.js +542 -0
  405. package/dist/core/storage/sink-graph.d.ts +65 -0
  406. package/dist/core/storage/sink-graph.d.ts.map +1 -0
  407. package/dist/core/storage/sink-graph.js +213 -0
  408. package/dist/core/storage/storm-control.d.ts +23 -0
  409. package/dist/core/storage/storm-control.d.ts.map +1 -0
  410. package/dist/core/storage/storm-control.js +59 -0
  411. package/dist/core/work/state.d.ts.map +1 -1
  412. package/dist/core/work/state.js +10 -14
  413. package/dist/core/workflow/live-session-v3.d.ts.map +1 -1
  414. package/dist/core/workflow/live-session-v3.js +2 -1
  415. package/dist/index.d.ts +2 -0
  416. package/dist/index.d.ts.map +1 -1
  417. package/dist/index.js +2 -0
  418. package/dist/lazy-commands.d.ts +26 -0
  419. package/dist/lazy-commands.d.ts.map +1 -0
  420. package/dist/lazy-commands.js +210 -0
  421. package/dist/lib/docs-lint.d.ts +2 -0
  422. package/dist/lib/docs-lint.d.ts.map +1 -1
  423. package/dist/lib/docs-lint.js +45 -80
  424. package/dist/lib/docs-metadata-audit.d.ts +3 -0
  425. package/dist/lib/docs-metadata-audit.d.ts.map +1 -1
  426. package/dist/lib/docs-metadata-audit.js +13 -23
  427. package/dist/lib/docs-metadata-sync.d.ts +1 -0
  428. package/dist/lib/docs-metadata-sync.d.ts.map +1 -1
  429. package/dist/lib/docs-metadata-sync.js +28 -10
  430. package/dist/lib/docs-repository-view.d.ts +22 -0
  431. package/dist/lib/docs-repository-view.d.ts.map +1 -0
  432. package/dist/lib/docs-repository-view.js +185 -0
  433. package/dist/lib/http/client.d.ts +11 -5
  434. package/dist/lib/http/client.d.ts.map +1 -1
  435. package/dist/lib/http/client.js +22 -4
  436. package/dist/lib/http/index.d.ts +1 -1
  437. package/dist/lib/http/index.d.ts.map +1 -1
  438. package/dist/lib/http/index.js +1 -1
  439. package/dist/lib/indexer-exclusions.d.ts +27 -0
  440. package/dist/lib/indexer-exclusions.d.ts.map +1 -0
  441. package/dist/lib/indexer-exclusions.js +176 -0
  442. package/dist/lib/instructions/templates.d.ts.map +1 -1
  443. package/dist/lib/instructions/templates.js +5 -0
  444. package/dist/lib/local-file-url.d.ts +9 -0
  445. package/dist/lib/local-file-url.d.ts.map +1 -0
  446. package/dist/lib/local-file-url.js +20 -0
  447. package/dist/lib/web-performance.d.ts +118 -0
  448. package/dist/lib/web-performance.d.ts.map +1 -0
  449. package/dist/lib/web-performance.js +192 -0
  450. package/package.json +29 -2
  451. package/schemas/config.schema.json +58 -0
  452. package/schemas/event-v3.schema.json +324 -112
  453. package/src/commander.ts +462 -95
  454. package/src/commands/agents.ts +517 -190
  455. package/src/commands/backup.ts +18 -14
  456. package/src/commands/browse-session.ts +12 -0
  457. package/src/commands/browse.ts +6 -7
  458. package/src/commands/conversations.ts +279 -0
  459. package/src/commands/deinit.ts +6 -0
  460. package/src/commands/docs.ts +25 -6
  461. package/src/commands/doctor.ts +75 -9
  462. package/src/commands/fetch.ts +7 -6
  463. package/src/commands/files.ts +143 -0
  464. package/src/commands/governor.ts +13 -12
  465. package/src/commands/inbox.ts +61 -0
  466. package/src/commands/init.ts +78 -16
  467. package/src/commands/ledger-v3.ts +421 -1
  468. package/src/commands/logs.ts +224 -0
  469. package/src/commands/messages.ts +61 -0
  470. package/src/commands/outline.ts +6 -2
  471. package/src/commands/read.ts +6 -2
  472. package/src/commands/semantic.ts +66 -1
  473. package/src/commands/storage.ts +310 -0
  474. package/src/commands/tunnel.ts +66 -52
  475. package/src/commands/web.ts +77 -7
  476. package/src/commands/work.ts +3 -3
  477. package/src/commands/workflow.ts +13 -13
  478. package/src/core/agents/cli.ts +259 -53
  479. package/src/core/agents/live-authority-v3.ts +341 -10
  480. package/src/core/agents/live-lifecycle-v3.ts +6 -1
  481. package/src/core/agents/live-observation-v3.ts +6 -1
  482. package/src/core/agents/render/prompt-context.ts +27 -6
  483. package/src/core/agents/rules/stop-hook.ts +107 -16
  484. package/src/core/agents/rules/verdict.ts +1 -0
  485. package/src/core/agents/session-events.ts +4 -0
  486. package/src/core/agents/session-finalization-state-v3.ts +1 -1
  487. package/src/core/agents/session-finalizer-v3.ts +4 -1
  488. package/src/core/agents/state/activity-log.ts +7 -2
  489. package/src/core/agents/state/heartbeat-writer.ts +27 -0
  490. package/src/core/agents/state/live-coordination-view.ts +16 -105
  491. package/src/core/agents/state/live-coordination-writer.ts +141 -0
  492. package/src/core/artifacts/index.ts +64 -1
  493. package/src/core/config.ts +126 -5
  494. package/src/core/conversations/archive.ts +237 -0
  495. package/src/core/conversations/catalog.ts +79 -0
  496. package/src/core/conversations/context-pack.ts +72 -0
  497. package/src/core/conversations/contract.ts +178 -0
  498. package/src/core/conversations/evaluation.ts +30 -0
  499. package/src/core/conversations/index.ts +9 -0
  500. package/src/core/conversations/lifecycle.ts +63 -0
  501. package/src/core/conversations/normalize.ts +105 -0
  502. package/src/core/conversations/query.ts +318 -0
  503. package/src/core/conversations/search-index.ts +48 -0
  504. package/src/core/events/legacy-storage/canary.ts +177 -0
  505. package/src/core/events/legacy-storage/fence.ts +129 -0
  506. package/src/core/events/legacy-storage/index.ts +5 -0
  507. package/src/core/events/legacy-storage/inventory.ts +51 -0
  508. package/src/core/events/legacy-storage/manifest.ts +136 -0
  509. package/src/core/events/legacy-storage/reader.ts +178 -0
  510. package/src/core/events/v3/authority-outbox.ts +22 -1
  511. package/src/core/events/v3/base-contract.ts +17 -7
  512. package/src/core/events/v3/bootstrap.ts +195 -2
  513. package/src/core/events/v3/capabilities.ts +3 -3
  514. package/src/core/events/v3/control-witness.ts +339 -0
  515. package/src/core/events/v3/control-writer.ts +16 -0
  516. package/src/core/events/v3/control.ts +54 -18
  517. package/src/core/events/v3/coordination-transaction-recovery.ts +477 -0
  518. package/src/core/events/v3/coordination-view.ts +17 -1
  519. package/src/core/events/v3/generated.ts +1 -1
  520. package/src/core/events/v3/index.ts +5 -2
  521. package/src/core/events/v3/live-route-observer.ts +80 -0
  522. package/src/core/events/v3/live-routing.ts +64 -46
  523. package/src/core/events/v3/producers/coordination-recorder.ts +95 -10
  524. package/src/core/events/v3/producers/diagnostic-summaries.ts +593 -0
  525. package/src/core/events/v3/producers/hook-base.ts +1 -1
  526. package/src/core/events/v3/producers/intake.ts +30 -2
  527. package/src/core/events/v3/producers/producer-state-observer.ts +52 -0
  528. package/src/core/events/v3/producers/recorder.ts +571 -33
  529. package/src/core/events/v3/reader.ts +243 -2
  530. package/src/core/events/v3/rotation-config.ts +98 -0
  531. package/src/core/events/v3/runtime-build.ts +41 -0
  532. package/src/core/events/v3/runtime-identity.ts +16 -0
  533. package/src/core/events/v3/runtime-telemetry-capabilities.ts +14 -8
  534. package/src/core/events/v3/support-storage/classify.ts +209 -0
  535. package/src/core/events/v3/support-storage/eligibility.ts +58 -0
  536. package/src/core/events/v3/support-storage/index.ts +9 -0
  537. package/src/core/events/v3/support-storage/inventory.ts +135 -0
  538. package/src/core/events/v3/support-storage/logical-authority.ts +117 -0
  539. package/src/core/events/v3/support-storage/maintenance.ts +501 -0
  540. package/src/core/events/v3/support-storage/pack-contract.ts +337 -0
  541. package/src/core/events/v3/support-storage/pack-reader.ts +346 -0
  542. package/src/core/events/v3/support-storage/pack-writer.ts +352 -0
  543. package/src/core/events/v3/support-storage/recovery.ts +43 -0
  544. package/src/core/events/v3/turn-telemetry.ts +58 -7
  545. package/src/core/events/v3/writer.ts +168 -16
  546. package/src/core/governor/plan-read.ts +5 -18
  547. package/src/core/governor/planning.ts +5 -8
  548. package/src/core/governor/service-read.ts +320 -2
  549. package/src/core/governor/service.ts +49 -22
  550. package/src/core/guard/evidence-v3.ts +1 -0
  551. package/src/core/hooks/adapter/events.ts +1 -0
  552. package/src/core/hooks/adapter/output.ts +12 -5
  553. package/src/core/hooks/adapter/runtime-telemetry.ts +408 -13
  554. package/src/core/hooks/cli.ts +226 -32
  555. package/src/core/hooks/effects/image-capture.ts +4 -1
  556. package/src/core/hooks/remediation-cap.ts +12 -6
  557. package/src/core/hooks/resolve/coord-root.ts +91 -11
  558. package/src/core/hooks/resolve/owner.ts +4 -0
  559. package/src/core/hooks/resolve/transcript.ts +18 -0
  560. package/src/core/inbox/contract.ts +81 -0
  561. package/src/core/inbox/index.ts +4 -0
  562. package/src/core/inbox/lifecycle.ts +104 -0
  563. package/src/core/inbox/service.ts +305 -0
  564. package/src/core/inbox/watcher.ts +36 -0
  565. package/src/core/presence/codec-digest.ts +15 -9
  566. package/src/core/presence/relay-client.ts +55 -7
  567. package/src/core/semantic/adapters.ts +200 -41
  568. package/src/core/semantic/contract.ts +135 -3
  569. package/src/core/semantic/index.ts +76 -2
  570. package/src/core/semantic/once.ts +93 -7
  571. package/src/core/semantic/prompt.ts +13 -1
  572. package/src/core/semantic/review.ts +803 -0
  573. package/src/core/semantic/routes.ts +30 -0
  574. package/src/core/semantic/scheduler.ts +49 -5
  575. package/src/core/semantic/service-status.ts +99 -5
  576. package/src/core/semantic/service.ts +116 -15
  577. package/src/core/semantic/soak.ts +638 -0
  578. package/src/core/semantic/storage.ts +20 -6
  579. package/src/core/semantic/usage.ts +272 -0
  580. package/src/core/semantic/validate.ts +31 -0
  581. package/src/core/storage/buffer.ts +124 -0
  582. package/src/core/storage/builtins.ts +916 -0
  583. package/src/core/storage/catalog.ts +467 -0
  584. package/src/core/storage/config.ts +423 -0
  585. package/src/core/storage/contract.ts +342 -0
  586. package/src/core/storage/durable-history.ts +738 -0
  587. package/src/core/storage/health.ts +76 -0
  588. package/src/core/storage/index.ts +15 -0
  589. package/src/core/storage/inventory.ts +859 -0
  590. package/src/core/storage/jsonl.ts +213 -0
  591. package/src/core/storage/log-retention.ts +555 -0
  592. package/src/core/storage/logger.ts +546 -0
  593. package/src/core/storage/maintenance-providers.ts +200 -0
  594. package/src/core/storage/maintenance.ts +929 -0
  595. package/src/core/storage/metrics.ts +199 -0
  596. package/src/core/storage/policy.ts +141 -0
  597. package/src/core/storage/process-log.ts +203 -0
  598. package/src/core/storage/query.ts +360 -0
  599. package/src/core/storage/receipts.ts +162 -0
  600. package/src/core/storage/segments.ts +721 -0
  601. package/src/core/storage/sink-graph.ts +343 -0
  602. package/src/core/storage/storm-control.ts +91 -0
  603. package/src/core/work/state.ts +12 -21
  604. package/src/core/workflow/live-session-v3.ts +2 -4
  605. package/src/index.ts +2 -0
  606. package/src/lazy-commands.ts +294 -0
  607. package/src/lib/docs-lint.ts +58 -77
  608. package/src/lib/docs-metadata-audit.ts +26 -21
  609. package/src/lib/docs-metadata-sync.ts +32 -9
  610. package/src/lib/docs-repository-view.ts +220 -0
  611. package/src/lib/http/client.ts +44 -8
  612. package/src/lib/http/index.ts +6 -1
  613. package/src/lib/indexer-exclusions.ts +173 -0
  614. package/src/lib/instructions/templates.ts +5 -0
  615. package/src/lib/local-file-url.ts +31 -0
  616. package/src/lib/web-performance.ts +335 -0
@@ -0,0 +1,929 @@
1
+ import { createHash, randomUUID } from "node:crypto";
2
+ import {
3
+ appendFileSync,
4
+ closeSync,
5
+ existsSync,
6
+ fsyncSync,
7
+ lstatSync,
8
+ mkdirSync,
9
+ openSync,
10
+ readdirSync,
11
+ readFileSync,
12
+ renameSync,
13
+ statSync,
14
+ writeFileSync,
15
+ } from "node:fs";
16
+ import { basename, dirname, join, resolve } from "node:path";
17
+ import { acquireNoClobberLease } from "../workflow/workspaces/leases.ts";
18
+ import type { HarneryStorageCatalog } from "./catalog.ts";
19
+ import {
20
+ HARNERY_STRUCTURED_LOG_PROVIDER_ID,
21
+ type HarneryRegisteredStorageFamily,
22
+ } from "./contract.ts";
23
+
24
+ export const HARNERY_MAINTENANCE_TRANSACTION_SCHEMA =
25
+ "harnery.storage-maintenance-transaction/v1" as const;
26
+ export const HARNERY_MAINTENANCE_RECEIPT_SCHEMA = "harnery.storage-maintenance-receipt/v1" as const;
27
+ export const HARNERY_MAINTENANCE_CURSOR_SCHEMA = "harnery.storage-maintenance-cursor/v1" as const;
28
+ export const HARNERY_MAINTENANCE_PRESSURE_SCHEMA = "harnery.storage-pressure/v1" as const;
29
+
30
+ const SAFE_ID = /^[a-z0-9][a-z0-9._-]{0,127}$/;
31
+ const MAX_JSON_BYTES = 4 * 1024 * 1024;
32
+ const DEFAULT_STALE_MS = 10 * 60 * 1_000;
33
+ const DAY_MS = 24 * 60 * 60 * 1_000;
34
+
35
+ export interface HarneryMaintenanceBudget {
36
+ max_duration_ms: number;
37
+ max_files: number;
38
+ max_bytes: number;
39
+ }
40
+
41
+ export interface HarneryStoragePressureFamily {
42
+ family_id: string;
43
+ logical_bytes: number;
44
+ regular_files: number;
45
+ needs_maintenance: boolean;
46
+ observed_at: string;
47
+ }
48
+
49
+ export interface HarneryStoragePressureSummary {
50
+ schema: typeof HARNERY_MAINTENANCE_PRESSURE_SCHEMA;
51
+ captured_at: string;
52
+ families: readonly HarneryStoragePressureFamily[];
53
+ }
54
+
55
+ export interface HarneryMaintenanceAction {
56
+ action_id: string;
57
+ family_id: string;
58
+ kind: string;
59
+ target_ref: string;
60
+ files: number;
61
+ bytes: number;
62
+ destructive: boolean;
63
+ /** Exact provider-owned destructive scope; generic destructive actions are never authorized. */
64
+ authorization_scope?: string;
65
+ /** Effective user/source retention policy frozen into the plan. */
66
+ effective_policy_fingerprint?: string;
67
+ expected_sha256?: string;
68
+ metadata?: Readonly<Record<string, string | number | boolean>>;
69
+ }
70
+
71
+ export interface HarneryMaintenanceProviderPlan {
72
+ actions: readonly HarneryMaintenanceAction[];
73
+ next_cursor?: string;
74
+ }
75
+
76
+ export interface HarneryMaintenanceProvider {
77
+ family_id: string;
78
+ /** The sole destructive scope this provider may plan and apply. */
79
+ destructive_scope?: string;
80
+ budget?: Partial<HarneryMaintenanceBudget>;
81
+ plan(input: {
82
+ coord_root: string;
83
+ pressure: HarneryStoragePressureFamily;
84
+ cursor?: string;
85
+ budget: HarneryMaintenanceBudget;
86
+ now: Date;
87
+ }): HarneryMaintenanceProviderPlan | Promise<HarneryMaintenanceProviderPlan>;
88
+ apply(input: {
89
+ coord_root: string;
90
+ transaction_id: string;
91
+ action: HarneryMaintenanceAction;
92
+ now: Date;
93
+ }): HarneryMaintenanceMutation | Promise<HarneryMaintenanceMutation>;
94
+ }
95
+
96
+ export interface HarneryMaintenanceMutation {
97
+ outcome: "applied" | "already_applied" | "refused";
98
+ output_sha256?: string;
99
+ detail?: string;
100
+ }
101
+
102
+ export interface HarneryMaintenanceTransaction {
103
+ schema: typeof HARNERY_MAINTENANCE_TRANSACTION_SCHEMA;
104
+ transaction_id: string;
105
+ created_at: string;
106
+ updated_at: string;
107
+ state: "planned" | "running" | "committed" | "refused" | "failed";
108
+ dry_run: boolean;
109
+ catalog_policies: Readonly<Record<string, string>>;
110
+ effective_policy_fingerprints: Readonly<Record<string, string>>;
111
+ budget: HarneryMaintenanceBudget;
112
+ actions: readonly HarneryMaintenanceAction[];
113
+ next_cursors: Readonly<Record<string, string>>;
114
+ reason_codes: readonly string[];
115
+ }
116
+
117
+ export interface HarneryMaintenanceReceipt {
118
+ schema: typeof HARNERY_MAINTENANCE_RECEIPT_SCHEMA;
119
+ transaction_id: string;
120
+ action_id: string;
121
+ family_id: string;
122
+ action_sha256: string;
123
+ outcome: HarneryMaintenanceMutation["outcome"];
124
+ output_sha256?: string;
125
+ detail?: string;
126
+ committed_at: string;
127
+ }
128
+
129
+ export interface HarneryAutomaticMaintenanceResult {
130
+ ran: boolean;
131
+ reason: "disabled" | "no-pressure" | "fresh" | "contended" | "planned" | "committed";
132
+ transaction_id?: string;
133
+ actions: number;
134
+ files: number;
135
+ bytes: number;
136
+ }
137
+
138
+ export const DEFAULT_MAINTENANCE_BUDGET: HarneryMaintenanceBudget = Object.freeze({
139
+ max_duration_ms: 1_500,
140
+ max_files: 250,
141
+ max_bytes: 64 * 1024 * 1024,
142
+ });
143
+
144
+ export async function planStorageMaintenance(
145
+ catalog: HarneryStorageCatalog,
146
+ providers: readonly HarneryMaintenanceProvider[],
147
+ pressure: HarneryStoragePressureSummary,
148
+ options: {
149
+ now?: Date;
150
+ budget?: HarneryMaintenanceBudget;
151
+ family_id?: string;
152
+ persist?: boolean;
153
+ transaction_id?: string;
154
+ } = {},
155
+ ): Promise<HarneryMaintenanceTransaction> {
156
+ const now = options.now ?? new Date();
157
+ assertDate(now);
158
+ const budget = validateBudget(options.budget ?? DEFAULT_MAINTENANCE_BUDGET);
159
+ validatePressure(pressure);
160
+ const providerMap = providerRegistry(catalog, providers);
161
+ if (options.family_id && !catalog.get(options.family_id)) {
162
+ throw new HarneryMaintenanceError(
163
+ "unknown_family",
164
+ `unknown storage family: ${options.family_id}`,
165
+ );
166
+ }
167
+ const started = Date.now();
168
+ const actions: HarneryMaintenanceAction[] = [];
169
+ const nextCursors: Record<string, string> = {};
170
+ const reasons = new Set<string>();
171
+ for (const row of pressure.families) {
172
+ if (!row.needs_maintenance || (options.family_id && row.family_id !== options.family_id))
173
+ continue;
174
+ const provider = providerMap.get(row.family_id);
175
+ if (!provider) {
176
+ reasons.add(`provider_unavailable:${row.family_id}`);
177
+ continue;
178
+ }
179
+ const family = catalog.require(row.family_id);
180
+ if (family.provider.maintenance !== "storage") {
181
+ reasons.add(`maintenance_not_storage_owned:${row.family_id}`);
182
+ continue;
183
+ }
184
+ const remaining = remainingBudget(budget, actions, started);
185
+ if (remaining.max_duration_ms <= 0 || remaining.max_files <= 0 || remaining.max_bytes <= 0) {
186
+ reasons.add("global_budget_exhausted");
187
+ break;
188
+ }
189
+ const cursor = readFamilyCursor(catalog.context.coord_root, row.family_id);
190
+ const providerBudget = intersectBudget(remaining, provider.budget);
191
+ const providerActions: HarneryMaintenanceAction[] = [];
192
+ const planned = await provider.plan({
193
+ coord_root: catalog.context.coord_root,
194
+ pressure: row,
195
+ cursor,
196
+ budget: providerBudget,
197
+ now,
198
+ });
199
+ if (planned.next_cursor !== undefined)
200
+ nextCursors[row.family_id] = boundedCursor(planned.next_cursor);
201
+ for (const action of planned.actions) {
202
+ validateAction(action, row.family_id);
203
+ if (
204
+ action.destructive &&
205
+ (!provider.destructive_scope || action.authorization_scope !== provider.destructive_scope)
206
+ ) {
207
+ throw new HarneryMaintenanceError(
208
+ "destructive_scope_mismatch",
209
+ `provider ${row.family_id} did not own destructive scope for ${action.action_id}`,
210
+ );
211
+ }
212
+ if (
213
+ action.authorization_scope === "structured-log-retention" &&
214
+ !isStructuredLogAuthority(family)
215
+ ) {
216
+ throw new HarneryMaintenanceError(
217
+ "destructive_provider_mismatch",
218
+ `family ${row.family_id} is not owned by the structured log provider`,
219
+ );
220
+ }
221
+ if (
222
+ !fitsBudget(action, budget, actions, started) ||
223
+ !fitsBudget(action, providerBudget, providerActions, started)
224
+ ) {
225
+ reasons.add("global_budget_exhausted");
226
+ break;
227
+ }
228
+ actions.push(structuredClone(action));
229
+ providerActions.push(action);
230
+ }
231
+ }
232
+ const transaction: HarneryMaintenanceTransaction = {
233
+ schema: HARNERY_MAINTENANCE_TRANSACTION_SCHEMA,
234
+ transaction_id:
235
+ options.transaction_id ?? `maint-${now.toISOString().slice(0, 10)}-${randomUUID()}`,
236
+ created_at: now.toISOString(),
237
+ updated_at: now.toISOString(),
238
+ state: "planned",
239
+ dry_run: true,
240
+ catalog_policies: Object.fromEntries(
241
+ [...new Set(actions.map(({ family_id }) => family_id))].map((familyId) => [
242
+ familyId,
243
+ catalog.require(familyId).policy.policy_version,
244
+ ]),
245
+ ),
246
+ effective_policy_fingerprints: transactionFingerprints(actions),
247
+ budget,
248
+ actions,
249
+ next_cursors: nextCursors,
250
+ reason_codes: [...reasons].sort(),
251
+ };
252
+ validateTransaction(transaction);
253
+ if (options.persist) writeTransaction(catalog.context.coord_root, transaction, false);
254
+ return transaction;
255
+ }
256
+
257
+ export async function executeStorageMaintenance(
258
+ catalog: HarneryStorageCatalog,
259
+ providers: readonly HarneryMaintenanceProvider[],
260
+ transactionId: string,
261
+ options: { yes: boolean; now?: Date; authorize_structured_log_deletion?: boolean },
262
+ ): Promise<HarneryMaintenanceTransaction> {
263
+ if (!options.yes) {
264
+ throw new HarneryMaintenanceError(
265
+ "confirmation_required",
266
+ "maintenance execution requires the exact transaction id and --yes",
267
+ );
268
+ }
269
+ const now = options.now ?? new Date();
270
+ assertDate(now);
271
+ const transaction = readMaintenanceTransaction(catalog.context.coord_root, transactionId);
272
+ if (transaction.transaction_id !== transactionId) {
273
+ throw new HarneryMaintenanceError(
274
+ "transaction_mismatch",
275
+ "transaction id does not match its record",
276
+ );
277
+ }
278
+ if (transaction.state === "committed") return transaction;
279
+ for (const [familyId, version] of Object.entries(transaction.catalog_policies)) {
280
+ if (catalog.require(familyId).policy.policy_version !== version) {
281
+ throw new HarneryMaintenanceError("policy_changed", `storage policy changed for ${familyId}`);
282
+ }
283
+ }
284
+ for (const [familyId, fingerprint] of Object.entries(transaction.effective_policy_fingerprints)) {
285
+ const current = catalog.require(familyId).effective_log_retention;
286
+ if (current?.state !== "valid" || current.effective_policy_fingerprint !== fingerprint) {
287
+ throw new HarneryMaintenanceError(
288
+ "effective_policy_changed",
289
+ `effective log retention policy changed for ${familyId}`,
290
+ );
291
+ }
292
+ }
293
+ const providerMap = providerRegistry(catalog, providers);
294
+ const unauthorized = transaction.actions.find((action) => {
295
+ if (!action.destructive) return false;
296
+ const provider = providerMap.get(action.family_id);
297
+ const family = catalog.require(action.family_id);
298
+ return (
299
+ action.authorization_scope !== "structured-log-retention" ||
300
+ provider?.destructive_scope !== action.authorization_scope ||
301
+ !isStructuredLogAuthority(family) ||
302
+ options.authorize_structured_log_deletion !== true
303
+ );
304
+ });
305
+ if (unauthorized) {
306
+ const refused = updateTransaction(transaction, now, "refused", [
307
+ ...transaction.reason_codes,
308
+ "destructive_activation_required",
309
+ ]);
310
+ writeTransaction(catalog.context.coord_root, refused, true);
311
+ throw new HarneryMaintenanceError(
312
+ "destructive_activation_required",
313
+ "structured-log deletion requires its provider scope, exact transaction, --yes, and explicit authorization",
314
+ );
315
+ }
316
+ let current = updateTransaction(transaction, now, "running");
317
+ writeTransaction(catalog.context.coord_root, current, true);
318
+ try {
319
+ for (const action of current.actions) {
320
+ const existing = readReceipt(catalog.context.coord_root, current.transaction_id, action);
321
+ if (existing) continue;
322
+ const provider = providerMap.get(action.family_id);
323
+ if (!provider) throw new HarneryMaintenanceError("provider_unavailable", action.family_id);
324
+ const mutation = await provider.apply({
325
+ coord_root: catalog.context.coord_root,
326
+ transaction_id: current.transaction_id,
327
+ action,
328
+ now,
329
+ });
330
+ writeReceipt(catalog.context.coord_root, current.transaction_id, action, mutation, now);
331
+ }
332
+ for (const [familyId, cursor] of Object.entries(current.next_cursors)) {
333
+ writeFamilyCursor(catalog.context.coord_root, familyId, cursor, now);
334
+ }
335
+ current = updateTransaction(current, now, "committed");
336
+ writeTransaction(catalog.context.coord_root, current, true);
337
+ writeRunOutcome(catalog.context.coord_root, current, now);
338
+ return current;
339
+ } catch (error) {
340
+ current = updateTransaction(current, now, "failed", [
341
+ ...current.reason_codes,
342
+ error instanceof HarneryMaintenanceError ? error.code : "provider_failed",
343
+ ]);
344
+ writeTransaction(catalog.context.coord_root, current, true);
345
+ writeRunOutcome(catalog.context.coord_root, current, now);
346
+ throw error;
347
+ }
348
+ }
349
+
350
+ export async function runAutomaticMaintenanceSlice(
351
+ catalog: HarneryStorageCatalog,
352
+ providers: readonly HarneryMaintenanceProvider[],
353
+ options: { now?: Date; budget?: HarneryMaintenanceBudget; execute?: boolean } = {},
354
+ ): Promise<HarneryAutomaticMaintenanceResult> {
355
+ const now = options.now ?? new Date();
356
+ assertDate(now);
357
+ const coordRoot = catalog.context.coord_root;
358
+ if (maintenanceDisabled(coordRoot)) return emptyAutomatic("disabled");
359
+ const pressure = readPressureSummary(coordRoot);
360
+ if (!pressure?.families.some(({ needs_maintenance }) => needs_maintenance)) {
361
+ return emptyAutomatic("no-pressure");
362
+ }
363
+ const cursor = readDailyCursor(coordRoot);
364
+ if (cursor?.state === "complete" && now.getTime() - Date.parse(cursor.claimed_at) < DAY_MS) {
365
+ return emptyAutomatic("fresh");
366
+ }
367
+ const leasePath = join(resolve(coordRoot), ".harnery", "maintenance", "cursors", "daily-lease");
368
+ let lease: ReturnType<typeof acquireNoClobberLease>;
369
+ try {
370
+ lease = acquireNoClobberLease({
371
+ path: leasePath,
372
+ scope: "storage-maintenance-daily",
373
+ authoritySha256: sha256(JSON.stringify(pressure)),
374
+ staleAfterMs: DEFAULT_STALE_MS,
375
+ now: () => now.getTime(),
376
+ });
377
+ } catch {
378
+ return emptyAutomatic("contended");
379
+ }
380
+ try {
381
+ const refreshed = readDailyCursor(coordRoot);
382
+ if (
383
+ refreshed?.state === "complete" &&
384
+ now.getTime() - Date.parse(refreshed.claimed_at) < DAY_MS
385
+ ) {
386
+ return emptyAutomatic("fresh");
387
+ }
388
+ if (refreshed?.state === "running" && refreshed.transaction_id) {
389
+ const resumed = options.execute
390
+ ? await executeStorageMaintenance(catalog, providers, refreshed.transaction_id, {
391
+ yes: true,
392
+ now,
393
+ authorize_structured_log_deletion: false,
394
+ })
395
+ : readMaintenanceTransaction(coordRoot, refreshed.transaction_id);
396
+ if (resumed.state === "committed")
397
+ writeDailyCursor(coordRoot, now, "complete", resumed.transaction_id);
398
+ return automaticResult(resumed, resumed.state === "committed" ? "committed" : "planned");
399
+ }
400
+ const transactionId = `auto-${now.toISOString().slice(0, 10)}-${randomUUID()}`;
401
+ writeDailyCursor(coordRoot, now, "running", transactionId);
402
+ const transaction = await planStorageMaintenance(catalog, providers, pressure, {
403
+ now,
404
+ budget: options.budget,
405
+ persist: true,
406
+ transaction_id: transactionId,
407
+ });
408
+ if (!options.execute || transaction.actions.length === 0) {
409
+ writeDailyCursor(coordRoot, now, "complete", transaction.transaction_id);
410
+ return automaticResult(transaction, "planned");
411
+ }
412
+ const committed = await executeStorageMaintenance(
413
+ catalog,
414
+ providers,
415
+ transaction.transaction_id,
416
+ {
417
+ yes: true,
418
+ now,
419
+ authorize_structured_log_deletion: false,
420
+ },
421
+ );
422
+ writeDailyCursor(coordRoot, now, "complete", committed.transaction_id);
423
+ return automaticResult(committed, "committed");
424
+ } finally {
425
+ lease.release();
426
+ }
427
+ }
428
+
429
+ export function readMaintenanceTransaction(
430
+ coordRoot: string,
431
+ transactionId: string,
432
+ ): HarneryMaintenanceTransaction {
433
+ assertSafeId(transactionId, "transaction id");
434
+ const path = transactionPath(coordRoot, transactionId);
435
+ const value = readJsonFile(path) as HarneryMaintenanceTransaction;
436
+ validateTransaction(value);
437
+ return value;
438
+ }
439
+
440
+ export function listMaintenanceTransactions(coordRoot: string): HarneryMaintenanceTransaction[] {
441
+ const root = transactionsRoot(coordRoot);
442
+ if (!existsSync(root)) return [];
443
+ return readdirSync(root)
444
+ .filter((name) => name.endsWith(".json") && SAFE_ID.test(name.slice(0, -5)))
445
+ .map((name) => readMaintenanceTransaction(coordRoot, name.slice(0, -5)))
446
+ .sort((left, right) => right.created_at.localeCompare(left.created_at));
447
+ }
448
+
449
+ export function writePressureSummary(
450
+ coordRoot: string,
451
+ summary: HarneryStoragePressureSummary,
452
+ ): void {
453
+ validatePressure(summary);
454
+ atomicJson(pressurePath(coordRoot), summary, true);
455
+ }
456
+
457
+ export function readPressureSummary(coordRoot: string): HarneryStoragePressureSummary | undefined {
458
+ const path = pressurePath(coordRoot);
459
+ if (!existsSync(path)) return undefined;
460
+ const summary = readJsonFile(path) as HarneryStoragePressureSummary;
461
+ validatePressure(summary);
462
+ return summary;
463
+ }
464
+
465
+ function providerRegistry(
466
+ catalog: HarneryStorageCatalog,
467
+ providers: readonly HarneryMaintenanceProvider[],
468
+ ): Map<string, HarneryMaintenanceProvider> {
469
+ const result = new Map<string, HarneryMaintenanceProvider>();
470
+ for (const provider of providers) {
471
+ assertSafeId(provider.family_id, "maintenance provider family id");
472
+ if (!catalog.get(provider.family_id))
473
+ throw new HarneryMaintenanceError("unknown_family", provider.family_id);
474
+ if (result.has(provider.family_id))
475
+ throw new HarneryMaintenanceError("duplicate_provider", provider.family_id);
476
+ if (provider.budget) intersectBudget(DEFAULT_MAINTENANCE_BUDGET, provider.budget);
477
+ if (provider.destructive_scope !== undefined) {
478
+ assertSafeId(provider.destructive_scope, "maintenance provider destructive scope");
479
+ }
480
+ result.set(provider.family_id, provider);
481
+ }
482
+ return result;
483
+ }
484
+
485
+ function isStructuredLogAuthority(family: HarneryRegisteredStorageFamily): boolean {
486
+ return (
487
+ family.provider.provider_id === HARNERY_STRUCTURED_LOG_PROVIDER_ID &&
488
+ (family.storage_class === "operational-log" || family.storage_class === "debug-log") &&
489
+ family.format === "jsonl"
490
+ );
491
+ }
492
+
493
+ function validateAction(action: HarneryMaintenanceAction, expectedFamily: string): void {
494
+ assertSafeId(action.action_id, "action id");
495
+ assertSafeId(action.family_id, "action family id");
496
+ if (action.family_id !== expectedFamily)
497
+ throw new HarneryMaintenanceError("action_family_mismatch", action.action_id);
498
+ if (
499
+ !SAFE_ID.test(action.kind) ||
500
+ typeof action.target_ref !== "string" ||
501
+ action.target_ref.length > 512
502
+ ) {
503
+ throw new HarneryMaintenanceError("invalid_action", action.action_id);
504
+ }
505
+ if (
506
+ !Number.isSafeInteger(action.files) ||
507
+ action.files < 0 ||
508
+ !Number.isSafeInteger(action.bytes) ||
509
+ action.bytes < 0
510
+ ) {
511
+ throw new HarneryMaintenanceError("invalid_action_budget", action.action_id);
512
+ }
513
+ if (action.expected_sha256 !== undefined && !/^[a-f0-9]{64}$/.test(action.expected_sha256)) {
514
+ throw new HarneryMaintenanceError("invalid_action_digest", action.action_id);
515
+ }
516
+ if (action.authorization_scope !== undefined) {
517
+ assertSafeId(action.authorization_scope, "action authorization scope");
518
+ }
519
+ if (
520
+ action.effective_policy_fingerprint !== undefined &&
521
+ !/^[a-f0-9]{64}$/.test(action.effective_policy_fingerprint)
522
+ ) {
523
+ throw new HarneryMaintenanceError("invalid_action_policy_fingerprint", action.action_id);
524
+ }
525
+ if (action.destructive && !action.authorization_scope) {
526
+ throw new HarneryMaintenanceError("destructive_scope_required", action.action_id);
527
+ }
528
+ }
529
+
530
+ function validateTransaction(value: HarneryMaintenanceTransaction): void {
531
+ if (value?.schema !== HARNERY_MAINTENANCE_TRANSACTION_SCHEMA)
532
+ throw new HarneryMaintenanceError("invalid_transaction", "schema");
533
+ assertSafeId(value.transaction_id, "transaction id");
534
+ assertDate(new Date(value.created_at));
535
+ assertDate(new Date(value.updated_at));
536
+ validateBudget(value.budget);
537
+ if (
538
+ !Array.isArray(value.actions) ||
539
+ !Array.isArray(value.reason_codes) ||
540
+ !value.effective_policy_fingerprints ||
541
+ typeof value.effective_policy_fingerprints !== "object" ||
542
+ Array.isArray(value.effective_policy_fingerprints)
543
+ )
544
+ throw new HarneryMaintenanceError("invalid_transaction", "arrays");
545
+ for (const action of value.actions) validateAction(action, action.family_id);
546
+ for (const [familyId, fingerprint] of Object.entries(value.effective_policy_fingerprints)) {
547
+ assertSafeId(familyId, "effective policy family id");
548
+ if (!/^[a-f0-9]{64}$/.test(fingerprint)) {
549
+ throw new HarneryMaintenanceError("invalid_transaction", "effective policy fingerprint");
550
+ }
551
+ }
552
+ if (
553
+ stableJson(value.effective_policy_fingerprints) !==
554
+ stableJson(transactionFingerprints(value.actions))
555
+ ) {
556
+ throw new HarneryMaintenanceError(
557
+ "invalid_transaction",
558
+ "effective policy fingerprint binding",
559
+ );
560
+ }
561
+ if (new Set(value.actions.map(({ action_id }) => action_id)).size !== value.actions.length) {
562
+ throw new HarneryMaintenanceError("duplicate_action", value.transaction_id);
563
+ }
564
+ }
565
+
566
+ function transactionFingerprints(
567
+ actions: readonly HarneryMaintenanceAction[],
568
+ ): Readonly<Record<string, string>> {
569
+ const result: Record<string, string> = {};
570
+ for (const action of actions) {
571
+ const fingerprint = action.effective_policy_fingerprint;
572
+ if (!fingerprint) continue;
573
+ const current = result[action.family_id];
574
+ if (current && current !== fingerprint) {
575
+ throw new HarneryMaintenanceError(
576
+ "mixed_effective_policy",
577
+ `maintenance actions mix effective policies for ${action.family_id}`,
578
+ );
579
+ }
580
+ result[action.family_id] = fingerprint;
581
+ }
582
+ return Object.fromEntries(
583
+ Object.entries(result).sort(([left], [right]) => left.localeCompare(right)),
584
+ );
585
+ }
586
+
587
+ function validatePressure(value: HarneryStoragePressureSummary): void {
588
+ if (value?.schema !== HARNERY_MAINTENANCE_PRESSURE_SCHEMA || !Array.isArray(value.families)) {
589
+ throw new HarneryMaintenanceError("invalid_pressure_summary", "unsupported pressure summary");
590
+ }
591
+ assertDate(new Date(value.captured_at));
592
+ for (const row of value.families) {
593
+ assertSafeId(row.family_id, "pressure family id");
594
+ if (
595
+ !Number.isSafeInteger(row.logical_bytes) ||
596
+ row.logical_bytes < 0 ||
597
+ !Number.isSafeInteger(row.regular_files) ||
598
+ row.regular_files < 0
599
+ ) {
600
+ throw new HarneryMaintenanceError("invalid_pressure_summary", row.family_id);
601
+ }
602
+ assertDate(new Date(row.observed_at));
603
+ }
604
+ }
605
+
606
+ function readReceipt(
607
+ coordRoot: string,
608
+ transactionId: string,
609
+ action: HarneryMaintenanceAction,
610
+ ): HarneryMaintenanceReceipt | undefined {
611
+ const path = receiptPath(coordRoot, transactionId, action.action_id);
612
+ if (!existsSync(path)) return undefined;
613
+ const receipt = readJsonFile(path) as HarneryMaintenanceReceipt;
614
+ if (
615
+ receipt.schema !== HARNERY_MAINTENANCE_RECEIPT_SCHEMA ||
616
+ receipt.transaction_id !== transactionId ||
617
+ receipt.action_id !== action.action_id ||
618
+ receipt.action_sha256 !== sha256(stableJson(action))
619
+ ) {
620
+ throw new HarneryMaintenanceError("receipt_mismatch", action.action_id);
621
+ }
622
+ return receipt;
623
+ }
624
+
625
+ function writeReceipt(
626
+ coordRoot: string,
627
+ transactionId: string,
628
+ action: HarneryMaintenanceAction,
629
+ mutation: HarneryMaintenanceMutation,
630
+ now: Date,
631
+ ): void {
632
+ const receipt: HarneryMaintenanceReceipt = {
633
+ schema: HARNERY_MAINTENANCE_RECEIPT_SCHEMA,
634
+ transaction_id: transactionId,
635
+ action_id: action.action_id,
636
+ family_id: action.family_id,
637
+ action_sha256: sha256(stableJson(action)),
638
+ outcome: mutation.outcome,
639
+ output_sha256: mutation.output_sha256,
640
+ detail: mutation.detail,
641
+ committed_at: now.toISOString(),
642
+ };
643
+ const path = receiptPath(coordRoot, transactionId, action.action_id);
644
+ mkdirSync(dirname(path), { recursive: true, mode: 0o700 });
645
+ writeExclusiveDurable(path, `${JSON.stringify(receipt, null, 2)}\n`);
646
+ }
647
+
648
+ function writeTransaction(
649
+ coordRoot: string,
650
+ transaction: HarneryMaintenanceTransaction,
651
+ replace: boolean,
652
+ ): void {
653
+ validateTransaction(transaction);
654
+ const path = transactionPath(coordRoot, transaction.transaction_id);
655
+ if (!replace && existsSync(path))
656
+ throw new HarneryMaintenanceError("transaction_exists", transaction.transaction_id);
657
+ atomicJson(path, transaction, true);
658
+ }
659
+
660
+ function updateTransaction(
661
+ transaction: HarneryMaintenanceTransaction,
662
+ now: Date,
663
+ state: HarneryMaintenanceTransaction["state"],
664
+ reasonCodes = transaction.reason_codes,
665
+ ): HarneryMaintenanceTransaction {
666
+ return {
667
+ ...transaction,
668
+ updated_at: now.toISOString(),
669
+ state,
670
+ dry_run: state === "planned",
671
+ reason_codes: [...new Set(reasonCodes)].sort(),
672
+ };
673
+ }
674
+
675
+ function writeRunOutcome(
676
+ coordRoot: string,
677
+ transaction: HarneryMaintenanceTransaction,
678
+ now: Date,
679
+ ): void {
680
+ const root = join(resolve(coordRoot), ".harnery", "logs", "storage-maintenance");
681
+ mkdirSync(root, { recursive: true, mode: 0o700 });
682
+ const active = join(root, "active.jsonl");
683
+ if (existsSync(active) && statSync(active).size > 1_048_576) {
684
+ renameSync(active, join(root, `sealed-${now.toISOString().replaceAll(/[:.]/g, "-")}.jsonl`));
685
+ }
686
+ appendFileSync(
687
+ active,
688
+ `${JSON.stringify({ ts: now.toISOString(), transaction_id: transaction.transaction_id, state: transaction.state, actions: transaction.actions.length, files: transaction.actions.reduce((n, action) => n + action.files, 0), bytes: transaction.actions.reduce((n, action) => n + action.bytes, 0), reason_codes: transaction.reason_codes })}\n`,
689
+ { encoding: "utf8", mode: 0o600 },
690
+ );
691
+ }
692
+
693
+ interface DailyCursor {
694
+ schema: typeof HARNERY_MAINTENANCE_CURSOR_SCHEMA;
695
+ state: "running" | "complete";
696
+ claimed_at: string;
697
+ transaction_id: string;
698
+ }
699
+
700
+ function readDailyCursor(coordRoot: string): DailyCursor | undefined {
701
+ const path = join(resolve(coordRoot), ".harnery", "maintenance", "cursors", "daily.json");
702
+ if (!existsSync(path)) return undefined;
703
+ const cursor = readJsonFile(path) as DailyCursor;
704
+ if (cursor.schema !== HARNERY_MAINTENANCE_CURSOR_SCHEMA || !SAFE_ID.test(cursor.transaction_id)) {
705
+ throw new HarneryMaintenanceError("invalid_cursor", "daily cursor");
706
+ }
707
+ return cursor;
708
+ }
709
+
710
+ function writeDailyCursor(
711
+ coordRoot: string,
712
+ now: Date,
713
+ state: DailyCursor["state"],
714
+ transactionId: string,
715
+ ): void {
716
+ atomicJson(
717
+ join(resolve(coordRoot), ".harnery", "maintenance", "cursors", "daily.json"),
718
+ {
719
+ schema: HARNERY_MAINTENANCE_CURSOR_SCHEMA,
720
+ state,
721
+ claimed_at: now.toISOString(),
722
+ transaction_id: transactionId,
723
+ },
724
+ true,
725
+ );
726
+ }
727
+
728
+ function readFamilyCursor(coordRoot: string, familyId: string): string | undefined {
729
+ const path = join(resolve(coordRoot), ".harnery", "maintenance", "cursors", `${familyId}.json`);
730
+ if (!existsSync(path)) return undefined;
731
+ const value = readJsonFile(path) as { schema?: string; cursor?: string };
732
+ return value.schema === HARNERY_MAINTENANCE_CURSOR_SCHEMA && typeof value.cursor === "string"
733
+ ? boundedCursor(value.cursor)
734
+ : undefined;
735
+ }
736
+
737
+ function writeFamilyCursor(coordRoot: string, familyId: string, cursor: string, now: Date): void {
738
+ atomicJson(
739
+ join(resolve(coordRoot), ".harnery", "maintenance", "cursors", `${familyId}.json`),
740
+ {
741
+ schema: HARNERY_MAINTENANCE_CURSOR_SCHEMA,
742
+ family_id: familyId,
743
+ cursor: boundedCursor(cursor),
744
+ updated_at: now.toISOString(),
745
+ },
746
+ true,
747
+ );
748
+ }
749
+
750
+ function maintenanceDisabled(coordRoot: string): boolean {
751
+ const env = process.env.HARNERY_STORAGE_MAINTENANCE?.toLowerCase();
752
+ return (
753
+ env === "0" ||
754
+ env === "false" ||
755
+ existsSync(join(resolve(coordRoot), ".harnery", "maintenance.disabled"))
756
+ );
757
+ }
758
+
759
+ function remainingBudget(
760
+ budget: HarneryMaintenanceBudget,
761
+ actions: readonly HarneryMaintenanceAction[],
762
+ started: number,
763
+ ): HarneryMaintenanceBudget {
764
+ return {
765
+ max_duration_ms: Math.max(0, budget.max_duration_ms - (Date.now() - started)),
766
+ max_files: Math.max(0, budget.max_files - actions.reduce((n, action) => n + action.files, 0)),
767
+ max_bytes: Math.max(0, budget.max_bytes - actions.reduce((n, action) => n + action.bytes, 0)),
768
+ };
769
+ }
770
+
771
+ function fitsBudget(
772
+ action: HarneryMaintenanceAction,
773
+ budget: HarneryMaintenanceBudget,
774
+ actions: readonly HarneryMaintenanceAction[],
775
+ started: number,
776
+ ): boolean {
777
+ const remaining = remainingBudget(budget, actions, started);
778
+ return (
779
+ remaining.max_duration_ms > 0 &&
780
+ action.files <= remaining.max_files &&
781
+ action.bytes <= remaining.max_bytes
782
+ );
783
+ }
784
+
785
+ function validateBudget(value: HarneryMaintenanceBudget): HarneryMaintenanceBudget {
786
+ for (const [key, amount] of Object.entries(value)) {
787
+ if (!Number.isSafeInteger(amount) || amount <= 0)
788
+ throw new HarneryMaintenanceError("invalid_budget", key);
789
+ }
790
+ return { ...value };
791
+ }
792
+
793
+ function intersectBudget(
794
+ global: HarneryMaintenanceBudget,
795
+ provider: Partial<HarneryMaintenanceBudget> | undefined,
796
+ ): HarneryMaintenanceBudget {
797
+ const result = {
798
+ max_duration_ms: Math.min(
799
+ global.max_duration_ms,
800
+ provider?.max_duration_ms ?? global.max_duration_ms,
801
+ ),
802
+ max_files: Math.min(global.max_files, provider?.max_files ?? global.max_files),
803
+ max_bytes: Math.min(global.max_bytes, provider?.max_bytes ?? global.max_bytes),
804
+ };
805
+ return validateBudget(result);
806
+ }
807
+
808
+ function boundedCursor(value: string): string {
809
+ if (value.length === 0 || value.length > 2_048)
810
+ throw new HarneryMaintenanceError("invalid_cursor", "family cursor");
811
+ return value;
812
+ }
813
+
814
+ function automaticResult(
815
+ transaction: HarneryMaintenanceTransaction,
816
+ reason: "planned" | "committed",
817
+ ): HarneryAutomaticMaintenanceResult {
818
+ return {
819
+ ran: true,
820
+ reason,
821
+ transaction_id: transaction.transaction_id,
822
+ actions: transaction.actions.length,
823
+ files: transaction.actions.reduce((n, action) => n + action.files, 0),
824
+ bytes: transaction.actions.reduce((n, action) => n + action.bytes, 0),
825
+ };
826
+ }
827
+
828
+ function emptyAutomatic(
829
+ reason: HarneryAutomaticMaintenanceResult["reason"],
830
+ ): HarneryAutomaticMaintenanceResult {
831
+ return { ran: false, reason, actions: 0, files: 0, bytes: 0 };
832
+ }
833
+
834
+ function transactionsRoot(coordRoot: string): string {
835
+ return join(resolve(coordRoot), ".harnery", "maintenance", "transactions");
836
+ }
837
+
838
+ function transactionPath(coordRoot: string, transactionId: string): string {
839
+ assertSafeId(transactionId, "transaction id");
840
+ return join(transactionsRoot(coordRoot), `${transactionId}.json`);
841
+ }
842
+
843
+ function receiptPath(coordRoot: string, transactionId: string, actionId: string): string {
844
+ assertSafeId(transactionId, "transaction id");
845
+ assertSafeId(actionId, "action id");
846
+ return join(
847
+ resolve(coordRoot),
848
+ ".harnery",
849
+ "maintenance",
850
+ "receipts",
851
+ transactionId,
852
+ `${actionId}.json`,
853
+ );
854
+ }
855
+
856
+ function pressurePath(coordRoot: string): string {
857
+ return join(resolve(coordRoot), ".harnery", "maintenance", "pressure.json");
858
+ }
859
+
860
+ function readJsonFile(path: string): unknown {
861
+ const metadata = lstatSync(path);
862
+ if (
863
+ !metadata.isFile() ||
864
+ metadata.isSymbolicLink() ||
865
+ metadata.size <= 0 ||
866
+ metadata.size > MAX_JSON_BYTES
867
+ ) {
868
+ throw new HarneryMaintenanceError("unsafe_state_file", basename(path));
869
+ }
870
+ return JSON.parse(readFileSync(path, "utf8"));
871
+ }
872
+
873
+ function atomicJson(path: string, value: unknown, fsync: boolean): void {
874
+ mkdirSync(dirname(path), { recursive: true, mode: 0o700 });
875
+ const temporary = `${path}.tmp-${process.pid}-${randomUUID()}`;
876
+ const fd = openSync(temporary, "wx", 0o600);
877
+ try {
878
+ writeFileSync(fd, `${JSON.stringify(value, null, 2)}\n`, "utf8");
879
+ if (fsync) fsyncSync(fd);
880
+ } finally {
881
+ closeSync(fd);
882
+ }
883
+ renameSync(temporary, path);
884
+ }
885
+
886
+ function writeExclusiveDurable(path: string, value: string): void {
887
+ const fd = openSync(path, "wx", 0o600);
888
+ try {
889
+ writeFileSync(fd, value, "utf8");
890
+ fsyncSync(fd);
891
+ } finally {
892
+ closeSync(fd);
893
+ }
894
+ }
895
+
896
+ function stableJson(value: unknown): string {
897
+ if (Array.isArray(value)) return `[${value.map(stableJson).join(",")}]`;
898
+ if (value && typeof value === "object") {
899
+ return `{${Object.entries(value as Record<string, unknown>)
900
+ .sort(([left], [right]) => left.localeCompare(right))
901
+ .map(([key, child]) => `${JSON.stringify(key)}:${stableJson(child)}`)
902
+ .join(",")}}`;
903
+ }
904
+ return JSON.stringify(value);
905
+ }
906
+
907
+ function sha256(value: string): string {
908
+ return createHash("sha256").update(value).digest("hex");
909
+ }
910
+
911
+ function assertSafeId(value: string, label: string): void {
912
+ if (typeof value !== "string" || !SAFE_ID.test(value))
913
+ throw new HarneryMaintenanceError("invalid_id", label);
914
+ }
915
+
916
+ function assertDate(value: Date): void {
917
+ if (!Number.isFinite(value.getTime()))
918
+ throw new HarneryMaintenanceError("invalid_time", "invalid timestamp");
919
+ }
920
+
921
+ export class HarneryMaintenanceError extends Error {
922
+ constructor(
923
+ readonly code: string,
924
+ message: string,
925
+ ) {
926
+ super(message);
927
+ this.name = "HarneryMaintenanceError";
928
+ }
929
+ }