@ouro.bot/cli 0.1.0-alpha.83 → 0.1.0-alpha.831

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 (647) 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/assets/sanctuary-host-launcher.sh +16 -0
  15. package/changelog.json +5308 -16
  16. package/deploy/unraid/Dockerfile +54 -0
  17. package/deploy/unraid/README.txt +2983 -0
  18. package/deploy/unraid/audit-container-spec.sh +4 -0
  19. package/deploy/unraid/container-runtime.json +1 -0
  20. package/deploy/unraid/docker-man-template-transaction.mjs +711 -0
  21. package/deploy/unraid/docker-man-template-xml.cjs +105 -0
  22. package/deploy/unraid/migrate-sanctuary-bundle.mjs +60 -0
  23. package/deploy/unraid/ouro-events/bootstrap-spool.sh +92 -0
  24. package/deploy/unraid/ouro-events/emit-event.mjs +310 -0
  25. package/deploy/unraid/ouro-events/emit-usenet-event.sh +54 -0
  26. package/deploy/unraid/ouro-events/install-usenet-guard.sh +435 -0
  27. package/deploy/unraid/ouro-events/usenet-health.sh +176 -0
  28. package/deploy/unraid/sanctuary-acceptance-adapter.sh +43 -0
  29. package/deploy/unraid/sanctuary-acceptance-contract.json +198 -0
  30. package/deploy/unraid/sanctuary-acceptance-harness.sh +23 -0
  31. package/deploy/unraid/sanctuary-authority-installation.json +36 -0
  32. package/deploy/unraid/sanctuary-authority-service.sh +30 -0
  33. package/deploy/unraid/sanctuary-deployment-target.mjs +710 -0
  34. package/deploy/unraid/sanctuary-safe-rename.py +24 -0
  35. package/deploy/unraid/sanctuary-unit16-host-broker.mjs +2084 -0
  36. package/deploy/unraid/sanctuary-unit16-run.sh +560 -0
  37. package/deploy/unraid/sanctuary-unit18-target-audit.sh +27 -0
  38. package/deploy/unraid/sanctuary.ouro/agent.json +66 -0
  39. package/deploy/unraid/sanctuary.ouro/arc/README.md +3 -0
  40. package/deploy/unraid/sanctuary.ouro/bundle-meta.json +5 -0
  41. package/deploy/unraid/sanctuary.ouro/habits/sanctuary-health.md +8 -0
  42. package/deploy/unraid/sanctuary.ouro/mcp/media-mcp.mjs +795 -0
  43. package/deploy/unraid/sanctuary.ouro/provider-readiness.json +11 -0
  44. package/deploy/unraid/sanctuary.ouro/psyche/ASPIRATIONS.md +5 -0
  45. package/deploy/unraid/sanctuary.ouro/psyche/IDENTITY.md +5 -0
  46. package/deploy/unraid/sanctuary.ouro/psyche/LORE.md +15 -0
  47. package/deploy/unraid/sanctuary.ouro/psyche/SOUL.md +25 -0
  48. package/deploy/unraid/sanctuary.ouro/psyche/TACIT.md +15 -0
  49. package/deploy/unraid/sanctuary.ouro/state/policy/steward.json +7 -0
  50. package/deploy/unraid/sanctuary.ouro/tool-profiles.json +135 -0
  51. package/deploy/unraid/sanctuary.xml +28 -0
  52. package/dist/a2a/card.js +57 -0
  53. package/dist/a2a/client.js +180 -0
  54. package/dist/a2a/config.js +50 -0
  55. package/dist/a2a/delegation-stores.js +56 -0
  56. package/dist/a2a/did-resolution.js +93 -0
  57. package/dist/a2a/identity.js +170 -0
  58. package/dist/a2a/inbound-share.js +107 -0
  59. package/dist/a2a/mission-result-wire.js +196 -0
  60. package/dist/a2a/onboarding.js +115 -0
  61. package/dist/a2a/pin-store.js +132 -0
  62. package/dist/a2a/seen-ledger.js +120 -0
  63. package/dist/a2a/server.js +604 -0
  64. package/dist/a2a/task-store.js +69 -0
  65. package/dist/a2a/transport.js +45 -0
  66. package/dist/a2a/types.js +3 -0
  67. package/dist/arc/attention-types.js +8 -0
  68. package/dist/arc/cares.js +360 -0
  69. package/dist/arc/episodes.js +118 -0
  70. package/dist/arc/evolution.js +490 -0
  71. package/dist/arc/flight-recorder.js +1034 -0
  72. package/dist/arc/intentions.js +134 -0
  73. package/dist/arc/json-store.js +117 -0
  74. package/dist/arc/obligations.js +386 -0
  75. package/dist/arc/packets.js +336 -0
  76. package/dist/arc/presence.js +185 -0
  77. package/dist/arc/task-lifecycle.js +57 -0
  78. package/dist/commerce/store.js +755 -0
  79. package/dist/commerce/types.js +3 -0
  80. package/dist/heart/active-work.js +798 -40
  81. package/dist/heart/agent-entry.js +252 -3
  82. package/dist/heart/approval-files.js +113 -0
  83. package/dist/heart/approval-store.js +960 -0
  84. package/dist/heart/attachments/image-normalize.js +194 -0
  85. package/dist/heart/attachments/materialize.js +97 -0
  86. package/dist/heart/attachments/originals.js +88 -0
  87. package/dist/heart/attachments/render.js +29 -0
  88. package/dist/heart/attachments/sources/bluebubbles.js +156 -0
  89. package/dist/heart/attachments/sources/cli-local-file.js +78 -0
  90. package/dist/heart/attachments/sources/index.js +18 -0
  91. package/dist/heart/attachments/sources/telegram.js +71 -0
  92. package/dist/heart/attachments/store.js +132 -0
  93. package/dist/heart/attachments/types.js +93 -0
  94. package/dist/heart/auth/auth-flow.js +495 -0
  95. package/dist/heart/autonomy-budget.js +478 -0
  96. package/dist/heart/awaiting/await-alert.js +148 -0
  97. package/dist/heart/awaiting/await-expiry.js +143 -0
  98. package/dist/heart/awaiting/await-loader.js +91 -0
  99. package/dist/heart/awaiting/await-parser.js +149 -0
  100. package/dist/heart/awaiting/await-runtime-state.js +111 -0
  101. package/dist/heart/awaiting/await-scheduler.js +382 -0
  102. package/dist/heart/background-operations.js +281 -0
  103. package/dist/heart/bridges/manager.js +137 -17
  104. package/dist/heart/bridges/store.js +14 -2
  105. package/dist/heart/bundle-state.js +168 -0
  106. package/dist/heart/commitments.js +44 -18
  107. package/dist/heart/config-registry.js +331 -0
  108. package/dist/heart/config.js +174 -131
  109. package/dist/heart/context-loss-gauntlet.js +387 -0
  110. package/dist/heart/context-loss-sentinel.js +1157 -0
  111. package/dist/heart/core.js +2169 -581
  112. package/dist/heart/cross-chat-delivery.js +9 -22
  113. package/dist/heart/daemon/agent-config-check.js +451 -0
  114. package/dist/heart/daemon/agent-discovery.js +185 -3
  115. package/dist/heart/daemon/agent-service.js +542 -0
  116. package/dist/heart/daemon/agentic-repair.js +547 -0
  117. package/dist/heart/daemon/await-private-wake.js +59 -0
  118. package/dist/heart/daemon/bluebubbles-cli-integration.js +71 -0
  119. package/dist/heart/daemon/bluebubbles-health-diagnostics.js +122 -0
  120. package/dist/heart/daemon/bluebubbles-host-protocol.js +433 -0
  121. package/dist/heart/daemon/bluebubbles-host.js +273 -0
  122. package/dist/heart/daemon/boot-sync-probe.js +197 -0
  123. package/dist/heart/daemon/cadence.js +180 -0
  124. package/dist/heart/daemon/cli-defaults.js +854 -0
  125. package/dist/heart/daemon/cli-desk.js +322 -0
  126. package/dist/heart/daemon/cli-exec.js +8822 -0
  127. package/dist/heart/daemon/cli-help.js +716 -0
  128. package/dist/heart/daemon/cli-parse.js +2771 -0
  129. package/dist/heart/daemon/cli-render-doctor.js +57 -0
  130. package/dist/heart/daemon/cli-render.js +777 -0
  131. package/dist/heart/daemon/cli-types.js +8 -0
  132. package/dist/heart/daemon/connect-bay.js +323 -0
  133. package/dist/heart/daemon/container-credential-bootstrap.js +273 -0
  134. package/dist/heart/daemon/container-healthcheck.js +112 -0
  135. package/dist/heart/daemon/container-runtime.js +97 -0
  136. package/dist/heart/daemon/container-spec-auditor-main.js +171 -0
  137. package/dist/heart/daemon/container-spec-auditor.js +229 -0
  138. package/dist/heart/daemon/daemon-bootstrap-startup.js +115 -0
  139. package/dist/heart/daemon/daemon-cli.js +29 -2349
  140. package/dist/heart/daemon/daemon-entry.js +930 -11
  141. package/dist/heart/daemon/daemon-health.js +186 -0
  142. package/dist/heart/daemon/daemon-rollup.js +57 -0
  143. package/dist/heart/daemon/daemon-runtime-sync.js +88 -13
  144. package/dist/heart/daemon/daemon-tombstone.js +236 -0
  145. package/dist/heart/daemon/daemon.js +2084 -105
  146. package/dist/heart/daemon/dns-workflow.js +394 -0
  147. package/dist/heart/daemon/doctor-types.js +8 -0
  148. package/dist/heart/daemon/doctor.js +1755 -0
  149. package/dist/heart/daemon/freshness.js +345 -0
  150. package/dist/heart/daemon/habit-private-wake.js +61 -0
  151. package/dist/heart/daemon/health-monitor.js +142 -11
  152. package/dist/heart/daemon/hooks/agent-config-v2.js +33 -0
  153. package/dist/heart/daemon/hooks/bundle-meta.js +135 -1
  154. package/dist/heart/daemon/http-health-probe.js +80 -0
  155. package/dist/heart/daemon/human-command-screens.js +234 -0
  156. package/dist/heart/daemon/human-readiness.js +114 -0
  157. package/dist/heart/daemon/inner-status.js +78 -0
  158. package/dist/heart/daemon/interactive-repair.js +394 -0
  159. package/dist/heart/daemon/launchd.js +37 -8
  160. package/dist/heart/daemon/log-tailer.js +79 -10
  161. package/dist/heart/daemon/logs-prune.js +132 -0
  162. package/dist/heart/daemon/mcp-canary.js +524 -0
  163. package/dist/heart/daemon/message-router.js +65 -5
  164. package/dist/heart/daemon/migrate-to-desk.js +848 -0
  165. package/dist/heart/daemon/os-cron-deps.js +135 -0
  166. package/dist/heart/daemon/os-cron.js +22 -14
  167. package/dist/heart/daemon/ouro-bot-entry.js +4 -2
  168. package/dist/heart/daemon/ouro-entry.js +3 -1
  169. package/dist/heart/daemon/plugin-cli.js +432 -0
  170. package/dist/heart/daemon/process-manager.js +733 -61
  171. package/dist/heart/daemon/provider-discovery.js +137 -0
  172. package/dist/heart/daemon/provider-ping-progress.js +83 -0
  173. package/dist/heart/daemon/prunable-bundle.js +144 -0
  174. package/dist/heart/daemon/pulse.js +511 -0
  175. package/dist/heart/daemon/readiness-repair.js +365 -0
  176. package/dist/heart/daemon/run-hooks.js +2 -0
  177. package/dist/heart/daemon/runtime-logging.js +47 -14
  178. package/dist/heart/daemon/runtime-metadata.js +2 -30
  179. package/dist/heart/daemon/runtime-mode.js +13 -2
  180. package/dist/heart/daemon/safe-mode.js +161 -0
  181. package/dist/heart/daemon/sanctuary-acceptance-adapter.js +2654 -0
  182. package/dist/heart/daemon/sanctuary-acceptance-harness.js +1830 -0
  183. package/dist/heart/daemon/sanctuary-acceptance-marker.js +267 -0
  184. package/dist/heart/daemon/sanctuary-acceptance-scenarios.js +955 -0
  185. package/dist/heart/daemon/sanctuary-authority-codec.js +86 -0
  186. package/dist/heart/daemon/sanctuary-authority-epoch.js +256 -0
  187. package/dist/heart/daemon/sanctuary-authority-installation.js +140 -0
  188. package/dist/heart/daemon/sanctuary-authority-ledger.js +241 -0
  189. package/dist/heart/daemon/sanctuary-authority-root-lifecycle.js +819 -0
  190. package/dist/heart/daemon/sanctuary-authority-vault-migration.js +79 -0
  191. package/dist/heart/daemon/sanctuary-bundle-migration.js +729 -0
  192. package/dist/heart/daemon/sanctuary-host-authority.js +1008 -0
  193. package/dist/heart/daemon/sanctuary-host-detached-supervisor.js +494 -0
  194. package/dist/heart/daemon/sanctuary-host-executor.js +670 -0
  195. package/dist/heart/daemon/sanctuary-host-linux-kernel.js +334 -0
  196. package/dist/heart/daemon/sanctuary-host-supervisor-entry.js +207 -0
  197. package/dist/heart/daemon/sanctuary-host-supervisor.js +95 -0
  198. package/dist/heart/daemon/sanctuary-package-management.js +103 -0
  199. package/dist/heart/daemon/sanctuary-scheduler-liveness.js +227 -0
  200. package/dist/heart/daemon/sanctuary-scheduler-origin.js +132 -0
  201. package/dist/heart/daemon/sanctuary-telegram-authority-entry.js +445 -0
  202. package/dist/heart/daemon/sanctuary-telegram-authority-gateway.js +585 -0
  203. package/dist/heart/daemon/sanctuary-telegram-authority-service.js +630 -0
  204. package/dist/heart/daemon/sense-manager.js +699 -48
  205. package/dist/heart/daemon/session-id-resolver.js +131 -0
  206. package/dist/heart/daemon/skill-management-installer.js +1 -1
  207. package/dist/heart/daemon/socket-client.js +180 -12
  208. package/dist/heart/daemon/stale-bundle-prune.js +113 -0
  209. package/dist/heart/daemon/startup-tui.js +339 -0
  210. package/dist/heart/daemon/supercronic-supervisor.js +282 -0
  211. package/dist/heart/daemon/task-scheduler.js +117 -39
  212. package/dist/heart/daemon/terminal-ui.js +499 -0
  213. package/dist/heart/daemon/thoughts.js +138 -54
  214. package/dist/heart/daemon/up-progress.js +366 -0
  215. package/dist/heart/daemon/vault-items.js +56 -0
  216. package/dist/heart/delegation.js +1 -4
  217. package/dist/heart/external-events/router.js +1430 -0
  218. package/dist/heart/frontend-approval-runtime.js +506 -0
  219. package/dist/heart/frontend-journal.js +215 -0
  220. package/dist/heart/frontend-session-service.js +237 -0
  221. package/dist/heart/frontend-socket-client.js +164 -0
  222. package/dist/heart/frontend-socket.js +400 -0
  223. package/dist/heart/habits/habit-cancel.js +810 -0
  224. package/dist/heart/habits/habit-lifecycle.js +1327 -0
  225. package/dist/heart/habits/habit-migration.js +194 -0
  226. package/dist/heart/habits/habit-parser.js +278 -0
  227. package/dist/heart/habits/habit-runtime-state.js +93 -0
  228. package/dist/heart/habits/habit-scheduler.js +653 -0
  229. package/dist/heart/habits/habit-session-summary.js +318 -0
  230. package/dist/heart/habits/habit-session.js +636 -0
  231. package/dist/heart/{daemon → hatch}/hatch-flow.js +55 -62
  232. package/dist/heart/{daemon → hatch}/hatch-specialist.js +6 -8
  233. package/dist/heart/{daemon → hatch}/specialist-prompt.js +12 -9
  234. package/dist/heart/{daemon → hatch}/specialist-tools.js +53 -18
  235. package/dist/heart/identity.js +196 -59
  236. package/dist/heart/kept-notes.js +289 -0
  237. package/dist/heart/kicks.js +1 -1
  238. package/dist/heart/machine-identity.js +161 -0
  239. package/dist/heart/mail-import-discovery.js +356 -0
  240. package/dist/heart/mailbox/mailbox-http-hooks.js +98 -0
  241. package/dist/heart/mailbox/mailbox-http-response.js +7 -0
  242. package/dist/heart/mailbox/mailbox-http-routes.js +398 -0
  243. package/dist/heart/mailbox/mailbox-http-static.js +103 -0
  244. package/dist/heart/mailbox/mailbox-http-transport.js +129 -0
  245. package/dist/heart/mailbox/mailbox-http.js +99 -0
  246. package/dist/heart/mailbox/mailbox-read.js +39 -0
  247. package/dist/heart/mailbox/mailbox-types.js +27 -0
  248. package/dist/heart/mailbox/mailbox-view.js +197 -0
  249. package/dist/heart/mailbox/readers/agent-machine.js +428 -0
  250. package/dist/heart/mailbox/readers/continuity-readers.js +329 -0
  251. package/dist/heart/mailbox/readers/mail.js +375 -0
  252. package/dist/heart/mailbox/readers/runtime-readers.js +823 -0
  253. package/dist/heart/mailbox/readers/sessions.js +231 -0
  254. package/dist/heart/mailbox/readers/shared.js +111 -0
  255. package/dist/heart/mcp/mcp-server.js +696 -0
  256. package/dist/heart/migrate-config.js +100 -0
  257. package/dist/heart/model-capabilities.js +11 -0
  258. package/dist/heart/orientation-frame.js +345 -0
  259. package/dist/heart/platform.js +81 -0
  260. package/dist/heart/primitives.js +1 -1
  261. package/dist/heart/private-runtime/decision-renderer.js +158 -0
  262. package/dist/heart/private-runtime/index.js +18 -0
  263. package/dist/heart/private-runtime/ledger.js +221 -0
  264. package/dist/heart/private-runtime/policy.js +252 -0
  265. package/dist/heart/private-runtime/types.js +2 -0
  266. package/dist/heart/progress-story.js +1 -1
  267. package/dist/heart/provider-attempt.js +137 -0
  268. package/dist/heart/provider-binding-resolver.js +273 -0
  269. package/dist/heart/provider-credentials.js +449 -0
  270. package/dist/heart/provider-failover.js +311 -0
  271. package/dist/heart/provider-models.js +96 -0
  272. package/dist/heart/provider-ping.js +265 -0
  273. package/dist/heart/provider-readiness-cache.js +119 -0
  274. package/dist/heart/provider-visibility.js +188 -0
  275. package/dist/heart/providers/anthropic-token.js +131 -0
  276. package/dist/heart/providers/anthropic.js +173 -60
  277. package/dist/heart/providers/azure.js +23 -11
  278. package/dist/heart/providers/error-classification.js +127 -0
  279. package/dist/heart/providers/github-copilot.js +34 -38
  280. package/dist/heart/providers/minimax-vlm.js +189 -0
  281. package/dist/heart/providers/minimax.js +26 -8
  282. package/dist/heart/providers/openai-codex-token.js +349 -0
  283. package/dist/heart/providers/openai-codex.js +55 -40
  284. package/dist/heart/providers/openai-compatible.js +177 -0
  285. package/dist/heart/run-ledger.js +247 -0
  286. package/dist/heart/runtime-capability-check.js +170 -0
  287. package/dist/heart/runtime-credentials.js +607 -0
  288. package/dist/heart/runtime-cwd.js +87 -0
  289. package/dist/heart/sense-truth.js +22 -4
  290. package/dist/heart/session-activity.js +92 -24
  291. package/dist/heart/session-events.js +1504 -0
  292. package/dist/heart/session-playback-cli-main.js +5 -0
  293. package/dist/heart/session-playback-cli.js +36 -0
  294. package/dist/heart/session-playback.js +231 -0
  295. package/dist/heart/session-redaction-repair-cli-main.js +31 -0
  296. package/dist/heart/session-redaction-repair.js +552 -0
  297. package/dist/heart/session-stats-cli-main.js +5 -0
  298. package/dist/heart/session-stats.js +183 -0
  299. package/dist/heart/session-transcript.js +133 -0
  300. package/dist/heart/start-of-turn-packet.js +372 -0
  301. package/dist/heart/steward-policy.js +618 -0
  302. package/dist/heart/streaming.js +604 -207
  303. package/dist/heart/structured-output.js +196 -0
  304. package/dist/heart/sync-classification.js +176 -0
  305. package/dist/heart/sync.js +449 -0
  306. package/dist/heart/target-resolution.js +11 -7
  307. package/dist/heart/tempo.js +93 -0
  308. package/dist/heart/temporal-view.js +41 -0
  309. package/dist/heart/timeouts.js +101 -0
  310. package/dist/heart/tool-activity-callbacks.js +59 -0
  311. package/dist/heart/tool-approval.js +382 -0
  312. package/dist/heart/tool-description.js +155 -0
  313. package/dist/heart/tool-friction.js +55 -0
  314. package/dist/heart/tool-loop.js +200 -0
  315. package/dist/heart/turn-context.js +482 -0
  316. package/dist/heart/turn-execution-lease.js +30 -0
  317. package/dist/heart/{daemon → versioning}/ouro-bot-global-installer.js +6 -5
  318. package/dist/heart/{daemon → versioning}/ouro-bot-wrapper.js +1 -1
  319. package/dist/heart/versioning/ouro-path-installer.js +432 -0
  320. package/dist/heart/versioning/ouro-recovery-launcher.js +76 -0
  321. package/dist/heart/versioning/ouro-version-manager.js +409 -0
  322. package/dist/heart/{daemon → versioning}/staged-restart.js +40 -8
  323. package/dist/heart/{daemon → versioning}/update-checker.js +6 -1
  324. package/dist/heart/versioning/update-hooks.js +154 -0
  325. package/dist/heart/versioning/version-intent.js +114 -0
  326. package/dist/heart/work-card.js +380 -0
  327. package/dist/mailbox-ui/assets/index-Cyv4mw3Y.js +1 -0
  328. package/dist/mailbox-ui/assets/index-Du_9G9WO.css +1 -0
  329. package/dist/mailbox-ui/assets/vendor-CcN1XpQ9.js +61 -0
  330. package/dist/mailbox-ui/index.html +16 -0
  331. package/dist/mailroom/attention.js +167 -0
  332. package/dist/mailroom/autonomy.js +209 -0
  333. package/dist/mailroom/blob-store.js +889 -0
  334. package/dist/mailroom/body-cache.js +61 -0
  335. package/dist/mailroom/cache-sync-cli.js +58 -0
  336. package/dist/mailroom/core.js +803 -0
  337. package/dist/mailroom/entry.js +160 -0
  338. package/dist/mailroom/file-store.js +568 -0
  339. package/dist/mailroom/hosted-cache-sync.js +384 -0
  340. package/dist/mailroom/mbox-import.js +393 -0
  341. package/dist/mailroom/migration.js +164 -0
  342. package/dist/mailroom/outbound.js +380 -0
  343. package/dist/mailroom/policy.js +263 -0
  344. package/dist/mailroom/reader.js +292 -0
  345. package/dist/mailroom/search-cache.js +513 -0
  346. package/dist/mailroom/search-relevance.js +319 -0
  347. package/dist/mailroom/smtp-ingress.js +176 -0
  348. package/dist/mailroom/source-state.js +176 -0
  349. package/dist/mailroom/thread.js +109 -0
  350. package/dist/mailroom/travel-extract.js +89 -0
  351. package/dist/mind/bundle-manifest.js +24 -2
  352. package/dist/mind/context.js +309 -111
  353. package/dist/mind/desk-section.js +362 -0
  354. package/dist/mind/diary-integrity.js +60 -0
  355. package/dist/mind/{memory.js → diary.js} +68 -77
  356. package/dist/mind/embedding-provider.js +60 -0
  357. package/dist/mind/file-state.js +179 -0
  358. package/dist/mind/{associative-recall.js → note-search.js} +47 -58
  359. package/dist/mind/obligation-steering.js +199 -3
  360. package/dist/mind/pending.js +36 -10
  361. package/dist/mind/prompt-budget.js +479 -0
  362. package/dist/mind/prompt-refresh.js +3 -2
  363. package/dist/mind/prompt.js +1050 -189
  364. package/dist/mind/provenance-trust.js +26 -0
  365. package/dist/mind/record-paths.js +312 -0
  366. package/dist/mind/scrutiny.js +173 -0
  367. package/dist/mind/session-transaction.js +564 -0
  368. package/dist/nerves/cli-logging.js +7 -1
  369. package/dist/nerves/coverage/audit-rules.js +15 -6
  370. package/dist/nerves/coverage/audit.js +28 -2
  371. package/dist/nerves/coverage/cli.js +1 -1
  372. package/dist/nerves/coverage/contract.js +5 -5
  373. package/dist/nerves/coverage/file-completeness.js +139 -5
  374. package/dist/nerves/coverage/run-artifacts.js +55 -35
  375. package/dist/nerves/coverage/source-scanner.js +4 -4
  376. package/dist/nerves/event-buffer.js +111 -0
  377. package/dist/nerves/index.js +293 -9
  378. package/dist/nerves/observation.js +20 -0
  379. package/dist/nerves/redact.js +79 -0
  380. package/dist/nerves/review/cli-main.js +5 -0
  381. package/dist/nerves/review/cli.js +156 -0
  382. package/dist/nerves/review/core.js +159 -0
  383. package/dist/nerves/runtime.js +17 -1
  384. package/dist/repertoire/ado-client.js +15 -56
  385. package/dist/repertoire/ado-semantic.js +16 -10
  386. package/dist/repertoire/api-client.js +97 -0
  387. package/dist/repertoire/bitwarden-store.js +1045 -0
  388. package/dist/repertoire/bundle-templates.js +71 -0
  389. package/dist/repertoire/bw-installer.js +180 -0
  390. package/dist/repertoire/coding/codex-jsonl.js +64 -0
  391. package/dist/repertoire/coding/context-pack.js +331 -0
  392. package/dist/repertoire/coding/feedback.js +174 -32
  393. package/dist/repertoire/coding/index.js +15 -3
  394. package/dist/repertoire/coding/manager.js +159 -10
  395. package/dist/repertoire/coding/spawner.js +58 -11
  396. package/dist/repertoire/coding/tools.js +221 -15
  397. package/dist/repertoire/coding/workbench-client.js +272 -0
  398. package/dist/repertoire/coding/workbench-manager.js +346 -0
  399. package/dist/repertoire/commerce-errors.js +109 -0
  400. package/dist/repertoire/commerce-self-test.js +156 -0
  401. package/dist/repertoire/credential-access.js +178 -0
  402. package/dist/repertoire/desk/classifier.js +362 -0
  403. package/dist/repertoire/duffel-client.js +185 -0
  404. package/dist/repertoire/github-client.js +14 -55
  405. package/dist/repertoire/graph-client.js +11 -52
  406. package/dist/repertoire/guardrails.js +811 -18
  407. package/dist/repertoire/mcp-client.js +44 -3
  408. package/dist/repertoire/mcp-manager.js +427 -139
  409. package/dist/repertoire/mcp-tools.js +124 -0
  410. package/dist/repertoire/plugin-mcp.js +175 -0
  411. package/dist/repertoire/plugins.js +253 -0
  412. package/dist/repertoire/relationship-authorization.js +322 -0
  413. package/dist/repertoire/shell-sessions.js +134 -0
  414. package/dist/repertoire/skills.js +48 -4
  415. package/dist/repertoire/stripe-client.js +131 -0
  416. package/dist/repertoire/tool-arguments.js +170 -0
  417. package/dist/repertoire/tool-results.js +29 -0
  418. package/dist/repertoire/tools-a2a.js +699 -0
  419. package/dist/repertoire/tools-attachments.js +322 -0
  420. package/dist/repertoire/tools-awaiting.js +575 -0
  421. package/dist/repertoire/tools-base.js +77 -1138
  422. package/dist/repertoire/tools-bluebubbles.js +2 -0
  423. package/dist/repertoire/tools-bridge.js +144 -0
  424. package/dist/repertoire/tools-bundle.js +993 -0
  425. package/dist/repertoire/tools-commerce.js +253 -0
  426. package/dist/repertoire/tools-config.js +186 -0
  427. package/dist/repertoire/tools-continuity.js +575 -0
  428. package/dist/repertoire/tools-credential.js +383 -0
  429. package/dist/repertoire/tools-evolution.js +527 -0
  430. package/dist/repertoire/tools-files.js +344 -0
  431. package/dist/repertoire/tools-flight.js +290 -0
  432. package/dist/repertoire/tools-flow.js +119 -0
  433. package/dist/repertoire/tools-github.js +3 -8
  434. package/dist/repertoire/tools-habits.js +103 -0
  435. package/dist/repertoire/tools-mail.js +1828 -0
  436. package/dist/repertoire/tools-notes.js +485 -0
  437. package/dist/repertoire/tools-obligations.js +182 -0
  438. package/dist/repertoire/tools-orientation.js +31 -0
  439. package/dist/repertoire/tools-record.js +482 -0
  440. package/dist/repertoire/tools-rsvp.js +139 -0
  441. package/dist/repertoire/tools-runtime.js +150 -0
  442. package/dist/repertoire/tools-sanctuary-host.js +202 -0
  443. package/dist/repertoire/tools-session.js +1133 -0
  444. package/dist/repertoire/tools-shell.js +149 -0
  445. package/dist/repertoire/tools-steward-policy.js +119 -0
  446. package/dist/repertoire/tools-stripe.js +224 -0
  447. package/dist/repertoire/tools-surface.js +369 -0
  448. package/dist/repertoire/tools-teams.js +12 -39
  449. package/dist/repertoire/tools-telegram-contacts.js +37 -0
  450. package/dist/repertoire/tools-travel.js +125 -0
  451. package/dist/repertoire/tools-trip.js +982 -0
  452. package/dist/repertoire/tools-unraid.js +531 -0
  453. package/dist/repertoire/tools-user-profile.js +146 -0
  454. package/dist/repertoire/tools-vault.js +40 -0
  455. package/dist/repertoire/tools-voice.js +145 -0
  456. package/dist/repertoire/tools.js +494 -148
  457. package/dist/repertoire/travel-api-client.js +360 -0
  458. package/dist/repertoire/unraid-client.js +155 -0
  459. package/dist/repertoire/unraid-restart.js +339 -0
  460. package/dist/repertoire/user-profile.js +131 -0
  461. package/dist/repertoire/vault-setup.js +246 -0
  462. package/dist/repertoire/vault-unlock.js +628 -0
  463. package/dist/rsvp/aisleplanner-client.js +354 -0
  464. package/dist/rsvp/cli.js +926 -0
  465. package/dist/rsvp/config.js +327 -0
  466. package/dist/rsvp/cutover.js +433 -0
  467. package/dist/rsvp/diagnostics.js +232 -0
  468. package/dist/rsvp/diff-renderer.js +176 -0
  469. package/dist/rsvp/habit-policy.js +110 -0
  470. package/dist/rsvp/habit-stage.js +123 -0
  471. package/dist/rsvp/incident-bundle.js +97 -0
  472. package/dist/rsvp/migration.js +204 -0
  473. package/dist/rsvp/native-habit-runner.js +630 -0
  474. package/dist/rsvp/outbound-state.js +628 -0
  475. package/dist/rsvp/query.js +118 -0
  476. package/dist/rsvp/replay.js +233 -0
  477. package/dist/rsvp/snapshot.js +232 -0
  478. package/dist/rsvp/spend-ledger.js +175 -0
  479. package/dist/scripts/claude-code-hook.js +41 -0
  480. package/dist/scripts/claude-code-stop-hook.js +47 -0
  481. package/dist/senses/a2a-entry.js +115 -0
  482. package/dist/senses/acp-server.js +386 -0
  483. package/dist/senses/attention-queue.js +186 -0
  484. package/dist/senses/await-turn-message.js +58 -0
  485. package/dist/senses/bluebubbles/active-turns.js +216 -0
  486. package/dist/senses/bluebubbles/attachment-cache.js +53 -0
  487. package/dist/senses/bluebubbles/attachment-download.js +137 -0
  488. package/dist/senses/{bluebubbles-client.js → bluebubbles/client.js} +355 -40
  489. package/dist/senses/bluebubbles/context-packet.js +207 -0
  490. package/dist/senses/bluebubbles/context-smoke.js +144 -0
  491. package/dist/senses/bluebubbles/entry.js +90 -0
  492. package/dist/senses/{bluebubbles-inbound-log.js → bluebubbles/inbound-log.js} +21 -3
  493. package/dist/senses/bluebubbles/index.js +3841 -0
  494. package/dist/senses/bluebubbles/latest-turn.js +311 -0
  495. package/dist/senses/{bluebubbles-media.js → bluebubbles/media.js} +121 -71
  496. package/dist/senses/{bluebubbles-model.js → bluebubbles/model.js} +159 -28
  497. package/dist/senses/{bluebubbles-mutation-log.js → bluebubbles/mutation-log.js} +24 -13
  498. package/dist/senses/bluebubbles/outbound-state.js +308 -0
  499. package/dist/senses/bluebubbles/processed-log.js +133 -0
  500. package/dist/senses/bluebubbles/reaction-policy.js +47 -0
  501. package/dist/senses/bluebubbles/replay.js +284 -0
  502. package/dist/senses/{bluebubbles-runtime-state.js → bluebubbles/runtime-state.js} +30 -2
  503. package/dist/senses/bluebubbles/semantic-receipts.js +1986 -0
  504. package/dist/senses/{bluebubbles-session-cleanup.js → bluebubbles/session-cleanup.js} +1 -1
  505. package/dist/senses/bluebubbles/webhook-registration.js +228 -0
  506. package/dist/senses/bluebubbles-meta-guard.js +39 -0
  507. package/dist/senses/cli/bracketed-paste.js +82 -0
  508. package/dist/senses/cli/image-paste.js +287 -0
  509. package/dist/senses/cli/image-ref-navigation.js +75 -0
  510. package/dist/senses/cli/ink-app.js +156 -0
  511. package/dist/senses/cli/inline-diff.js +64 -0
  512. package/dist/senses/cli/input-keys.js +174 -0
  513. package/dist/senses/cli/kill-ring.js +86 -0
  514. package/dist/senses/cli/message-list.js +51 -0
  515. package/dist/senses/cli/ouro-tui.js +607 -0
  516. package/dist/senses/cli/spinner-imperative.js +135 -0
  517. package/dist/senses/cli/spinner.js +101 -0
  518. package/dist/senses/cli/status-line.js +60 -0
  519. package/dist/senses/cli/streaming-markdown.js +526 -0
  520. package/dist/senses/cli/tool-display.js +85 -0
  521. package/dist/senses/cli/tool-render.js +85 -0
  522. package/dist/senses/cli/tui-store.js +241 -0
  523. package/dist/senses/cli/virtual-list.js +35 -0
  524. package/dist/senses/cli-entry.js +60 -8
  525. package/dist/senses/cli.js +604 -320
  526. package/dist/senses/commands.js +66 -3
  527. package/dist/senses/context-packet-ledger.js +371 -0
  528. package/dist/senses/context-packets.js +154 -0
  529. package/dist/senses/habit-lifecycle-guard.js +24 -0
  530. package/dist/senses/habit-turn-message.js +160 -0
  531. package/dist/senses/ingress-evidence.js +2 -0
  532. package/dist/senses/mail-entry.js +66 -0
  533. package/dist/senses/mail.js +524 -0
  534. package/dist/senses/pipeline.js +1115 -204
  535. package/dist/senses/private-runtime-worker.js +859 -0
  536. package/dist/senses/private-runtime.js +1517 -0
  537. package/dist/senses/proactive-content-guard.js +51 -0
  538. package/dist/senses/root-host-approval-port.js +345 -0
  539. package/dist/senses/root-host-approval-runtime.js +393 -0
  540. package/dist/senses/sanctuary-authority-resident.js +102 -0
  541. package/dist/senses/sanctuary-download-credit-presentation.js +31 -0
  542. package/dist/senses/sanctuary-full-visibility-contract.js +302 -0
  543. package/dist/senses/sanctuary-grounding.js +118 -0
  544. package/dist/senses/sanctuary-health-acceptance-probe-entry.js +12 -0
  545. package/dist/senses/sanctuary-health-acceptance-probe.js +774 -0
  546. package/dist/senses/sanctuary-health-runner.js +100 -0
  547. package/dist/senses/sanctuary-health.js +408 -0
  548. package/dist/senses/sanctuary-install-state-contract.js +126 -0
  549. package/dist/senses/sanctuary-interactive-control.js +410 -0
  550. package/dist/senses/sanctuary-media-catalog-contract.js +297 -0
  551. package/dist/senses/sanctuary-media-optimization.js +514 -0
  552. package/dist/senses/sanctuary-runtime.js +340 -0
  553. package/dist/senses/sanctuary-sab.js +85 -0
  554. package/dist/senses/sanctuary-storage-optimization-contract.js +32 -0
  555. package/dist/senses/shared-turn.js +772 -0
  556. package/dist/senses/surface-tool.js +109 -0
  557. package/dist/senses/teams-entry.js +60 -8
  558. package/dist/senses/teams.js +519 -218
  559. package/dist/senses/telegram-admission.js +718 -0
  560. package/dist/senses/telegram-approval-runtime.js +793 -0
  561. package/dist/senses/telegram-attachments.js +186 -0
  562. package/dist/senses/telegram-audit-ledger.js +220 -0
  563. package/dist/senses/telegram-authority-transport.js +231 -0
  564. package/dist/senses/telegram-client.js +1749 -0
  565. package/dist/senses/telegram-effect-adapter.js +896 -0
  566. package/dist/senses/telegram-entry.js +83 -0
  567. package/dist/senses/telegram.js +2324 -0
  568. package/dist/senses/trust-gate.js +104 -9
  569. package/dist/senses/voice/audio-playback.js +237 -0
  570. package/dist/senses/voice/audio-routing.js +119 -0
  571. package/dist/senses/voice/elevenlabs.js +202 -0
  572. package/dist/senses/voice/floor-control.js +431 -0
  573. package/dist/senses/voice/floor-controller.js +115 -0
  574. package/dist/senses/voice/golden-path.js +116 -0
  575. package/dist/senses/voice/index.js +29 -0
  576. package/dist/senses/voice/meeting.js +113 -0
  577. package/dist/senses/voice/outbound.js +190 -0
  578. package/dist/senses/voice/phone.js +33 -0
  579. package/dist/senses/voice/playback.js +139 -0
  580. package/dist/senses/voice/realtime-eval.js +496 -0
  581. package/dist/senses/voice/realtime-trace.js +531 -0
  582. package/dist/senses/voice/transcript.js +70 -0
  583. package/dist/senses/voice/turn.js +191 -0
  584. package/dist/senses/voice/twilio-phone-runtime.js +807 -0
  585. package/dist/senses/voice/twilio-phone.js +5031 -0
  586. package/dist/senses/voice/types.js +2 -0
  587. package/dist/senses/voice/whisper.js +161 -0
  588. package/dist/senses/voice-entry.js +81 -0
  589. package/dist/senses/voice-realtime-eval-command.js +99 -0
  590. package/dist/senses/voice-realtime-eval-entry.js +21 -0
  591. package/dist/senses/voice-twilio-entry.js +87 -0
  592. package/dist/trips/core.js +138 -0
  593. package/dist/trips/store.js +265 -0
  594. package/dist/util/frontmatter.js +69 -0
  595. package/npm-shrinkwrap.json +8138 -0
  596. package/package.json +72 -13
  597. package/skills/agent-commerce.md +113 -0
  598. package/skills/browser-navigation.md +117 -0
  599. package/skills/commerce-setup-guide.md +116 -0
  600. package/skills/commerce-setup.md +84 -0
  601. package/skills/configure-dev-tools.md +99 -0
  602. package/skills/travel-planning.md +138 -0
  603. package/dist/heart/daemon/auth-flow.js +0 -430
  604. package/dist/heart/daemon/ouro-path-installer.js +0 -260
  605. package/dist/heart/daemon/ouro-version-manager.js +0 -171
  606. package/dist/heart/daemon/update-hooks.js +0 -138
  607. package/dist/heart/obligations.js +0 -191
  608. package/dist/heart/safe-workspace.js +0 -228
  609. package/dist/heart/session-recall.js +0 -116
  610. package/dist/mind/friends/channel.js +0 -84
  611. package/dist/mind/friends/group-context.js +0 -144
  612. package/dist/mind/friends/resolver.js +0 -84
  613. package/dist/mind/friends/store-file.js +0 -190
  614. package/dist/mind/friends/store.js +0 -4
  615. package/dist/mind/friends/tokens.js +0 -26
  616. package/dist/mind/friends/trust-explanation.js +0 -74
  617. package/dist/mind/friends/types.js +0 -29
  618. package/dist/repertoire/tasks/board.js +0 -134
  619. package/dist/repertoire/tasks/index.js +0 -224
  620. package/dist/repertoire/tasks/lifecycle.js +0 -80
  621. package/dist/repertoire/tasks/middleware.js +0 -65
  622. package/dist/repertoire/tasks/parser.js +0 -173
  623. package/dist/repertoire/tasks/scanner.js +0 -132
  624. package/dist/repertoire/tasks/transitions.js +0 -144
  625. package/dist/senses/bluebubbles-entry.js +0 -13
  626. package/dist/senses/bluebubbles.js +0 -1181
  627. package/dist/senses/debug-activity.js +0 -154
  628. package/dist/senses/inner-dialog-worker.js +0 -90
  629. package/dist/senses/inner-dialog.js +0 -525
  630. package/subagents/README.md +0 -7
  631. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/basilisk.md +0 -0
  632. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/jafar.md +0 -0
  633. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/jormungandr.md +0 -0
  634. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/kaa.md +0 -0
  635. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/medusa.md +0 -0
  636. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/monty.md +0 -0
  637. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/nagini.md +0 -0
  638. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/ouroboros.md +0 -0
  639. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/python.md +0 -0
  640. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/quetzalcoatl.md +0 -0
  641. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/sir-hiss.md +0 -0
  642. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/the-snake.md +0 -0
  643. /package/dist/{repertoire/tasks/types.js → heart/attachments/sources/adapter.js} +0 -0
  644. /package/dist/heart/{daemon → hatch}/hatch-animation.js +0 -0
  645. /package/dist/heart/{daemon → hatch}/specialist-orchestrator.js +0 -0
  646. /package/dist/heart/{daemon → versioning}/ouro-uti.js +0 -0
  647. /package/dist/heart/{daemon → versioning}/wrapper-publish-guard.js +0 -0
@@ -0,0 +1,819 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.SanctuaryAuthorityRootLifecycle = void 0;
37
+ exports.runSanctuaryAuthorityRootCli = runSanctuaryAuthorityRootCli;
38
+ const node_child_process_1 = require("node:child_process");
39
+ const node_crypto_1 = require("node:crypto");
40
+ const fs = __importStar(require("node:fs"));
41
+ const path = __importStar(require("node:path"));
42
+ const telegram_client_1 = require("../../senses/telegram-client");
43
+ const sanctuary_authority_installation_1 = require("./sanctuary-authority-installation");
44
+ const sanctuary_authority_epoch_1 = require("./sanctuary-authority-epoch");
45
+ const sanctuary_telegram_authority_gateway_1 = require("./sanctuary-telegram-authority-gateway");
46
+ const sanctuary_authority_resident_1 = require("../../senses/sanctuary-authority-resident");
47
+ const sanctuary_authority_vault_migration_1 = require("./sanctuary-authority-vault-migration");
48
+ const session_transaction_1 = require("../../mind/session-transaction");
49
+ const runtime_1 = require("../../nerves/runtime");
50
+ const ROOT = "/mnt/user/appdata/ouro-authority";
51
+ const BUNDLE = "/mnt/user/appdata/ouro-butler/agent/sanctuary.ouro";
52
+ const RUNTIME = "/mnt/user/appdata/ouro-butler/runtime/.ouro-cli";
53
+ const OFFSET = `${BUNDLE}/state/senses/telegram/offset.json`;
54
+ const SOCKET = "/run/ouro-authority";
55
+ const STAGING = "/var/lib/ouro-authority/staging";
56
+ const CGROUP = "/sys/fs/cgroup/ouro-authority";
57
+ const BOOT = "/boot/config/custom/ouro-authority/start.sh";
58
+ const BOOT_LINE = `/bin/sh ${BOOT} --boot & # ouro-authority`;
59
+ const DIGEST = /^sha256:[a-f0-9]{64}$/u;
60
+ const digest = (value) => `sha256:${(0, node_crypto_1.createHash)("sha256").update(value).digest("hex")}`;
61
+ // The prefix is a filesystem-fixture seam; the production CLI never accepts it.
62
+ class SanctuaryAuthorityRootLifecycle {
63
+ #request;
64
+ #transaction;
65
+ #prefix;
66
+ #uid;
67
+ #gid;
68
+ #socketGid;
69
+ constructor(transaction, options = {}) {
70
+ this.#prefix = options.prefix ?? "";
71
+ this.#uid = options.expectedUid ?? 0;
72
+ this.#gid = options.expectedGid ?? 0;
73
+ this.#socketGid = options.socketGroupId ?? 10001;
74
+ if (process.getuid() !== this.#uid || process.getgid() !== this.#gid)
75
+ throw new Error("Sanctuary root lifecycle requires root");
76
+ if (!DIGEST.test(transaction.targetImageId) || !DIGEST.test(transaction.rollbackImageId) || transaction.targetImageId === transaction.rollbackImageId)
77
+ throw new Error("Sanctuary root lifecycle image identity is invalid");
78
+ this.#transaction = { targetImageId: transaction.targetImageId, rollbackImageId: transaction.rollbackImageId };
79
+ this.#directory(ROOT);
80
+ const request = JSON.parse(this.#private(`${ROOT}/request.json`));
81
+ if (!request || Object.keys(request).sort().join(",") !== "botId,epochId,nodeDigest,ownerChatId,ownerUserId,packageDigest,prlimitDigest,schemaVersion,setsidDigest,shellDigest"
82
+ || request.schemaVersion !== 1 || !/^[A-Za-z0-9_-]{1,128}$/u.test(request.epochId)
83
+ || ![request.botId, request.ownerUserId, request.ownerChatId].every((id) => typeof id === "string" && /^[1-9][0-9]*$/u.test(id))
84
+ || request.ownerUserId !== request.ownerChatId
85
+ || ![request.packageDigest, request.nodeDigest, request.prlimitDigest, request.setsidDigest, request.shellDigest].every((pin) => typeof pin === "string" && DIGEST.test(pin)))
86
+ throw new Error("Sanctuary root lifecycle request is invalid");
87
+ this.#request = request;
88
+ (0, runtime_1.emitNervesEvent)({ component: "daemon", event: "daemon.sanctuary_root_lifecycle_loaded", message: "Sanctuary root lifecycle identity loaded", meta: { epochId: request.epochId, targetImageId: transaction.targetImageId } });
89
+ }
90
+ plan() {
91
+ const { epochId, botId, ownerUserId, ownerChatId, packageDigest } = this.#request;
92
+ return { schemaVersion: 1, epochId, botId, ownerUserId, ownerChatId, packageDigest, publicKeyDigest: null, predecessorContract: "canonical-pre-gateway", targetContract: "canonical-gateway" };
93
+ }
94
+ async boot() {
95
+ if (!fs.existsSync(this.#p(`${ROOT}/activation.json`)))
96
+ return false;
97
+ this.#record(`${ROOT}/activation.json`, { ...this.#transaction, state: "active", epochId: this.#request.epochId });
98
+ if (fs.existsSync(this.#p("/boot/config/custom/ouro-butler/docker-man-template-transaction.json")))
99
+ throw new Error("Sanctuary pending installation requires recovery, not boot");
100
+ this.#assertNoForeignPoller();
101
+ this.#target();
102
+ if (this.#containers().some((container) => container.Name !== "/ouro-butler" && container.State.Running))
103
+ throw new Error("Sanctuary rollback resident is running");
104
+ this.#runtimeDirectories();
105
+ this.#verifyPackage(undefined, false);
106
+ // Boot may already have a tokenless resident under Docker's restart policy.
107
+ // Only handoff requires us to start it strictly after gateway readiness.
108
+ await this.#startGateway(true);
109
+ return true;
110
+ }
111
+ repinExecution(prlimitDigest, setsidDigest) {
112
+ if (![prlimitDigest, setsidDigest].every(pin => DIGEST.test(pin)))
113
+ throw new Error("Reviewed execution digests are required");
114
+ if (fs.existsSync(this.#p("/boot/config/custom/ouro-butler/docker-man-template-transaction.json")))
115
+ throw new Error("Finish the pending installation before re-pinning");
116
+ this.#record(`${ROOT}/activation.json`, { ...this.#transaction, state: "active", epochId: this.#request.epochId });
117
+ this.#verifyPackage(undefined, false);
118
+ if (this.#pid() !== null)
119
+ throw new Error("Stop the verified gateway process before re-pinning");
120
+ for (const name of ["executions", "supervisors"]) {
121
+ const directory = `${this.#epochRoot()}/${name}`;
122
+ if (fs.existsSync(this.#p(directory))) {
123
+ this.#directory(directory);
124
+ if (fs.readdirSync(this.#p(directory)).length !== 0)
125
+ throw new Error("Resolve pending execution state before re-pinning");
126
+ }
127
+ }
128
+ if (fs.readdirSync(this.#p(CGROUP)).some(name => fs.lstatSync(this.#p(`${CGROUP}/${name}`)).isDirectory()))
129
+ throw new Error("Resolve pending cgroups before re-pinning");
130
+ this.#verifyPrimitive("/usr/bin/prlimit", prlimitDigest);
131
+ this.#verifyPrimitive("/usr/bin/setsid", setsidDigest);
132
+ const currentRequest = JSON.parse(this.#private(`${ROOT}/request.json`));
133
+ if (JSON.stringify(currentRequest) !== JSON.stringify(this.#request))
134
+ throw new Error("Sanctuary re-pin request changed");
135
+ const configuration = { ...this.#configuration(), hostPrlimitDigest: prlimitDigest, hostSetsidDigest: setsidDigest };
136
+ const current = JSON.parse(this.#private(`${ROOT}/active.json`));
137
+ if (JSON.stringify({ ...current, hostPrlimitDigest: prlimitDigest, hostSetsidDigest: setsidDigest }) !== JSON.stringify(configuration))
138
+ throw new Error("Sanctuary re-pin configuration changed");
139
+ const request = { ...this.#request, prlimitDigest, setsidDigest };
140
+ // Request-first interruption leaves the old runtime pins fail-closed; repeating the command completes publication.
141
+ this.#write(`${ROOT}/request.json`, JSON.stringify(request));
142
+ this.#write(`${ROOT}/active.json`, JSON.stringify(configuration));
143
+ this.#request.prlimitDigest = prlimitDigest;
144
+ this.#request.setsidDigest = setsidDigest;
145
+ (0, runtime_1.emitNervesEvent)({ component: "daemon", event: "daemon.sanctuary_execution_pins_refreshed", message: "Reviewed Sanctuary execution pins refreshed; gateway restart required", meta: { prlimitDigest, setsidDigest } });
146
+ }
147
+ effect(step) {
148
+ const effects = {
149
+ "freeze-resident": { read: () => this.#frozen(), apply: () => this.#freeze() },
150
+ "stage-authority": { read: () => this.#staged(), apply: () => this.#stage() },
151
+ "verify-token-rotation": { read: () => this.#rotated(), apply: () => this.#rotate() },
152
+ "transfer-cursor": { read: () => this.#transferred(), apply: () => this.#transfer() },
153
+ "remove-resident-token": { read: () => this.#tokensAbsent(), apply: () => { this.#requireStopped(); this.#vault("remove"); } },
154
+ "start-gateway": { read: () => this.#ready(), apply: () => this.#startGateway() },
155
+ "configure-resident": { read: () => this.#configured(), apply: () => this.#configure() },
156
+ "start-resident": { read: () => this.#target().State.Running, apply: () => this.#startResident() },
157
+ "verify-install": { read: () => this.#active(), apply: () => this.#activate() },
158
+ "rollback:freeze-resident": { read: () => this.#frozen() && !fs.existsSync(this.#p(`${ROOT}/activation.json`)), apply: () => { this.#remove(`${ROOT}/activation.json`); this.#freeze(); } },
159
+ "rollback:retire-registrations": { read: () => this.#retiredProcess(), apply: () => this.#retireProcess() },
160
+ "rollback:reconcile-executions": { read: () => this.#retiredProcess(), apply: () => this.#retireProcess() },
161
+ "rollback:stop-gateway": { read: () => this.#retiredProcess() && this.#pid() === null, apply: () => this.#stopGateway() },
162
+ "rollback:end-epoch": { read: () => this.#epoch().state === "retired", apply: () => this.#endEpoch() },
163
+ "rollback:restore-token-cursor": { read: () => this.#restoredToken(), apply: () => this.#restoreToken() },
164
+ "rollback:restore-resident": { read: () => this.#rollback().State.Running, apply: () => this.#restoreResident() },
165
+ "rollback:verify-rollback": { read: () => this.#rollbackVerified(), apply: () => this.#verifyRollback() },
166
+ };
167
+ const effect = effects[step];
168
+ if (!effect)
169
+ throw new Error("Sanctuary root lifecycle effect is invalid");
170
+ const beforeDigest = digest(`${this.#request.epochId}:${step}:before`);
171
+ const afterDigest = digest(`${this.#request.epochId}:${step}:after`);
172
+ return {
173
+ beforeDigest, afterDigest,
174
+ readback: async () => await effect.read() ? afterDigest : beforeDigest,
175
+ apply: async () => { await effect.apply(); },
176
+ };
177
+ }
178
+ #frozen() {
179
+ this.#assertNoForeignPoller();
180
+ if (!this.#containers().every((container) => !container.State.Running) || !fs.existsSync(this.#p(`${this.#epochRoot()}/migration.json`)))
181
+ return false;
182
+ this.#migration();
183
+ return true;
184
+ }
185
+ #freeze() {
186
+ this.#assertNoForeignPoller();
187
+ for (const container of this.#containers()) {
188
+ if (![this.#transaction.targetImageId, this.#transaction.rollbackImageId].includes(container.Image))
189
+ throw new Error("Sanctuary resident image changed");
190
+ if (container.State.Running)
191
+ this.#docker(["stop", "--time", "30", container.Name.slice(1)]);
192
+ }
193
+ if (this.#containers().some((container) => container.State.Running))
194
+ throw new Error("Sanctuary resident did not stop");
195
+ if (fs.existsSync(this.#p(`${this.#epochRoot()}/migration.json`))) {
196
+ this.#migration();
197
+ return;
198
+ }
199
+ if (fs.existsSync(this.#p(`${ROOT}/active.json`))) {
200
+ const previous = JSON.parse(this.#private(`${ROOT}/active.json`));
201
+ const epoch = (0, sanctuary_authority_epoch_1.readSanctuaryAuthorityEpoch)(previous.epochRoot, { expectedUid: this.#uid, expectedGid: this.#gid });
202
+ if (epoch.state !== "retired" || !fs.existsSync(path.join(previous.epochRoot, "handoff.json"))
203
+ || fs.existsSync(this.#p(`${ROOT}/activation.json`)))
204
+ throw new Error("Sanctuary previous epoch handoff is incomplete");
205
+ }
206
+ // A missing cursor is not a fresh install: it is lost handoff evidence.
207
+ fs.lstatSync(this.#p(OFFSET));
208
+ const predecessorCursor = new telegram_client_1.FileTelegramOffsetStore(this.#p(OFFSET)).load();
209
+ const snapshot = this.#vault("snapshot");
210
+ if (["botId", "ownerUserId", "ownerChatId"].some((key) => snapshot[key] !== this.#request[key]))
211
+ throw new Error("Sanctuary predecessor identity changed");
212
+ this.#write(`${ROOT}/epochs/${this.#request.epochId}/previous-token`, snapshot.token);
213
+ this.#write(`${this.#epochRoot()}/migration.json`, JSON.stringify({ ...this.plan(), ...this.#transaction, predecessorCursor }));
214
+ }
215
+ #epochRoot() { return `${ROOT}/epochs/${this.#request.epochId}`; }
216
+ #migration() {
217
+ const value = JSON.parse(this.#private(`${this.#epochRoot()}/migration.json`));
218
+ for (const [key, expected] of Object.entries({ ...this.plan(), ...this.#transaction })) {
219
+ if (value[key] !== expected)
220
+ throw new Error("Sanctuary root migration identity changed");
221
+ }
222
+ if (!Number.isSafeInteger(value.predecessorCursor) || value.predecessorCursor < 0)
223
+ throw new Error("Sanctuary root migration cursor is invalid");
224
+ return value;
225
+ }
226
+ #directory(absolute, mode = 0o700, gid = this.#gid) {
227
+ const directory = this.#p(absolute);
228
+ if (!fs.existsSync(directory)) {
229
+ fs.mkdirSync(directory, { recursive: true, mode });
230
+ fs.chownSync(directory, this.#uid, gid);
231
+ fs.chmodSync(directory, mode);
232
+ }
233
+ const stat = fs.lstatSync(directory);
234
+ if (!stat.isDirectory() || stat.uid !== this.#uid || stat.gid !== gid || (stat.mode & 0o7777) !== mode || fs.realpathSync(directory) !== directory)
235
+ throw new Error("Sanctuary root lifecycle directory is unsafe");
236
+ }
237
+ #verifyPackage(packageRoot = `${ROOT}/package`, hostExecution = true) {
238
+ (0, sanctuary_authority_installation_1.verifySanctuaryAuthorityInstallation)({
239
+ packageRoot: this.#p(packageRoot), manifestPath: this.#p(`${ROOT}/package-manifest.json`), manifestDigest: this.#request.packageDigest,
240
+ stateRoot: this.#p(this.#epochRoot()), stagingRoot: this.#p(STAGING), socketRoot: this.#p(SOCKET), cgroupRoot: this.#p(CGROUP),
241
+ expectedUid: this.#uid, expectedGid: this.#gid, socketGroupId: this.#socketGid, mountInfo: fs.readFileSync(this.#p("/proc/self/mountinfo"), "utf8"),
242
+ });
243
+ const manifest = JSON.parse(this.#private(`${ROOT}/package-manifest.json`, 0o600, 8 * 1024 * 1024));
244
+ for (const program of ["dist/heart/daemon/sanctuary-telegram-authority-entry.js", "dist/heart/daemon/sanctuary-authority-root-lifecycle.js", "dist/heart/daemon/sanctuary-host-supervisor-entry.js", "deploy/unraid/sanctuary-host-launcher.sh", "deploy/unraid/sanctuary-authority-service.sh"]) {
245
+ if (!Object.hasOwn(manifest.files, program))
246
+ throw new Error("Sanctuary authority package program is absent");
247
+ }
248
+ for (const [file, expected] of [
249
+ ["/usr/local/bin/node", this.#request.nodeDigest], ["/bin/sh", this.#request.shellDigest],
250
+ ...(hostExecution ? [["/usr/bin/prlimit", this.#request.prlimitDigest], ["/usr/bin/setsid", this.#request.setsidDigest]] : []),
251
+ ]) {
252
+ this.#verifyPrimitive(file, expected);
253
+ }
254
+ }
255
+ #verifyPrimitive(file, expected) {
256
+ const actual = this.#p(file);
257
+ const stat = fs.statSync(actual);
258
+ if (!stat.isFile() || stat.uid !== this.#uid || stat.gid !== this.#gid || (stat.mode & 0o022) !== 0 || (stat.mode & 0o111) === 0 || digest(fs.readFileSync(actual)) !== expected)
259
+ throw new Error("Sanctuary host primitive pin changed");
260
+ }
261
+ #staged(hostExecution = true) {
262
+ if (!fs.existsSync(this.#p(`${this.#epochRoot()}/stage.json`)))
263
+ return false;
264
+ this.#record(`${this.#epochRoot()}/stage.json`, { packageDigest: this.#request.packageDigest });
265
+ this.#verifyPackage(undefined, hostExecution);
266
+ if (this.#bootScript().text.split("\n").filter((line) => line === BOOT_LINE).length !== 1
267
+ || digest(this.#private(BOOT)) !== digest(fs.readFileSync(this.#p(`${ROOT}/package/deploy/unraid/sanctuary-authority-service.sh`))))
268
+ throw new Error("Sanctuary installed boot lifecycle changed");
269
+ return true;
270
+ }
271
+ #runtimeDirectories() {
272
+ for (const directory of [this.#epochRoot(), STAGING, CGROUP])
273
+ this.#directory(directory);
274
+ this.#directory(SOCKET, 0o750, this.#socketGid);
275
+ // Add only the required controllers; never replace another owner's controls.
276
+ for (const root of ["/sys/fs/cgroup", CGROUP]) {
277
+ const control = this.#p(`${root}/cgroup.subtree_control`);
278
+ if (fs.existsSync(control)) {
279
+ const enabled = fs.readFileSync(control, "utf8").trim().split(/\s+/u);
280
+ const missing = ["cpu", "memory", "pids"].filter((name) => !enabled.includes(name));
281
+ if (missing.length)
282
+ fs.writeFileSync(control, missing.map((name) => `+${name}`).join(" "));
283
+ }
284
+ }
285
+ }
286
+ #stage(hostExecution = true) {
287
+ this.#migration();
288
+ this.#runtimeDirectories();
289
+ this.#verifyPackage(`${ROOT}/incoming-package`, hostExecution);
290
+ const manifest = JSON.parse(this.#private(`${ROOT}/package-manifest.json`, 0o600, 8 * 1024 * 1024));
291
+ this.#directory(`${ROOT}/package`);
292
+ for (const [relative, pin] of Object.entries(manifest.files)) {
293
+ const destination = `${ROOT}/package/${relative}`;
294
+ this.#directory(path.dirname(destination));
295
+ if (fs.existsSync(this.#p(destination))) {
296
+ const stat = fs.lstatSync(this.#p(destination));
297
+ if (!stat.isFile() || stat.isSymbolicLink() || stat.nlink !== 1 || stat.uid !== this.#uid || stat.gid !== this.#gid || (stat.mode & 0o7777) !== pin.mode || fs.realpathSync(this.#p(destination)) !== this.#p(destination))
298
+ throw new Error("Sanctuary installed package destination is unsafe");
299
+ if (digest(fs.readFileSync(this.#p(destination))) !== pin.digest)
300
+ throw new Error("Sanctuary installed package conflicts with its pin");
301
+ }
302
+ else {
303
+ fs.copyFileSync(this.#p(`${ROOT}/incoming-package/${relative}`), this.#p(destination), fs.constants.COPYFILE_EXCL);
304
+ }
305
+ fs.chmodSync(this.#p(destination), pin.mode);
306
+ }
307
+ this.#verifyPackage(undefined, hostExecution);
308
+ const { text: boot, mode: bootMode } = this.#bootScript();
309
+ if (boot.split("\n").some((line) => line.includes("ouro-authority") && line !== BOOT_LINE))
310
+ throw new Error("Sanctuary boot ownership is ambiguous");
311
+ this.#write(BOOT, fs.readFileSync(this.#p(`${ROOT}/package/deploy/unraid/sanctuary-authority-service.sh`), "utf8"));
312
+ if (!boot.split("\n").includes(BOOT_LINE))
313
+ this.#write("/boot/config/go", `${boot}${boot.endsWith("\n") ? "" : "\n"}${BOOT_LINE}\n`, bootMode);
314
+ this.#write(`${this.#epochRoot()}/stage.json`, JSON.stringify({ packageDigest: this.#request.packageDigest }));
315
+ }
316
+ #epoch() {
317
+ const epoch = (0, sanctuary_authority_epoch_1.readSanctuaryAuthorityEpoch)(this.#p(this.#epochRoot()), { expectedUid: this.#uid, expectedGid: this.#gid });
318
+ if (epoch.epochId !== this.#request.epochId || epoch.botId !== this.#request.botId || epoch.ownerUserId !== this.#request.ownerUserId
319
+ || epoch.ownerChatId !== this.#request.ownerChatId || epoch.packageDigest !== this.#request.packageDigest
320
+ || epoch.predecessorCursor !== this.#migration().predecessorCursor)
321
+ throw new Error("Sanctuary root epoch identity changed");
322
+ return epoch;
323
+ }
324
+ #rotated() {
325
+ if (!fs.existsSync(this.#p(`${this.#epochRoot()}/epoch.json`)))
326
+ return false;
327
+ if (this.#epoch().state !== "prepared")
328
+ throw new Error("Sanctuary root epoch is retired");
329
+ return true;
330
+ }
331
+ async #rotate(hostExecution = true) {
332
+ if (!this.#frozen() || !this.#staged(hostExecution))
333
+ throw new Error("Sanctuary root installation must be frozen and staged");
334
+ const token = this.#private(`${ROOT}/incoming-token`).trim();
335
+ this.#write(`${this.#epochRoot()}/current-token`, token);
336
+ await (0, sanctuary_authority_epoch_1.prepareSanctuaryAuthorityEpoch)({
337
+ root: this.#p(this.#epochRoot()), epochId: this.#request.epochId, tokenPath: this.#p(`${this.#epochRoot()}/current-token`),
338
+ previousTokenPath: this.#p(`${this.#epochRoot()}/previous-token`), botId: this.#request.botId,
339
+ ownerUserId: this.#request.ownerUserId, ownerChatId: this.#request.ownerChatId,
340
+ predecessorCursor: this.#migration().predecessorCursor, packageDigest: this.#request.packageDigest,
341
+ }, {
342
+ expectedUid: this.#uid, expectedGid: this.#gid, now: () => new Date().toISOString(),
343
+ probe: async (value) => {
344
+ const response = await fetch(`https://api.telegram.org/bot${value}/getMe`, { signal: AbortSignal.timeout(15_000) });
345
+ const body = await response.json();
346
+ return { status: response.status, botId: body.ok === true && Number.isSafeInteger(body.result?.id) ? String(body.result.id) : null };
347
+ },
348
+ });
349
+ }
350
+ #gateway() {
351
+ const epoch = this.#epoch();
352
+ return new sanctuary_telegram_authority_gateway_1.FileSanctuaryTelegramAuthorityGateway(this.#p(`${this.#epochRoot()}/agent`), {
353
+ targetHost: "sanctuary", botId: epoch.botId, ownerUserId: epoch.ownerUserId, ownerChatId: epoch.ownerChatId,
354
+ keyId: epoch.epochId, publicKeyDigest: epoch.publicKeyDigest, privateKey: (0, node_crypto_1.createPrivateKey)(this.#private(`${this.#epochRoot()}/issuer.pem`)),
355
+ });
356
+ }
357
+ #transferred() {
358
+ if (!fs.existsSync(this.#p(`${ROOT}/active.json`)))
359
+ return false;
360
+ if (!fs.existsSync((0, sanctuary_telegram_authority_gateway_1.sanctuaryTelegramAuthorityStatePath)(this.#p(`${this.#epochRoot()}/agent`))))
361
+ return false;
362
+ if (JSON.stringify(JSON.parse(this.#private(`${ROOT}/active.json`))) !== JSON.stringify(this.#configuration()))
363
+ throw new Error("Sanctuary root configuration changed during handoff");
364
+ if (this.#gateway().cursor() !== this.#migration().predecessorCursor)
365
+ throw new Error("Sanctuary root handoff cursor changed");
366
+ return true;
367
+ }
368
+ #transfer() {
369
+ if (!this.#frozen() || !this.#rotated())
370
+ throw new Error("Sanctuary root epoch is not ready for cursor handoff");
371
+ this.#gateway().initializeCursor(this.#epoch().predecessorCursor);
372
+ this.#write(`${ROOT}/active.json`, JSON.stringify(this.#configuration()));
373
+ }
374
+ #configuration() {
375
+ const epoch = this.#epoch();
376
+ const program = `${ROOT}/package/dist/heart/daemon/sanctuary-host-supervisor-entry.js`;
377
+ const launcher = `${ROOT}/package/deploy/unraid/sanctuary-host-launcher.sh`;
378
+ const config = {
379
+ schemaVersion: 1, targetHost: "sanctuary", botId: epoch.botId, ownerUserId: epoch.ownerUserId, ownerChatId: epoch.ownerChatId,
380
+ keyId: epoch.epochId, publicKeyDigest: epoch.publicKeyDigest, socketGroupId: this.#socketGid,
381
+ agentRoot: this.#p(`${this.#epochRoot()}/agent`), epochRoot: this.#p(this.#epochRoot()),
382
+ tokenPath: epoch.tokenPath, privateKeyPath: this.#p(`${this.#epochRoot()}/issuer.pem`),
383
+ socketPath: this.#p(`${SOCKET}/authority.sock`), readinessPath: this.#p(`${this.#epochRoot()}/readiness.json`), lockPath: this.#p(`${this.#epochRoot()}/authority.lock`),
384
+ hostStagingRoot: this.#p(STAGING), hostExecutionStateRoot: this.#p(`${this.#epochRoot()}/executions`), hostSupervisorStateRoot: this.#p(`${this.#epochRoot()}/supervisors`),
385
+ hostCgroupRoot: this.#p(CGROUP), hostSupervisorProgramPath: this.#p(program), hostSupervisorProgramDigest: digest(fs.readFileSync(this.#p(program))),
386
+ hostLauncherPath: this.#p(launcher), hostLauncherDigest: digest(fs.readFileSync(this.#p(launcher))),
387
+ hostPrlimitPath: this.#p("/usr/bin/prlimit"), hostPrlimitDigest: this.#request.prlimitDigest,
388
+ hostSetsidPath: this.#p("/usr/bin/setsid"), hostSetsidDigest: this.#request.setsidDigest, hostShellPath: this.#p("/bin/sh"), hostShellDigest: this.#request.shellDigest,
389
+ packageRoot: this.#p(`${ROOT}/package`), packageManifestPath: this.#p(`${ROOT}/package-manifest.json`), packageManifestDigest: this.#request.packageDigest,
390
+ };
391
+ return config;
392
+ }
393
+ #requireStopped() {
394
+ this.#assertNoForeignPoller();
395
+ if (this.#containers().some((container) => container.State.Running))
396
+ throw new Error("Sanctuary resident must be stopped");
397
+ }
398
+ #tokensAbsent() {
399
+ const presence = this.#vault("presence");
400
+ if (!presence || Object.keys(presence).join(",") !== "tokenPresent" || typeof presence.tokenPresent !== "boolean")
401
+ throw new Error("Sanctuary vault presence readback is invalid");
402
+ return !presence.tokenPresent;
403
+ }
404
+ #pid() {
405
+ const lock = `${this.#epochRoot()}/authority.lock`;
406
+ if (!fs.existsSync(this.#p(lock)))
407
+ return null;
408
+ const pid = Number(this.#private(lock));
409
+ if (!Number.isSafeInteger(pid) || pid <= 0)
410
+ throw new Error("Sanctuary gateway process lock is invalid");
411
+ if (!fs.existsSync(this.#p(`/proc/${pid}`)))
412
+ return null;
413
+ const status = fs.readFileSync(this.#p(`/proc/${pid}/status`), "utf8");
414
+ const command = fs.readFileSync(this.#p(`/proc/${pid}/cmdline`), "utf8");
415
+ const expected = [this.#p("/usr/local/bin/node"), this.#p(`${ROOT}/package/dist/heart/daemon/sanctuary-telegram-authority-entry.js`), "--config", this.#p(`${ROOT}/active.json`), ""].join("\0");
416
+ if (!status.split("\n").includes(`Uid:\t${this.#uid}\t${this.#uid}\t${this.#uid}\t${this.#uid}`) || command !== expected)
417
+ throw new Error("Sanctuary gateway process identity changed");
418
+ return pid;
419
+ }
420
+ async #ready() {
421
+ if (!fs.existsSync(this.#p(`${this.#epochRoot()}/readiness.json`)))
422
+ return false;
423
+ if (this.#pid() === null)
424
+ throw new Error("Sanctuary gateway readiness has no live process");
425
+ const epoch = this.#epoch();
426
+ const readiness = JSON.parse(this.#private(`${this.#epochRoot()}/readiness.json`));
427
+ if (readiness.status !== "ready" || readiness.botId !== epoch.botId || readiness.publicKeyDigest !== epoch.publicKeyDigest)
428
+ throw new Error("Sanctuary gateway readiness identity changed");
429
+ const authority = (0, sanctuary_authority_resident_1.openSanctuaryResidentAuthority)({}, {}, { configPath: this.#p(`${SOCKET}/resident.json`), expectedUid: this.#uid, expectedGid: this.#socketGid });
430
+ try {
431
+ const snapshot = await authority.cursorSnapshot();
432
+ if (snapshot.keyId !== epoch.epochId || snapshot.botId !== epoch.botId || snapshot.publicKeyDigest !== epoch.publicKeyDigest || snapshot.cursor < epoch.predecessorCursor)
433
+ throw new Error("Sanctuary gateway signed readiness changed");
434
+ return true;
435
+ }
436
+ finally {
437
+ authority.authorityTransport.api.stop();
438
+ }
439
+ }
440
+ async #wait(check, timeoutMs = 120_000) {
441
+ const until = Date.now() + timeoutMs;
442
+ while (!await check()) {
443
+ if (Date.now() >= until)
444
+ throw new Error("Sanctuary root lifecycle readback timed out");
445
+ await new Promise((resolve) => setTimeout(resolve, 100));
446
+ }
447
+ }
448
+ async #startGateway(boot = false) {
449
+ if (!boot)
450
+ this.#requireStopped();
451
+ this.#verifyPackage(undefined, !boot);
452
+ if (!this.#tokensAbsent() || !this.#rotated())
453
+ throw new Error("Sanctuary root token custody is not exclusive");
454
+ if (this.#pid() === null) {
455
+ this.#remove(`${this.#epochRoot()}/readiness.json`);
456
+ const child = (0, node_child_process_1.spawn)(this.#p("/usr/local/bin/node"), [this.#p(`${ROOT}/package/dist/heart/daemon/sanctuary-telegram-authority-entry.js`), "--config", this.#p(`${ROOT}/active.json`)], {
457
+ cwd: "/", env: { PATH: "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" }, detached: true, stdio: "ignore",
458
+ });
459
+ child.unref();
460
+ let failed = false;
461
+ child.once("error", () => { failed = true; });
462
+ await this.#wait(async () => {
463
+ if (failed)
464
+ throw new Error("Sanctuary gateway launch failed");
465
+ return this.#ready();
466
+ });
467
+ }
468
+ else
469
+ await this.#wait(() => this.#ready());
470
+ }
471
+ #target() {
472
+ const target = this.#containers().find((container) => container.Name === "/ouro-butler");
473
+ if (!target || target.Image !== this.#transaction.targetImageId || target.Config.User !== "10001:10001"
474
+ || target.Config.Env.some((entry) => /telegram.*token|bot.*token/iu.test(entry))
475
+ || target.Mounts.filter((mount) => mount.Source === SOCKET && mount.Destination === SOCKET && mount.RW === false).length !== 1
476
+ || target.Mounts.some((mount) => mount.Source === "/var/run/docker.sock" || mount.Source.startsWith(ROOT)))
477
+ throw new Error("Sanctuary target resident is not tokenless and gateway-mounted");
478
+ return target;
479
+ }
480
+ #configured() {
481
+ if (!fs.existsSync(this.#p(`${this.#epochRoot()}/configured.json`)))
482
+ return false;
483
+ this.#record(`${this.#epochRoot()}/configured.json`, { epochId: this.#request.epochId, targetImageId: this.#transaction.targetImageId });
484
+ this.#target();
485
+ return this.#tokensAbsent() && this.#directAutostartDisabled();
486
+ }
487
+ async #configure() {
488
+ this.#requireStopped();
489
+ this.#target();
490
+ if (!this.#tokensAbsent() || !this.#directAutostartDisabled() || !await this.#ready())
491
+ throw new Error("Sanctuary gateway must be ready and direct autostart disabled before configuring the resident");
492
+ this.#write(`${this.#epochRoot()}/configured.json`, JSON.stringify({ epochId: this.#request.epochId, targetImageId: this.#transaction.targetImageId }));
493
+ }
494
+ async #startResident() {
495
+ this.#assertNoForeignPoller();
496
+ if (!this.#configured() || !await this.#ready())
497
+ throw new Error("Sanctuary gateway must be ready before resident startup");
498
+ if (this.#containers().some((container) => container.Name !== "/ouro-butler" && container.State.Running))
499
+ throw new Error("Sanctuary rollback resident is running");
500
+ if (!this.#target().State.Running)
501
+ this.#docker(["start", "ouro-butler"]);
502
+ await this.#wait(() => this.#target().State.Running && this.#target().State.Health?.Status === "healthy");
503
+ }
504
+ async #active() {
505
+ if (!fs.existsSync(this.#p(`${ROOT}/activation.json`)))
506
+ return false;
507
+ this.#record(`${ROOT}/activation.json`, { ...this.#transaction, state: "active", epochId: this.#request.epochId });
508
+ return this.#configured() && this.#target().State.Running && this.#target().State.Health?.Status === "healthy" && await this.#ready();
509
+ }
510
+ async #activate() {
511
+ if (!this.#configured() || !this.#target().State.Running || this.#target().State.Health?.Status !== "healthy" || !await this.#ready())
512
+ throw new Error("Sanctuary activation readback failed");
513
+ this.#write(`${ROOT}/activation.json`, JSON.stringify({ ...this.#transaction, state: "active", epochId: this.#request.epochId }));
514
+ }
515
+ #retiredProcess() {
516
+ const retirement = `${this.#epochRoot()}/retirement.json`;
517
+ if (!fs.existsSync(this.#p(retirement)))
518
+ return false;
519
+ const proof = JSON.parse(this.#private(retirement));
520
+ const epoch = this.#epoch();
521
+ if (proof.schemaVersion !== 1 || proof.keyId !== epoch.epochId || proof.publicKeyDigest !== epoch.publicKeyDigest || proof.quiescent !== true
522
+ || !Number.isSafeInteger(proof.cursor) || proof.cursor < epoch.predecessorCursor
523
+ || fs.readdirSync(this.#p(CGROUP)).some((entry) => fs.lstatSync(this.#p(`${CGROUP}/${entry}`)).isDirectory()))
524
+ throw new Error("Sanctuary gateway retirement cleanup is unproven");
525
+ return true;
526
+ }
527
+ async #retireProcess() {
528
+ this.#requireStopped();
529
+ if (this.#retiredProcess())
530
+ return;
531
+ // An interrupted installation may never have published an epoch/config.
532
+ // Complete only the root prerequisites needed to retire it; never start
533
+ // ingress or a resident while recovering this rollback intent.
534
+ if (!fs.existsSync(this.#p(`${this.#epochRoot()}/epoch.json`))) {
535
+ if (!this.#staged(false))
536
+ this.#stage(false);
537
+ await this.#rotate(false);
538
+ }
539
+ if (!fs.existsSync(this.#p(`${ROOT}/active.json`)) || JSON.parse(this.#private(`${ROOT}/active.json`)).keyId !== this.#request.epochId)
540
+ this.#transfer();
541
+ this.#verifyPackage(undefined, false);
542
+ const pid = this.#pid();
543
+ if (pid !== null) {
544
+ process.kill(pid, "SIGUSR2");
545
+ await this.#wait(() => this.#retiredProcess(), 1_020_000);
546
+ }
547
+ else {
548
+ (0, node_child_process_1.execFileSync)(this.#p("/usr/local/bin/node"), [this.#p(`${ROOT}/package/dist/heart/daemon/sanctuary-telegram-authority-entry.js`), "--config", this.#p(`${ROOT}/active.json`), "--retire-only"], {
549
+ encoding: "utf8", timeout: 1_020_000, stdio: ["ignore", "pipe", "pipe"], env: { PATH: "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" }, cwd: "/",
550
+ });
551
+ if (!this.#retiredProcess())
552
+ throw new Error("Sanctuary gateway retirement readback failed");
553
+ }
554
+ }
555
+ async #stopGateway() {
556
+ if (!this.#retiredProcess())
557
+ throw new Error("Sanctuary gateway must retire before stopping");
558
+ const pid = this.#pid();
559
+ if (pid !== null) {
560
+ process.kill(pid, "SIGTERM");
561
+ await this.#wait(() => this.#pid() === null);
562
+ }
563
+ this.#remove(`${this.#epochRoot()}/readiness.json`);
564
+ }
565
+ #endEpoch() {
566
+ if (!this.#retiredProcess() || this.#pid() !== null)
567
+ throw new Error("Sanctuary gateway must retire and stop before ending its epoch");
568
+ const proof = JSON.parse(this.#private(`${this.#epochRoot()}/retirement.json`));
569
+ (0, sanctuary_authority_epoch_1.retireSanctuaryAuthorityEpoch)(this.#p(this.#epochRoot()), { expectedUid: this.#uid, expectedGid: this.#gid, quiescent: true, cursor: proof.cursor });
570
+ }
571
+ #restoredToken() {
572
+ if (this.#epoch().state !== "retired" || !fs.existsSync(this.#p(`${this.#epochRoot()}/restored.json`))
573
+ || !fs.existsSync(this.#p(`${this.#epochRoot()}/handoff.json`)) || fs.existsSync(this.#p(`${this.#epochRoot()}/current-token`)) || fs.existsSync(this.#p(`${ROOT}/incoming-token`)))
574
+ return false;
575
+ this.#record(`${this.#epochRoot()}/restored.json`, { tokenDigest: this.#epoch().tokenDigest, cursor: this.#epoch().terminalCursor });
576
+ const token = this.#vault("snapshot");
577
+ return digest(token.token) === this.#epoch().tokenDigest && new telegram_client_1.FileTelegramOffsetStore(this.#p(OFFSET)).load() >= this.#epoch().terminalCursor;
578
+ }
579
+ #restoreToken() {
580
+ this.#requireStopped();
581
+ const epoch = this.#epoch();
582
+ if (epoch.state !== "retired" || this.#pid() !== null || !this.#retiredProcess())
583
+ throw new Error("Sanctuary epoch must retire before token handoff");
584
+ if (this.#restoredToken())
585
+ return;
586
+ const token = fs.existsSync(this.#p(`${this.#epochRoot()}/current-token`))
587
+ ? this.#private(`${this.#epochRoot()}/current-token`).trim() : this.#vault("snapshot").token;
588
+ if (digest(token) !== epoch.tokenDigest)
589
+ throw new Error("Sanctuary current token handoff identity changed");
590
+ this.#vault("restore", { token, botId: epoch.botId, ownerUserId: epoch.ownerUserId, ownerChatId: epoch.ownerChatId });
591
+ new telegram_client_1.FileTelegramOffsetStore(this.#p(OFFSET)).save(epoch.terminalCursor);
592
+ // Restore the original resident ownership; root must not strand a root-owned cursor.
593
+ fs.chownSync(this.#p(OFFSET), this.#uid === 0 ? 10001 : this.#uid, this.#uid === 0 ? 10001 : this.#gid);
594
+ this.#write(`${this.#epochRoot()}/restored.json`, JSON.stringify({ tokenDigest: epoch.tokenDigest, cursor: epoch.terminalCursor }));
595
+ const restored = this.#vault("snapshot");
596
+ if (digest(restored.token) !== epoch.tokenDigest || new telegram_client_1.FileTelegramOffsetStore(this.#p(OFFSET)).load() !== epoch.terminalCursor)
597
+ throw new Error("Sanctuary token and cursor handoff readback failed");
598
+ (0, sanctuary_authority_epoch_1.releaseSanctuaryAuthorityToken)(this.#p(this.#epochRoot()), { expectedUid: this.#uid, expectedGid: this.#gid, tokenDigest: epoch.tokenDigest, cursor: epoch.terminalCursor });
599
+ if (fs.existsSync(this.#p(`${ROOT}/incoming-token`))) {
600
+ if (digest(this.#private(`${ROOT}/incoming-token`).trim()) !== epoch.tokenDigest)
601
+ throw new Error("Sanctuary incoming token changed during handoff");
602
+ this.#remove(`${ROOT}/incoming-token`);
603
+ }
604
+ }
605
+ #rollback() {
606
+ const resident = this.#containers().find((container) => container.Name === "/ouro-butler");
607
+ if (!resident || resident.Image !== this.#transaction.rollbackImageId || resident.Config.User !== "10001:10001"
608
+ || resident.Mounts.some((mount) => mount.Source === SOCKET || mount.Destination === SOCKET))
609
+ throw new Error("Sanctuary rollback image or socket mount has not been restored");
610
+ return resident;
611
+ }
612
+ async #restoreResident() {
613
+ this.#assertNoForeignPoller();
614
+ if (!this.#restoredToken() || this.#pid() !== null)
615
+ throw new Error("Sanctuary rollback token handoff is incomplete");
616
+ const resident = this.#rollback();
617
+ if (!resident.State.Running)
618
+ this.#docker(["start", "ouro-butler"]);
619
+ await this.#wait(() => this.#rollback().State.Health?.Status === "healthy" && this.#rollback().State.Running);
620
+ }
621
+ #rollbackVerified() {
622
+ return fs.existsSync(this.#p(`${this.#epochRoot()}/rollback.json`)) && this.#restoredToken() && this.#pid() === null && this.#rollback().State.Running && this.#rollback().State.Health?.Status === "healthy";
623
+ }
624
+ #verifyRollback() {
625
+ if (!this.#restoredToken() || this.#pid() !== null || !this.#rollback().State.Running || this.#rollback().State.Health?.Status !== "healthy")
626
+ throw new Error("Sanctuary rollback readback failed");
627
+ this.#write(`${this.#epochRoot()}/rollback.json`, JSON.stringify({ epochId: this.#request.epochId, state: "retired" }));
628
+ }
629
+ #remove(absolute) {
630
+ if (!fs.existsSync(this.#p(absolute)))
631
+ return;
632
+ this.#private(absolute);
633
+ fs.unlinkSync(this.#p(absolute));
634
+ const directory = fs.openSync(path.dirname(this.#p(absolute)), fs.constants.O_RDONLY);
635
+ try {
636
+ fs.fsyncSync(directory);
637
+ }
638
+ finally {
639
+ fs.closeSync(directory);
640
+ }
641
+ }
642
+ #directAutostartDisabled() {
643
+ const file = this.#p("/var/lib/docker/unraid-autostart");
644
+ const stat = fs.lstatSync(file);
645
+ if (!stat.isFile() || stat.isSymbolicLink() || stat.uid !== this.#uid || stat.gid !== this.#gid || (stat.mode & 0o7777) !== 0o644)
646
+ throw new Error("Sanctuary DockerMan autostart state is unsafe");
647
+ return !fs.readFileSync(file, "utf8").split("\n").some((line) => /^ouro-butler(?:-rollback|-staging|-legacy-evidence)?(?:\s|$)/u.test(line));
648
+ }
649
+ #p(absolute) { return `${this.#prefix}${absolute}`; }
650
+ #bootScript() {
651
+ const mode = fs.lstatSync(this.#p("/boot/config/go")).mode & 0o7777;
652
+ if (![0o600, 0o644, 0o700, 0o755].includes(mode))
653
+ throw new Error("Sanctuary boot script metadata is unsafe");
654
+ return { text: this.#private("/boot/config/go", mode), mode };
655
+ }
656
+ #record(absolute, expected) {
657
+ const value = JSON.parse(this.#private(absolute));
658
+ if (!value || Object.keys(value).sort().join(",") !== Object.keys(expected).sort().join(",")
659
+ || Object.entries(expected).some(([key, entry]) => value[key] !== entry))
660
+ throw new Error("Sanctuary root lifecycle record identity changed");
661
+ }
662
+ #private(absolute, mode = 0o600, maxBytes = 1024 * 1024) {
663
+ const file = this.#p(absolute);
664
+ const fd = fs.openSync(file, fs.constants.O_RDONLY | fs.constants.O_NOFOLLOW);
665
+ try {
666
+ const stat = fs.fstatSync(fd);
667
+ if (!stat.isFile() || stat.nlink !== 1 || stat.uid !== this.#uid || stat.gid !== this.#gid || (stat.mode & 0o7777) !== mode
668
+ || fs.realpathSync(file) !== file || stat.size < 1 || stat.size > maxBytes)
669
+ throw new Error("Sanctuary root lifecycle private file is unsafe");
670
+ return fs.readFileSync(fd, "utf8");
671
+ }
672
+ finally {
673
+ fs.closeSync(fd);
674
+ }
675
+ }
676
+ #write(absolute, value, mode = 0o600) {
677
+ const file = this.#p(absolute);
678
+ fs.mkdirSync(path.dirname(file), { recursive: true, mode: 0o700 });
679
+ const parent = fs.lstatSync(path.dirname(file));
680
+ if (parent.uid !== this.#uid || parent.gid !== this.#gid
681
+ || !(absolute === "/boot/config/go" ? [0o700, 0o755] : [0o700]).includes(parent.mode & 0o7777)
682
+ || fs.realpathSync(path.dirname(file)) !== path.dirname(file))
683
+ throw new Error("Sanctuary root lifecycle parent is unsafe");
684
+ // This temporary belongs exclusively to the fenced root transaction.
685
+ const temporary = `${file}.tmp`;
686
+ const fd = fs.openSync(temporary, fs.constants.O_WRONLY | fs.constants.O_CREAT | fs.constants.O_NOFOLLOW, 0o600);
687
+ let owned = false;
688
+ try {
689
+ try {
690
+ const stat = fs.fstatSync(fd);
691
+ if (stat.nlink !== 1 || !stat.isFile() || stat.uid !== this.#uid || stat.gid !== this.#gid || ![0o600, mode].includes(stat.mode & 0o7777))
692
+ throw new Error("Sanctuary root lifecycle temporary is unsafe");
693
+ owned = true;
694
+ fs.ftruncateSync(fd, 0);
695
+ fs.writeFileSync(fd, value);
696
+ fs.fchmodSync(fd, mode);
697
+ fs.fsyncSync(fd);
698
+ }
699
+ finally {
700
+ fs.closeSync(fd);
701
+ }
702
+ fs.renameSync(temporary, file);
703
+ }
704
+ catch (error) {
705
+ if (owned)
706
+ fs.unlinkSync(temporary);
707
+ throw error;
708
+ }
709
+ const directory = fs.openSync(path.dirname(file), fs.constants.O_RDONLY);
710
+ try {
711
+ fs.fsyncSync(directory);
712
+ }
713
+ finally {
714
+ fs.closeSync(directory);
715
+ }
716
+ }
717
+ #docker(args, input) {
718
+ return (0, node_child_process_1.execFileSync)("/usr/bin/docker", args, { encoding: "utf8", input, maxBuffer: 1024 * 1024, timeout: 120_000, stdio: ["pipe", "pipe", "pipe"] });
719
+ }
720
+ #containers() {
721
+ const names = this.#docker(["ps", "-a", "--format", "{{json .}}"]).trim().split("\n").filter(Boolean).map((line) => String(JSON.parse(line).Names));
722
+ return names.filter((name) => ["ouro-butler", "ouro-butler-rollback"].includes(name)).flatMap((name) => JSON.parse(this.#docker(["inspect", name])));
723
+ }
724
+ #assertNoForeignPoller() {
725
+ const names = this.#docker(["ps", "--format", "{{json .}}"]).trim().split("\n").filter(Boolean).map((line) => String(JSON.parse(line).Names));
726
+ for (const name of names) {
727
+ if (["ouro-butler", "ouro-butler-rollback"].includes(name))
728
+ continue;
729
+ const containers = JSON.parse(this.#docker(["inspect", name]));
730
+ if (containers.some((container) => container.Mounts.some((mount) => mount.Source === BUNDLE || mount.Source === RUNTIME)))
731
+ throw new Error("Sanctuary has an unowned possible direct poller");
732
+ }
733
+ }
734
+ #vault(operation, input) {
735
+ // The fenced container runs root with every capability dropped, but the
736
+ // resident credential store is owned by the resident uid (10001), so a bare
737
+ // cap-dropped root cannot even read it (EACCES on agent.json → surfaces as
738
+ // "vault owner unavailable"). CAP_DAC_OVERRIDE is restored — the single
739
+ // capability needed to reach the resident's files, nothing broader.
740
+ //
741
+ // bitwarden cannot unlock (or sync) a store it cannot write to, so the store
742
+ // dir must be writable inside the fence. The two verbs split by intent:
743
+ // • snapshot / presence are READS: the read-only store is copied into a
744
+ // throwaway tmpfs, so the fence never mutates resident state.
745
+ // • remove / restore are WRITES whose whole purpose is to change the vault
746
+ // (remove-resident-token, rollback restore-token-cursor). They bind the
747
+ // real ${RUNTIME}/bitwarden writable (rw over the read-only .ouro-cli),
748
+ // so the edit — and bitwarden's own sync-on-unlock — persists. Copying to
749
+ // tmpfs here would silently discard the write and fail the step's readback.
750
+ // `operation` is one of the four fixed literals validated by the CLI.
751
+ const cli = "/opt/ouro/dist/heart/daemon/sanctuary-authority-root-lifecycle.js";
752
+ const persists = operation === "remove" || operation === "restore";
753
+ const bitwarden = persists
754
+ ? ["--mount", `type=bind,src=${RUNTIME}/bitwarden,dst=/home/ouro/.ouro-cli/bitwarden`]
755
+ : [
756
+ "--mount", `type=bind,src=${RUNTIME}/bitwarden,dst=/home/ouro/.bw-src,readonly`,
757
+ "--tmpfs", "/home/ouro/.ouro-cli/bitwarden:rw,nosuid,nodev,noexec,mode=0700",
758
+ ];
759
+ const prepare = persists ? "" : "cp -r /home/ouro/.bw-src/. /home/ouro/.ouro-cli/bitwarden/ && ";
760
+ return JSON.parse(this.#docker([
761
+ "run", "--rm", "-i", "--pull=never", "--network", "host", "--user", "0:0", "--read-only",
762
+ "--cap-drop=ALL", "--cap-add=DAC_OVERRIDE", "--security-opt=no-new-privileges", "--entrypoint", "/bin/sh",
763
+ "--mount", `type=bind,src=${RUNTIME},dst=/home/ouro/.ouro-cli,readonly`,
764
+ ...bitwarden,
765
+ "--mount", `type=bind,src=${BUNDLE},dst=/home/ouro/AgentBundles/sanctuary.ouro,readonly`,
766
+ "--tmpfs", "/home/ouro/.config:rw,nosuid,nodev,noexec,mode=0700",
767
+ "--tmpfs", "/tmp:rw,nosuid,nodev,noexec,mode=0700",
768
+ "--env", `OURO_AUTHORITY_REMOVE_INTERLOCK=${this.#request.epochId}`,
769
+ this.#transaction.targetImageId, "-c",
770
+ `${prepare}exec /usr/local/bin/node ${cli} vault ${operation}`,
771
+ ], input === undefined ? undefined : JSON.stringify(input)));
772
+ }
773
+ }
774
+ exports.SanctuaryAuthorityRootLifecycle = SanctuaryAuthorityRootLifecycle;
775
+ async function runSanctuaryAuthorityRootCli(argv, write = (text) => process.stdout.write(text)) {
776
+ if (process.getuid() !== 0 || process.getgid() !== 0)
777
+ throw new Error("Sanctuary root lifecycle requires root");
778
+ if (argv.length === 2 && argv[0] === "vault") {
779
+ // `remove` is the one vault verb that can strand the household. It is safe
780
+ // only inside the install transaction, which has already proven the same
781
+ // token into root custody at `verify-token-rotation`. Run bare against a
782
+ // host with no installed root authority it leaves the Butler with no route
783
+ // to Telegram at all, which is how Sanctuary lost its front door on
784
+ // 2026-09-21. This is a safety interlock against invoking it outside its
785
+ // transaction, not a privilege boundary: root can always set the variable,
786
+ // but it can no longer do this by reaching for a documented verb.
787
+ if (argv[1] === "remove" && !process.env.OURO_AUTHORITY_REMOVE_INTERLOCK) {
788
+ throw new Error("Sanctuary vault removal runs only inside the authority install transaction; use docker-man-template-transaction.mjs authority-install");
789
+ }
790
+ const input = argv[1] === "restore" ? JSON.parse(fs.readFileSync(0, "utf8")) : undefined;
791
+ write(`${JSON.stringify(await (0, sanctuary_authority_vault_migration_1.migrateSanctuaryAuthorityVault)(argv[1], input))}\n`);
792
+ return;
793
+ }
794
+ const repin = argv.length === 3 && argv[0] === "repin-execution";
795
+ if (!repin && (argv.length !== 1 || argv[0] !== "boot"))
796
+ throw new Error("Usage: sanctuary-authority-root-lifecycle <boot|repin-execution <prlimit-sha256> <setsid-sha256>|vault snapshot|vault presence|vault remove|vault restore>");
797
+ await (0, session_transaction_1.withSessionTurnLease)("/boot/config/custom/ouro-butler/docker-man-template-transaction.json", async () => {
798
+ const activationPath = `${ROOT}/activation.json`;
799
+ if (!fs.existsSync(activationPath)) {
800
+ if (repin)
801
+ throw new Error("Sanctuary authority is not active");
802
+ return;
803
+ }
804
+ const activation = JSON.parse(fs.readFileSync(activationPath, "utf8"));
805
+ const lifecycle = new SanctuaryAuthorityRootLifecycle(activation);
806
+ if (repin) {
807
+ lifecycle.repinExecution(argv[1], argv[2]);
808
+ write('{"repinned":true,"gatewayRestartRequired":true}\n');
809
+ }
810
+ else
811
+ await lifecycle.boot();
812
+ }, { timeoutMs: 0, confinementRoot: "/boot/config/custom/ouro-butler" });
813
+ }
814
+ if (process.argv[1] && fs.existsSync(process.argv[1]) && fs.realpathSync(process.argv[1]) === __filename) {
815
+ void runSanctuaryAuthorityRootCli(process.argv.slice(2)).catch(() => {
816
+ process.stderr.write("Sanctuary root lifecycle failed; inspect the root transaction and repair its failed boundary.\n");
817
+ process.exitCode = 1;
818
+ });
819
+ }