@ouro.bot/cli 0.1.0-alpha.81 → 0.1.0-alpha.810

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 (623) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +154 -23
  3. package/RepairGuide.ouro/agent.json +5 -0
  4. package/RepairGuide.ouro/psyche/IDENTITY.md +19 -0
  5. package/RepairGuide.ouro/psyche/SOUL.md +55 -0
  6. package/RepairGuide.ouro/skills/diagnose-broken-remote.md +63 -0
  7. package/RepairGuide.ouro/skills/diagnose-stacked-typed-issues.md +35 -0
  8. package/RepairGuide.ouro/skills/diagnose-sync-blocked.md +54 -0
  9. package/RepairGuide.ouro/skills/diagnose-vault-expired.md +60 -0
  10. package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/agent.json +4 -2
  11. package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/SOUL.md +2 -2
  12. package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/the-serpent.md +1 -1
  13. package/assets/bluebubbles-host +264 -0
  14. package/changelog.json +5177 -16
  15. package/deploy/unraid/Dockerfile +48 -0
  16. package/deploy/unraid/README.txt +3032 -0
  17. package/deploy/unraid/audit-container-spec.sh +4 -0
  18. package/deploy/unraid/container-runtime.json +1 -0
  19. package/deploy/unraid/docker-man-template-transaction.mjs +526 -0
  20. package/deploy/unraid/docker-man-template-xml.cjs +105 -0
  21. package/deploy/unraid/migrate-sanctuary-bundle.mjs +60 -0
  22. package/deploy/unraid/ouro-events/bootstrap-spool.sh +92 -0
  23. package/deploy/unraid/ouro-events/emit-event.mjs +310 -0
  24. package/deploy/unraid/ouro-events/emit-usenet-event.sh +54 -0
  25. package/deploy/unraid/ouro-events/install-usenet-guard.sh +435 -0
  26. package/deploy/unraid/ouro-events/usenet-health.sh +176 -0
  27. package/deploy/unraid/sanctuary-acceptance-adapter.sh +43 -0
  28. package/deploy/unraid/sanctuary-acceptance-contract.json +198 -0
  29. package/deploy/unraid/sanctuary-acceptance-harness.sh +23 -0
  30. package/deploy/unraid/sanctuary-deployment-target.mjs +709 -0
  31. package/deploy/unraid/sanctuary-safe-rename.py +24 -0
  32. package/deploy/unraid/sanctuary-unit16-host-broker.mjs +2030 -0
  33. package/deploy/unraid/sanctuary-unit16-run.sh +560 -0
  34. package/deploy/unraid/sanctuary-unit18-target-audit.sh +27 -0
  35. package/deploy/unraid/sanctuary.ouro/agent.json +24 -0
  36. package/deploy/unraid/sanctuary.ouro/arc/README.md +3 -0
  37. package/deploy/unraid/sanctuary.ouro/bundle-meta.json +5 -0
  38. package/deploy/unraid/sanctuary.ouro/habits/sanctuary-health.md +8 -0
  39. package/deploy/unraid/sanctuary.ouro/provider-readiness.json +11 -0
  40. package/deploy/unraid/sanctuary.ouro/psyche/ASPIRATIONS.md +5 -0
  41. package/deploy/unraid/sanctuary.ouro/psyche/IDENTITY.md +5 -0
  42. package/deploy/unraid/sanctuary.ouro/psyche/LORE.md +11 -0
  43. package/deploy/unraid/sanctuary.ouro/psyche/SOUL.md +25 -0
  44. package/deploy/unraid/sanctuary.ouro/psyche/TACIT.md +11 -0
  45. package/deploy/unraid/sanctuary.ouro/state/policy/steward.json +7 -0
  46. package/deploy/unraid/sanctuary.ouro/tool-profiles.json +23 -0
  47. package/deploy/unraid/sanctuary.xml +27 -0
  48. package/dist/a2a/card.js +57 -0
  49. package/dist/a2a/client.js +180 -0
  50. package/dist/a2a/config.js +50 -0
  51. package/dist/a2a/delegation-stores.js +56 -0
  52. package/dist/a2a/did-resolution.js +93 -0
  53. package/dist/a2a/identity.js +170 -0
  54. package/dist/a2a/inbound-share.js +107 -0
  55. package/dist/a2a/mission-result-wire.js +196 -0
  56. package/dist/a2a/onboarding.js +115 -0
  57. package/dist/a2a/pin-store.js +132 -0
  58. package/dist/a2a/seen-ledger.js +120 -0
  59. package/dist/a2a/server.js +604 -0
  60. package/dist/a2a/task-store.js +69 -0
  61. package/dist/a2a/transport.js +45 -0
  62. package/dist/a2a/types.js +3 -0
  63. package/dist/arc/attention-types.js +8 -0
  64. package/dist/arc/cares.js +360 -0
  65. package/dist/arc/episodes.js +118 -0
  66. package/dist/arc/evolution.js +490 -0
  67. package/dist/arc/flight-recorder.js +1034 -0
  68. package/dist/arc/intentions.js +134 -0
  69. package/dist/arc/json-store.js +117 -0
  70. package/dist/arc/obligations.js +386 -0
  71. package/dist/arc/packets.js +336 -0
  72. package/dist/arc/presence.js +185 -0
  73. package/dist/arc/task-lifecycle.js +57 -0
  74. package/dist/commerce/store.js +755 -0
  75. package/dist/commerce/types.js +3 -0
  76. package/dist/heart/active-work.js +820 -29
  77. package/dist/heart/agent-entry.js +252 -3
  78. package/dist/heart/approval-files.js +113 -0
  79. package/dist/heart/approval-store.js +950 -0
  80. package/dist/heart/attachments/image-normalize.js +194 -0
  81. package/dist/heart/attachments/materialize.js +97 -0
  82. package/dist/heart/attachments/originals.js +88 -0
  83. package/dist/heart/attachments/render.js +29 -0
  84. package/dist/heart/attachments/sources/bluebubbles.js +156 -0
  85. package/dist/heart/attachments/sources/cli-local-file.js +78 -0
  86. package/dist/heart/attachments/sources/index.js +18 -0
  87. package/dist/heart/attachments/sources/telegram.js +71 -0
  88. package/dist/heart/attachments/store.js +132 -0
  89. package/dist/heart/attachments/types.js +93 -0
  90. package/dist/heart/auth/auth-flow.js +495 -0
  91. package/dist/heart/autonomy-budget.js +478 -0
  92. package/dist/heart/awaiting/await-alert.js +148 -0
  93. package/dist/heart/awaiting/await-expiry.js +143 -0
  94. package/dist/heart/awaiting/await-loader.js +91 -0
  95. package/dist/heart/awaiting/await-parser.js +149 -0
  96. package/dist/heart/awaiting/await-runtime-state.js +100 -0
  97. package/dist/heart/awaiting/await-scheduler.js +382 -0
  98. package/dist/heart/background-operations.js +281 -0
  99. package/dist/heart/bridges/manager.js +137 -17
  100. package/dist/heart/bridges/store.js +14 -2
  101. package/dist/heart/bundle-state.js +168 -0
  102. package/dist/heart/commitments.js +64 -18
  103. package/dist/heart/config-registry.js +331 -0
  104. package/dist/heart/config.js +142 -129
  105. package/dist/heart/context-loss-gauntlet.js +387 -0
  106. package/dist/heart/context-loss-sentinel.js +1157 -0
  107. package/dist/heart/core.js +2105 -574
  108. package/dist/heart/cross-chat-delivery.js +9 -22
  109. package/dist/heart/daemon/agent-config-check.js +451 -0
  110. package/dist/heart/daemon/agent-discovery.js +185 -3
  111. package/dist/heart/daemon/agent-service.js +542 -0
  112. package/dist/heart/daemon/agentic-repair.js +547 -0
  113. package/dist/heart/daemon/await-private-wake.js +59 -0
  114. package/dist/heart/daemon/bluebubbles-cli-integration.js +71 -0
  115. package/dist/heart/daemon/bluebubbles-health-diagnostics.js +122 -0
  116. package/dist/heart/daemon/bluebubbles-host-protocol.js +433 -0
  117. package/dist/heart/daemon/bluebubbles-host.js +273 -0
  118. package/dist/heart/daemon/boot-sync-probe.js +197 -0
  119. package/dist/heart/daemon/cadence.js +180 -0
  120. package/dist/heart/daemon/cli-defaults.js +854 -0
  121. package/dist/heart/daemon/cli-desk.js +322 -0
  122. package/dist/heart/daemon/cli-exec.js +8822 -0
  123. package/dist/heart/daemon/cli-help.js +716 -0
  124. package/dist/heart/daemon/cli-parse.js +2771 -0
  125. package/dist/heart/daemon/cli-render-doctor.js +57 -0
  126. package/dist/heart/daemon/cli-render.js +777 -0
  127. package/dist/heart/daemon/cli-types.js +8 -0
  128. package/dist/heart/daemon/connect-bay.js +323 -0
  129. package/dist/heart/daemon/container-credential-bootstrap.js +273 -0
  130. package/dist/heart/daemon/container-healthcheck.js +112 -0
  131. package/dist/heart/daemon/container-runtime.js +97 -0
  132. package/dist/heart/daemon/container-spec-auditor-main.js +170 -0
  133. package/dist/heart/daemon/container-spec-auditor.js +236 -0
  134. package/dist/heart/daemon/daemon-bootstrap-startup.js +115 -0
  135. package/dist/heart/daemon/daemon-cli.js +29 -2317
  136. package/dist/heart/daemon/daemon-entry.js +921 -11
  137. package/dist/heart/daemon/daemon-health.js +186 -0
  138. package/dist/heart/daemon/daemon-rollup.js +57 -0
  139. package/dist/heart/daemon/daemon-runtime-sync.js +88 -13
  140. package/dist/heart/daemon/daemon-tombstone.js +236 -0
  141. package/dist/heart/daemon/daemon.js +2073 -104
  142. package/dist/heart/daemon/dns-workflow.js +394 -0
  143. package/dist/heart/daemon/doctor-types.js +8 -0
  144. package/dist/heart/daemon/doctor.js +1755 -0
  145. package/dist/heart/daemon/freshness.js +345 -0
  146. package/dist/heart/daemon/habit-private-wake.js +61 -0
  147. package/dist/heart/daemon/health-monitor.js +142 -11
  148. package/dist/heart/daemon/hooks/agent-config-v2.js +33 -0
  149. package/dist/heart/daemon/hooks/bundle-meta.js +135 -1
  150. package/dist/heart/daemon/http-health-probe.js +80 -0
  151. package/dist/heart/daemon/human-command-screens.js +234 -0
  152. package/dist/heart/daemon/human-readiness.js +114 -0
  153. package/dist/heart/daemon/inner-status.js +78 -0
  154. package/dist/heart/daemon/interactive-repair.js +394 -0
  155. package/dist/heart/daemon/launchd.js +37 -8
  156. package/dist/heart/daemon/log-tailer.js +79 -10
  157. package/dist/heart/daemon/logs-prune.js +132 -0
  158. package/dist/heart/daemon/mcp-canary.js +524 -0
  159. package/dist/heart/daemon/message-router.js +65 -5
  160. package/dist/heart/daemon/migrate-to-desk.js +848 -0
  161. package/dist/heart/daemon/os-cron-deps.js +135 -0
  162. package/dist/heart/daemon/os-cron.js +22 -14
  163. package/dist/heart/daemon/ouro-bot-entry.js +4 -2
  164. package/dist/heart/daemon/ouro-entry.js +3 -1
  165. package/dist/heart/daemon/plugin-cli.js +432 -0
  166. package/dist/heart/daemon/process-manager.js +733 -61
  167. package/dist/heart/daemon/provider-discovery.js +137 -0
  168. package/dist/heart/daemon/provider-ping-progress.js +83 -0
  169. package/dist/heart/daemon/prunable-bundle.js +144 -0
  170. package/dist/heart/daemon/pulse.js +511 -0
  171. package/dist/heart/daemon/readiness-repair.js +365 -0
  172. package/dist/heart/daemon/run-hooks.js +2 -0
  173. package/dist/heart/daemon/runtime-logging.js +47 -14
  174. package/dist/heart/daemon/runtime-metadata.js +2 -30
  175. package/dist/heart/daemon/runtime-mode.js +13 -2
  176. package/dist/heart/daemon/safe-mode.js +161 -0
  177. package/dist/heart/daemon/sanctuary-acceptance-adapter.js +2609 -0
  178. package/dist/heart/daemon/sanctuary-acceptance-harness.js +1866 -0
  179. package/dist/heart/daemon/sanctuary-acceptance-marker.js +267 -0
  180. package/dist/heart/daemon/sanctuary-acceptance-scenarios.js +976 -0
  181. package/dist/heart/daemon/sanctuary-bundle-migration.js +728 -0
  182. package/dist/heart/daemon/sanctuary-package-management.js +103 -0
  183. package/dist/heart/daemon/sanctuary-scheduler-liveness.js +227 -0
  184. package/dist/heart/daemon/sanctuary-scheduler-origin.js +132 -0
  185. package/dist/heart/daemon/sense-manager.js +689 -48
  186. package/dist/heart/daemon/session-id-resolver.js +131 -0
  187. package/dist/heart/daemon/skill-management-installer.js +1 -1
  188. package/dist/heart/daemon/socket-client.js +180 -12
  189. package/dist/heart/daemon/stale-bundle-prune.js +113 -0
  190. package/dist/heart/daemon/startup-tui.js +339 -0
  191. package/dist/heart/daemon/supercronic-supervisor.js +282 -0
  192. package/dist/heart/daemon/task-scheduler.js +117 -39
  193. package/dist/heart/daemon/terminal-ui.js +499 -0
  194. package/dist/heart/daemon/thoughts.js +138 -54
  195. package/dist/heart/daemon/up-progress.js +366 -0
  196. package/dist/heart/daemon/vault-items.js +56 -0
  197. package/dist/heart/delegation.js +1 -4
  198. package/dist/heart/external-events/router.js +1414 -0
  199. package/dist/heart/frontend-approval-runtime.js +432 -0
  200. package/dist/heart/frontend-journal.js +215 -0
  201. package/dist/heart/frontend-session-service.js +237 -0
  202. package/dist/heart/frontend-socket-client.js +154 -0
  203. package/dist/heart/frontend-socket.js +400 -0
  204. package/dist/heart/habits/habit-cancel.js +810 -0
  205. package/dist/heart/habits/habit-lifecycle.js +1327 -0
  206. package/dist/heart/habits/habit-migration.js +194 -0
  207. package/dist/heart/habits/habit-parser.js +278 -0
  208. package/dist/heart/habits/habit-runtime-state.js +93 -0
  209. package/dist/heart/habits/habit-scheduler.js +653 -0
  210. package/dist/heart/habits/habit-session-summary.js +318 -0
  211. package/dist/heart/habits/habit-session.js +636 -0
  212. package/dist/heart/{daemon → hatch}/hatch-flow.js +55 -62
  213. package/dist/heart/{daemon → hatch}/hatch-specialist.js +6 -8
  214. package/dist/heart/{daemon → hatch}/specialist-prompt.js +12 -9
  215. package/dist/heart/{daemon → hatch}/specialist-tools.js +53 -18
  216. package/dist/heart/identity.js +184 -59
  217. package/dist/heart/kept-notes.js +289 -0
  218. package/dist/heart/kicks.js +1 -1
  219. package/dist/heart/machine-identity.js +161 -0
  220. package/dist/heart/mail-import-discovery.js +356 -0
  221. package/dist/heart/mailbox/mailbox-http-hooks.js +98 -0
  222. package/dist/heart/mailbox/mailbox-http-response.js +7 -0
  223. package/dist/heart/mailbox/mailbox-http-routes.js +398 -0
  224. package/dist/heart/mailbox/mailbox-http-static.js +103 -0
  225. package/dist/heart/mailbox/mailbox-http-transport.js +129 -0
  226. package/dist/heart/mailbox/mailbox-http.js +99 -0
  227. package/dist/heart/mailbox/mailbox-read.js +39 -0
  228. package/dist/heart/mailbox/mailbox-types.js +27 -0
  229. package/dist/heart/mailbox/mailbox-view.js +197 -0
  230. package/dist/heart/mailbox/readers/agent-machine.js +428 -0
  231. package/dist/heart/mailbox/readers/continuity-readers.js +329 -0
  232. package/dist/heart/mailbox/readers/mail.js +375 -0
  233. package/dist/heart/mailbox/readers/runtime-readers.js +823 -0
  234. package/dist/heart/mailbox/readers/sessions.js +231 -0
  235. package/dist/heart/mailbox/readers/shared.js +111 -0
  236. package/dist/heart/mcp/mcp-server.js +696 -0
  237. package/dist/heart/migrate-config.js +100 -0
  238. package/dist/heart/model-capabilities.js +11 -0
  239. package/dist/heart/orientation-frame.js +345 -0
  240. package/dist/heart/platform.js +81 -0
  241. package/dist/heart/primitives.js +1 -1
  242. package/dist/heart/private-runtime/decision-renderer.js +158 -0
  243. package/dist/heart/private-runtime/index.js +18 -0
  244. package/dist/heart/private-runtime/ledger.js +221 -0
  245. package/dist/heart/private-runtime/policy.js +252 -0
  246. package/dist/heart/private-runtime/types.js +2 -0
  247. package/dist/heart/progress-story.js +1 -1
  248. package/dist/heart/provider-attempt.js +137 -0
  249. package/dist/heart/provider-binding-resolver.js +273 -0
  250. package/dist/heart/provider-credentials.js +449 -0
  251. package/dist/heart/provider-failover.js +311 -0
  252. package/dist/heart/provider-models.js +96 -0
  253. package/dist/heart/provider-ping.js +265 -0
  254. package/dist/heart/provider-readiness-cache.js +119 -0
  255. package/dist/heart/provider-visibility.js +188 -0
  256. package/dist/heart/providers/anthropic-token.js +131 -0
  257. package/dist/heart/providers/anthropic.js +173 -60
  258. package/dist/heart/providers/azure.js +23 -11
  259. package/dist/heart/providers/error-classification.js +127 -0
  260. package/dist/heart/providers/github-copilot.js +34 -38
  261. package/dist/heart/providers/minimax-vlm.js +189 -0
  262. package/dist/heart/providers/minimax.js +26 -8
  263. package/dist/heart/providers/openai-codex-token.js +349 -0
  264. package/dist/heart/providers/openai-codex.js +55 -40
  265. package/dist/heart/providers/openai-compatible.js +177 -0
  266. package/dist/heart/run-ledger.js +247 -0
  267. package/dist/heart/runtime-capability-check.js +170 -0
  268. package/dist/heart/runtime-credentials.js +607 -0
  269. package/dist/heart/runtime-cwd.js +87 -0
  270. package/dist/heart/sense-truth.js +22 -4
  271. package/dist/heart/session-activity.js +92 -24
  272. package/dist/heart/session-events.js +1433 -0
  273. package/dist/heart/session-playback-cli-main.js +5 -0
  274. package/dist/heart/session-playback-cli.js +36 -0
  275. package/dist/heart/session-playback.js +231 -0
  276. package/dist/heart/session-redaction-repair-cli-main.js +31 -0
  277. package/dist/heart/session-redaction-repair.js +543 -0
  278. package/dist/heart/session-stats-cli-main.js +5 -0
  279. package/dist/heart/session-stats.js +182 -0
  280. package/dist/heart/session-transcript.js +133 -0
  281. package/dist/heart/start-of-turn-packet.js +372 -0
  282. package/dist/heart/steward-policy.js +302 -0
  283. package/dist/heart/streaming.js +604 -207
  284. package/dist/heart/structured-output.js +196 -0
  285. package/dist/heart/sync-classification.js +176 -0
  286. package/dist/heart/sync.js +449 -0
  287. package/dist/heart/target-resolution.js +11 -7
  288. package/dist/heart/tempo.js +93 -0
  289. package/dist/heart/temporal-view.js +41 -0
  290. package/dist/heart/timeouts.js +101 -0
  291. package/dist/heart/tool-activity-callbacks.js +59 -0
  292. package/dist/heart/tool-approval.js +342 -0
  293. package/dist/heart/tool-description.js +155 -0
  294. package/dist/heart/tool-friction.js +55 -0
  295. package/dist/heart/tool-loop.js +200 -0
  296. package/dist/heart/turn-context.js +482 -0
  297. package/dist/heart/turn-execution-lease.js +30 -0
  298. package/dist/heart/{daemon → versioning}/ouro-bot-global-installer.js +6 -5
  299. package/dist/heart/{daemon → versioning}/ouro-bot-wrapper.js +1 -1
  300. package/dist/heart/versioning/ouro-path-installer.js +432 -0
  301. package/dist/heart/versioning/ouro-recovery-launcher.js +76 -0
  302. package/dist/heart/versioning/ouro-version-manager.js +409 -0
  303. package/dist/heart/{daemon → versioning}/staged-restart.js +40 -8
  304. package/dist/heart/{daemon → versioning}/update-checker.js +6 -1
  305. package/dist/heart/versioning/update-hooks.js +154 -0
  306. package/dist/heart/versioning/version-intent.js +114 -0
  307. package/dist/heart/work-card.js +380 -0
  308. package/dist/mailbox-ui/assets/index-Du_9G9WO.css +1 -0
  309. package/dist/mailbox-ui/assets/index-J8zDwHmE.js +1 -0
  310. package/dist/mailbox-ui/assets/vendor-CcN1XpQ9.js +61 -0
  311. package/dist/mailbox-ui/index.html +16 -0
  312. package/dist/mailroom/attention.js +167 -0
  313. package/dist/mailroom/autonomy.js +209 -0
  314. package/dist/mailroom/blob-store.js +889 -0
  315. package/dist/mailroom/body-cache.js +61 -0
  316. package/dist/mailroom/cache-sync-cli.js +58 -0
  317. package/dist/mailroom/core.js +803 -0
  318. package/dist/mailroom/entry.js +160 -0
  319. package/dist/mailroom/file-store.js +568 -0
  320. package/dist/mailroom/hosted-cache-sync.js +384 -0
  321. package/dist/mailroom/mbox-import.js +393 -0
  322. package/dist/mailroom/migration.js +164 -0
  323. package/dist/mailroom/outbound.js +380 -0
  324. package/dist/mailroom/policy.js +263 -0
  325. package/dist/mailroom/reader.js +292 -0
  326. package/dist/mailroom/search-cache.js +513 -0
  327. package/dist/mailroom/search-relevance.js +319 -0
  328. package/dist/mailroom/smtp-ingress.js +176 -0
  329. package/dist/mailroom/source-state.js +176 -0
  330. package/dist/mailroom/thread.js +109 -0
  331. package/dist/mailroom/travel-extract.js +89 -0
  332. package/dist/mind/bundle-manifest.js +24 -2
  333. package/dist/mind/context.js +279 -111
  334. package/dist/mind/desk-section.js +362 -0
  335. package/dist/mind/diary-integrity.js +60 -0
  336. package/dist/mind/{memory.js → diary.js} +68 -77
  337. package/dist/mind/embedding-provider.js +60 -0
  338. package/dist/mind/file-state.js +179 -0
  339. package/dist/mind/{associative-recall.js → note-search.js} +47 -58
  340. package/dist/mind/obligation-steering.js +227 -0
  341. package/dist/mind/pending.js +34 -8
  342. package/dist/mind/prompt-budget.js +479 -0
  343. package/dist/mind/prompt-refresh.js +3 -2
  344. package/dist/mind/prompt.js +1059 -187
  345. package/dist/mind/provenance-trust.js +26 -0
  346. package/dist/mind/record-paths.js +312 -0
  347. package/dist/mind/scrutiny.js +173 -0
  348. package/dist/mind/session-transaction.js +564 -0
  349. package/dist/nerves/cli-logging.js +7 -1
  350. package/dist/nerves/coverage/audit-rules.js +15 -6
  351. package/dist/nerves/coverage/audit.js +28 -2
  352. package/dist/nerves/coverage/cli.js +1 -1
  353. package/dist/nerves/coverage/contract.js +5 -5
  354. package/dist/nerves/coverage/file-completeness.js +139 -5
  355. package/dist/nerves/coverage/run-artifacts.js +55 -35
  356. package/dist/nerves/coverage/source-scanner.js +4 -4
  357. package/dist/nerves/event-buffer.js +111 -0
  358. package/dist/nerves/index.js +293 -9
  359. package/dist/nerves/observation.js +20 -0
  360. package/dist/nerves/redact.js +79 -0
  361. package/dist/nerves/review/cli-main.js +5 -0
  362. package/dist/nerves/review/cli.js +156 -0
  363. package/dist/nerves/review/core.js +159 -0
  364. package/dist/nerves/runtime.js +17 -1
  365. package/dist/repertoire/ado-client.js +15 -56
  366. package/dist/repertoire/ado-semantic.js +16 -10
  367. package/dist/repertoire/api-client.js +97 -0
  368. package/dist/repertoire/bitwarden-store.js +1045 -0
  369. package/dist/repertoire/bundle-templates.js +71 -0
  370. package/dist/repertoire/bw-installer.js +180 -0
  371. package/dist/repertoire/coding/codex-jsonl.js +64 -0
  372. package/dist/repertoire/coding/context-pack.js +331 -0
  373. package/dist/repertoire/coding/feedback.js +218 -30
  374. package/dist/repertoire/coding/index.js +15 -3
  375. package/dist/repertoire/coding/manager.js +166 -10
  376. package/dist/repertoire/coding/spawner.js +58 -11
  377. package/dist/repertoire/coding/tools.js +242 -14
  378. package/dist/repertoire/coding/workbench-client.js +272 -0
  379. package/dist/repertoire/coding/workbench-manager.js +346 -0
  380. package/dist/repertoire/commerce-errors.js +109 -0
  381. package/dist/repertoire/commerce-self-test.js +156 -0
  382. package/dist/repertoire/credential-access.js +178 -0
  383. package/dist/repertoire/desk/classifier.js +362 -0
  384. package/dist/repertoire/duffel-client.js +185 -0
  385. package/dist/repertoire/github-client.js +14 -55
  386. package/dist/repertoire/graph-client.js +11 -52
  387. package/dist/repertoire/guardrails.js +811 -18
  388. package/dist/repertoire/mcp-client.js +44 -3
  389. package/dist/repertoire/mcp-manager.js +331 -27
  390. package/dist/repertoire/mcp-tools.js +83 -0
  391. package/dist/repertoire/plugin-mcp.js +175 -0
  392. package/dist/repertoire/plugins.js +253 -0
  393. package/dist/repertoire/relationship-authorization.js +194 -0
  394. package/dist/repertoire/shell-sessions.js +133 -0
  395. package/dist/repertoire/skills.js +48 -4
  396. package/dist/repertoire/stripe-client.js +131 -0
  397. package/dist/repertoire/tool-arguments.js +148 -0
  398. package/dist/repertoire/tool-results.js +29 -0
  399. package/dist/repertoire/tools-a2a.js +699 -0
  400. package/dist/repertoire/tools-attachments.js +322 -0
  401. package/dist/repertoire/tools-awaiting.js +575 -0
  402. package/dist/repertoire/tools-base.js +82 -1139
  403. package/dist/repertoire/tools-bluebubbles.js +2 -0
  404. package/dist/repertoire/tools-bridge.js +144 -0
  405. package/dist/repertoire/tools-bundle.js +993 -0
  406. package/dist/repertoire/tools-commerce.js +253 -0
  407. package/dist/repertoire/tools-config.js +186 -0
  408. package/dist/repertoire/tools-continuity.js +575 -0
  409. package/dist/repertoire/tools-credential.js +383 -0
  410. package/dist/repertoire/tools-evolution.js +527 -0
  411. package/dist/repertoire/tools-files.js +344 -0
  412. package/dist/repertoire/tools-flight.js +290 -0
  413. package/dist/repertoire/tools-flow.js +119 -0
  414. package/dist/repertoire/tools-github.js +3 -8
  415. package/dist/repertoire/tools-habits.js +103 -0
  416. package/dist/repertoire/tools-mail.js +1828 -0
  417. package/dist/repertoire/tools-notes.js +485 -0
  418. package/dist/repertoire/tools-obligations.js +182 -0
  419. package/dist/repertoire/tools-orientation.js +31 -0
  420. package/dist/repertoire/tools-record.js +482 -0
  421. package/dist/repertoire/tools-rsvp.js +139 -0
  422. package/dist/repertoire/tools-runtime.js +150 -0
  423. package/dist/repertoire/tools-session.js +1108 -0
  424. package/dist/repertoire/tools-shell.js +133 -0
  425. package/dist/repertoire/tools-steward-policy.js +95 -0
  426. package/dist/repertoire/tools-stripe.js +224 -0
  427. package/dist/repertoire/tools-surface.js +369 -0
  428. package/dist/repertoire/tools-teams.js +12 -39
  429. package/dist/repertoire/tools-telegram-contacts.js +37 -0
  430. package/dist/repertoire/tools-travel.js +125 -0
  431. package/dist/repertoire/tools-trip.js +982 -0
  432. package/dist/repertoire/tools-unraid.js +484 -0
  433. package/dist/repertoire/tools-user-profile.js +146 -0
  434. package/dist/repertoire/tools-vault.js +40 -0
  435. package/dist/repertoire/tools-voice.js +145 -0
  436. package/dist/repertoire/tools.js +377 -80
  437. package/dist/repertoire/travel-api-client.js +360 -0
  438. package/dist/repertoire/unraid-client.js +155 -0
  439. package/dist/repertoire/unraid-restart.js +212 -0
  440. package/dist/repertoire/user-profile.js +131 -0
  441. package/dist/repertoire/vault-setup.js +246 -0
  442. package/dist/repertoire/vault-unlock.js +628 -0
  443. package/dist/rsvp/aisleplanner-client.js +354 -0
  444. package/dist/rsvp/cli.js +926 -0
  445. package/dist/rsvp/config.js +327 -0
  446. package/dist/rsvp/cutover.js +433 -0
  447. package/dist/rsvp/diagnostics.js +232 -0
  448. package/dist/rsvp/diff-renderer.js +176 -0
  449. package/dist/rsvp/habit-policy.js +110 -0
  450. package/dist/rsvp/habit-stage.js +123 -0
  451. package/dist/rsvp/incident-bundle.js +97 -0
  452. package/dist/rsvp/migration.js +204 -0
  453. package/dist/rsvp/native-habit-runner.js +630 -0
  454. package/dist/rsvp/outbound-state.js +628 -0
  455. package/dist/rsvp/query.js +118 -0
  456. package/dist/rsvp/replay.js +233 -0
  457. package/dist/rsvp/snapshot.js +232 -0
  458. package/dist/rsvp/spend-ledger.js +175 -0
  459. package/dist/scripts/claude-code-hook.js +41 -0
  460. package/dist/scripts/claude-code-stop-hook.js +47 -0
  461. package/dist/senses/a2a-entry.js +115 -0
  462. package/dist/senses/acp-server.js +386 -0
  463. package/dist/senses/attention-queue.js +186 -0
  464. package/dist/senses/await-turn-message.js +58 -0
  465. package/dist/senses/bluebubbles/active-turns.js +216 -0
  466. package/dist/senses/bluebubbles/attachment-cache.js +53 -0
  467. package/dist/senses/bluebubbles/attachment-download.js +137 -0
  468. package/dist/senses/{bluebubbles-client.js → bluebubbles/client.js} +355 -40
  469. package/dist/senses/bluebubbles/context-packet.js +207 -0
  470. package/dist/senses/bluebubbles/context-smoke.js +144 -0
  471. package/dist/senses/bluebubbles/entry.js +90 -0
  472. package/dist/senses/{bluebubbles-inbound-log.js → bluebubbles/inbound-log.js} +21 -3
  473. package/dist/senses/bluebubbles/index.js +3839 -0
  474. package/dist/senses/bluebubbles/latest-turn.js +311 -0
  475. package/dist/senses/{bluebubbles-media.js → bluebubbles/media.js} +121 -71
  476. package/dist/senses/{bluebubbles-model.js → bluebubbles/model.js} +159 -28
  477. package/dist/senses/{bluebubbles-mutation-log.js → bluebubbles/mutation-log.js} +24 -13
  478. package/dist/senses/bluebubbles/outbound-state.js +308 -0
  479. package/dist/senses/bluebubbles/processed-log.js +133 -0
  480. package/dist/senses/bluebubbles/reaction-policy.js +47 -0
  481. package/dist/senses/bluebubbles/replay.js +284 -0
  482. package/dist/senses/{bluebubbles-runtime-state.js → bluebubbles/runtime-state.js} +30 -2
  483. package/dist/senses/bluebubbles/semantic-receipts.js +1986 -0
  484. package/dist/senses/{bluebubbles-session-cleanup.js → bluebubbles/session-cleanup.js} +1 -1
  485. package/dist/senses/bluebubbles/webhook-registration.js +228 -0
  486. package/dist/senses/bluebubbles-meta-guard.js +39 -0
  487. package/dist/senses/cli/bracketed-paste.js +82 -0
  488. package/dist/senses/cli/image-paste.js +287 -0
  489. package/dist/senses/cli/image-ref-navigation.js +75 -0
  490. package/dist/senses/cli/ink-app.js +156 -0
  491. package/dist/senses/cli/inline-diff.js +64 -0
  492. package/dist/senses/cli/input-keys.js +174 -0
  493. package/dist/senses/cli/kill-ring.js +86 -0
  494. package/dist/senses/cli/message-list.js +51 -0
  495. package/dist/senses/cli/ouro-tui.js +607 -0
  496. package/dist/senses/cli/spinner-imperative.js +135 -0
  497. package/dist/senses/cli/spinner.js +101 -0
  498. package/dist/senses/cli/status-line.js +60 -0
  499. package/dist/senses/cli/streaming-markdown.js +526 -0
  500. package/dist/senses/cli/tool-display.js +85 -0
  501. package/dist/senses/cli/tool-render.js +85 -0
  502. package/dist/senses/cli/tui-store.js +241 -0
  503. package/dist/senses/cli/virtual-list.js +35 -0
  504. package/dist/senses/cli-entry.js +60 -8
  505. package/dist/senses/cli.js +600 -317
  506. package/dist/senses/commands.js +66 -3
  507. package/dist/senses/context-packet-ledger.js +371 -0
  508. package/dist/senses/context-packets.js +154 -0
  509. package/dist/senses/habit-lifecycle-guard.js +24 -0
  510. package/dist/senses/habit-turn-message.js +160 -0
  511. package/dist/senses/ingress-evidence.js +2 -0
  512. package/dist/senses/mail-entry.js +66 -0
  513. package/dist/senses/mail.js +524 -0
  514. package/dist/senses/pipeline.js +1115 -204
  515. package/dist/senses/private-runtime-worker.js +859 -0
  516. package/dist/senses/private-runtime.js +1496 -0
  517. package/dist/senses/proactive-content-guard.js +51 -0
  518. package/dist/senses/sanctuary-download-credit-presentation.js +31 -0
  519. package/dist/senses/sanctuary-full-visibility-contract.js +302 -0
  520. package/dist/senses/sanctuary-grounding.js +118 -0
  521. package/dist/senses/sanctuary-health-acceptance-probe-entry.js +12 -0
  522. package/dist/senses/sanctuary-health-acceptance-probe.js +774 -0
  523. package/dist/senses/sanctuary-health-runner.js +101 -0
  524. package/dist/senses/sanctuary-health.js +408 -0
  525. package/dist/senses/sanctuary-install-state-contract.js +126 -0
  526. package/dist/senses/sanctuary-interactive-control.js +306 -0
  527. package/dist/senses/sanctuary-media-catalog-contract.js +235 -0
  528. package/dist/senses/sanctuary-media-optimization.js +514 -0
  529. package/dist/senses/sanctuary-runtime.js +338 -0
  530. package/dist/senses/sanctuary-sab.js +85 -0
  531. package/dist/senses/sanctuary-storage-optimization-contract.js +32 -0
  532. package/dist/senses/shared-turn.js +756 -0
  533. package/dist/senses/surface-tool.js +109 -0
  534. package/dist/senses/teams-entry.js +60 -8
  535. package/dist/senses/teams.js +515 -217
  536. package/dist/senses/telegram-admission.js +711 -0
  537. package/dist/senses/telegram-approval-runtime.js +566 -0
  538. package/dist/senses/telegram-attachments.js +182 -0
  539. package/dist/senses/telegram-audit-ledger.js +220 -0
  540. package/dist/senses/telegram-client.js +1677 -0
  541. package/dist/senses/telegram-effect-adapter.js +859 -0
  542. package/dist/senses/telegram-entry.js +83 -0
  543. package/dist/senses/telegram.js +2045 -0
  544. package/dist/senses/trust-gate.js +104 -9
  545. package/dist/senses/voice/audio-playback.js +237 -0
  546. package/dist/senses/voice/audio-routing.js +119 -0
  547. package/dist/senses/voice/elevenlabs.js +202 -0
  548. package/dist/senses/voice/floor-control.js +431 -0
  549. package/dist/senses/voice/floor-controller.js +115 -0
  550. package/dist/senses/voice/golden-path.js +116 -0
  551. package/dist/senses/voice/index.js +29 -0
  552. package/dist/senses/voice/meeting.js +113 -0
  553. package/dist/senses/voice/outbound.js +190 -0
  554. package/dist/senses/voice/phone.js +33 -0
  555. package/dist/senses/voice/playback.js +139 -0
  556. package/dist/senses/voice/realtime-eval.js +496 -0
  557. package/dist/senses/voice/realtime-trace.js +531 -0
  558. package/dist/senses/voice/transcript.js +70 -0
  559. package/dist/senses/voice/turn.js +191 -0
  560. package/dist/senses/voice/twilio-phone-runtime.js +807 -0
  561. package/dist/senses/voice/twilio-phone.js +5081 -0
  562. package/dist/senses/voice/types.js +2 -0
  563. package/dist/senses/voice/whisper.js +161 -0
  564. package/dist/senses/voice-entry.js +81 -0
  565. package/dist/senses/voice-realtime-eval-command.js +99 -0
  566. package/dist/senses/voice-realtime-eval-entry.js +21 -0
  567. package/dist/senses/voice-twilio-entry.js +87 -0
  568. package/dist/trips/core.js +138 -0
  569. package/dist/trips/store.js +265 -0
  570. package/dist/util/frontmatter.js +69 -0
  571. package/npm-shrinkwrap.json +8138 -0
  572. package/package.json +72 -13
  573. package/skills/agent-commerce.md +113 -0
  574. package/skills/browser-navigation.md +117 -0
  575. package/skills/commerce-setup-guide.md +116 -0
  576. package/skills/commerce-setup.md +84 -0
  577. package/skills/configure-dev-tools.md +99 -0
  578. package/skills/travel-planning.md +138 -0
  579. package/dist/heart/daemon/auth-flow.js +0 -430
  580. package/dist/heart/daemon/ouro-path-installer.js +0 -260
  581. package/dist/heart/daemon/ouro-version-manager.js +0 -164
  582. package/dist/heart/daemon/update-hooks.js +0 -138
  583. package/dist/heart/obligations.js +0 -141
  584. package/dist/heart/safe-workspace.js +0 -228
  585. package/dist/heart/session-recall.js +0 -116
  586. package/dist/mind/friends/channel.js +0 -84
  587. package/dist/mind/friends/group-context.js +0 -144
  588. package/dist/mind/friends/resolver.js +0 -84
  589. package/dist/mind/friends/store-file.js +0 -190
  590. package/dist/mind/friends/store.js +0 -4
  591. package/dist/mind/friends/tokens.js +0 -26
  592. package/dist/mind/friends/trust-explanation.js +0 -74
  593. package/dist/mind/friends/types.js +0 -29
  594. package/dist/repertoire/tasks/board.js +0 -134
  595. package/dist/repertoire/tasks/index.js +0 -224
  596. package/dist/repertoire/tasks/lifecycle.js +0 -80
  597. package/dist/repertoire/tasks/middleware.js +0 -65
  598. package/dist/repertoire/tasks/parser.js +0 -173
  599. package/dist/repertoire/tasks/scanner.js +0 -132
  600. package/dist/repertoire/tasks/transitions.js +0 -144
  601. package/dist/senses/bluebubbles-entry.js +0 -13
  602. package/dist/senses/bluebubbles.js +0 -1181
  603. package/dist/senses/debug-activity.js +0 -154
  604. package/dist/senses/inner-dialog-worker.js +0 -90
  605. package/dist/senses/inner-dialog.js +0 -525
  606. package/subagents/README.md +0 -7
  607. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/basilisk.md +0 -0
  608. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/jafar.md +0 -0
  609. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/jormungandr.md +0 -0
  610. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/kaa.md +0 -0
  611. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/medusa.md +0 -0
  612. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/monty.md +0 -0
  613. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/nagini.md +0 -0
  614. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/ouroboros.md +0 -0
  615. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/python.md +0 -0
  616. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/quetzalcoatl.md +0 -0
  617. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/sir-hiss.md +0 -0
  618. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/the-snake.md +0 -0
  619. /package/dist/{repertoire/tasks/types.js → heart/attachments/sources/adapter.js} +0 -0
  620. /package/dist/heart/{daemon → hatch}/hatch-animation.js +0 -0
  621. /package/dist/heart/{daemon → hatch}/specialist-orchestrator.js +0 -0
  622. /package/dist/heart/{daemon → versioning}/ouro-uti.js +0 -0
  623. /package/dist/heart/{daemon → versioning}/wrapper-publish-guard.js +0 -0
@@ -0,0 +1,2030 @@
1
+ #!/usr/local/bin/node
2
+
3
+ import { spawn, spawnSync } from "node:child_process"
4
+ import { createHash, createHmac, timingSafeEqual } from "node:crypto"
5
+ import { chmodSync, chownSync, closeSync, constants, fstatSync, fsyncSync, mkdirSync, openSync, opendirSync, readFileSync, readSync, readdirSync, renameSync, rmSync, statSync, unlinkSync, writeFileSync } from "node:fs"
6
+ import { createServer } from "node:net"
7
+ import { targetProfile } from "./sanctuary-deployment-target.mjs"
8
+
9
+ const KEY_ROOT = "/boot/config/plugins/dynamix.my.servers/keys"
10
+ const RECOVERY_ROOT = "/mnt/user/appdata/ouro-butler/acceptance/revoked-key-proof"
11
+ const UNRAID_API = "/usr/local/sbin/unraid-api"
12
+ const DOCKER = "/usr/bin/docker"
13
+ let activeContainer = "ouro-butler"
14
+ let activeContainerId = "0".repeat(64)
15
+ let activeProfile = targetProfile("final")
16
+ const AUTOSTART_FILE = "/var/lib/docker/unraid-autostart"
17
+ const RUNTIME_POLICY_FILE = "/opt/ouro/container-runtime.json"
18
+ const PRODUCTION_RUNTIME_SOURCE = "/mnt/user/appdata/ouro-butler/runtime/.ouro-cli"
19
+ const PRODUCTION_BUNDLE_SOURCE = "/mnt/user/appdata/ouro-butler/agent/sanctuary.ouro"
20
+ const PRODUCTION_EVENT_SPOOL_SOURCE = "/boot/config/custom/ouro-events/spool"
21
+ const GRAPHQL_ENDPOINT = "http://127.0.0.1/graphql"
22
+ const BOOT_ID = "/proc/sys/kernel/random/boot_id"
23
+ const MDCMD = "/usr/local/sbin/mdcmd"
24
+ const TAILSCALE = "/usr/local/sbin/tailscale"
25
+ const PGREP = "/usr/bin/pgrep"
26
+ const REBOOT = "/sbin/reboot"
27
+ const TARGET_SERVER = "sanctuary-unraid"
28
+ const TARGET_HOST = "sanctuary"
29
+ const KEY_ID = /^[A-Za-z0-9._:-]+$/u
30
+ const KEY_NAME = /^Butler (?:RO|RW)(?: Rotation [0-9a-f]{16})?$/u
31
+ const PERMISSION = /^[A-Z_]+:(?:CREATE|READ|UPDATE|DELETE)_(?:ANY|OWN)$/u
32
+ const MAX_REQUEST = 256 * 1024
33
+ const IMAGE_ID = /^sha256:[0-9a-f]{64}$/u
34
+ const SHA256 = /^[0-9a-f]{64}$/u
35
+ const HEALTH_PROBE_ENTRY = "/opt/ouro/dist/senses/sanctuary-health-acceptance-probe-entry.js"
36
+ const ACCEPTANCE_ADAPTER = "/opt/ouro/deploy/unraid/sanctuary-acceptance-adapter.sh"
37
+ const HEALTH_PROBE_TERM_GRACE_MS = 5_000
38
+ const HEALTH_PROBE_KILL_GRACE_MS = 5_000
39
+ const HEALTH_PROBE_LABELS = new Set(["unit-16f-cron-fingerprint", "unit-16g-health-transition", "unit-16h-acceptance-delivery-probe"])
40
+ const ASYNC_RESTART_SCENARIO = Symbol("asyncRestartScenario")
41
+ const HEALTH_PROBE_RECEIPT_KEYS = [
42
+ "schemaVersion", "label", "scenarioHandleDigest", "ownerImageDigestBefore", "ownerImageDigestAfter",
43
+ "ownerContainerDigestBefore", "ownerContainerDigestAfter", "beforeStateDigest", "restoredStateDigest",
44
+ "cronFingerprintBefore", "cronFingerprintAfter", "cronRegisteredBefore", "cronRegisteredAfter",
45
+ "cronDegradedBefore", "cronDegradedAfter", "fixtureSequenceDigest", "clockMode", "effectiveNow", "phases",
46
+ "providerInvocationCount", "privateTurnCount", "deliveryCount", "workspaceAbsent", "socketAbsent",
47
+ "snapshotAbsent", "realCheckEquivalent", "productionRestored", "schedulerReceipt",
48
+ ]
49
+ const RO_PERMISSIONS = ["ARRAY", "DASHBOARD", "DISK", "DOCKER", "INFO", "LOGS", "NOTIFICATIONS", "SHARE", "VARS"]
50
+ .map((resource) => `${resource}:READ_ANY`).sort()
51
+ let expectedImageId = ""
52
+ const activeHealthProbes = new Map()
53
+ const ownerMutationCoordinator = createOwnerMutationCoordinator()
54
+ const interactiveRestartDriver = createInteractiveRestartDriver()
55
+ const healthProbeCoordinator = {
56
+ start: (scenario, operation) => ownerMutationCoordinator.healthStart(scenario, operation),
57
+ recover: (scenario, operation) => ownerMutationCoordinator.healthRecover(scenario, operation),
58
+ }
59
+
60
+ function object(value, label) {
61
+ if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error(`${label} must be an object`)
62
+ return value
63
+ }
64
+
65
+ function text(value, label, pattern) {
66
+ if (typeof value !== "string" || !value || (pattern && !pattern.test(value))) throw new Error(`${label} is invalid`)
67
+ return value
68
+ }
69
+
70
+ function exactKeys(value, expected, label) {
71
+ if (JSON.stringify(Object.keys(value).sort()) !== JSON.stringify([...expected].sort())) throw new Error(`${label} shape is invalid`)
72
+ }
73
+
74
+ function observeRebootPreflight(dependencies = {
75
+ readArrayStatus: () => {
76
+ const result = spawnSync(MDCMD, ["status"], { cwd: "/", encoding: "utf8", timeout: 10_000, maxBuffer: 1024 * 1024, env: { PATH: "/usr/local/sbin:/usr/sbin:/usr/bin:/sbin:/bin" }, stdio: ["ignore", "pipe", "ignore"] })
77
+ if (result.error || result.status !== 0) throw new Error("reboot preflight array state is unknown")
78
+ return result.stdout
79
+ },
80
+ readMoverStatus: () => spawnSync(PGREP, ["-x", "mover"], { cwd: "/", encoding: "utf8", timeout: 5_000, env: { PATH: "/usr/local/sbin:/usr/sbin:/usr/bin:/sbin:/bin" }, stdio: ["ignore", "ignore", "ignore"] }),
81
+ mutationActive: () => healthOwnerMutationActive() || ownerMutationCoordinator.active(),
82
+ }) {
83
+ const values = Object.fromEntries(String(dependencies.readArrayStatus()).split(/\r?\n/u).filter(Boolean).map((line) => {
84
+ const separator = line.indexOf("=")
85
+ if (separator < 1) throw new Error("reboot preflight array state is unknown")
86
+ return [line.slice(0, separator), line.slice(separator + 1)]
87
+ }))
88
+ if (values.mdState !== "STARTED" || !/^[0-9]+$/u.test(values.mdResync ?? "")) throw new Error("reboot preflight array state is unknown")
89
+ const mover = dependencies.readMoverStatus()
90
+ if (mover.error || (mover.status !== 0 && mover.status !== 1)) throw new Error("reboot preflight mover state is unknown")
91
+ const state = { arrayReady: true, parityActive: Number(values.mdResync) !== 0, moverActive: mover.status === 0, mutationActive: dependencies.mutationActive() === true }
92
+ if (state.parityActive || state.moverActive || state.mutationActive) throw new Error("reboot preflight found an active host operation")
93
+ const digest = createHash("sha256").update(JSON.stringify(state)).digest("hex")
94
+ return { ...state, safe: true, digest }
95
+ }
96
+
97
+ function readPrivateJson(file) {
98
+ const fd = openSync(file, constants.O_RDONLY | constants.O_NOFOLLOW)
99
+ try {
100
+ const metadata = fstatSync(fd)
101
+ if (!metadata.isFile() || metadata.uid !== 0 || (metadata.mode & 0o777) !== 0o600) throw new Error("Unraid key file metadata is invalid")
102
+ return object(JSON.parse(readFileSync(fd, "utf8")), "Unraid key record")
103
+ } finally { closeSync(fd) }
104
+ }
105
+
106
+ function normalizeRecord(raw) {
107
+ const id = text(raw.id, "key id", KEY_ID)
108
+ const name = text(raw.name, "key name")
109
+ if (!Array.isArray(raw.permissions) || !Array.isArray(raw.roles)) throw new Error("Unraid key record is invalid")
110
+ const permissions = raw.permissions.map((entry) => {
111
+ const value = object(entry, "permission")
112
+ exactKeys(value, ["resource", "actions"], "permission")
113
+ const resource = text(value.resource, "permission resource", /^[A-Z_]+$/u)
114
+ if (!Array.isArray(value.actions) || value.actions.length === 0) throw new Error("permission actions are invalid")
115
+ const actions = value.actions.map((action) => text(action, "permission action", /^(?:CREATE|READ|UPDATE|DELETE)_(?:ANY|OWN)$/u)).sort()
116
+ if (new Set(actions).size !== actions.length) throw new Error("permission actions are ambiguous")
117
+ return { resource, actions }
118
+ })
119
+ if (!raw.roles.every((role) => typeof role === "string")) throw new Error("Unraid key roles are invalid")
120
+ return { id, name, permissions, roles: [...raw.roles], key: text(raw.key, "key descriptor") }
121
+ }
122
+
123
+ function inventoryRecords() {
124
+ const root = statSync(KEY_ROOT)
125
+ if (!root.isDirectory() || root.uid !== 0 || (root.mode & 0o077) !== 0) throw new Error("Unraid key directory metadata is invalid")
126
+ const records = readdirSync(KEY_ROOT, { withFileTypes: true }).map((entry) => {
127
+ if (!entry.isFile() || !entry.name.endsWith(".json")) throw new Error("unexpected Unraid key directory entry")
128
+ return normalizeRecord(readPrivateJson(`${KEY_ROOT}/${entry.name}`))
129
+ })
130
+ if (new Set(records.map(({ id }) => id)).size !== records.length || new Set(records.map(({ name }) => name)).size !== records.length) {
131
+ throw new Error("Unraid key inventory is ambiguous")
132
+ }
133
+ return records.sort((left, right) => left.id.localeCompare(right.id))
134
+ }
135
+
136
+ function publicRecord({ key: _key, ...record }) { return record }
137
+
138
+ function flattened(record) {
139
+ return record.permissions.flatMap(({ resource, actions }) => actions.map((action) => `${resource}:${action}`)).sort()
140
+ }
141
+
142
+ function runUnraid(args) {
143
+ const result = spawnSync(UNRAID_API, args, {
144
+ cwd: "/", encoding: "utf8", timeout: 20_000, maxBuffer: 1024 * 1024,
145
+ env: { PATH: "/usr/local/sbin:/usr/sbin:/usr/bin:/sbin:/bin" },
146
+ stdio: ["ignore", "pipe", "ignore"],
147
+ })
148
+ if (result.error || result.status !== 0) throw new Error("bounded Unraid API operation failed")
149
+ return object(JSON.parse(result.stdout), "Unraid API response")
150
+ }
151
+
152
+ function permissionObjects(values) {
153
+ const grouped = new Map()
154
+ for (const value of values) {
155
+ const [resource, action] = value.split(":")
156
+ const actions = grouped.get(resource) ?? []
157
+ actions.push(action)
158
+ grouped.set(resource, actions)
159
+ }
160
+ return [...grouped.entries()].sort(([left], [right]) => left.localeCompare(right))
161
+ .map(([resource, actions]) => ({ resource, actions: actions.sort() }))
162
+ }
163
+
164
+ async function createUnraidKey(name, requested, dependencies = {
165
+ runUnraid, inventoryRecords, persistRecovery, readRecovery: optionalRecoveryRecord,
166
+ persistIntent: persistCreateIntent, readIntent: optionalCreateIntent, removeIntent: removeCreateIntent,
167
+ fetchImpl: fetch,
168
+ }) {
169
+ const before = dependencies.inventoryRecords()
170
+ const beforeIds = new Set(before.map((record) => record.id))
171
+ const provisionalPermissions = [...requested, "API_KEY:UPDATE_ANY"].sort()
172
+ let recoveryId = null
173
+ let cliIdentity = null
174
+ try {
175
+ const occupied = before.filter((record) => record.name === name)
176
+ const existingIntent = dependencies.readIntent?.(name) ?? null
177
+ if (existingIntent === null) {
178
+ if (occupied.length !== 0) throw new Error("existing provisional key has no durable creation intent")
179
+ dependencies.persistIntent(name, provisionalPermissions)
180
+ } else if (existingIntent.name !== name || JSON.stringify(existingIntent.permissions) !== JSON.stringify(provisionalPermissions)) {
181
+ throw new Error("Unraid key creation intent is invalid")
182
+ }
183
+ let created
184
+ let provisional
185
+ let resumed = false
186
+ let resumedFinal = false
187
+ if (occupied.length === 1) {
188
+ provisional = occupied[0]
189
+ let recovery = dependencies.readRecovery?.(provisional.id) ?? null
190
+ const provisionalExact = JSON.stringify(flattened(provisional)) === JSON.stringify(provisionalPermissions)
191
+ && JSON.stringify(provisional.roles) === JSON.stringify(["GUEST"])
192
+ if (recovery === null && provisionalExact) {
193
+ dependencies.persistRecovery(provisional)
194
+ recovery = provisional
195
+ }
196
+ if (!recovery || recovery.id !== provisional.id || recovery.name !== provisional.name || recovery.key !== provisional.key
197
+ || JSON.stringify(flattened(recovery)) !== JSON.stringify(provisionalPermissions)
198
+ || JSON.stringify(recovery.roles) !== JSON.stringify(["GUEST"])
199
+ || !(provisionalExact
200
+ || (JSON.stringify(flattened(provisional)) === JSON.stringify(requested) && provisional.roles.length === 0))) {
201
+ throw new Error("existing provisional key is not exact owned recovery")
202
+ }
203
+ resumedFinal = provisional.roles.length === 0
204
+ created = { id: provisional.id, key: provisional.key, name: provisional.name }
205
+ resumed = true
206
+ } else if (occupied.length === 0) {
207
+ created = object(dependencies.runUnraid(["apikey", "--name", name, "--create", "--roles", "GUEST", "--permissions", provisionalPermissions.join(","), "--json"]), "created Unraid key")
208
+ exactKeys(created, ["id", "key", "name"], "created Unraid key")
209
+ cliIdentity = { id: text(created.id, "created key id", KEY_ID), key: text(created.key, "created key descriptor"), name: created.name }
210
+ } else {
211
+ throw new Error("created provisional key name is ambiguous")
212
+ }
213
+ const id = text(created.id, "created key id", KEY_ID)
214
+ const key = text(created.key, "created key descriptor")
215
+ if (created.name !== name || (!resumed && beforeIds.has(id))) throw new Error("created key identity is invalid")
216
+ const matches = resumed ? [provisional] : dependencies.inventoryRecords().filter((record) => record.id === id)
217
+ if (matches.length !== 1 || matches[0].name !== name || matches[0].key !== key
218
+ || (!resumedFinal && (JSON.stringify(flattened(matches[0])) !== JSON.stringify(provisionalPermissions)
219
+ || JSON.stringify(matches[0].roles) !== JSON.stringify(["GUEST"])))) {
220
+ throw new Error("created provisional key record does not match the exact CLI result")
221
+ }
222
+ if (!resumed) dependencies.persistRecovery(matches[0])
223
+ recoveryId = id
224
+ const headers = { "content-type": "application/json", "x-api-key": key }
225
+ if (!resumedFinal) try {
226
+ const updateResponse = await dependencies.fetchImpl(GRAPHQL_ENDPOINT, {
227
+ method: "POST", headers,
228
+ body: JSON.stringify({
229
+ query: "mutation AcceptanceApiKeyDowngrade($input: UpdateApiKeyInput!) { apiKey { update(input: $input) { id name roles permissions { resource actions } } } }",
230
+ variables: { input: { id, roles: [], permissions: permissionObjects(requested) } },
231
+ }),
232
+ signal: AbortSignal.timeout(10_000),
233
+ })
234
+ const updateEnvelope = object(await updateResponse.json(), "Unraid key downgrade response")
235
+ if (!updateResponse.ok || updateEnvelope.errors || !updateEnvelope.data) throw new Error("Unraid key downgrade was rejected")
236
+ } catch {
237
+ // A lost response is accepted only when disk and live-auth attestation below prove convergence.
238
+ }
239
+ const finalized = dependencies.inventoryRecords().filter((record) => record.id === id)
240
+ if (finalized.length !== 1 || finalized[0].name !== name || finalized[0].key !== key
241
+ || JSON.stringify(flattened(finalized[0])) !== JSON.stringify(requested) || finalized[0].roles.length !== 0) {
242
+ throw new Error("created key did not converge to its exact final scope")
243
+ }
244
+ const probeResponse = await dependencies.fetchImpl(GRAPHQL_ENDPOINT, {
245
+ method: "POST", headers,
246
+ body: JSON.stringify({ query: "query AcceptanceAuthProbe { info { os { hostname } } }", variables: {} }),
247
+ signal: AbortSignal.timeout(10_000),
248
+ })
249
+ const probeEnvelope = object(await probeResponse.json(), "Unraid key readiness response")
250
+ if (!probeResponse.ok || probeEnvelope.errors || !probeEnvelope.data) throw new Error("created key readiness probe failed")
251
+ return finalized[0]
252
+ } catch (error) {
253
+ const candidates = dependencies.inventoryRecords().filter((record) => record.name === name
254
+ && !beforeIds.has(record.id)
255
+ && JSON.stringify(flattened(record)) === JSON.stringify(provisionalPermissions)
256
+ && JSON.stringify(record.roles) === JSON.stringify(["GUEST"])
257
+ && (cliIdentity === null || (record.id === cliIdentity.id && record.key === cliIdentity.key && record.name === cliIdentity.name)))
258
+ if (candidates.length === 1 && candidates[0].id !== recoveryId) dependencies.persistRecovery(candidates[0])
259
+ throw error
260
+ }
261
+ }
262
+
263
+ function autostartFileExact() {
264
+ const fd = openSync(AUTOSTART_FILE, constants.O_RDONLY | constants.O_NOFOLLOW)
265
+ let content
266
+ try {
267
+ if (!fstatSync(fd).isFile()) throw new Error("Unraid autostart state is invalid")
268
+ content = readFileSync(fd, "utf8")
269
+ } finally { closeSync(fd) }
270
+ const counts = { production: 0, staging: 0, rollback: 0, legacy: 0 }
271
+ for (const line of content.split(/\r?\n/u)) {
272
+ const name = line.trim().split(/\s+/u)[0]
273
+ if (name === "ouro-butler") counts.production += 1
274
+ else if (name === "ouro-butler-staging") counts.staging += 1
275
+ else if (name === "ouro-butler-rollback") counts.rollback += 1
276
+ else if (name === "ouro-butler-legacy-evidence") counts.legacy += 1
277
+ }
278
+ return activeProfile.name === "staging"
279
+ ? counts.production === 0 && counts.staging === 1 && counts.rollback === 0 && counts.legacy === 0
280
+ : counts.production === 1 && counts.staging === 0 && counts.rollback === 0 && counts.legacy === 0
281
+ }
282
+
283
+ function optionalStoppedContainerExact(name, containerId, run = spawnSync) {
284
+ if (name !== "ouro-butler-rollback" || !SHA256.test(containerId)) return false
285
+ const template = '{"containerId":{{json .Id}},"name":{{json .Name}},"running":{{json .State.Running}}}'
286
+ const result = run(DOCKER, ["inspect", "--format", template, containerId], {
287
+ cwd: "/", encoding: "utf8", timeout: 20_000, maxBuffer: 64 * 1024,
288
+ env: { PATH: "/usr/local/sbin:/usr/sbin:/usr/bin:/sbin:/bin" }, stdio: ["ignore", "pipe", "ignore"],
289
+ })
290
+ if (result.error || result.status !== 0) return false
291
+ try {
292
+ const value = object(JSON.parse(result.stdout ?? ""), "optional rollback inspection")
293
+ return value.containerId === containerId && value.name === `/${name}` && value.running === false
294
+ } catch { return false }
295
+ }
296
+
297
+ async function queryGraphqlAutostart(records = inventoryRecords(), fetchImpl = fetch, expectedContainerId = activeContainerId, profile = activeProfile, inspectOptionalStopped = optionalStoppedContainerExact) {
298
+ text(expectedContainerId, "attested target container id", SHA256)
299
+ const matches = records.filter((record) => record.name === "Butler RO" && record.roles.length === 0
300
+ && JSON.stringify(flattened(record)) === JSON.stringify(RO_PERMISSIONS))
301
+ if (matches.length !== 1) throw new Error("canonical read-only Unraid key is absent or ambiguous")
302
+ const descriptor = text(matches[0].key, "canonical read-only key descriptor")
303
+ const response = await fetchImpl(GRAPHQL_ENDPOINT, {
304
+ method: "POST",
305
+ headers: { "content-type": "application/json", "x-api-key": descriptor },
306
+ body: JSON.stringify({ query: "query AcceptanceContainerTopology { vars { id } docker { containers(skipCache: true) { id names autoStart } } }", variables: {} }),
307
+ signal: AbortSignal.timeout(10_000),
308
+ })
309
+ if (!response.ok) throw new Error("Unraid container topology query failed")
310
+ const envelope = object(await response.json(), "Unraid container topology response")
311
+ if (envelope.errors || !envelope.data) throw new Error("Unraid container topology query was rejected")
312
+ const data = object(envelope.data, "Unraid topology data")
313
+ const serverIdentity = /^([0-9a-f]{64}):vars$/u.exec(object(data.vars, "Unraid topology vars").id)
314
+ if (!serverIdentity) throw new Error("Unraid server identity is invalid")
315
+ const containers = object(data.docker, "Unraid topology docker").containers
316
+ if (!Array.isArray(containers)) throw new Error("Unraid container topology is invalid")
317
+ const canonicalNames = new Set(["ouro-butler", "ouro-butler-staging", "ouro-butler-rollback"])
318
+ const topology = new Map()
319
+ for (const raw of containers) {
320
+ const container = object(raw, "Unraid topology container")
321
+ if (!Array.isArray(container.names) || container.names.some((name) => typeof name !== "string")) throw new Error("Unraid container topology identity is invalid")
322
+ const canonical = container.names.map((name) => name.replace(/^\//u, "")).filter((name) => canonicalNames.has(name))
323
+ if (canonical.length > 1) return false
324
+ if (canonical.length === 0) continue
325
+ const identity = typeof container.id === "string" ? /^([0-9a-f]{64}):([0-9a-f]{64})$/u.exec(container.id) : null
326
+ if (!identity || identity[1] !== serverIdentity[1] || typeof container.autoStart !== "boolean" || topology.has(canonical[0])) return false
327
+ topology.set(canonical[0], { containerId: identity[2], autoStart: container.autoStart })
328
+ }
329
+ const requiredNames = new Set([profile.containerName, ...profile.requiredStopped])
330
+ const allowedNames = new Set([...requiredNames, ...profile.optionalStopped])
331
+ if ([...requiredNames].some((name) => !topology.has(name)) || [...topology.keys()].some((name) => !allowedNames.has(name))
332
+ || profile.forbidden.some((name) => topology.has(name))) return false
333
+ const target = topology.get(profile.containerName)
334
+ if (!target || target.containerId !== expectedContainerId || target.autoStart !== true) return false
335
+ return [...profile.requiredStopped, ...profile.optionalStopped].every((name) => {
336
+ const stopped = topology.get(name)
337
+ return !stopped || (stopped.autoStart === false && inspectOptionalStopped(name, stopped.containerId))
338
+ })
339
+ }
340
+
341
+ function updaterDisabled(expectedImage) {
342
+ const result = spawnSync(DOCKER, ["run", "--rm", "--pull=never", "--network", "none", "--entrypoint", "/bin/cat", expectedImage, RUNTIME_POLICY_FILE], {
343
+ cwd: "/", encoding: "utf8", timeout: 20_000, maxBuffer: 64 * 1024,
344
+ env: { PATH: "/usr/local/sbin:/usr/sbin:/usr/bin:/sbin:/bin" }, stdio: ["ignore", "pipe", "ignore"],
345
+ })
346
+ if (result.error || result.status !== 0) throw new Error("container runtime policy read failed")
347
+ const policy = object(JSON.parse(result.stdout), "container runtime policy")
348
+ exactKeys(policy, ["scheduler", "updates"], "container runtime policy")
349
+ return policy.scheduler === "supercronic" && policy.updates === "disabled"
350
+ }
351
+
352
+ function parseVaultStatus(output, succeeded) {
353
+ const lines = output.split(/\r?\n/u)
354
+ const runtimeLine = lines.find((line) => line.startsWith("runtime credentials: "))
355
+ const runtimeMatch = /^runtime credentials: (.+) \([^)]+\)$/u.exec(runtimeLine ?? "")
356
+ const runtimeFields = runtimeMatch ? runtimeMatch[1].split(", ") : []
357
+ const providerReady = (provider) => {
358
+ const line = lines.find((candidate) => candidate.startsWith(` ${provider}: `))
359
+ const match = /^ [a-z0-9-]+: credential fields (.+), config fields (.+)$/u.exec(line ?? "")
360
+ if (!match) return false
361
+ return match[1].split(", ").includes("apiKey") && match[2].split(", ").includes("baseUrl")
362
+ }
363
+ const unlocked = succeeded && /^local unlock: available$/mu.test(output)
364
+ && ["telegramBotToken", "telegramAuthorizedUserId", "telegramAuthorizedChatId"].every((field) => runtimeFields.includes(field))
365
+ && providerReady("openai-compatible") && providerReady("openai-compatible-gemini")
366
+ return { vaultUnlocked: unlocked, manualAuthRequired: !unlocked }
367
+ }
368
+
369
+ function vaultStatus(running, healthy) {
370
+ if (!running || !healthy) return { vaultUnlocked: false, manualAuthRequired: true }
371
+ const result = spawnSync(DOCKER, ["exec", activeContainerId, "node", "/opt/ouro/dist/heart/daemon/ouro-entry.js", "vault", "status", "--agent", "sanctuary", "--store", "plaintext-file"], {
372
+ cwd: "/", encoding: "utf8", timeout: 30_000, maxBuffer: 1024 * 1024,
373
+ env: { PATH: "/usr/local/sbin:/usr/sbin:/usr/bin:/sbin:/bin" }, stdio: ["ignore", "pipe", "ignore"],
374
+ })
375
+ return parseVaultStatus(result.stdout ?? "", !result.error && result.status === 0)
376
+ }
377
+
378
+ function recoveryMilestones(running, healthy) {
379
+ const array = spawnSync(MDCMD, ["status"], { cwd: "/", encoding: "utf8", timeout: 10_000, stdio: ["ignore", "pipe", "ignore"] })
380
+ const tailscale = spawnSync(TAILSCALE, ["status", "--json"], { cwd: "/", encoding: "utf8", timeout: 10_000, stdio: ["ignore", "pipe", "ignore"] })
381
+ const ssh = spawnSync(PGREP, ["-x", "sshd"], { cwd: "/", encoding: "utf8", timeout: 10_000, stdio: ["ignore", "pipe", "ignore"] })
382
+ let tailscaleReady = false
383
+ try {
384
+ const status = object(JSON.parse(tailscale.stdout), "Tailscale status")
385
+ tailscaleReady = !tailscale.error && tailscale.status === 0 && status.BackendState === "Running"
386
+ } catch { tailscaleReady = false }
387
+ return {
388
+ hostReady: /^[A-Za-z0-9-]{4,128}$/u.test(readFileSync(BOOT_ID, "utf8").trim()),
389
+ arrayReady: !array.error && array.status === 0 && /^mdState=STARTED$/mu.test(array.stdout),
390
+ dockerReady: true,
391
+ butlerReady: running && healthy,
392
+ tailscaleReady,
393
+ sshReady: !ssh.error && ssh.status === 0 && ssh.stdout.trim().length > 0,
394
+ }
395
+ }
396
+
397
+ function readBoundedProcStatus(statusPath) {
398
+ const fd = openSync(statusPath, constants.O_RDONLY | constants.O_NOFOLLOW)
399
+ try {
400
+ const metadata = fstatSync(fd)
401
+ if (!metadata.isFile()) throw new Error("container PID1 status is invalid")
402
+ const buffer = Buffer.alloc(128 * 1024 + 1)
403
+ const length = readSync(fd, buffer, 0, buffer.length, 0)
404
+ if (length > 128 * 1024) throw new Error("container PID1 status exceeds its bound")
405
+ return buffer.subarray(0, length).toString("utf8")
406
+ } finally { closeSync(fd) }
407
+ }
408
+
409
+ function readBoundedProcIdentityFile(filePath) {
410
+ const fd = openSync(filePath, constants.O_RDONLY | constants.O_NOFOLLOW)
411
+ try {
412
+ const metadata = fstatSync(fd, { bigint: true })
413
+ if (!metadata.isFile()) throw new Error("container PID1 process file is invalid")
414
+ const buffer = Buffer.alloc(128 * 1024 + 1)
415
+ const length = readSync(fd, buffer, 0, buffer.length, 0)
416
+ if (length > 128 * 1024) throw new Error("container PID1 process file exceeds its bound")
417
+ return { content: buffer.subarray(0, length).toString("utf8"), inode: metadata.ino.toString() }
418
+ } finally { closeSync(fd) }
419
+ }
420
+
421
+ function parseProcStartTime(raw) {
422
+ if (typeof raw !== "string" || Buffer.byteLength(raw, "utf8") > 128 * 1024) throw new Error("container PID1 stat is invalid")
423
+ const close = raw.lastIndexOf(")")
424
+ if (close < 3) throw new Error("container PID1 stat is invalid")
425
+ const fields = raw.slice(close + 1).trim().split(/\s+/u)
426
+ const startTime = fields[19]
427
+ if (!startTime || !/^[0-9]+$/u.test(startTime)) throw new Error("container PID1 stat start time is invalid")
428
+ return startTime
429
+ }
430
+
431
+ function liveContainerProcessIdentity(pid, dependencies = { readFile: readBoundedProcIdentityFile }) {
432
+ if (!Number.isSafeInteger(pid) || pid <= 0 || pid > 4_194_304) throw new Error("container PID1 is invalid")
433
+ const status = dependencies.readFile(`/proc/${pid}/status`)
434
+ const stat = dependencies.readFile(`/proc/${pid}/stat`)
435
+ const user = liveContainerProcessUser(pid, { readFile: () => status.content })
436
+ return { user, processStartTime: parseProcStartTime(stat.content), processInode: `${status.inode}:${stat.inode}` }
437
+ }
438
+
439
+ function liveContainerProcessUser(pid, dependencies = { readFile: readBoundedProcStatus }) {
440
+ if (!Number.isSafeInteger(pid) || pid <= 0 || pid > 4_194_304) throw new Error("container PID1 is invalid")
441
+ const statusPath = `/proc/${pid}/status`
442
+ const status = dependencies.readFile(statusPath, "utf8")
443
+ if (typeof status !== "string" || Buffer.byteLength(status, "utf8") > 128 * 1024) throw new Error("container PID1 status is invalid")
444
+ const lines = status.split(/\r?\n/u)
445
+ const parseEffective = (key) => {
446
+ const matches = lines.filter((line) => line.startsWith(`${key}:`))
447
+ if (matches.length !== 1) throw new Error(`container PID1 ${key} status is invalid`)
448
+ const match = new RegExp(`^${key}:\\s+(\\d+)\\s+(\\d+)\\s+(\\d+)\\s+(\\d+)$`, "u").exec(matches[0])
449
+ if (!match) throw new Error(`container PID1 ${key} status is invalid`)
450
+ return Number(match[2])
451
+ }
452
+ const uid = parseEffective("Uid")
453
+ const gid = parseEffective("Gid")
454
+ if (uid !== 10001 || gid !== 10001) throw new Error("container PID1 effective identity is invalid")
455
+ return `${uid}:${gid}`
456
+ }
457
+
458
+ function assertStableContainerProcess(before, after) {
459
+ const stableFields = ["containerId", "imageId", "pid", "restartCount", "startedAt", "health", "processStartTime", "processInode"]
460
+ if (before.running !== true || after.running !== true || stableFields.some((field) => before[field] !== after[field])) throw new Error("production container PID1 changed during attestation")
461
+ }
462
+
463
+ function productionProcessBindingDigest(value) {
464
+ const generation = {
465
+ containerId: text(value.containerId, "process binding container id", SHA256),
466
+ imageId: text(value.imageId, "process binding image id", IMAGE_ID),
467
+ pid: value.pid,
468
+ restartCount: value.restartCount,
469
+ startedAt: text(value.startedAt, "process binding started at"),
470
+ processStartTime: text(value.processStartTime, "process binding start time", /^[0-9]+$/u),
471
+ processInode: text(value.processInode, "process binding inode", /^[0-9]+:[0-9]+$/u),
472
+ }
473
+ if (!Number.isSafeInteger(generation.pid) || generation.pid <= 0 || !Number.isSafeInteger(generation.restartCount) || generation.restartCount < 0) {
474
+ throw new Error("production process binding generation is invalid")
475
+ }
476
+ return createHash("sha256").update(JSON.stringify(generation)).digest("hex")
477
+ }
478
+
479
+ async function containerSnapshot(expectedImage) {
480
+ text(expectedImage, "expected image id", IMAGE_ID)
481
+ const template = '{"containerId":{{json .Id}},"imageId":{{json .Image}},"running":{{json .State.Running}},"pid":{{json .State.Pid}},"startedAt":{{json .State.StartedAt}},"health":{{json .State.Health.Status}},"user":{{json .Config.User}},"readOnlyRoot":{{json .HostConfig.ReadonlyRootfs}},"mounts":{{json .Mounts}},"ports":{{json .NetworkSettings.Ports}},"networkMode":{{json .HostConfig.NetworkMode}},"restartPolicy":{{json .HostConfig.RestartPolicy.Name}},"restartCount":{{json .RestartCount}},"privileged":{{json .HostConfig.Privileged}},"capAdd":{{json .HostConfig.CapAdd}},"capDrop":{{json .HostConfig.CapDrop}},"securityOpt":{{json .HostConfig.SecurityOpt}}}'
482
+ const result = spawnSync(DOCKER, ["inspect", "--format", template, activeContainerId], {
483
+ cwd: "/", encoding: "utf8", timeout: 20_000, maxBuffer: 1024 * 1024,
484
+ env: { PATH: "/usr/local/sbin:/usr/sbin:/usr/bin:/sbin:/bin" },
485
+ stdio: ["ignore", "pipe", "ignore"],
486
+ })
487
+ if (result.error || result.status !== 0) throw new Error("bounded production container inspection failed")
488
+ const value = object(JSON.parse(result.stdout), "production container inspection")
489
+ if (value.containerId !== activeContainerId || value.imageId !== expectedImage
490
+ || !Number.isSafeInteger(value.pid) || value.pid <= 0 || value.pid > 4_194_304
491
+ || !Number.isSafeInteger(value.restartCount) || value.restartCount < 0
492
+ || typeof value.startedAt !== "string" || !/^[0-9]{4}-[0-9]{2}-[0-9]{2}T[^\s]{1,64}Z$/u.test(value.startedAt)) {
493
+ throw new Error("production container identity is invalid")
494
+ }
495
+ const ports = object(value.ports ?? {}, "published ports")
496
+ const publishedPortCount = Object.values(ports).reduce((count, bindings) => count + (Array.isArray(bindings) ? bindings.length : 0), 0)
497
+ const mounts = Array.isArray(value.mounts) ? value.mounts.map((raw) => {
498
+ const mount = object(raw, "container mount")
499
+ return { destination: mount.Destination, source: mount.Source, mode: mount.Mode, propagation: mount.Propagation, rw: mount.RW, type: mount.Type }
500
+ }).sort((left, right) => String(left.destination).localeCompare(String(right.destination))) : []
501
+ const expectedMounts = [
502
+ { destination: "/home/ouro/.ouro-cli", source: PRODUCTION_RUNTIME_SOURCE, mode: "rw", propagation: "rprivate", rw: true, type: "bind" },
503
+ { destination: "/home/ouro/AgentBundles/sanctuary.ouro", source: PRODUCTION_BUNDLE_SOURCE, mode: "rw", propagation: "rprivate", rw: true, type: "bind" },
504
+ { destination: "/run/ouro-events", source: PRODUCTION_EVENT_SPOOL_SOURCE, mode: "ro", propagation: "rprivate", rw: false, type: "bind" },
505
+ ]
506
+ const mountsExact = mounts.length === expectedMounts.length && expectedMounts.every((expected) => mounts.some((mount) => mount.destination === expected.destination && mount.source === expected.source && mount.mode === expected.mode && mount.propagation === expected.propagation && mount.rw === expected.rw && mount.type === expected.type))
507
+ const securityExact = value.privileged === false && (value.capAdd === null || (Array.isArray(value.capAdd) && value.capAdd.length === 0))
508
+ && JSON.stringify(value.capDrop) === JSON.stringify(["ALL"])
509
+ && JSON.stringify(value.securityOpt) === JSON.stringify(["no-new-privileges"])
510
+ const running = value.running === true
511
+ if (!running) throw new Error("production container is not running")
512
+ const configuredUser = text(value.user, "container user")
513
+ if (configuredUser !== "10001:10001") throw new Error("production container configured identity is invalid")
514
+ const processBefore = liveContainerProcessIdentity(value.pid)
515
+ const reboundResult = spawnSync(DOCKER, ["inspect", "--format", template, activeContainerId], {
516
+ cwd: "/", encoding: "utf8", timeout: 20_000, maxBuffer: 1024 * 1024,
517
+ env: { PATH: "/usr/local/sbin:/usr/sbin:/usr/bin:/sbin:/bin" }, stdio: ["ignore", "pipe", "ignore"],
518
+ })
519
+ if (reboundResult.error || reboundResult.status !== 0) throw new Error("bounded production container rebound inspection failed")
520
+ const rebound = object(JSON.parse(reboundResult.stdout ?? ""), "production container rebound inspection")
521
+ const processAfter = liveContainerProcessIdentity(rebound.pid)
522
+ assertStableContainerProcess({ ...value, ...processBefore }, { ...rebound, ...processAfter })
523
+ const liveProcessUser = processAfter.user
524
+ const processBindingDigest = productionProcessBindingDigest({ ...rebound, ...processAfter })
525
+ const healthy = value.health === "healthy"
526
+ const vault = vaultStatus(running, healthy)
527
+ const milestones = recoveryMilestones(running, healthy)
528
+ return {
529
+ schemaVersion: 1,
530
+ containerId: text(value.containerId, "container id", /^[0-9a-f]{64}$/u),
531
+ imageId: expectedImage,
532
+ running,
533
+ health: text(value.health, "container health", /^(?:healthy|starting|unhealthy|missing)$/u),
534
+ user: configuredUser,
535
+ liveProcessUser,
536
+ processBindingDigest,
537
+ readOnlyRoot: value.readOnlyRoot === true,
538
+ mountCount: mounts.length,
539
+ mountsDigest: createHash("sha256").update(JSON.stringify(mounts)).digest("hex"),
540
+ mountsExact,
541
+ publishedPortCount,
542
+ networkMode: text(value.networkMode, "container network mode"),
543
+ securityExact,
544
+ writableKeyExposure: mounts.some((mount) => String(mount.destination).endsWith("docker.sock") || mount.destination === KEY_ROOT),
545
+ restartPolicy: text(value.restartPolicy, "container restart policy"),
546
+ restartCount: value.restartCount,
547
+ autostartExact: autostartFileExact() && await queryGraphqlAutostart(),
548
+ updaterDisabled: updaterDisabled(expectedImage),
549
+ ...vault,
550
+ recoveryMilestones: milestones,
551
+ }
552
+ }
553
+
554
+ function inspectRebootOwner(containerId = PRODUCTION_CONTAINER) {
555
+ const template = '{"containerId":{{json .Id}},"name":{{json .Name}},"imageId":{{json .Image}},"running":{{json .State.Running}},"pid":{{json .State.Pid}},"startedAt":{{json .State.StartedAt}},"health":{{json .State.Health.Status}},"restartCount":{{json .RestartCount}}}'
556
+ const result = spawnSync(DOCKER, ["inspect", "--format", template, containerId], {
557
+ cwd: "/", encoding: "utf8", timeout: 20_000, maxBuffer: 64 * 1024,
558
+ env: { PATH: "/usr/local/sbin:/usr/sbin:/usr/bin:/sbin:/bin" }, stdio: ["ignore", "pipe", "ignore"],
559
+ })
560
+ if (result.error || result.status !== 0) throw new Error("bounded reboot owner inspection failed")
561
+ return object(JSON.parse(result.stdout ?? ""), "reboot owner inspection")
562
+ }
563
+
564
+ function runningRebootOwnerGeneration() {
565
+ const before = inspectRebootOwner()
566
+ if (before.name !== `/${PRODUCTION_CONTAINER}` || before.imageId !== expectedImageId || before.running !== true || before.health !== "healthy"
567
+ || !Number.isSafeInteger(before.pid) || before.pid <= 0 || !Number.isSafeInteger(before.restartCount) || before.restartCount < 0) {
568
+ throw new Error("reboot owner generation is invalid")
569
+ }
570
+ const processBefore = liveContainerProcessIdentity(before.pid)
571
+ const after = inspectRebootOwner(before.containerId)
572
+ const processAfter = liveContainerProcessIdentity(after.pid)
573
+ assertStableContainerProcess({ ...before, ...processBefore }, { ...after, ...processAfter })
574
+ return { ...after, ...processAfter, processBindingDigest: productionProcessBindingDigest({ ...after, ...processAfter }) }
575
+ }
576
+
577
+ function stopExactRebootOwner(expectedBinding) {
578
+ text(expectedBinding, "reboot process binding", SHA256)
579
+ const generation = runningRebootOwnerGeneration()
580
+ if (generation.processBindingDigest !== expectedBinding) throw new Error("production process generation changed before exact stop")
581
+ const result = spawnSync(DOCKER, ["stop", "--time", "30", generation.containerId], {
582
+ cwd: "/", encoding: "utf8", timeout: 45_000, maxBuffer: 64 * 1024,
583
+ env: { PATH: "/usr/local/sbin:/usr/sbin:/usr/bin:/sbin:/bin" }, stdio: ["ignore", "ignore", "ignore"],
584
+ })
585
+ if (result.error || result.status !== 0) throw new Error("exact production owner stop failed")
586
+ const proof = { containerId: generation.containerId, imageId: generation.imageId, restartCount: generation.restartCount, startedAt: generation.startedAt, processBindingDigest: expectedBinding }
587
+ verifyStoppedRebootOwner(proof)
588
+ return proof
589
+ }
590
+
591
+ function verifyStoppedRebootOwner(proof) {
592
+ const value = inspectRebootOwner(text(proof.containerId, "stopped owner container id", SHA256))
593
+ if (value.containerId !== proof.containerId || value.name !== `/${PRODUCTION_CONTAINER}` || value.imageId !== proof.imageId
594
+ || value.restartCount !== proof.restartCount || value.startedAt !== proof.startedAt || value.running !== false || value.pid !== 0) {
595
+ throw new Error("exact stopped production owner generation changed")
596
+ }
597
+ }
598
+
599
+ function denialTargetSnapshot(dependencies = { run: spawnSync }) {
600
+ const template = '{"containerId":{{json .Id}},"imageId":{{json .Image}},"running":{{json .State.Running}},"status":{{json .State.Status}},"restartCount":{{json .RestartCount}},"startedAt":{{json .State.StartedAt}}}'
601
+ const result = dependencies.run(DOCKER, ["inspect", "--format", template, "calibre-web"], {
602
+ cwd: "/", encoding: "utf8", timeout: 10_000, maxBuffer: 64 * 1024,
603
+ env: { PATH: "/usr/local/sbin:/usr/sbin:/usr/bin:/sbin:/bin" }, stdio: ["ignore", "pipe", "ignore"],
604
+ })
605
+ if (result.error || result.status !== 0) throw new Error("bounded denial target inspection failed")
606
+ const value = object(JSON.parse(result.stdout ?? ""), "denial target inspection")
607
+ exactKeys(value, ["containerId", "imageId", "running", "status", "restartCount", "startedAt"], "denial target inspection")
608
+ const containerId = text(value.containerId, "denial target container id", SHA256)
609
+ const imageId = text(value.imageId, "denial target image id", IMAGE_ID)
610
+ const status = text(value.status, "denial target status", /^(?:created|running|paused|restarting|removing|exited|dead)$/u)
611
+ const startedAt = text(value.startedAt, "denial target started at", /^[0-9]{4}-[0-9]{2}-[0-9]{2}T[^\s]{1,64}Z$/u)
612
+ if (typeof value.running !== "boolean" || !Number.isSafeInteger(value.restartCount) || value.restartCount < 0) {
613
+ throw new Error("denial target lifecycle is invalid")
614
+ }
615
+ return {
616
+ containerIdDigest: createHash("sha256").update(containerId).digest("hex"),
617
+ imageDigest: createHash("sha256").update(imageId).digest("hex"),
618
+ running: value.running,
619
+ status,
620
+ restartCount: value.restartCount,
621
+ startedAtDigest: createHash("sha256").update(startedAt).digest("hex"),
622
+ }
623
+ }
624
+
625
+ function containerOwnerSnapshot(expectedImage) {
626
+ text(expectedImage, "expected image id", IMAGE_ID)
627
+ const template = '{"containerId":{{json .Id}},"imageId":{{json .Image}}}'
628
+ const result = spawnSync(DOCKER, ["inspect", "--format", template, activeContainerId], {
629
+ cwd: "/", encoding: "utf8", timeout: 10_000, maxBuffer: 64 * 1024,
630
+ env: { PATH: "/usr/local/sbin:/usr/sbin:/usr/bin:/sbin:/bin" }, stdio: ["ignore", "pipe", "ignore"],
631
+ })
632
+ if (result.error || result.status !== 0) throw new Error("bounded production owner inspection failed")
633
+ const value = object(JSON.parse(result.stdout), "production owner inspection")
634
+ if (value.containerId !== activeContainerId || value.imageId !== expectedImage) throw new Error("production owner identity is invalid")
635
+ return {
636
+ imageId: expectedImage,
637
+ containerId: text(value.containerId, "container id", /^[0-9a-f]{64}$/u),
638
+ }
639
+ }
640
+
641
+ function containerRestartSnapshot(expectedImage) {
642
+ text(expectedImage, "expected image id", IMAGE_ID)
643
+ const template = '{"containerId":{{json .Id}},"imageId":{{json .Image}},"running":{{json .State.Running}},"health":{{json .State.Health.Status}},"restartCount":{{json .RestartCount}}}'
644
+ const result = spawnSync(DOCKER, ["inspect", "--format", template, activeContainerId], {
645
+ cwd: "/", encoding: "utf8", timeout: 10_000, maxBuffer: 64 * 1024,
646
+ env: { PATH: "/usr/local/sbin:/usr/sbin:/usr/bin:/sbin:/bin" }, stdio: ["ignore", "pipe", "ignore"],
647
+ })
648
+ if (result.error || result.status !== 0) throw new Error("bounded production restart inspection failed")
649
+ const value = object(JSON.parse(result.stdout), "production restart inspection")
650
+ if (value.containerId !== activeContainerId || value.imageId !== expectedImage || !Number.isSafeInteger(value.restartCount) || value.restartCount < 0) throw new Error("production restart identity is invalid")
651
+ return {
652
+ imageId: expectedImage, containerId: text(value.containerId, "container id", SHA256),
653
+ running: value.running === true, health: text(value.health, "container health", /^(?:healthy|starting|unhealthy|missing)$/u),
654
+ restartCount: value.restartCount,
655
+ }
656
+ }
657
+
658
+ function recoveryPath(id) { return `${RECOVERY_ROOT}/${id}.json` }
659
+ function createIntentPath(name) { return `${RECOVERY_ROOT}/create-${createHash("sha256").update(name).digest("hex")}.json` }
660
+ function rejectionReceiptPath(id) { return `${RECOVERY_ROOT}/${id}.rejected.json` }
661
+
662
+ function persistPrivateJson(target, value) {
663
+ mkdirSync(RECOVERY_ROOT, { recursive: true, mode: 0o700 })
664
+ chmodSync(RECOVERY_ROOT, 0o700)
665
+ chownSync(RECOVERY_ROOT, 0, 0)
666
+ const temporary = `${target}.tmp-${process.pid}`
667
+ const fd = openSync(temporary, constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL, 0o600)
668
+ try { chownSync(temporary, 0, 0); writeFileSync(fd, `${JSON.stringify(value)}\n`); fsyncSync(fd) } finally { closeSync(fd) }
669
+ renameSync(temporary, target)
670
+ const directory = openSync(RECOVERY_ROOT, constants.O_RDONLY)
671
+ try { fsyncSync(directory) } finally { closeSync(directory) }
672
+ }
673
+
674
+ function optionalPrivateJson(target) {
675
+ try { return object(readPrivateJson(target), "private acceptance record") }
676
+ catch (error) { if (error?.code === "ENOENT") return null; throw error }
677
+ }
678
+
679
+ function persistCreateIntent(name, requested) {
680
+ mkdirSync(RECOVERY_ROOT, { recursive: true, mode: 0o700 })
681
+ chmodSync(RECOVERY_ROOT, 0o700)
682
+ chownSync(RECOVERY_ROOT, 0, 0)
683
+ const target = createIntentPath(name)
684
+ const fd = openSync(target, constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL, 0o600)
685
+ try {
686
+ chownSync(target, 0, 0)
687
+ writeFileSync(fd, `${JSON.stringify({ schemaVersion: 1, name, permissions: requested })}\n`)
688
+ fsyncSync(fd)
689
+ } finally { closeSync(fd) }
690
+ const directory = openSync(RECOVERY_ROOT, constants.O_RDONLY)
691
+ try { fsyncSync(directory) } finally { closeSync(directory) }
692
+ }
693
+
694
+ function optionalCreateIntent(name) {
695
+ const value = optionalPrivateJson(createIntentPath(name))
696
+ if (value === null) return null
697
+ exactKeys(value, ["schemaVersion", "name", "permissions"], "Unraid key creation intent")
698
+ if (value.schemaVersion !== 1 || value.name !== name) throw new Error("Unraid key creation intent is invalid")
699
+ return { name, permissions: permissions(value.permissions) }
700
+ }
701
+
702
+ function removePrivateFile(target) {
703
+ try { unlinkSync(target) } catch (error) { if (error?.code !== "ENOENT") throw error }
704
+ const directory = openSync(RECOVERY_ROOT, constants.O_RDONLY)
705
+ try { fsyncSync(directory) } finally { closeSync(directory) }
706
+ }
707
+
708
+ function removeCreateIntent(name) { removePrivateFile(createIntentPath(name)) }
709
+
710
+ function optionalRejectionReceipt(id) {
711
+ const value = optionalPrivateJson(rejectionReceiptPath(id))
712
+ if (value === null) return null
713
+ exactKeys(value, ["schemaVersion", "id", "status", "proofDigest"], "revoked key rejection receipt")
714
+ if (value.schemaVersion !== 1 || value.id !== id || (value.status !== 401 && value.status !== 403)
715
+ || value.proofDigest !== createHash("sha256").update(`revoked-key-rejected\0${id}\0${value.status}`).digest("hex")) {
716
+ throw new Error("revoked key rejection receipt is invalid")
717
+ }
718
+ return { valid: false, status: value.status, id }
719
+ }
720
+
721
+ function persistRejectionReceipt(id, status) {
722
+ persistPrivateJson(rejectionReceiptPath(id), {
723
+ schemaVersion: 1, id, status,
724
+ proofDigest: createHash("sha256").update(`revoked-key-rejected\0${id}\0${status}`).digest("hex"),
725
+ })
726
+ }
727
+
728
+ async function probeRevokedUnraidKey(id, dependencies = {
729
+ readReceipt: optionalRejectionReceipt,
730
+ readRecovery: optionalRecoveryRecord,
731
+ persistReceipt: persistRejectionReceipt,
732
+ removeRecovery,
733
+ fetchImpl: fetch,
734
+ }) {
735
+ text(id, "key id", KEY_ID)
736
+ const receipt = dependencies.readReceipt(id)
737
+ if (receipt !== null) {
738
+ const staleRecovery = dependencies.readRecovery(id)
739
+ if (staleRecovery !== null) {
740
+ if (staleRecovery.id !== id) throw new Error("recovery key id is invalid")
741
+ dependencies.removeRecovery(id)
742
+ }
743
+ return receipt
744
+ }
745
+ const record = dependencies.readRecovery(id)
746
+ if (record === null || record.id !== id) throw new Error("recovery key id is invalid")
747
+ const response = await dependencies.fetchImpl(GRAPHQL_ENDPOINT, {
748
+ method: "POST", headers: { "content-type": "application/json", "x-api-key": record.key },
749
+ body: JSON.stringify({ query: "query AcceptanceAuthProbe { info { os { hostname } } }", variables: {} }),
750
+ signal: AbortSignal.timeout(10_000),
751
+ })
752
+ if (response.status !== 401 && response.status !== 403) throw new Error("revoked key still authenticates")
753
+ dependencies.persistReceipt(id, response.status)
754
+ dependencies.removeRecovery(id)
755
+ return { valid: false, status: response.status, id }
756
+ }
757
+
758
+ function optionalRecoveryRecord(id) {
759
+ try { return normalizeRecord(readPrivateJson(recoveryPath(id))) }
760
+ catch (error) { if (error?.code === "ENOENT") return null; throw error }
761
+ }
762
+
763
+ function persistRecovery(record) {
764
+ persistPrivateJson(recoveryPath(record.id), record)
765
+ }
766
+
767
+ function removeRecovery(id) {
768
+ unlinkSync(recoveryPath(id))
769
+ const directory = openSync(RECOVERY_ROOT, constants.O_RDONLY)
770
+ try { fsyncSync(directory) } finally { closeSync(directory) }
771
+ }
772
+
773
+ function acknowledgeStoredUnraidKey(id, dependencies = {
774
+ readRecovery: optionalRecoveryRecord,
775
+ inventoryRecords,
776
+ removeRecovery,
777
+ removeIntent: removeCreateIntent,
778
+ }) {
779
+ text(id, "key id", KEY_ID)
780
+ const matches = dependencies.inventoryRecords().filter((record) => record.id === id)
781
+ if (matches.length !== 1 || !KEY_NAME.test(matches[0].name)) throw new Error("Unraid key final storage acknowledgement is invalid")
782
+ const expected = matches[0].name.startsWith("Butler RO") ? RO_PERMISSIONS : [...RO_PERMISSIONS, "DOCKER:UPDATE_ANY"].sort()
783
+ if (matches[0].roles.length !== 0 || JSON.stringify(flattened(matches[0])) !== JSON.stringify(expected)) {
784
+ throw new Error("Unraid key final storage acknowledgement is invalid")
785
+ }
786
+ const recovery = dependencies.readRecovery(id)
787
+ if (recovery === null) {
788
+ dependencies.removeIntent?.(matches[0].name)
789
+ return { acknowledged: true, id }
790
+ }
791
+ const provisional = [...expected, "API_KEY:UPDATE_ANY"].sort()
792
+ if (recovery.id !== id || recovery.name !== matches[0].name || recovery.key !== matches[0].key
793
+ || JSON.stringify(recovery.roles) !== JSON.stringify(["GUEST"])
794
+ || JSON.stringify(flattened(recovery)) !== JSON.stringify(provisional)) {
795
+ throw new Error("Unraid key recovery ownership is invalid")
796
+ }
797
+ dependencies.removeRecovery(id)
798
+ dependencies.removeIntent?.(matches[0].name)
799
+ return { acknowledged: true, id }
800
+ }
801
+
802
+ function permissions(value) {
803
+ if (!Array.isArray(value) || value.length === 0) throw new Error("permissions are invalid")
804
+ const result = value.map((item) => text(item, "permission", PERMISSION)).sort()
805
+ if (new Set(result).size !== result.length) throw new Error("permissions are ambiguous")
806
+ return result
807
+ }
808
+
809
+ function canonicalHealthProbeInput(input) {
810
+ const value = object(input, "health probe input")
811
+ exactKeys(value, ["label", "scenarioHandleDigest", "ownerImageDigest", "ownerContainerDigest"], "health probe input")
812
+ if (!HEALTH_PROBE_LABELS.has(value.label)) throw new Error("health probe label is invalid")
813
+ for (const key of ["scenarioHandleDigest", "ownerImageDigest", "ownerContainerDigest"]) text(value[key], `health probe ${key}`, SHA256)
814
+ return value
815
+ }
816
+
817
+ function canonicalHealthProbeRequest(input) {
818
+ const value = object(input, "health probe request")
819
+ exactKeys(value, ["label", "scenarioHandleDigest"], "health probe request")
820
+ if (!HEALTH_PROBE_LABELS.has(value.label)) throw new Error("health probe label is invalid")
821
+ text(value.scenarioHandleDigest, "health probe scenario digest", SHA256)
822
+ return value
823
+ }
824
+
825
+ function healthProbeDockerArgs(mode, input) {
826
+ if (mode !== "run" && mode !== "stop" && mode !== "recover") throw new Error("health probe mode is invalid")
827
+ const value = canonicalHealthProbeInput(input)
828
+ return [
829
+ "exec", activeContainerId, "/usr/local/bin/node", HEALTH_PROBE_ENTRY, mode,
830
+ "--label", value.label,
831
+ "--scenario", value.scenarioHandleDigest,
832
+ "--owner-image", value.ownerImageDigest,
833
+ "--owner-container", value.ownerContainerDigest,
834
+ ]
835
+ }
836
+
837
+ function healthProbeFinalizeDockerArgs(before, after) {
838
+ const initial = canonicalHealthProbeInput(before)
839
+ const observed = canonicalHealthProbeInput(after)
840
+ return [
841
+ "exec", activeContainerId, "/usr/local/bin/node", HEALTH_PROBE_ENTRY, "finalize",
842
+ "--label", initial.label,
843
+ "--scenario", initial.scenarioHandleDigest,
844
+ "--owner-image", initial.ownerImageDigest,
845
+ "--owner-container", initial.ownerContainerDigest,
846
+ "--owner-image-after", observed.ownerImageDigest,
847
+ "--owner-container-after", observed.ownerContainerDigest,
848
+ ]
849
+ }
850
+
851
+ function requireStableHealthProbeOwner(before, after) {
852
+ const initial = canonicalHealthProbeInput(before)
853
+ const observed = canonicalHealthProbeInput(after)
854
+ if (initial.label !== observed.label || initial.scenarioHandleDigest !== observed.scenarioHandleDigest) throw new Error("health probe scenario binding drifted")
855
+ if (initial.ownerImageDigest !== observed.ownerImageDigest || initial.ownerContainerDigest !== observed.ownerContainerDigest) throw new Error("health probe owner binding drifted")
856
+ }
857
+
858
+ function healthProbeReceiptPath(scenarioHandleDigest) {
859
+ return `${PRODUCTION_BUNDLE_SOURCE}/state/acceptance/health-probe-receipts/${scenarioHandleDigest}.json`
860
+ }
861
+
862
+ function healthProbeWorkspacePath(scenarioHandleDigest) {
863
+ return `${PRODUCTION_BUNDLE_SOURCE}/state/acceptance/health-probe-workspaces/${scenarioHandleDigest}`
864
+ }
865
+
866
+ function healthProbePendingPath(scenarioHandleDigest) {
867
+ return `${PRODUCTION_BUNDLE_SOURCE}/state/acceptance/health-probe-pending/${scenarioHandleDigest}.json`
868
+ }
869
+
870
+ function healthProbeProcessPath(scenarioHandleDigest) {
871
+ return `${PRODUCTION_BUNDLE_SOURCE}/state/acceptance/health-probe-processes/${scenarioHandleDigest}.json`
872
+ }
873
+
874
+ function healthOwnerMutationActive() {
875
+ if (activeHealthProbes.size > 0) return true
876
+ for (const root of ["health-probe-workspaces", "health-probe-pending", "health-probe-processes"]
877
+ .map((name) => `${PRODUCTION_BUNDLE_SOURCE}/state/acceptance/${name}`)) {
878
+ let directory
879
+ try { directory = opendirSync(root) } catch (error) { if (error.code === "ENOENT") continue; throw error }
880
+ try { if (directory.readSync() !== null) return true } finally { directory.closeSync() }
881
+ }
882
+ return false
883
+ }
884
+
885
+ function healthProbeArtifactDisposition({ receipt, workspace, pending }) {
886
+ if (receipt) return "complete"
887
+ if (workspace || pending) return "recovery_required"
888
+ return "absent"
889
+ }
890
+
891
+ function healthProbeOperationBudgets() {
892
+ return { startMaxMs: 115_000, completeStatusMaxMs: 130_000, recoveryMaxMs: 85_000, composedCaptureMaxMs: 165_000 }
893
+ }
894
+
895
+ const UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/u
896
+ const SCHEDULER_COMMAND = "/usr/local/bin/node /opt/ouro/dist/heart/daemon/ouro-entry.js poke sanctuary --habit sanctuary-health --trigger cron"
897
+ const SCHEDULER_CRONTAB = "/home/ouro/.ouro-cli/scheduler/sanctuary.crontab"
898
+
899
+ function canonicalIso(value) {
900
+ if (typeof value !== "string") return false
901
+ const date = new Date(value)
902
+ return Number.isFinite(date.getTime()) && date.toISOString() === value
903
+ }
904
+
905
+ function safeMacEqual(observed, expected) {
906
+ return typeof observed === "string" && SHA256.test(observed) && timingSafeEqual(Buffer.from(observed, "hex"), Buffer.from(expected, "hex"))
907
+ }
908
+
909
+ function requireExactSchedulerReceipt(scheduler, request, identityKey, phases) {
910
+ exactKeys(scheduler, ["after", "before", "deliveryDelta", "label", "nonReplay", "occurrenceId", "privateTurnCount", "providerInvocationCount", "receiptMac", "recordedAt", "runnerId", "scenarioHandleDigest", "schedulerOrigin", "schemaVersion", "supervisor", "sweep", "sweepDelta", "trigger"], "scheduler receipt")
911
+ const before = object(scheduler.before, "scheduler before cursor")
912
+ const after = object(scheduler.after, "scheduler after cursor")
913
+ const sweep = object(scheduler.sweep, "scheduler sweep")
914
+ const supervisor = object(scheduler.supervisor, "scheduler supervisor")
915
+ const origin = object(scheduler.schedulerOrigin, "scheduler origin")
916
+ exactKeys(before, ["deliveryCount", "sweepCount"], "scheduler before cursor")
917
+ exactKeys(after, ["deliveryCount", "sweepCount"], "scheduler after cursor")
918
+ exactKeys(sweep, ["deliveryId", "digestDue", "opened", "recordDigest", "recovered"], "scheduler sweep")
919
+ exactKeys(supervisor, ["args", "binaryPath", "childCount", "childPid", "crontabPath", "daemonPid", "healthy", "manifest", "namespace", "renderedCrontab", "schemaVersion"], "scheduler supervisor")
920
+ exactKeys(origin, ["invocationPid", "invocationStartTime", "occurrenceId", "parentPid", "parentStartTime", "proofMac", "scenarioHandleDigest", "schedulerRunId", "slot"], "scheduler origin")
921
+ const manifest = Array.isArray(supervisor.manifest) ? supervisor.manifest : []
922
+ if (manifest.length !== 1) throw new Error("scheduler manifest is invalid")
923
+ const job = object(manifest[0], "scheduler manifest job")
924
+ exactKeys(job, ["agent", "command", "id", "lastRun", "schedule", "taskId", "taskPath"], "scheduler manifest job")
925
+ const unsignedScheduler = Object.fromEntries(Object.entries(scheduler).filter(([key]) => key !== "receiptMac"))
926
+ const expectedReceiptMac = createHmac("sha256", identityKey).update(`sanctuary-scheduler-liveness-receipt-v2\0${JSON.stringify(unsignedScheduler)}`).digest("hex")
927
+ const proofCommand = {
928
+ kind: "habit.scheduler-fire", agent: "sanctuary", habitName: "sanctuary-health", trigger: "cron",
929
+ slot: origin.slot, occurrenceId: origin.occurrenceId, schedulerRunId: origin.schedulerRunId,
930
+ invocationPid: origin.invocationPid, parentPid: origin.parentPid, parentStartTime: origin.parentStartTime,
931
+ invocationStartTime: origin.invocationStartTime, scenarioHandleDigest: origin.scenarioHandleDigest,
932
+ }
933
+ const expectedProofMac = createHmac("sha256", identityKey).update(JSON.stringify(proofCommand)).digest("hex")
934
+ if (scheduler.schemaVersion !== "sanctuary-scheduler-liveness-receipt-v1" || scheduler.label !== request.label
935
+ || scheduler.scenarioHandleDigest !== request.scenarioHandleDigest || scheduler.trigger !== "cron"
936
+ || !UUID.test(scheduler.runnerId) || !canonicalIso(scheduler.recordedAt)
937
+ || !Number.isSafeInteger(before.sweepCount) || before.sweepCount < 0 || !Number.isSafeInteger(before.deliveryCount) || before.deliveryCount < 0
938
+ || after.sweepCount !== before.sweepCount + 1 || after.deliveryCount !== before.deliveryCount
939
+ || scheduler.sweepDelta !== 1 || scheduler.deliveryDelta !== 0 || scheduler.providerInvocationCount !== 0 || scheduler.privateTurnCount !== 0 || scheduler.nonReplay !== true
940
+ || !SHA256.test(sweep.recordDigest) || sweep.opened !== 0 || sweep.recovered !== 0 || sweep.digestDue !== false || sweep.deliveryId !== null
941
+ || phases.length !== 1 || sweep.recordDigest !== phases[0]?.sweepReceiptDigest
942
+ || supervisor.schemaVersion !== "supercronic-supervisor-snapshot-v1" || supervisor.daemonPid !== 1 || supervisor.childCount !== 1
943
+ || !Number.isSafeInteger(supervisor.childPid) || supervisor.childPid <= 1 || supervisor.healthy !== true
944
+ || supervisor.binaryPath !== "/usr/local/bin/supercronic" || supervisor.crontabPath !== SCHEDULER_CRONTAB
945
+ || JSON.stringify(supervisor.args) !== JSON.stringify(["-split-logs", "-inotify", SCHEDULER_CRONTAB]) || supervisor.namespace !== "habit:sanctuary"
946
+ || job.id !== "sanctuary:sanctuary-health" || job.agent !== "sanctuary" || job.taskId !== "sanctuary-health" || job.schedule !== "*/15 * * * *"
947
+ || (job.lastRun !== null && !canonicalIso(job.lastRun)) || job.command !== SCHEDULER_COMMAND || job.taskPath !== "/home/ouro/AgentBundles/sanctuary.ouro/habits/sanctuary-health.md"
948
+ || typeof supervisor.renderedCrontab !== "string" || !supervisor.renderedCrontab.includes(`# ouro:habit:sanctuary:sanctuary:sanctuary-health\n*/15 * * * * ${SCHEDULER_COMMAND}\n`)
949
+ || typeof origin.slot !== "string" || !canonicalIso(origin.slot) || scheduler.occurrenceId !== `cron:${origin.slot}` || origin.occurrenceId !== scheduler.occurrenceId
950
+ || !UUID.test(origin.schedulerRunId) || origin.scenarioHandleDigest !== request.scenarioHandleDigest
951
+ || !Number.isSafeInteger(origin.invocationPid) || origin.invocationPid <= 1 || !Number.isSafeInteger(origin.parentPid) || origin.parentPid !== supervisor.childPid
952
+ || typeof origin.parentStartTime !== "string" || !/^[0-9]+$/u.test(origin.parentStartTime)
953
+ || typeof origin.invocationStartTime !== "string" || !/^[0-9]+$/u.test(origin.invocationStartTime)
954
+ || !safeMacEqual(origin.proofMac, expectedProofMac) || !safeMacEqual(scheduler.receiptMac, expectedReceiptMac)) {
955
+ throw new Error("scheduler receipt semantics are invalid")
956
+ }
957
+ }
958
+
959
+ function requireHealthProbeCompleteAttestationUnchecked(receipt, snapshot, input, readSchedulerIdentityKey = () => readFileSync(`${PRODUCTION_BUNDLE_SOURCE}/state/senses/telegram/identity.key`, "utf8").trim()) {
960
+ const request = canonicalHealthProbeRequest(input)
961
+ const value = object(receipt, "health probe receipt")
962
+ exactKeys(value, HEALTH_PROBE_RECEIPT_KEYS, "health probe receipt")
963
+ const observed = object(snapshot, "health probe complete owner")
964
+ const phases = Array.isArray(value.phases) ? value.phases.map((phase) => object(phase, "health probe phase")) : []
965
+ const digestFields = ["ownerImageDigestBefore", "ownerImageDigestAfter", "ownerContainerDigestBefore", "ownerContainerDigestAfter", "beforeStateDigest", "restoredStateDigest", "cronFingerprintBefore", "cronFingerprintAfter", "fixtureSequenceDigest"]
966
+ const booleanFields = ["cronRegisteredBefore", "cronRegisteredAfter", "cronDegradedBefore", "cronDegradedAfter", "workspaceAbsent", "socketAbsent", "snapshotAbsent", "realCheckEquivalent", "productionRestored"]
967
+ for (const phase of phases) exactKeys(phase, ["deliveryKind", "deliveryReceiptDigest", "digestDue", "fixtureStatus", "name", "opened", "ordinal", "recovered", "sweepReceiptDigest", "trigger"], "health probe phase")
968
+ const scheduler = request.label === "unit-16f-cron-fingerprint" ? object(value.schedulerReceipt, "scheduler receipt") : null
969
+ if (scheduler) requireExactSchedulerReceipt(scheduler, request, readSchedulerIdentityKey(), phases)
970
+ const unit16fPhase = phases.length === 1 && phases[0].ordinal === 1 && phases[0].name === "cron-unchanged" && phases[0].trigger === "cron"
971
+ && phases[0].fixtureStatus === null && phases[0].opened === 0 && phases[0].recovered === 0 && phases[0].digestDue === false
972
+ && phases[0].deliveryKind === null && phases[0].deliveryReceiptDigest === null && SHA256.test(phases[0].sweepReceiptDigest)
973
+ const unit16fEvidence = request.label !== "unit-16f-cron-fingerprint" || (
974
+ digestFields.every((field) => typeof value[field] === "string" && SHA256.test(value[field]))
975
+ && booleanFields.every((field) => typeof value[field] === "boolean") && canonicalIso(value.effectiveNow)
976
+ && value.clockMode === "ambient" && value.providerInvocationCount === 0 && value.privateTurnCount === 0 && value.deliveryCount === 0
977
+ && value.fixtureSequenceDigest === createHash("sha256").update(JSON.stringify([])).digest("hex")
978
+ && value.cronFingerprintBefore === value.cronFingerprintAfter && value.cronRegisteredBefore === true && value.cronRegisteredAfter === true
979
+ && value.cronDegradedBefore === false && value.cronDegradedAfter === false && value.workspaceAbsent === true && value.socketAbsent === true
980
+ && value.snapshotAbsent === true && value.realCheckEquivalent === true && value.productionRestored === true && unit16fPhase
981
+ )
982
+ if (value.schemaVersion !== "sanctuary-health-probe-receipt-v1" || value.label !== request.label
983
+ || value.scenarioHandleDigest !== request.scenarioHandleDigest
984
+ || !SHA256.test(value.ownerImageDigestBefore) || !SHA256.test(value.ownerContainerDigestBefore)
985
+ || !SHA256.test(value.ownerImageDigestAfter) || !SHA256.test(value.ownerContainerDigestAfter)
986
+ || value.ownerImageDigestBefore !== value.ownerImageDigestAfter || value.ownerContainerDigestBefore !== value.ownerContainerDigestAfter
987
+ || observed.imageId !== `sha256:${value.ownerImageDigestAfter}` || observed.containerId !== value.ownerContainerDigestAfter
988
+ || observed.running !== true || observed.health !== "healthy" || !unit16fEvidence
989
+ || (request.label !== "unit-16f-cron-fingerprint" && value.schedulerReceipt !== null)) {
990
+ throw new Error("health probe complete attestation is invalid")
991
+ }
992
+ }
993
+
994
+ function requireHealthProbeCompleteAttestation(receipt, snapshot, input, readSchedulerIdentityKey) {
995
+ try { requireHealthProbeCompleteAttestationUnchecked(receipt, snapshot, input, readSchedulerIdentityKey) }
996
+ catch { throw new Error("health probe complete attestation is invalid") }
997
+ }
998
+
999
+ function finalizeHealthProbeAfterAttestation(receipt, snapshot, request, finalize, readSchedulerIdentityKey) {
1000
+ requireHealthProbeCompleteAttestation(receipt, snapshot, request, readSchedulerIdentityKey)
1001
+ return finalize()
1002
+ }
1003
+
1004
+ function completeHealthProbeFromReceipt(request, snapshot, readReceipt) {
1005
+ const value = object(snapshot, "health probe complete owner")
1006
+ requireHealthProbeCompleteAttestation(readReceipt(), value, request)
1007
+ return { state: "complete", containerSnapshot: value }
1008
+ }
1009
+
1010
+ function readHealthProbeReceipt(file) {
1011
+ const fd = openSync(file, constants.O_RDONLY | constants.O_NOFOLLOW)
1012
+ try {
1013
+ const metadata = fstatSync(fd)
1014
+ if (!metadata.isFile() || metadata.uid !== 10001 || (metadata.mode & 0o777) !== 0o600 || metadata.size > MAX_REQUEST) {
1015
+ throw new Error("health probe receipt metadata is invalid")
1016
+ }
1017
+ return object(JSON.parse(readFileSync(fd, "utf8")), "health probe receipt")
1018
+ } finally { closeSync(fd) }
1019
+ }
1020
+
1021
+ function readHealthProbePendingReceipt(file) {
1022
+ const envelope = readHealthProbeReceipt(file)
1023
+ exactKeys(envelope, ["receipt", "schemaVersion"], "health probe pending envelope")
1024
+ if (envelope.schemaVersion !== "sanctuary-health-probe-pending-v1") throw new Error("health probe pending envelope is invalid")
1025
+ return object(envelope.receipt, "health probe pending receipt")
1026
+ }
1027
+
1028
+ function attestHealthProbeProcessAbsent(input, dependencies = {
1029
+ run: spawnSync,
1030
+ markerPresent: () => Boolean(statIfPresent(healthProbeProcessPath(input.scenarioHandleDigest))),
1031
+ }) {
1032
+ const value = canonicalHealthProbeInput(input)
1033
+ const stopped = dependencies.run(DOCKER, healthProbeDockerArgs("stop", value), {
1034
+ cwd: "/", encoding: "utf8", timeout: 15_000, maxBuffer: 64 * 1024,
1035
+ env: { PATH: "/usr/local/sbin:/usr/sbin:/usr/bin:/sbin:/bin" }, stdio: ["ignore", "ignore", "ignore"],
1036
+ })
1037
+ if (stopped.error || stopped.status !== 0 || dependencies.markerPresent()) throw new Error("health probe in-container process termination failed")
1038
+ }
1039
+
1040
+ async function waitForHealthProbeProcessMarker(record, timeoutMs = 5_000) {
1041
+ const marker = healthProbeProcessPath(record.input.scenarioHandleDigest)
1042
+ const deadline = Date.now() + timeoutMs
1043
+ while (Date.now() < deadline) {
1044
+ const metadata = statIfPresent(marker)
1045
+ if (metadata) {
1046
+ if (!metadata.isFile() || metadata.uid !== 10001 || (metadata.mode & 0o777) !== 0o600) throw new Error("health probe process marker metadata is invalid")
1047
+ return
1048
+ }
1049
+ if (record.state !== "running") throw new Error("health probe exited before process registration")
1050
+ await new Promise((resolve) => setTimeout(resolve, 50))
1051
+ }
1052
+ throw new Error("health probe process registration timed out")
1053
+ }
1054
+
1055
+ async function startHealthProbe(input) {
1056
+ const value = canonicalHealthProbeInput(input)
1057
+ const existing = activeHealthProbes.get(value.scenarioHandleDigest)
1058
+ const artifacts = {
1059
+ receipt: statIfPresent(healthProbeReceiptPath(value.scenarioHandleDigest)),
1060
+ workspace: statIfPresent(healthProbeWorkspacePath(value.scenarioHandleDigest)),
1061
+ pending: statIfPresent(healthProbePendingPath(value.scenarioHandleDigest)),
1062
+ }
1063
+ if (existing || healthProbeArtifactDisposition(artifacts) !== "absent" || statIfPresent(healthProbeProcessPath(value.scenarioHandleDigest))) {
1064
+ throw new Error("health probe requires inspect-before-retry")
1065
+ }
1066
+ const child = spawn(DOCKER, healthProbeDockerArgs("run", value), {
1067
+ cwd: "/", env: { PATH: "/usr/local/sbin:/usr/sbin:/usr/bin:/sbin:/bin" }, stdio: "ignore",
1068
+ })
1069
+ const record = { input: value, child, state: "running", exitCode: null }
1070
+ activeHealthProbes.set(value.scenarioHandleDigest, record)
1071
+ child.once("error", () => {
1072
+ if (record.state !== "terminating") record.state = "failed"
1073
+ record.exitCode = -1
1074
+ })
1075
+ child.once("exit", (code) => {
1076
+ record.state = record.state === "terminating" ? "terminated" : code === 0 ? "exited" : "failed"
1077
+ record.exitCode = code
1078
+ })
1079
+ child.unref()
1080
+ record.readyPromise = waitForHealthProbeProcessMarker(record)
1081
+ await record.readyPromise
1082
+ return {
1083
+ state: "started",
1084
+ operationDigest: createHash("sha256").update(JSON.stringify({ operation: "start_health_probe", ...value })).digest("hex"),
1085
+ }
1086
+ }
1087
+
1088
+ function statIfPresent(file) {
1089
+ try { return statSync(file) } catch (error) { if (error.code === "ENOENT") return null; throw error }
1090
+ }
1091
+
1092
+ async function healthProbeStatus(input, fullSnapshot = () => containerSnapshot(expectedImageId)) {
1093
+ const request = canonicalHealthProbeRequest(input)
1094
+ const receipt = statIfPresent(healthProbeReceiptPath(request.scenarioHandleDigest))
1095
+ if (receipt) {
1096
+ if (!receipt.isFile() || receipt.uid !== 10001 || (receipt.mode & 0o777) !== 0o600) throw new Error("health probe receipt metadata is invalid")
1097
+ const snapshot = object(await fullSnapshot(), "health probe complete owner")
1098
+ return completeHealthProbeFromReceipt(request, snapshot, () => readHealthProbeReceipt(healthProbeReceiptPath(request.scenarioHandleDigest)))
1099
+ }
1100
+ const record = activeHealthProbes.get(request.scenarioHandleDigest)
1101
+ if (!record) return { state: healthProbeArtifactDisposition({
1102
+ receipt: null,
1103
+ workspace: statIfPresent(healthProbeWorkspacePath(request.scenarioHandleDigest)),
1104
+ pending: statIfPresent(healthProbePendingPath(request.scenarioHandleDigest)),
1105
+ }) }
1106
+ if (record.input.label !== request.label) throw new Error("health probe scenario binding drifted")
1107
+ if (record.state === "failed") return { state: "failed" }
1108
+ if (record.state === "running") return { state: "running" }
1109
+ if (record.state === "terminating" || record.state === "terminated" || record.state === "recovering") return { state: "recovery_required" }
1110
+ const snapshot = object(await fullSnapshot(), "health probe production owner")
1111
+ const value = healthProbeCoordinates({ targetId: TARGET_HOST, ...request }, snapshot)
1112
+ requireStableHealthProbeOwner(record.input, value)
1113
+ const finalized = finalizeHealthProbeAfterAttestation(
1114
+ readHealthProbePendingReceipt(healthProbePendingPath(value.scenarioHandleDigest)), snapshot, request,
1115
+ () => spawnSync(DOCKER, healthProbeFinalizeDockerArgs(record.input, value), {
1116
+ cwd: "/", encoding: "utf8", timeout: 20_000, maxBuffer: 64 * 1024,
1117
+ env: { PATH: "/usr/local/sbin:/usr/sbin:/usr/bin:/sbin:/bin" }, stdio: ["ignore", "ignore", "ignore"],
1118
+ }),
1119
+ )
1120
+ if (finalized.error || finalized.status !== 0) throw new Error("health probe final attestation failed")
1121
+ const finalReceipt = statIfPresent(healthProbeReceiptPath(value.scenarioHandleDigest))
1122
+ if (!finalReceipt || !finalReceipt.isFile() || finalReceipt.uid !== 10001 || (finalReceipt.mode & 0o777) !== 0o600) {
1123
+ throw new Error("health probe final receipt is absent or invalid")
1124
+ }
1125
+ requireHealthProbeCompleteAttestation(readHealthProbeReceipt(healthProbeReceiptPath(value.scenarioHandleDigest)), snapshot, request)
1126
+ activeHealthProbes.delete(value.scenarioHandleDigest)
1127
+ return { state: "complete", containerSnapshot: snapshot }
1128
+ }
1129
+
1130
+ function healthProbeChildExited(child) {
1131
+ return child.exitCode !== null || child.signalCode !== null
1132
+ }
1133
+
1134
+ function waitForHealthProbeChildExit(child, timeoutMs) {
1135
+ if (healthProbeChildExited(child)) return Promise.resolve(true)
1136
+ return new Promise((resolve) => {
1137
+ let settled = false
1138
+ let timer
1139
+ const finish = (exited) => {
1140
+ if (settled) return
1141
+ settled = true
1142
+ if (timer) clearTimeout(timer)
1143
+ child.removeListener("exit", onExit)
1144
+ resolve(exited)
1145
+ }
1146
+ const onExit = () => finish(true)
1147
+ child.once("exit", onExit)
1148
+ timer = setTimeout(() => finish(healthProbeChildExited(child)), timeoutMs)
1149
+ if (healthProbeChildExited(child)) finish(true)
1150
+ })
1151
+ }
1152
+
1153
+ function terminateHealthProbeChild(record, options = {}) {
1154
+ if (record.terminationPromise) return record.terminationPromise
1155
+ if (record.state !== "running" && record.state !== "terminating") return Promise.resolve()
1156
+ const termGraceMs = options.termGraceMs ?? HEALTH_PROBE_TERM_GRACE_MS
1157
+ const killGraceMs = options.killGraceMs ?? HEALTH_PROBE_KILL_GRACE_MS
1158
+ record.state = "terminating"
1159
+ const termination = (async () => {
1160
+ try {
1161
+ if (!healthProbeChildExited(record.child)) record.child.kill("SIGTERM")
1162
+ if (!await waitForHealthProbeChildExit(record.child, termGraceMs)) {
1163
+ record.child.kill("SIGKILL")
1164
+ if (!await waitForHealthProbeChildExit(record.child, killGraceMs)) throw new Error("health probe child did not exit")
1165
+ }
1166
+ record.state = "terminated"
1167
+ } catch (error) {
1168
+ record.state = "failed"
1169
+ throw error
1170
+ }
1171
+ })()
1172
+ record.terminationPromise = termination
1173
+ return termination
1174
+ }
1175
+
1176
+ async function recoverAfterHealthProbeTermination(record, recovery, options = {}) {
1177
+ if (record) await terminateHealthProbeChild(record, options)
1178
+ return await recovery()
1179
+ }
1180
+
1181
+ async function recoverHealthProbe(input) {
1182
+ const value = canonicalHealthProbeInput(input)
1183
+ const record = activeHealthProbes.get(value.scenarioHandleDigest)
1184
+ if (record && JSON.stringify(record.input) !== JSON.stringify(value)) throw new Error("health probe owner binding drifted")
1185
+ if (record?.recoveryPromise) return await record.recoveryPromise
1186
+ const recovery = (async () => {
1187
+ if (record?.readyPromise) {
1188
+ try { await record.readyPromise } catch { /* recovery still terminates an unready launch */ }
1189
+ }
1190
+ const processMarker = statIfPresent(healthProbeProcessPath(value.scenarioHandleDigest))
1191
+ if (processMarker) {
1192
+ if (!processMarker.isFile() || processMarker.uid !== 10001 || (processMarker.mode & 0o777) !== 0o600) throw new Error("health probe process marker metadata is invalid")
1193
+ }
1194
+ attestHealthProbeProcessAbsent(value)
1195
+ return await recoverAfterHealthProbeTermination(record, () => {
1196
+ if (record) record.state = "recovering"
1197
+ const workspace = statIfPresent(healthProbeWorkspacePath(value.scenarioHandleDigest))
1198
+ const pending = statIfPresent(healthProbePendingPath(value.scenarioHandleDigest))
1199
+ if (!workspace && !pending) {
1200
+ activeHealthProbes.delete(value.scenarioHandleDigest)
1201
+ return { recovered: true }
1202
+ }
1203
+ const result = spawnSync(DOCKER, healthProbeDockerArgs("recover", value), {
1204
+ cwd: "/", encoding: "utf8", timeout: 45_000, maxBuffer: 64 * 1024,
1205
+ env: { PATH: "/usr/local/sbin:/usr/sbin:/usr/bin:/sbin:/bin" }, stdio: ["ignore", "ignore", "ignore"],
1206
+ })
1207
+ if (result.error || result.status !== 0 || statIfPresent(healthProbeWorkspacePath(value.scenarioHandleDigest)) || statIfPresent(healthProbePendingPath(value.scenarioHandleDigest))) {
1208
+ throw new Error("health probe recovery failed")
1209
+ }
1210
+ activeHealthProbes.delete(value.scenarioHandleDigest)
1211
+ return { recovered: true }
1212
+ })
1213
+ })()
1214
+ if (record) record.recoveryPromise = recovery
1215
+ try {
1216
+ return await recovery
1217
+ } catch (error) {
1218
+ if (record) record.recoveryPromise = undefined
1219
+ throw error
1220
+ }
1221
+ }
1222
+
1223
+ function healthProbeCoordinates(payload, snapshot) {
1224
+ if (payload.targetId !== TARGET_HOST) throw new Error("target host is invalid")
1225
+ const label = text(payload.label, "health probe label")
1226
+ if (!HEALTH_PROBE_LABELS.has(label)) throw new Error("health probe label is invalid")
1227
+ const scenarioHandleDigest = text(payload.scenarioHandleDigest, "health probe scenario digest", SHA256)
1228
+ const ownerImage = text(snapshot.imageId, "health probe owner image", IMAGE_ID)
1229
+ const ownerContainerDigest = text(snapshot.containerId, "health probe owner container", SHA256)
1230
+ if (expectedImageId && ownerImage !== expectedImageId) throw new Error("health probe production owner drifted")
1231
+ return { label, scenarioHandleDigest, ownerImageDigest: ownerImage.slice("sha256:".length), ownerContainerDigest }
1232
+ }
1233
+
1234
+ function createDispatchDrain() {
1235
+ let accepting = true
1236
+ const inFlight = new Set()
1237
+ return {
1238
+ run(operation) {
1239
+ if (!accepting) return Promise.reject(new Error("host broker is shutting down"))
1240
+ const task = Promise.resolve().then(operation)
1241
+ inFlight.add(task)
1242
+ void task.finally(() => inFlight.delete(task)).catch(() => {})
1243
+ return task
1244
+ },
1245
+ async stopAndDrain() {
1246
+ accepting = false
1247
+ await Promise.allSettled([...inFlight])
1248
+ },
1249
+ }
1250
+ }
1251
+
1252
+ function createHealthProbeOperationCoordinator() {
1253
+ const tails = new Map()
1254
+ const recovered = new Set()
1255
+ const enqueue = (scenario, operation) => {
1256
+ const prior = tails.get(scenario) ?? Promise.resolve()
1257
+ const task = prior.catch(() => {}).then(operation)
1258
+ tails.set(scenario, task)
1259
+ void task.finally(() => { if (tails.get(scenario) === task) tails.delete(scenario) }).catch(() => {})
1260
+ return task
1261
+ }
1262
+ return {
1263
+ start(scenario, operation) {
1264
+ if (recovered.has(scenario)) return Promise.reject(new Error("health probe recovered scenario cannot restart"))
1265
+ return enqueue(scenario, operation)
1266
+ },
1267
+ recover(scenario, operation) {
1268
+ recovered.add(scenario)
1269
+ return enqueue(scenario, operation)
1270
+ },
1271
+ }
1272
+ }
1273
+
1274
+ function createOwnerMutationCoordinator() {
1275
+ let ownerTail = Promise.resolve()
1276
+ let pendingOperations = 0
1277
+ let rebootReservation = null
1278
+ const activeHealth = new Set()
1279
+ const enqueue = (operation) => {
1280
+ if (rebootReservation !== null) return Promise.reject(new Error("owner mutation refused by reboot reservation"))
1281
+ pendingOperations += 1
1282
+ const task = ownerTail.catch(() => {}).then(async () => {
1283
+ try { return await operation() } finally { pendingOperations -= 1 }
1284
+ })
1285
+ ownerTail = task.then(() => {}, () => {})
1286
+ return task
1287
+ }
1288
+ return {
1289
+ active() { return pendingOperations > 0 },
1290
+ async reserveReboot(reservationId, processBindingDigest, operation) {
1291
+ text(reservationId, "reboot reservation", SHA256)
1292
+ text(processBindingDigest, "reboot process binding", SHA256)
1293
+ if (rebootReservation !== null) throw new Error("reboot reservation already exists")
1294
+ rebootReservation = { id: reservationId, processBindingDigest, stoppedProof: null, attempted: false }
1295
+ try {
1296
+ await ownerTail.catch(() => {})
1297
+ if (pendingOperations !== 0 || activeHealth.size !== 0) throw new Error("reboot reservation could not drain owner mutations")
1298
+ return await operation()
1299
+ } catch (error) {
1300
+ rebootReservation = null
1301
+ throw error
1302
+ }
1303
+ },
1304
+ async stopRebootOwner(reservationId, processBindingDigest, operation) {
1305
+ text(reservationId, "reboot reservation", SHA256)
1306
+ text(processBindingDigest, "reboot process binding", SHA256)
1307
+ if (rebootReservation?.id !== reservationId || rebootReservation.processBindingDigest !== processBindingDigest) throw new Error("reboot reservation process binding is absent or mismatched")
1308
+ if (rebootReservation.stoppedProof !== null) throw new Error("reboot owner was already stopped")
1309
+ const proof = object(await operation(), "stopped reboot owner proof")
1310
+ if (proof.processBindingDigest !== processBindingDigest) throw new Error("stopped reboot owner proof binding is invalid")
1311
+ rebootReservation.stoppedProof = proof
1312
+ return proof
1313
+ },
1314
+ async commitReboot(reservationId, processBindingDigest, operation) {
1315
+ text(reservationId, "reboot reservation", SHA256)
1316
+ text(processBindingDigest, "reboot process binding", SHA256)
1317
+ if (rebootReservation?.id !== reservationId || rebootReservation.processBindingDigest !== processBindingDigest) throw new Error("reboot reservation is absent or mismatched")
1318
+ if (rebootReservation.stoppedProof === null) throw new Error("reboot owner is not stopped")
1319
+ if (rebootReservation.attempted) throw new Error("reboot commit was already attempted")
1320
+ const markAttempted = () => {
1321
+ if (rebootReservation.attempted) throw new Error("reboot commit was already attempted")
1322
+ rebootReservation.attempted = true
1323
+ }
1324
+ const result = await operation(rebootReservation.stoppedProof, markAttempted)
1325
+ if (!rebootReservation.attempted) throw new Error("reboot commit was not attempted")
1326
+ return result
1327
+ },
1328
+ healthStart(scenario, operation) {
1329
+ text(scenario, "health owner scenario", SHA256)
1330
+ return enqueue(async () => {
1331
+ if (activeHealth.has(scenario)) throw new Error("health probe scenario is already active")
1332
+ activeHealth.add(scenario)
1333
+ try { return await operation() } catch (error) { activeHealth.delete(scenario); throw error }
1334
+ })
1335
+ },
1336
+ healthRecover(scenario, operation) {
1337
+ text(scenario, "health owner scenario", SHA256)
1338
+ return enqueue(async () => {
1339
+ try { const result = await operation(); activeHealth.delete(scenario); return result } catch (error) { throw error }
1340
+ })
1341
+ },
1342
+ healthOperation(scenario, operation) {
1343
+ text(scenario, "health owner scenario", SHA256)
1344
+ return enqueue(operation)
1345
+ },
1346
+ interactive(operation) {
1347
+ return enqueue(async () => {
1348
+ if (activeHealth.size > 0) throw new Error("owner mutation refused while health probe is active")
1349
+ return await operation()
1350
+ })
1351
+ },
1352
+ }
1353
+ }
1354
+
1355
+ function createInteractiveRestartDriver() {
1356
+ const records = new Map()
1357
+ const tasks = new Set()
1358
+ return {
1359
+ poll(input, operation) {
1360
+ const key = input.scenarioHandleDigest
1361
+ const existing = records.get(key)
1362
+ if (existing?.state === "complete") return { state: "complete", receipt: existing.receipt }
1363
+ if (existing?.state === "failed") return { state: "failed", errorDigest: existing.errorDigest }
1364
+ if (existing) return { state: "waiting" }
1365
+ records.set(key, { state: "waiting", operation })
1366
+ return { state: "waiting" }
1367
+ },
1368
+ arm(key) {
1369
+ const record = records.get(key)
1370
+ if (!record || record.state !== "waiting" || !record.operation) return
1371
+ const operation = record.operation
1372
+ records.set(key, { state: "waiting" })
1373
+ const task = Promise.resolve().then(operation).then(
1374
+ (receipt) => { records.set(key, { state: "complete", receipt }) },
1375
+ (error) => { records.set(key, { state: "failed", errorDigest: createHash("sha256").update(interactiveFailureCategory(error)).digest("hex") }) },
1376
+ )
1377
+ tasks.add(task)
1378
+ void task.finally(() => tasks.delete(task)).catch(() => {})
1379
+ },
1380
+ async stopAndDrain() { await Promise.allSettled([...tasks]) },
1381
+ }
1382
+ }
1383
+
1384
+ function armRestartAfterResponseClosed(connection, scenarioHandleDigest, driver = interactiveRestartDriver) {
1385
+ let written = false
1386
+ let closed = false
1387
+ let armed = false
1388
+ const maybeArm = () => {
1389
+ if (!armed && written && closed) { armed = true; driver.arm(scenarioHandleDigest) }
1390
+ }
1391
+ connection.once("close", () => { closed = true; maybeArm() })
1392
+ return () => { written = true; maybeArm() }
1393
+ }
1394
+
1395
+ function interactiveFailureCategory(error) {
1396
+ const message = error instanceof Error ? error.message : ""
1397
+ if (/inspect-before-retry/u.test(message)) return "inspect-before-retry"
1398
+ if (/production runtime/u.test(message)) return "production-runtime"
1399
+ if (/owner restart|did not return|restart failed/u.test(message)) return "owner-restart"
1400
+ if (/prepared receipt/u.test(message)) return "prepared-receipt"
1401
+ if (/reconciliation/u.test(message)) return "reconciliation"
1402
+ if (/receipt/u.test(message)) return "receipt-validation"
1403
+ return "unknown"
1404
+ }
1405
+
1406
+ async function waitForInteractiveRuntimeReady(input, dependencies) {
1407
+ for (let attempt = 0; attempt < 60; attempt += 1) {
1408
+ const readiness = object(await dependencies.runtime("interactive_runtime_ready", input), "interactive runtime readiness")
1409
+ exactKeys(readiness, ["ready"], "interactive runtime readiness")
1410
+ if (readiness.ready === true) return
1411
+ if (readiness.ready !== false) throw new Error("interactive runtime readiness is invalid")
1412
+ await dependencies.sleep(1_000)
1413
+ }
1414
+ throw new Error("interactive production runtime readiness timed out")
1415
+ }
1416
+
1417
+ function canonicalInteractiveRequest(input, expectedLabel) {
1418
+ const value = object(input, "interactive driver input")
1419
+ exactKeys(value, ["label", "scenarioHandleDigest"], "interactive driver input")
1420
+ if (value.label !== expectedLabel) throw new Error("interactive driver label is invalid")
1421
+ text(value.scenarioHandleDigest, "interactive driver scenario digest", SHA256)
1422
+ return value
1423
+ }
1424
+
1425
+ const DUPLICATE_RECEIPT_KEYS = [
1426
+ "schemaVersion", "phase", "label", "scenarioHandleDigest", "approvalIdDigest", "checkpointDigest",
1427
+ "suspendedSessionRevisionDigest", "approvalEpochBefore", "callbackAttempts", "distinctQueryCount",
1428
+ "callbackDataDigest", "barrierObserved", "settledCount", "claimCount", "mutationCount",
1429
+ "staleReplayAttempts", "staleReplaySettled", "staleReplayMutationCount", "promptTerminal",
1430
+ "writeCredentialObserved",
1431
+ ]
1432
+ const TIMEOUT_RECEIPT_KEYS = [
1433
+ "schemaVersion", "phase", "label", "scenarioHandleDigest", "approvalIdDigest", "checkpointDigest",
1434
+ "suspendedSessionRevisionDigest", "approvalEpochBefore", "callbackAttempts", "distinctQueryCount",
1435
+ "callbackDataDigest", "settledCount", "claimCount", "mutationCount", "staleAcknowledged", "promptTerminal",
1436
+ ]
1437
+
1438
+ const RESTART_RECEIPT_KEYS = [
1439
+ "schemaVersion", "phase", "label", "scenarioHandleDigest", "approvalIdDigest", "checkpointDigest",
1440
+ "suspendedSessionRevisionDigest", "approvalEpochBefore", "approvalEpochAfterRestart", "continuationEpochAfter",
1441
+ "ownerImageDigest", "ownerContainerDigest", "restartCountBefore", "restartCountAfter", "pendingDigestBefore",
1442
+ "pendingDigestAfter", "pendingRestored", "callbackAttempts", "mutationCount", "indeterminateRecoveryObserved",
1443
+ "attemptedRecoveryReopened", "attemptedRecordDigest", "recoveredRecordDigest", "indeterminateRetryCount",
1444
+ ]
1445
+ const PREPARED_RESTART_RECEIPT_KEYS = [
1446
+ "schemaVersion", "phase", "label", "scenarioHandleDigest", "approvalIdDigest", "checkpointDigest",
1447
+ "suspendedSessionRevisionDigest", "approvalEpochBefore", "pendingDigestBefore", "indeterminateRecoveryObserved",
1448
+ "attemptedRecoveryReopened", "attemptedRecordDigest", "recoveredRecordDigest",
1449
+ "ownerImageDigest", "ownerContainerDigest", "restartCountBefore",
1450
+ ]
1451
+
1452
+ function requireInteractiveReceipt(receipt, input) {
1453
+ const value = object(receipt, "interactive driver receipt")
1454
+ const duplicate = input.label === "unit-16l-duplicate-callback"
1455
+ exactKeys(value, duplicate ? DUPLICATE_RECEIPT_KEYS : RESTART_RECEIPT_KEYS, "interactive driver receipt")
1456
+ if (value.schemaVersion !== "sanctuary-interactive-driver-receipt-v2" || value.phase !== "complete"
1457
+ || value.label !== input.label || value.scenarioHandleDigest !== input.scenarioHandleDigest
1458
+ || ![value.approvalIdDigest, value.checkpointDigest, value.suspendedSessionRevisionDigest].every((item) => typeof item === "string" && SHA256.test(item))
1459
+ || !Number.isSafeInteger(value.approvalEpochBefore) || value.approvalEpochBefore < 0) {
1460
+ throw new Error("interactive driver receipt is invalid")
1461
+ }
1462
+ if (duplicate) {
1463
+ if (!SHA256.test(value.callbackDataDigest) || value.callbackAttempts !== 2 || value.distinctQueryCount !== 2
1464
+ || value.barrierObserved !== true || value.settledCount !== 2 || value.claimCount !== 1 || value.mutationCount !== 1
1465
+ || value.staleReplayAttempts !== 1 || value.staleReplaySettled !== true || value.staleReplayMutationCount !== 0
1466
+ || value.promptTerminal !== true || value.writeCredentialObserved !== false) throw new Error("interactive driver receipt is invalid")
1467
+ } else if (!SHA256.test(value.ownerImageDigest) || !SHA256.test(value.ownerContainerDigest)
1468
+ || !SHA256.test(value.pendingDigestBefore) || value.pendingDigestAfter !== value.pendingDigestBefore
1469
+ || value.approvalEpochAfterRestart !== value.approvalEpochBefore || !Number.isSafeInteger(value.continuationEpochAfter)
1470
+ || value.continuationEpochAfter <= value.approvalEpochAfterRestart || !Number.isSafeInteger(value.restartCountBefore)
1471
+ || value.restartCountAfter !== value.restartCountBefore + 1 || value.pendingRestored !== true || value.callbackAttempts !== 1
1472
+ || value.mutationCount !== 1 || value.indeterminateRecoveryObserved !== true || value.attemptedRecoveryReopened !== true
1473
+ || !SHA256.test(value.attemptedRecordDigest) || !SHA256.test(value.recoveredRecordDigest)
1474
+ || value.attemptedRecordDigest === value.recoveredRecordDigest || value.indeterminateRetryCount !== 0) {
1475
+ throw new Error("interactive driver receipt is invalid")
1476
+ }
1477
+ return value
1478
+ }
1479
+
1480
+ function requireTimeoutReceipt(receipt, input) {
1481
+ const value = object(receipt, "timeout stale driver receipt")
1482
+ exactKeys(value, TIMEOUT_RECEIPT_KEYS, "timeout stale driver receipt")
1483
+ if (value.schemaVersion !== "sanctuary-timeout-stale-driver-receipt-v1" || value.phase !== "complete"
1484
+ || value.label !== input.label || value.scenarioHandleDigest !== input.scenarioHandleDigest
1485
+ || ![value.approvalIdDigest, value.checkpointDigest, value.suspendedSessionRevisionDigest, value.callbackDataDigest]
1486
+ .every((item) => typeof item === "string" && SHA256.test(item))
1487
+ || !Number.isSafeInteger(value.approvalEpochBefore) || value.approvalEpochBefore < 0
1488
+ || value.callbackAttempts !== 1 || value.distinctQueryCount !== 1 || value.settledCount !== 1
1489
+ || value.claimCount !== 0 || value.mutationCount !== 0 || value.staleAcknowledged !== true || value.promptTerminal !== true) {
1490
+ throw new Error("timeout stale driver receipt is invalid")
1491
+ }
1492
+ return value
1493
+ }
1494
+
1495
+ function requirePreparedRestartReceipt(receipt, input) {
1496
+ const value = object(receipt, "restart continuation prepared receipt")
1497
+ exactKeys(value, PREPARED_RESTART_RECEIPT_KEYS, "restart continuation prepared receipt")
1498
+ if (value.schemaVersion !== "sanctuary-interactive-driver-receipt-v2" || (value.phase !== "prepared" && value.phase !== "attempted_or_indeterminate")
1499
+ || value.label !== input.label || value.scenarioHandleDigest !== input.scenarioHandleDigest
1500
+ || ![value.approvalIdDigest, value.checkpointDigest, value.suspendedSessionRevisionDigest, value.pendingDigestBefore,
1501
+ value.ownerImageDigest, value.ownerContainerDigest, value.attemptedRecordDigest, value.recoveredRecordDigest].every((item) => typeof item === "string" && SHA256.test(item))
1502
+ || !Number.isSafeInteger(value.approvalEpochBefore) || value.approvalEpochBefore < 0 || value.indeterminateRecoveryObserved !== true
1503
+ || value.attemptedRecoveryReopened !== true || value.attemptedRecordDigest === value.recoveredRecordDigest
1504
+ || !Number.isSafeInteger(value.restartCountBefore) || value.restartCountBefore < 0) throw new Error("restart continuation prepared receipt is invalid")
1505
+ return value
1506
+ }
1507
+
1508
+ function runInteractiveRuntimeOperation(operation, value, dependencies = { run: spawnSync }) {
1509
+ const result = dependencies.run(DOCKER, ["exec", "-i", activeContainerId, ACCEPTANCE_ADAPTER], {
1510
+ cwd: "/", encoding: "utf8", timeout: 120_000, maxBuffer: MAX_REQUEST,
1511
+ env: { PATH: "/usr/local/sbin:/usr/sbin:/usr/bin:/sbin:/bin" }, stdio: ["pipe", "pipe", "ignore"],
1512
+ input: JSON.stringify({ operation, ...value }),
1513
+ })
1514
+ if (result.error || result.status !== 0) throw new Error("interactive production runtime driver failed")
1515
+ try { return object(JSON.parse(result.stdout ?? ""), "interactive driver receipt") } catch { throw new Error("interactive driver receipt is invalid") }
1516
+ }
1517
+
1518
+ function runInteractiveDriver(input, dependencies = { run: spawnSync }) {
1519
+ const value = canonicalInteractiveRequest(input, "unit-16l-duplicate-callback")
1520
+ return requireInteractiveReceipt(runInteractiveRuntimeOperation("drive_duplicate_callbacks", value, dependencies), value)
1521
+ }
1522
+
1523
+ function runTimeoutStaleDriver(input, dependencies = { run: spawnSync }) {
1524
+ const value = canonicalInteractiveRequest(input, "unit-16k-timeout-stale")
1525
+ const response = runInteractiveRuntimeOperation("drive_timeout_stale", value, dependencies)
1526
+ if (response.state === "waiting") {
1527
+ exactKeys(response, ["state"], "timeout stale waiting response")
1528
+ return response
1529
+ }
1530
+ return requireTimeoutReceipt(response, value)
1531
+ }
1532
+
1533
+ async function restartButlerForAcceptance(input, dependencies = {
1534
+ snapshot: () => containerRestartSnapshot(expectedImageId), run: spawnSync,
1535
+ sleep: (milliseconds) => new Promise((resolve) => setTimeout(resolve, milliseconds)),
1536
+ }) {
1537
+ canonicalInteractiveRequest(input, "unit-16m-restart-continuation")
1538
+ const before = object(await dependencies.snapshot(), "restart owner before")
1539
+ const result = dependencies.run(DOCKER, ["restart", activeContainerId], {
1540
+ cwd: "/", encoding: "utf8", timeout: 120_000, maxBuffer: 64 * 1024,
1541
+ env: { PATH: "/usr/local/sbin:/usr/sbin:/usr/bin:/sbin:/bin" }, stdio: ["ignore", "ignore", "ignore"],
1542
+ })
1543
+ if (result.error || result.status !== 0) throw new Error("production butler restart failed")
1544
+ const ownerImage = text(before.imageId, "restart owner image", IMAGE_ID)
1545
+ const ownerContainer = text(before.containerId, "restart owner container", SHA256)
1546
+ if (!Number.isSafeInteger(before.restartCount) || before.restartCount < 0) throw new Error("restart owner count is invalid")
1547
+ const beforeLifecycleDigest = createHash("sha256").update(JSON.stringify(before)).digest("hex")
1548
+ for (let attempt = 0; attempt < 60; attempt += 1) {
1549
+ const after = object(await dependencies.snapshot(), "restart owner after")
1550
+ const afterLifecycleDigest = createHash("sha256").update(JSON.stringify(after)).digest("hex")
1551
+ if (after.running === true && after.health === "healthy" && after.containerId === ownerContainer && after.imageId === ownerImage
1552
+ && after.restartCount === before.restartCount + 1 && afterLifecycleDigest !== beforeLifecycleDigest) {
1553
+ return {
1554
+ restarted: true,
1555
+ beforeLifecycleDigest,
1556
+ afterLifecycleDigest,
1557
+ restartInvocationCount: 1,
1558
+ ownerImageDigest: ownerImage.slice(7), ownerContainerDigest: ownerContainer,
1559
+ restartCountBefore: before.restartCount, restartCountAfter: after.restartCount,
1560
+ }
1561
+ }
1562
+ await dependencies.sleep(1_000)
1563
+ }
1564
+ throw new Error("production butler did not return with exact restart identity")
1565
+ }
1566
+
1567
+ function interactiveReceiptPath(scenarioHandleDigest) {
1568
+ return `${PRODUCTION_BUNDLE_SOURCE}/state/acceptance/interactive-driver-receipts/${scenarioHandleDigest}.json`
1569
+ }
1570
+
1571
+ function readInteractiveReceipt(input) {
1572
+ const file = interactiveReceiptPath(input.scenarioHandleDigest)
1573
+ let fd
1574
+ try { fd = openSync(file, constants.O_RDONLY | constants.O_NOFOLLOW) } catch (error) { if (error.code === "ENOENT") return null; throw error }
1575
+ try {
1576
+ const metadata = fstatSync(fd)
1577
+ if (!metadata.isFile() || metadata.uid !== 10001 || (metadata.mode & 0o777) !== 0o600 || metadata.size > MAX_REQUEST) throw new Error("interactive driver receipt metadata is invalid")
1578
+ return object(JSON.parse(readFileSync(fd, "utf8")), "interactive driver receipt")
1579
+ } finally { closeSync(fd) }
1580
+ }
1581
+
1582
+ function persistInteractiveReceipt(input, receipt) {
1583
+ const root = `${PRODUCTION_BUNDLE_SOURCE}/state/acceptance/interactive-driver-receipts`
1584
+ mkdirSync(root, { recursive: true, mode: 0o700 })
1585
+ chownSync(root, 10001, 10001)
1586
+ chmodSync(root, 0o700)
1587
+ const directory = openSync(root, constants.O_RDONLY | constants.O_DIRECTORY | constants.O_NOFOLLOW)
1588
+ const directoryMetadata = fstatSync(directory)
1589
+ if (!directoryMetadata.isDirectory() || directoryMetadata.uid !== 10001 || (directoryMetadata.mode & 0o777) !== 0o700) {
1590
+ closeSync(directory)
1591
+ throw new Error("interactive driver receipt directory metadata is invalid")
1592
+ }
1593
+ const file = interactiveReceiptPath(input.scenarioHandleDigest)
1594
+ const temporary = `${file}.${process.pid}.${Date.now()}.tmp`
1595
+ try {
1596
+ const fd = openSync(temporary, constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL | constants.O_NOFOLLOW, 0o600)
1597
+ try { chownSync(temporary, 10001, 10001); writeFileSync(fd, `${JSON.stringify(receipt)}\n`); fsyncSync(fd) } finally { closeSync(fd) }
1598
+ renameSync(temporary, file)
1599
+ fsyncSync(directory)
1600
+ } finally { closeSync(directory) }
1601
+ }
1602
+
1603
+ async function driveDuplicateCallbacks(input, dependencies = {
1604
+ readReceipt: readInteractiveReceipt,
1605
+ persistReceipt: persistInteractiveReceipt,
1606
+ runtime: runInteractiveDriver,
1607
+ }) {
1608
+ const value = canonicalInteractiveRequest(input, "unit-16l-duplicate-callback")
1609
+ const existing = dependencies.readReceipt(value)
1610
+ if (existing) {
1611
+ if (existing.phase === "complete") return requireInteractiveReceipt(existing, value)
1612
+ throw new Error("duplicate callback drive requires inspect-before-retry")
1613
+ }
1614
+ dependencies.persistReceipt(value, { schemaVersion: "sanctuary-interactive-driver-receipt-v2", phase: "attempting", ...value })
1615
+ try {
1616
+ const receipt = requireInteractiveReceipt(await dependencies.runtime(value), value)
1617
+ dependencies.persistReceipt(value, receipt)
1618
+ return receipt
1619
+ } catch (error) {
1620
+ dependencies.persistReceipt(value, { schemaVersion: "sanctuary-interactive-driver-receipt-v2", phase: "attempted_or_indeterminate", ...value })
1621
+ throw error
1622
+ }
1623
+ }
1624
+
1625
+ async function driveTimeoutStale(input, dependencies = {
1626
+ readReceipt: readInteractiveReceipt,
1627
+ persistReceipt: persistInteractiveReceipt,
1628
+ runtime: runTimeoutStaleDriver,
1629
+ }) {
1630
+ const value = canonicalInteractiveRequest(input, "unit-16k-timeout-stale")
1631
+ const existing = dependencies.readReceipt(value)
1632
+ if (existing?.phase === "complete") return requireTimeoutReceipt(existing, value)
1633
+ if (existing && existing.phase !== "waiting") throw new Error("timeout stale drive requires inspect-before-retry")
1634
+ dependencies.persistReceipt(value, {
1635
+ schemaVersion: "sanctuary-timeout-stale-driver-receipt-v1",
1636
+ phase: existing ? "attempting" : "preparing",
1637
+ ...value,
1638
+ })
1639
+ try {
1640
+ const response = await dependencies.runtime(value)
1641
+ if (response.state === "waiting") {
1642
+ exactKeys(response, ["state"], "timeout stale waiting response")
1643
+ dependencies.persistReceipt(value, { schemaVersion: "sanctuary-timeout-stale-driver-receipt-v1", phase: "waiting", ...value })
1644
+ return response
1645
+ }
1646
+ const receipt = requireTimeoutReceipt(response, value)
1647
+ dependencies.persistReceipt(value, receipt)
1648
+ return receipt
1649
+ } catch (error) {
1650
+ dependencies.persistReceipt(value, {
1651
+ schemaVersion: "sanctuary-timeout-stale-driver-receipt-v1",
1652
+ phase: existing ? "attempted_or_indeterminate" : "preparation_indeterminate",
1653
+ ...value,
1654
+ })
1655
+ throw error
1656
+ }
1657
+ }
1658
+
1659
+ async function driveRestartContinuation(input, dependencies = {
1660
+ readReceipt: readInteractiveReceipt,
1661
+ persistReceipt: persistInteractiveReceipt,
1662
+ runtime: runInteractiveRuntimeOperation,
1663
+ restart: restartButlerForAcceptance,
1664
+ snapshot: () => containerRestartSnapshot(expectedImageId),
1665
+ sleep: (milliseconds) => new Promise((resolve) => setTimeout(resolve, milliseconds)),
1666
+ }) {
1667
+ const value = canonicalInteractiveRequest(input, "unit-16m-restart-continuation")
1668
+ const existing = dependencies.readReceipt(value)
1669
+ if (existing) {
1670
+ if (existing.phase === "complete") return requireInteractiveReceipt(existing, value)
1671
+ let recoverable
1672
+ try { recoverable = requirePreparedRestartReceipt(existing, value) } catch { throw new Error("restart continuation requires inspect-before-retry") }
1673
+ const observed = object(await dependencies.snapshot(), "restart continuation recovery owner")
1674
+ if (observed.imageId !== `sha256:${recoverable.ownerImageDigest}` || observed.containerId !== recoverable.ownerContainerDigest
1675
+ || observed.running !== true || observed.health !== "healthy" || observed.restartCount !== recoverable.restartCountBefore + 1) {
1676
+ throw new Error("restart continuation requires inspect-before-retry")
1677
+ }
1678
+ await waitForInteractiveRuntimeReady(value, dependencies)
1679
+ const reconciled = object(await dependencies.runtime("reconcile_restart_continuation", value), "restart continuation reconciliation")
1680
+ const receipt = { ...recoverable, ...reconciled, phase: "complete", restartCountAfter: observed.restartCount }
1681
+ delete receipt.restarted
1682
+ requireInteractiveReceipt(receipt, value)
1683
+ dependencies.persistReceipt(value, receipt)
1684
+ return receipt
1685
+ }
1686
+ const attempting = { schemaVersion: "sanctuary-interactive-driver-receipt-v2", phase: "attempting", ...value }
1687
+ dependencies.persistReceipt(value, attempting)
1688
+ let prepared
1689
+ try {
1690
+ prepared = object(await dependencies.runtime("prepare_restart_continuation", value), "restart continuation prepared receipt")
1691
+ const expected = ["schemaVersion", "phase", "label", "scenarioHandleDigest", "approvalIdDigest", "checkpointDigest", "suspendedSessionRevisionDigest", "approvalEpochBefore", "pendingDigestBefore", "indeterminateRecoveryObserved", "attemptedRecoveryReopened", "attemptedRecordDigest", "recoveredRecordDigest"]
1692
+ exactKeys(prepared, expected, "restart continuation prepared receipt")
1693
+ if (prepared.schemaVersion !== "sanctuary-interactive-driver-receipt-v2" || prepared.phase !== "prepared" || prepared.label !== value.label
1694
+ || prepared.scenarioHandleDigest !== value.scenarioHandleDigest || ![prepared.approvalIdDigest, prepared.checkpointDigest, prepared.suspendedSessionRevisionDigest, prepared.pendingDigestBefore].every((item) => typeof item === "string" && SHA256.test(item))
1695
+ || !Number.isSafeInteger(prepared.approvalEpochBefore) || prepared.approvalEpochBefore < 0 || prepared.indeterminateRecoveryObserved !== true
1696
+ || prepared.attemptedRecoveryReopened !== true || !SHA256.test(prepared.attemptedRecordDigest) || !SHA256.test(prepared.recoveredRecordDigest)
1697
+ || prepared.attemptedRecordDigest === prepared.recoveredRecordDigest) throw new Error("restart continuation prepared receipt is invalid")
1698
+ const ownerBefore = object(await dependencies.snapshot(), "restart continuation owner before")
1699
+ if (!IMAGE_ID.test(ownerBefore.imageId) || !SHA256.test(ownerBefore.containerId) || ownerBefore.running !== true || ownerBefore.health !== "healthy"
1700
+ || !Number.isSafeInteger(ownerBefore.restartCount) || ownerBefore.restartCount < 0) throw new Error("restart continuation owner before is invalid")
1701
+ prepared = { ...prepared, ownerImageDigest: ownerBefore.imageId.slice(7), ownerContainerDigest: ownerBefore.containerId, restartCountBefore: ownerBefore.restartCount }
1702
+ dependencies.persistReceipt(value, prepared)
1703
+ const restarted = object(await dependencies.restart(value), "restart continuation owner restart")
1704
+ if (restarted.restarted !== true || restarted.restartInvocationCount !== 1 || !SHA256.test(restarted.ownerImageDigest) || !SHA256.test(restarted.ownerContainerDigest)
1705
+ || restarted.ownerImageDigest !== prepared.ownerImageDigest || restarted.ownerContainerDigest !== prepared.ownerContainerDigest
1706
+ || restarted.restartCountBefore !== prepared.restartCountBefore || restarted.restartCountAfter !== restarted.restartCountBefore + 1) throw new Error("restart continuation owner restart is invalid")
1707
+ await waitForInteractiveRuntimeReady(value, dependencies)
1708
+ const reconciled = object(await dependencies.runtime("reconcile_restart_continuation", value), "restart continuation reconciliation")
1709
+ const receipt = { ...prepared, ...reconciled, phase: "complete", ownerImageDigest: restarted.ownerImageDigest, ownerContainerDigest: restarted.ownerContainerDigest, restartCountBefore: restarted.restartCountBefore, restartCountAfter: restarted.restartCountAfter }
1710
+ requireInteractiveReceipt(receipt, value)
1711
+ dependencies.persistReceipt(value, receipt)
1712
+ return receipt
1713
+ } catch (error) {
1714
+ dependencies.persistReceipt(value, { ...(prepared ?? { schemaVersion: "sanctuary-interactive-driver-receipt-v2", ...value }), phase: "attempted_or_indeterminate" })
1715
+ throw error
1716
+ }
1717
+ }
1718
+
1719
+ async function dispatch(request, dependencies = {
1720
+ readBootId: () => readFileSync(BOOT_ID, "utf8"),
1721
+ containerSnapshot: () => containerSnapshot(expectedImageId),
1722
+ denialTargetSnapshot,
1723
+ containerOwnerSnapshot: () => containerOwnerSnapshot(expectedImageId),
1724
+ startHealthProbe,
1725
+ healthProbeStatus,
1726
+ recoverHealthProbe,
1727
+ healthProbeCoordinator,
1728
+ driveTimeoutStale,
1729
+ driveDuplicateCallbacks,
1730
+ driveRestartContinuation,
1731
+ ownerMutationCoordinator,
1732
+ interactiveRestartDriver,
1733
+ healthOwnerMutationActive,
1734
+ liveContainerProcessUser,
1735
+ liveContainerProcessIdentity,
1736
+ parseProcStartTime,
1737
+ productionProcessBindingDigest,
1738
+ rebootPreflightSnapshot: observeRebootPreflight,
1739
+ stopExactRebootOwner,
1740
+ verifyStoppedRebootOwner,
1741
+ commitHostReboot: () => new Promise((resolve, reject) => {
1742
+ const child = spawn(REBOOT, [], { cwd: "/", detached: true, stdio: "ignore", env: { PATH: "/usr/local/sbin:/usr/sbin:/usr/bin:/sbin:/bin" } })
1743
+ child.once("error", reject)
1744
+ child.once("spawn", () => { child.unref(); resolve() })
1745
+ }),
1746
+ }) {
1747
+ const payload = object(request, "broker request")
1748
+ const operation = text(payload.operation, "operation")
1749
+ if (operation === "inventory_keys") {
1750
+ exactKeys(payload, ["operation", "targetServerId"], operation)
1751
+ if (payload.targetServerId !== TARGET_SERVER) throw new Error("target server is invalid")
1752
+ return { keys: inventoryRecords().map(publicRecord) }
1753
+ }
1754
+ if (operation === "read_key_record") {
1755
+ exactKeys(payload, ["operation", "targetServerId", "keyId"], operation)
1756
+ if (payload.targetServerId !== TARGET_SERVER) throw new Error("target server is invalid")
1757
+ const id = text(payload.keyId, "key id", KEY_ID)
1758
+ const matches = inventoryRecords().filter((record) => record.id === id)
1759
+ if (matches.length !== 1) throw new Error("exact key id is absent or ambiguous")
1760
+ return matches[0]
1761
+ }
1762
+ if (operation === "create_key") {
1763
+ exactKeys(payload, ["operation", "targetServerId", "name", "permissions"], operation)
1764
+ if (payload.targetServerId !== TARGET_SERVER) throw new Error("target server is invalid")
1765
+ const name = text(payload.name, "key name", KEY_NAME)
1766
+ const requested = permissions(payload.permissions)
1767
+ return await createUnraidKey(name, requested)
1768
+ }
1769
+ if (operation === "acknowledge_key_storage") {
1770
+ exactKeys(payload, ["operation", "targetServerId", "keyId"], operation)
1771
+ if (payload.targetServerId !== TARGET_SERVER) throw new Error("target server is invalid")
1772
+ return acknowledgeStoredUnraidKey(text(payload.keyId, "key id", KEY_ID))
1773
+ }
1774
+ if (operation === "revoke_key") {
1775
+ exactKeys(payload, ["operation", "targetServerId", "keyId"], operation)
1776
+ if (payload.targetServerId !== TARGET_SERVER) throw new Error("target server is invalid")
1777
+ const id = text(payload.keyId, "key id", KEY_ID)
1778
+ const matches = inventoryRecords().filter((record) => record.id === id)
1779
+ if (matches.length !== 1) throw new Error("exact key id is absent or ambiguous")
1780
+ const record = matches[0]
1781
+ persistRecovery(record)
1782
+ const deleted = runUnraid(["apikey", "--name", record.name, "--delete", "--json"])
1783
+ if (deleted.deleted !== 1 || !Array.isArray(deleted.keys) || deleted.keys.length !== 1
1784
+ || deleted.keys[0]?.id !== id || deleted.keys[0]?.name !== record.name) throw new Error("exact key revoke attestation failed")
1785
+ return { revoked: true, id }
1786
+ }
1787
+ if (operation === "probe_revoked_key") {
1788
+ exactKeys(payload, ["operation", "targetServerId", "keyId"], operation)
1789
+ if (payload.targetServerId !== TARGET_SERVER) throw new Error("target server is invalid")
1790
+ return await probeRevokedUnraidKey(text(payload.keyId, "key id", KEY_ID))
1791
+ }
1792
+ if (operation === "request_reboot") {
1793
+ exactKeys(payload, ["operation", "targetId", "idempotencyKey", "preflightDigest", "processBindingDigest"], operation)
1794
+ if (payload.targetId !== TARGET_HOST) throw new Error("target host is invalid")
1795
+ const key = text(payload.idempotencyKey, "idempotency key", /^[0-9a-f]{32}$/u)
1796
+ const preflightDigest = text(payload.preflightDigest, "preflight digest", SHA256)
1797
+ const processBindingDigest = text(payload.processBindingDigest, "process binding digest", SHA256)
1798
+ const requestId = createHash("sha256").update(`sanctuary-reboot\0${key}`).digest("hex")
1799
+ const reservationId = createHash("sha256").update(`sanctuary-reboot-reservation\0${requestId}`).digest("hex")
1800
+ const reserve = dependencies.ownerMutationCoordinator?.reserveReboot
1801
+ if (!reserve) throw new Error("reboot reservation coordinator is unavailable")
1802
+ await reserve.call(dependencies.ownerMutationCoordinator, reservationId, processBindingDigest, async () => {
1803
+ const preflight = object(dependencies.rebootPreflightSnapshot(), "reboot preflight")
1804
+ if (preflight.digest !== preflightDigest) throw new Error("reboot preflight changed before commit")
1805
+ if (preflight.safe !== true) throw new Error("reboot preflight is unsafe")
1806
+ const owner = object(await dependencies.containerSnapshot(), "reboot reservation production owner")
1807
+ if (owner.processBindingDigest !== processBindingDigest) throw new Error("production process generation changed before reboot reservation")
1808
+ })
1809
+ const prebootId = text(dependencies.readBootId().trim(), "boot id", /^[A-Za-z0-9-]{4,128}$/u)
1810
+ return { accepted: true, targetId: TARGET_HOST, requestId, reservationId, prebootId, preflightDigest, processBindingDigest, staged: true }
1811
+ }
1812
+ if (operation === "stop_reboot_owner") {
1813
+ exactKeys(payload, ["operation", "targetId", "requestId", "reservationId", "processBindingDigest"], operation)
1814
+ if (payload.targetId !== TARGET_HOST) throw new Error("target host is invalid")
1815
+ const requestId = text(payload.requestId, "reboot request id", SHA256)
1816
+ const reservationId = text(payload.reservationId, "reboot reservation", SHA256)
1817
+ const processBindingDigest = text(payload.processBindingDigest, "process binding digest", SHA256)
1818
+ if (reservationId !== createHash("sha256").update(`sanctuary-reboot-reservation\0${requestId}`).digest("hex")) throw new Error("reboot request reservation binding is invalid")
1819
+ const stop = dependencies.ownerMutationCoordinator?.stopRebootOwner
1820
+ if (!stop || !dependencies.stopExactRebootOwner) throw new Error("exact reboot owner stop is unavailable")
1821
+ await stop.call(dependencies.ownerMutationCoordinator, reservationId, processBindingDigest, () => dependencies.stopExactRebootOwner(processBindingDigest))
1822
+ return { stopped: true, targetId: TARGET_HOST, requestId, reservationId, processBindingDigest }
1823
+ }
1824
+ if (operation === "commit_reboot") {
1825
+ exactKeys(payload, ["operation", "targetId", "requestId", "reservationId", "processBindingDigest"], operation)
1826
+ if (payload.targetId !== TARGET_HOST) throw new Error("target host is invalid")
1827
+ const requestId = text(payload.requestId, "reboot request id", SHA256)
1828
+ const reservationId = text(payload.reservationId, "reboot reservation", SHA256)
1829
+ const processBindingDigest = text(payload.processBindingDigest, "process binding digest", SHA256)
1830
+ if (reservationId !== createHash("sha256").update(`sanctuary-reboot-reservation\0${requestId}`).digest("hex")) throw new Error("reboot request reservation binding is invalid")
1831
+ const commit = dependencies.ownerMutationCoordinator?.commitReboot
1832
+ if (!commit) throw new Error("reboot reservation coordinator is unavailable")
1833
+ await commit.call(dependencies.ownerMutationCoordinator, reservationId, processBindingDigest, async (stoppedProof, markAttempted) => {
1834
+ await dependencies.verifyStoppedRebootOwner(stoppedProof)
1835
+ const preflight = object(dependencies.rebootPreflightSnapshot(), "final reboot preflight")
1836
+ if (preflight.safe !== true || preflight.arrayReady !== true || preflight.parityActive !== false || preflight.moverActive !== false || preflight.mutationActive !== false) {
1837
+ throw new Error("final reboot preflight is unsafe")
1838
+ }
1839
+ await dependencies.verifyStoppedRebootOwner(stoppedProof)
1840
+ markAttempted()
1841
+ await dependencies.commitHostReboot()
1842
+ })
1843
+ return { committed: true, targetId: TARGET_HOST, requestId, reservationId, processBindingDigest }
1844
+ }
1845
+ if (operation === "reboot_preflight_snapshot") {
1846
+ exactKeys(payload, ["operation", "targetId", "processBindingDigest"], operation)
1847
+ if (payload.targetId !== TARGET_HOST) throw new Error("target host is invalid")
1848
+ const processBindingDigest = text(payload.processBindingDigest, "process binding digest", SHA256)
1849
+ const owner = object(await dependencies.containerSnapshot(), "reboot preflight production owner")
1850
+ if (owner.processBindingDigest !== processBindingDigest) throw new Error("production process generation changed before reboot preflight")
1851
+ return { ...dependencies.rebootPreflightSnapshot(), processBindingDigest }
1852
+ }
1853
+ if (operation === "container_snapshot") {
1854
+ exactKeys(payload, ["operation", "targetId"], operation)
1855
+ if (payload.targetId !== TARGET_HOST) throw new Error("target host is invalid")
1856
+ return await dependencies.containerSnapshot()
1857
+ }
1858
+ if (operation === "denial_target_snapshot") {
1859
+ exactKeys(payload, ["operation", "targetId"], operation)
1860
+ if (payload.targetId !== TARGET_HOST) throw new Error("target host is invalid")
1861
+ if (!dependencies.denialTargetSnapshot) throw new Error("denial target snapshot is unavailable")
1862
+ return await dependencies.denialTargetSnapshot()
1863
+ }
1864
+ if (operation === "start_health_probe" || operation === "health_probe_status" || operation === "recover_health_probe") {
1865
+ exactKeys(payload, ["operation", "targetId", "label", "scenarioHandleDigest"], operation)
1866
+ if (payload.targetId !== TARGET_HOST) throw new Error("target host is invalid")
1867
+ const request = canonicalHealthProbeRequest({ label: payload.label, scenarioHandleDigest: payload.scenarioHandleDigest })
1868
+ if (operation === "start_health_probe") {
1869
+ const start = async () => {
1870
+ const snapshot = object(await dependencies.containerSnapshot(), "health probe production owner")
1871
+ if (snapshot.running !== true || snapshot.health !== "healthy") throw new Error("health probe requires a healthy production owner")
1872
+ const coordinates = healthProbeCoordinates(payload, snapshot)
1873
+ if (!dependencies.startHealthProbe) throw new Error("health probe start is unavailable")
1874
+ return await dependencies.startHealthProbe(coordinates)
1875
+ }
1876
+ if (dependencies.ownerMutationCoordinator) return await dependencies.ownerMutationCoordinator.healthStart(request.scenarioHandleDigest, start)
1877
+ return dependencies.healthProbeCoordinator ? await dependencies.healthProbeCoordinator.start(request.scenarioHandleDigest, start) : await start()
1878
+ }
1879
+ if (operation === "health_probe_status") {
1880
+ if (!dependencies.healthProbeStatus) throw new Error("health probe status is unavailable")
1881
+ const status = () => dependencies.healthProbeStatus(request)
1882
+ return dependencies.ownerMutationCoordinator ? await dependencies.ownerMutationCoordinator.healthOperation(request.scenarioHandleDigest, status) : await status()
1883
+ }
1884
+ const recover = async () => {
1885
+ if (!dependencies.recoverHealthProbe) throw new Error("health probe recovery is unavailable")
1886
+ const ownerSnapshot = object(await (dependencies.containerOwnerSnapshot ?? dependencies.containerSnapshot)(), "health probe production owner")
1887
+ const coordinates = healthProbeCoordinates(payload, ownerSnapshot)
1888
+ return await dependencies.recoverHealthProbe(coordinates)
1889
+ }
1890
+ if (dependencies.ownerMutationCoordinator) return await dependencies.ownerMutationCoordinator.healthRecover(request.scenarioHandleDigest, recover)
1891
+ return dependencies.healthProbeCoordinator ? await dependencies.healthProbeCoordinator.recover(request.scenarioHandleDigest, recover) : await recover()
1892
+ }
1893
+ if (operation === "drive_timeout_stale" || operation === "drive_duplicate_callbacks" || operation === "drive_restart_continuation") {
1894
+ exactKeys(payload, ["operation", "targetId", "label", "scenarioHandleDigest"], operation)
1895
+ if (payload.targetId !== TARGET_HOST) throw new Error("target host is invalid")
1896
+ const expectedLabel = operation === "drive_timeout_stale" ? "unit-16k-timeout-stale"
1897
+ : operation === "drive_duplicate_callbacks" ? "unit-16l-duplicate-callback" : "unit-16m-restart-continuation"
1898
+ const input = canonicalInteractiveRequest({ label: payload.label, scenarioHandleDigest: payload.scenarioHandleDigest }, expectedLabel)
1899
+ if (dependencies.healthOwnerMutationActive?.()) throw new Error("owner mutation refused while durable health probe artifacts are active")
1900
+ const drive = operation === "drive_timeout_stale" ? dependencies.driveTimeoutStale
1901
+ : operation === "drive_duplicate_callbacks" ? dependencies.driveDuplicateCallbacks : dependencies.driveRestartContinuation
1902
+ if (!drive) throw new Error("interactive production runtime driver is unavailable")
1903
+ const execute = async () => operation === "drive_timeout_stale"
1904
+ ? await drive(input)
1905
+ : requireInteractiveReceipt(await drive(input), input)
1906
+ if (operation === "drive_restart_continuation") {
1907
+ if (!dependencies.interactiveRestartDriver) throw new Error("interactive restart driver is unavailable")
1908
+ const owned = () => dependencies.ownerMutationCoordinator ? dependencies.ownerMutationCoordinator.interactive(execute) : execute()
1909
+ const response = dependencies.interactiveRestartDriver.poll(input, owned)
1910
+ if (response.state === "waiting") Object.defineProperty(response, ASYNC_RESTART_SCENARIO, { value: input.scenarioHandleDigest })
1911
+ return response
1912
+ }
1913
+ return dependencies.ownerMutationCoordinator ? await dependencies.ownerMutationCoordinator.interactive(execute) : await execute()
1914
+ }
1915
+ throw new Error("host broker operation is not whitelisted")
1916
+ }
1917
+
1918
+ function writeClosedInventory(file) {
1919
+ const value = { keys: inventoryRecords().map(publicRecord) }
1920
+ const fd = openSync(file, constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL, 0o600)
1921
+ try { writeFileSync(fd, `${JSON.stringify(value)}\n`); fsyncSync(fd) } finally { closeSync(fd) }
1922
+ chownSync(file, 0, 0)
1923
+ }
1924
+
1925
+ function createBrokerServer(dispatchRequest = dispatch) {
1926
+ const dispatchDrain = createDispatchDrain()
1927
+ const connections = new Set()
1928
+ const server = createServer({ allowHalfOpen: true }, (connection) => {
1929
+ connections.add(connection)
1930
+ connection.setTimeout(30_000, () => connection.destroy())
1931
+ connection.once("close", () => connections.delete(connection))
1932
+ let input = ""
1933
+ connection.setEncoding("utf8")
1934
+ connection.on("data", (chunk) => {
1935
+ input += chunk
1936
+ if (Buffer.byteLength(input) > MAX_REQUEST) connection.destroy()
1937
+ })
1938
+ connection.on("end", async () => {
1939
+ try {
1940
+ const result = await dispatchDrain.run(() => dispatchRequest(JSON.parse(input)))
1941
+ const scenario = result?.[ASYNC_RESTART_SCENARIO]
1942
+ const completed = scenario ? armRestartAfterResponseClosed(connection, scenario) : undefined
1943
+ connection.end(`${JSON.stringify({ ok: true, result })}\n`, completed)
1944
+ } catch { connection.end(`${JSON.stringify({ ok: false, error: "host operation failed" })}\n`) }
1945
+ })
1946
+ })
1947
+ const destroyConnections = () => { for (const connection of connections) connection.destroy() }
1948
+ return { server, dispatchDrain, destroyConnections }
1949
+ }
1950
+
1951
+ async function main() {
1952
+ if (process.getuid?.() !== 0) throw new Error("host broker must run as root")
1953
+ const [profileName, targetContainerId, socket, closedInventory, expectedImage, initialSnapshot] = process.argv.slice(2)
1954
+ if (!profileName || !targetContainerId || !socket || !closedInventory || !expectedImage || !initialSnapshot || process.argv.length !== 8) throw new Error("usage: broker <staging|final> <target-container-id> <socket> <closed-inventory> <expected-image-id> <initial-snapshot>")
1955
+ activeProfile = targetProfile(profileName)
1956
+ activeContainer = activeProfile.containerName
1957
+ activeContainerId = text(targetContainerId, "attested target container id", SHA256)
1958
+ expectedImageId = text(expectedImage, "expected image id", IMAGE_ID)
1959
+ rmSync(socket, { force: true })
1960
+ writeClosedInventory(closedInventory)
1961
+ const snapshot = await containerSnapshot(expectedImageId)
1962
+ const snapshotFd = openSync(initialSnapshot, constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL, 0o600)
1963
+ try { writeFileSync(snapshotFd, `${JSON.stringify(snapshot)}\n`); fsyncSync(snapshotFd) } finally { closeSync(snapshotFd) }
1964
+ chownSync(initialSnapshot, 0, 0)
1965
+ const { server, dispatchDrain, destroyConnections } = createBrokerServer()
1966
+ server.listen(socket, () => {
1967
+ chownSync(socket, 0, 10001)
1968
+ chmodSync(socket, 0o660)
1969
+ })
1970
+ let shuttingDown = false
1971
+ const shutdown = async () => {
1972
+ if (shuttingDown) return
1973
+ shuttingDown = true
1974
+ server.close()
1975
+ destroyConnections()
1976
+ await dispatchDrain.stopAndDrain()
1977
+ await interactiveRestartDriver.stopAndDrain()
1978
+ let failed = false
1979
+ for (const record of [...activeHealthProbes.values()]) {
1980
+ try { await recoverHealthProbe(record.input) } catch { failed = true }
1981
+ }
1982
+ process.exitCode = failed ? 1 : 0
1983
+ }
1984
+ process.once("SIGTERM", () => { void shutdown() })
1985
+ process.once("SIGINT", () => { void shutdown() })
1986
+ }
1987
+
1988
+ if (process.argv[1]?.endsWith("sanctuary-unit16-host-broker.mjs")) {
1989
+ main().catch(() => { process.exitCode = 1 })
1990
+ }
1991
+
1992
+ export {
1993
+ acknowledgeStoredUnraidKey,
1994
+ assertStableContainerProcess,
1995
+ attestHealthProbeProcessAbsent,
1996
+ armRestartAfterResponseClosed,
1997
+ completeHealthProbeFromReceipt,
1998
+ createDispatchDrain,
1999
+ createBrokerServer,
2000
+ createHealthProbeOperationCoordinator,
2001
+ createOwnerMutationCoordinator,
2002
+ createUnraidKey,
2003
+ createInteractiveRestartDriver,
2004
+ dispatch,
2005
+ denialTargetSnapshot,
2006
+ healthProbeArtifactDisposition,
2007
+ healthProbeDockerArgs,
2008
+ healthProbeOperationBudgets,
2009
+ finalizeHealthProbeAfterAttestation,
2010
+ healthOwnerMutationActive,
2011
+ liveContainerProcessUser,
2012
+ liveContainerProcessIdentity,
2013
+ parseProcStartTime,
2014
+ productionProcessBindingDigest,
2015
+ observeRebootPreflight,
2016
+ parseVaultStatus,
2017
+ optionalStoppedContainerExact,
2018
+ probeRevokedUnraidKey,
2019
+ queryGraphqlAutostart,
2020
+ readBoundedProcStatus,
2021
+ driveDuplicateCallbacks,
2022
+ driveTimeoutStale,
2023
+ driveRestartContinuation,
2024
+ restartButlerForAcceptance,
2025
+ runInteractiveDriver,
2026
+ recoverAfterHealthProbeTermination,
2027
+ requireStableHealthProbeOwner,
2028
+ requireHealthProbeCompleteAttestation,
2029
+ terminateHealthProbeChild,
2030
+ }