@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,2654 @@
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
+ var __importDefault = (this && this.__importDefault) || function (mod) {
36
+ return (mod && mod.__esModule) ? mod : { "default": mod };
37
+ };
38
+ Object.defineProperty(exports, "__esModule", { value: true });
39
+ exports.proveAttemptedRecoveryWithoutRetry = void 0;
40
+ exports.evaluateSanctuaryProviderReadinessContract = evaluateSanctuaryProviderReadinessContract;
41
+ exports.createSanctuaryAcceptanceVaultProbeDependencies = createSanctuaryAcceptanceVaultProbeDependencies;
42
+ exports.createSanctuaryAcceptanceAdapterDependencies = createSanctuaryAcceptanceAdapterDependencies;
43
+ exports.createSanctuaryHealthAcceptanceScenarioDriver = createSanctuaryHealthAcceptanceScenarioDriver;
44
+ exports.createSanctuaryInteractiveAcceptanceScenarioDriver = createSanctuaryInteractiveAcceptanceScenarioDriver;
45
+ exports.createSanctuaryAcceptanceScenarioFinalizer = createSanctuaryAcceptanceScenarioFinalizer;
46
+ exports.auditContainsSensitiveMaterial = auditContainsSensitiveMaterial;
47
+ exports.canonicalDockerIdFromUnraidPrefixedId = canonicalDockerIdFromUnraidPrefixedId;
48
+ exports.runSanctuaryProductionBoundaryProbe = runSanctuaryProductionBoundaryProbe;
49
+ exports.createSanctuaryReadOnlyDenialScenarioDriver = createSanctuaryReadOnlyDenialScenarioDriver;
50
+ exports.readDefaultSanctuaryScenarioFacts = readDefaultSanctuaryScenarioFacts;
51
+ exports.callbackPlaybackSnapshot = callbackPlaybackSnapshot;
52
+ exports.recordCallbackPlayback = recordCallbackPlayback;
53
+ exports.executeSanctuaryAcceptanceAdapter = executeSanctuaryAcceptanceAdapter;
54
+ exports.executeSanctuaryAcceptanceVaultProbe = executeSanctuaryAcceptanceVaultProbe;
55
+ exports.executeSanctuaryAcceptanceRevokedProbe = executeSanctuaryAcceptanceRevokedProbe;
56
+ exports.executeSanctuaryInteractiveRuntimeOperation = executeSanctuaryInteractiveRuntimeOperation;
57
+ exports.executeSanctuaryAcceptanceCallbackProbe = executeSanctuaryAcceptanceCallbackProbe;
58
+ const node_child_process_1 = require("node:child_process");
59
+ const node_crypto_1 = require("node:crypto");
60
+ const node_fs_1 = require("node:fs");
61
+ const node_net_1 = require("node:net");
62
+ const path = __importStar(require("node:path"));
63
+ const node_util_1 = require("node:util");
64
+ const better_sqlite3_1 = __importDefault(require("better-sqlite3"));
65
+ const friends_1 = require("@ouro.bot/friends");
66
+ const runtime_1 = require("../../nerves/runtime");
67
+ const sanctuary_authority_resident_1 = require("../../senses/sanctuary-authority-resident");
68
+ const telegram_approval_runtime_1 = require("../../senses/telegram-approval-runtime");
69
+ const telegram_effect_adapter_1 = require("../../senses/telegram-effect-adapter");
70
+ const telegram_admission_1 = require("../../senses/telegram-admission");
71
+ const sanctuary_interactive_control_1 = require("../../senses/sanctuary-interactive-control");
72
+ const shared_turn_1 = require("../../senses/shared-turn");
73
+ const telegram_1 = require("../../senses/telegram");
74
+ const telegram_audit_ledger_1 = require("../../senses/telegram-audit-ledger");
75
+ const sanctuary_runtime_1 = require("../../senses/sanctuary-runtime");
76
+ const sanctuary_grounding_1 = require("../../senses/sanctuary-grounding");
77
+ const tools_1 = require("../../repertoire/tools");
78
+ const relationship_authorization_1 = require("../../repertoire/relationship-authorization");
79
+ const core_1 = require("../core");
80
+ const identity_1 = require("../identity");
81
+ const approval_store_1 = require("../approval-store");
82
+ const provider_credentials_1 = require("../provider-credentials");
83
+ const provider_ping_1 = require("../provider-ping");
84
+ const sanctuary_acceptance_harness_1 = require("./sanctuary-acceptance-harness");
85
+ const sanctuary_acceptance_marker_1 = require("./sanctuary-acceptance-marker");
86
+ const sanctuary_acceptance_scenarios_1 = require("./sanctuary-acceptance-scenarios");
87
+ const sanctuary_scheduler_liveness_1 = require("./sanctuary-scheduler-liveness");
88
+ const runtime_credentials_1 = require("../runtime-credentials");
89
+ const KEY_ID = /^[A-Za-z0-9._:-]+$/u;
90
+ const AUTH_PROBE = "query AcceptanceAuthProbe { info { os { hostname } } }";
91
+ const WRITE_PROBE = "mutation AcceptanceWriteProbe($id: PrefixedID!) { docker { restart(id: $id) { id } } }";
92
+ function evaluateSanctuaryProviderReadinessContract(input) {
93
+ return {
94
+ laneSelectionExact: input.outward.provider === "minimax" && input.outward.model === "MiniMax-M3"
95
+ && input.inner.provider === "minimax" && input.inner.model === "MiniMax-M3",
96
+ vaultCoordinatesExact: input.minimax.vaultItem === "providers/minimax"
97
+ && input.selectionPolicy === "explicit-same-lane-only",
98
+ singleCredentialExact: typeof input.minimax.apiKey === "string" && input.minimax.apiKey.trim().length > 0,
99
+ };
100
+ }
101
+ const MISSING_CONTAINER_ID = "Docker:ouro-acceptance-guaranteed-missing";
102
+ // container_snapshot may execute bounded 20s inspect + 30s vault + 30s recovery
103
+ // probes + 10s GraphQL + 20s image-policy checks sequentially in the host broker.
104
+ const ADAPTER_TIMEOUT_MS = 240_000;
105
+ const NETWORK_TIMEOUT_MS = 10_000;
106
+ const KEY_DIRECTORY = "/boot/config/plugins/dynamix.my.servers/keys";
107
+ const SELECTED_KEY_RECORD = "/run/ouro-acceptance/unraid-key.json";
108
+ const CALLBACK_PLAYBACK_JOURNAL = "state/approvals/sanctuary-callback-playback.sqlite";
109
+ const TELEGRAM_IDENTITY_KEY = "/home/ouro/AgentBundles/sanctuary.ouro/state/senses/telegram/identity.key";
110
+ const TELEGRAM_AUDIT = `/home/ouro/AgentBundles/sanctuary.ouro/${telegram_audit_ledger_1.TELEGRAM_ACCEPTANCE_AUDIT_RELATIVE_PATH}`;
111
+ const TELEGRAM_AUDIT_HEAD = `/home/ouro/AgentBundles/sanctuary.ouro/${telegram_audit_ledger_1.TELEGRAM_ACCEPTANCE_AUDIT_HEAD_RELATIVE_PATH}`;
112
+ const IMAGE_DIGEST_FILE = "/run/ouro-acceptance/image-digest";
113
+ const CONTAINER_DIGEST_FILE = "/run/ouro-acceptance/container-digest";
114
+ const PROCESS_BINDING_DIGEST_FILE = "/run/ouro-acceptance/process-binding-digest";
115
+ const POSTBOOT_HEALTH_FILE = "/run/ouro-acceptance/postboot-health.json";
116
+ const BOOT_ID_FILE = "/run/ouro-acceptance/boot-id";
117
+ const CONTRACT_FILE = "/opt/ouro/deploy/unraid/sanctuary-acceptance-contract.json";
118
+ const CLOSED_INVENTORY_FILE = "/run/ouro-acceptance/closed-inventory.json";
119
+ const HOST_BROKER_SOCKET = "/run/ouro-host-acceptance/adapter.sock";
120
+ const MAX_BROKER_RESPONSE = 256 * 1024;
121
+ const TARGET_SERVER_ID = "sanctuary-unraid";
122
+ const TARGET_ID = "sanctuary";
123
+ const SHA256 = /^[0-9a-f]{64}$/u;
124
+ const APPROVAL_EVIDENCE_LABELS = new Set([
125
+ "unit-15c-1-no-callback-terminalization", "unit-16i-delayed-approval", "unit-16j-denial",
126
+ "unit-16k-timeout-stale", "unit-16l-duplicate-callback", "unit-16m-restart-continuation",
127
+ ]);
128
+ const HEALTH_ACCEPTANCE_LABELS = new Set([
129
+ "unit-16f-cron-fingerprint",
130
+ "unit-16g-health-transition",
131
+ "unit-16h-acceptance-delivery-probe",
132
+ ]);
133
+ const PERMISSION_RESOURCES = new Set([
134
+ "ACTIVATION_CODE", "API_KEY", "ARRAY", "CLOUD", "CONFIG", "CONNECT", "CONNECT__REMOTE_ACCESS",
135
+ "CUSTOMIZATIONS", "DASHBOARD", "DISK", "DISPLAY", "DOCKER", "FLASH", "INFO", "LOGS", "ME",
136
+ "NETWORK", "NOTIFICATIONS", "ONLINE", "OS", "OWNER", "PERMISSION", "REGISTRATION", "SERVERS",
137
+ "SERVICES", "SHARE", "VARS", "VMS", "WELCOME",
138
+ ]);
139
+ const PERMISSION_ACTIONS = new Set(["CREATE_ANY", "CREATE_OWN", "READ_ANY", "READ_OWN", "UPDATE_ANY", "UPDATE_OWN", "DELETE_ANY", "DELETE_OWN"]);
140
+ const RO_PERMISSIONS = ["ARRAY", "DASHBOARD", "DISK", "DOCKER", "INFO", "LOGS", "NOTIFICATIONS", "SHARE", "VARS"]
141
+ .map((resource) => `${resource}:READ_ANY`).sort();
142
+ function object(value, label) {
143
+ if (!value || typeof value !== "object" || Array.isArray(value))
144
+ throw new Error(`${label} must be an object`);
145
+ return value;
146
+ }
147
+ function text(value, label) {
148
+ if (typeof value !== "string" || !value.trim())
149
+ throw new Error(`${label} must be nonempty text`);
150
+ return value.trim();
151
+ }
152
+ function keyId(value) {
153
+ const result = text(value, "keyId");
154
+ if (!KEY_ID.test(result))
155
+ throw new Error("keyId is invalid");
156
+ return result;
157
+ }
158
+ function readRawKeyDirectory(keyDirectory) {
159
+ return (0, node_fs_1.readdirSync)(keyDirectory, { withFileTypes: true })
160
+ .map((entry) => {
161
+ if (!entry.isFile() || !entry.name.endsWith(".json"))
162
+ throw new Error("unexpected key directory entry");
163
+ return entry;
164
+ })
165
+ .map((entry) => object(JSON.parse((0, node_fs_1.readFileSync)(`${keyDirectory}/${entry.name}`, "utf8")), "Unraid key file"));
166
+ }
167
+ function readKeyDirectory(keyDirectory) {
168
+ return readRawKeyDirectory(keyDirectory).map(normalizeKey);
169
+ }
170
+ function readKeyRecords(keyDirectory) {
171
+ return readRawKeyDirectory(keyDirectory).map((raw) => ({
172
+ ...normalizeKey(raw),
173
+ key: text(raw.key, "Unraid key descriptor"),
174
+ }));
175
+ }
176
+ function readSelectedKeyRecord(keyRecordPath) {
177
+ const raw = object(JSON.parse((0, node_fs_1.readFileSync)(keyRecordPath, "utf8")), "selected Unraid key file");
178
+ return { ...normalizeKey(raw), key: text(raw.key, "Unraid key descriptor") };
179
+ }
180
+ function createSanctuaryAcceptanceVaultProbeDependencies(options) {
181
+ const keyRecordPath = options.keyRecordPath;
182
+ return {
183
+ refresh: runtime_credentials_1.refreshMachineRuntimeCredentialConfig,
184
+ readKeyRecords: () => [readSelectedKeyRecord(keyRecordPath)],
185
+ fetch,
186
+ };
187
+ }
188
+ async function defaultExecFile(executable, args, timeoutMs) {
189
+ const result = (0, node_child_process_1.spawnSync)(executable, args, {
190
+ cwd: "/",
191
+ encoding: "utf8",
192
+ env: { PATH: "/usr/local/sbin:/usr/sbin:/usr/bin:/sbin:/bin" },
193
+ maxBuffer: 1_048_576,
194
+ timeout: timeoutMs,
195
+ stdio: ["ignore", "pipe", "ignore"],
196
+ });
197
+ if (result.error && "code" in result.error && result.error.code === "ETIMEDOUT")
198
+ throw new Error("acceptance adapter subprocess timed out");
199
+ if (result.error || result.status !== 0)
200
+ throw new Error("acceptance adapter subprocess failed");
201
+ return { status: result.status, stdout: result.stdout };
202
+ }
203
+ async function defaultHostRequest(payload, socketPath, timeoutMs) {
204
+ return await new Promise((resolve, reject) => {
205
+ const socket = (0, node_net_1.createConnection)(socketPath);
206
+ let response = "";
207
+ const fail = () => {
208
+ socket.destroy();
209
+ reject(new Error("Sanctuary host acceptance operation failed"));
210
+ };
211
+ socket.setEncoding("utf8");
212
+ socket.setTimeout(timeoutMs, fail);
213
+ socket.on("error", fail);
214
+ socket.on("data", (chunk) => {
215
+ response += chunk;
216
+ if (Buffer.byteLength(response) > MAX_BROKER_RESPONSE)
217
+ fail();
218
+ });
219
+ socket.on("end", () => {
220
+ try {
221
+ const envelope = object(JSON.parse(response), "host broker response");
222
+ if (JSON.stringify(Object.keys(envelope).sort()) !== JSON.stringify(envelope.ok === true ? ["ok", "result"] : ["error", "ok"]))
223
+ fail();
224
+ else if (envelope.ok !== true)
225
+ fail();
226
+ else {
227
+ resolve(envelope.result);
228
+ }
229
+ }
230
+ catch {
231
+ fail();
232
+ }
233
+ });
234
+ socket.end(`${JSON.stringify(payload)}\n`);
235
+ });
236
+ }
237
+ function acceptanceTelegramIdentity() {
238
+ const gateway = (0, sanctuary_authority_resident_1.openSanctuaryResidentAuthority)({}, {});
239
+ try {
240
+ return gateway.credentials;
241
+ }
242
+ finally {
243
+ gateway.authorityTransport.api.stop();
244
+ }
245
+ }
246
+ function createSanctuaryAcceptanceAdapterDependencies(secretFd = 3, options = {}) {
247
+ const keyDirectory = options.keyDirectory ?? KEY_DIRECTORY;
248
+ const adapterTimeoutMs = options.adapterTimeoutMs ?? ADAPTER_TIMEOUT_MS;
249
+ const hostBrokerSocket = options.hostBrokerSocket ?? HOST_BROKER_SOCKET;
250
+ const dependencies = {
251
+ gateway: () => (0, sanctuary_authority_resident_1.openSanctuaryResidentAuthority)({}, {}),
252
+ gatewayCursor: async () => {
253
+ const gateway = (0, sanctuary_authority_resident_1.openSanctuaryResidentAuthority)({}, {});
254
+ try {
255
+ const snapshot = await gateway.cursorSnapshot();
256
+ return { nextUpdateId: snapshot.cursor, progressDigest: snapshot.progressDigest };
257
+ }
258
+ finally {
259
+ gateway.authorityTransport.api.stop();
260
+ }
261
+ },
262
+ readKeyFiles: () => readKeyDirectory(keyDirectory),
263
+ readKeyRecords: () => readKeyRecords(keyDirectory),
264
+ readDescriptor: () => (0, node_fs_1.readFileSync)(secretFd, "utf8"),
265
+ execFile: (executable, args) => defaultExecFile(executable, args, adapterTimeoutMs),
266
+ fetch,
267
+ readFixedFile: (filePath) => (0, node_fs_1.readFileSync)(filePath, "utf8"),
268
+ refreshRuntime: runtime_credentials_1.refreshRuntimeCredentialConfig,
269
+ mergeRuntime: runtime_credentials_1.mergeRuntimeCredentialConfig,
270
+ refreshMachine: runtime_credentials_1.refreshMachineRuntimeCredentialConfig,
271
+ mergeMachine: runtime_credentials_1.mergeMachineRuntimeCredentialConfig,
272
+ callbackProbe: executeSanctuaryAcceptanceCallbackProbe,
273
+ callbackPlaybackSnapshot: (coordinateDigest) => callbackPlaybackSnapshot((0, identity_1.getAgentRoot)(TARGET_ID), coordinateDigest),
274
+ interactiveRuntime: executeSanctuaryInteractiveRuntimeOperation,
275
+ hostRequest: options.hostRequest ?? ((payload) => defaultHostRequest(payload, hostBrokerSocket, adapterTimeoutMs)),
276
+ telegramCredentials: acceptanceTelegramIdentity,
277
+ readLiveGrounding: readIndependentSanctuaryGrounding,
278
+ runProductionBoundaryProbe: runSanctuaryProductionBoundaryProbe,
279
+ providerRuntime: core_1.getProviderRuntime,
280
+ };
281
+ const healthDriver = createSanctuaryHealthAcceptanceScenarioDriver(dependencies.hostRequest);
282
+ const scenarioAgentRoot = options.scenarioCapture?.agentRoot ?? (0, identity_1.getAgentRoot)(TARGET_ID);
283
+ const interactiveDriver = createSanctuaryInteractiveAcceptanceScenarioDriver({
284
+ agentRoot: scenarioAgentRoot,
285
+ hostRequest: dependencies.hostRequest,
286
+ });
287
+ const denialDriver = createSanctuaryReadOnlyDenialScenarioDriver({
288
+ agentRoot: scenarioAgentRoot,
289
+ runProbe: (label, scenarioHandleDigest) => executeSanctuaryReadOnlyDenialProbe(label, scenarioHandleDigest, scenarioAgentRoot, dependencies),
290
+ });
291
+ dependencies.captureScenario = (0, sanctuary_acceptance_scenarios_1.createSanctuaryScenarioCapture)({
292
+ now: Date.now,
293
+ agentRoot: scenarioAgentRoot,
294
+ readFacts: (label, scenarioHandleDigest, readOptions) => readDefaultSanctuaryScenarioFacts(label, scenarioHandleDigest, dependencies, options.scenarioCapture?.agentRoot, readOptions),
295
+ healthDriver,
296
+ interactiveDriver,
297
+ denialDriver,
298
+ receiptRoot: options.scenarioCapture?.receiptRoot,
299
+ gateStatusPath: options.scenarioCapture?.gateStatusPath,
300
+ });
301
+ dependencies.finalizeScenarios = createSanctuaryAcceptanceScenarioFinalizer({
302
+ readActiveScenario: () => (0, sanctuary_acceptance_marker_1.readSanctuaryAcceptanceMarker)(TARGET_ID, scenarioAgentRoot),
303
+ recoverHealthScenario: healthDriver.recover,
304
+ finalizeInteractiveScenario: (label, scenarioHandleDigest) => {
305
+ interactiveDriver.complete(label, scenarioHandleDigest);
306
+ return "preserve";
307
+ },
308
+ finalizeLocal: () => (0, sanctuary_acceptance_scenarios_1.finalizeSanctuaryScenarioCapture)(options.scenarioCapture?.gateStatusPath, options.scenarioCapture?.receiptRoot, scenarioAgentRoot),
309
+ });
310
+ return dependencies;
311
+ }
312
+ function healthScenarioCoordinates(label, scenarioHandleDigest) {
313
+ if (!sanctuary_acceptance_harness_1.SANCTUARY_UNIT_16_EVIDENCE_LABELS.includes(label)
314
+ || !HEALTH_ACCEPTANCE_LABELS.has(label))
315
+ throw new Error("health probe label is invalid");
316
+ if (!SHA256.test(scenarioHandleDigest))
317
+ throw new Error("health probe scenario handle is invalid");
318
+ return { label: label, scenarioHandleDigest };
319
+ }
320
+ function ownerContainerSnapshot(value) {
321
+ const snapshot = object(value, "health probe owner snapshot");
322
+ exactKeys(snapshot, [
323
+ "schemaVersion", "containerId", "imageId", "running", "health", "user", "liveProcessUser", "processBindingDigest", "readOnlyRoot", "mountCount",
324
+ "mountsDigest", "mountsExact", "publishedPortCount", "networkMode", "securityExact", "writableKeyExposure",
325
+ "restartPolicy", "restartCount", "autostartExact", "updaterDisabled", "vaultUnlocked", "manualAuthRequired",
326
+ "recoveryMilestones",
327
+ ], "health probe owner snapshot");
328
+ const milestones = object(snapshot.recoveryMilestones, "health probe owner recovery milestones");
329
+ exactKeys(milestones, ["hostReady", "arrayReady", "dockerReady", "butlerReady", "tailscaleReady", "sshReady"], "health probe owner recovery milestones");
330
+ if (snapshot.schemaVersion !== 1 || typeof snapshot.containerId !== "string" || !SHA256.test(snapshot.containerId)
331
+ || typeof snapshot.imageId !== "string" || !/^sha256:[0-9a-f]{64}$/u.test(snapshot.imageId)
332
+ || typeof snapshot.running !== "boolean" || typeof snapshot.health !== "string" || !["healthy", "starting", "unhealthy", "missing"].includes(snapshot.health)
333
+ || typeof snapshot.user !== "string" || snapshot.user.length < 1 || snapshot.user.length > 64 || snapshot.liveProcessUser !== "10001:10001"
334
+ || typeof snapshot.processBindingDigest !== "string" || !SHA256.test(snapshot.processBindingDigest)
335
+ || typeof snapshot.readOnlyRoot !== "boolean" || !Number.isSafeInteger(snapshot.mountCount) || Number(snapshot.mountCount) < 0
336
+ || typeof snapshot.mountsDigest !== "string" || !SHA256.test(snapshot.mountsDigest) || typeof snapshot.mountsExact !== "boolean"
337
+ || !Number.isSafeInteger(snapshot.publishedPortCount) || Number(snapshot.publishedPortCount) < 0
338
+ || typeof snapshot.networkMode !== "string" || snapshot.networkMode.length < 1 || snapshot.networkMode.length > 64
339
+ || typeof snapshot.securityExact !== "boolean" || typeof snapshot.writableKeyExposure !== "boolean"
340
+ || typeof snapshot.restartPolicy !== "string" || snapshot.restartPolicy.length < 1 || snapshot.restartPolicy.length > 64
341
+ || !Number.isSafeInteger(snapshot.restartCount) || Number(snapshot.restartCount) < 0
342
+ || typeof snapshot.autostartExact !== "boolean" || typeof snapshot.updaterDisabled !== "boolean"
343
+ || typeof snapshot.vaultUnlocked !== "boolean" || typeof snapshot.manualAuthRequired !== "boolean"
344
+ || Object.values(milestones).some((entry) => typeof entry !== "boolean"))
345
+ throw new Error("health probe owner snapshot schema is invalid");
346
+ return snapshot;
347
+ }
348
+ function createSanctuaryHealthAcceptanceScenarioDriver(hostRequest) {
349
+ const payload = (operation, label, scenarioHandleDigest) => {
350
+ const coordinates = healthScenarioCoordinates(label, scenarioHandleDigest);
351
+ return { operation, targetId: TARGET_ID, ...coordinates };
352
+ };
353
+ return {
354
+ begin: async (label, scenarioHandleDigest) => {
355
+ const response = object(await hostRequest(payload("start_health_probe", label, scenarioHandleDigest)), "health probe start response");
356
+ exactKeys(response, ["state", "operationDigest"], "health probe start response");
357
+ if (response.state !== "started" || typeof response.operationDigest !== "string" || !SHA256.test(response.operationDigest)) {
358
+ throw new Error("health probe start response is invalid");
359
+ }
360
+ },
361
+ poll: async (label, scenarioHandleDigest) => {
362
+ const response = object(await hostRequest(payload("health_probe_status", label, scenarioHandleDigest)), "health probe status response");
363
+ if (response.state === "running") {
364
+ exactKeys(response, ["state"], "health probe status response");
365
+ return { state: "waiting" };
366
+ }
367
+ if (response.state === "complete") {
368
+ exactKeys(response, ["state", "containerSnapshot"], "health probe status response");
369
+ return { state: "ready", containerSnapshot: ownerContainerSnapshot(response.containerSnapshot) };
370
+ }
371
+ throw new Error("health probe status state is invalid");
372
+ },
373
+ recover: async (label, scenarioHandleDigest) => {
374
+ const response = object(await hostRequest(payload("recover_health_probe", label, scenarioHandleDigest)), "health probe recovery response");
375
+ exactKeys(response, ["recovered"], "health probe recovery response");
376
+ if (response.recovered !== true)
377
+ throw new Error("health probe recovery response is invalid");
378
+ },
379
+ };
380
+ }
381
+ function isInteractiveLabel(label) {
382
+ return label === "unit-16k-timeout-stale" || label === "unit-16l-duplicate-callback" || label === "unit-16m-restart-continuation";
383
+ }
384
+ function currentInteractiveProposal(records, label) {
385
+ const matches = records.filter(({ approval }) => (approval.state === "proposed" || (label === "unit-16k-timeout-stale" && approval.state === "expired")) && approval.toolName === "unraid_restart_container"
386
+ && approval.arguments.container === "calibre-web" && approval.transport === "telegram");
387
+ if (matches.length > 1)
388
+ throw new Error("interactive acceptance proposal is ambiguous");
389
+ return matches[0] ?? null;
390
+ }
391
+ function createSanctuaryInteractiveAcceptanceScenarioDriver(options) {
392
+ const receiptRoot = path.join(options.agentRoot, "state/acceptance/interactive-driver-receipts");
393
+ const receiptPath = (scenarioHandleDigest) => path.join(receiptRoot, `${scenarioHandleDigest}.json`);
394
+ const approvals = options.readApprovals ?? approval_store_1.readApprovalsByScenarioHandleDigest;
395
+ return {
396
+ async poll(label, scenarioHandleDigest) {
397
+ if (!isInteractiveLabel(label))
398
+ return { state: "waiting" };
399
+ const filePath = receiptPath(scenarioHandleDigest);
400
+ if ((0, node_fs_1.existsSync)(filePath)) {
401
+ const existing = object(JSON.parse((0, node_fs_1.readFileSync)(filePath, "utf8")), "interactive driver receipt");
402
+ if (existing.label !== label || existing.scenarioHandleDigest !== scenarioHandleDigest)
403
+ throw new Error("interactive driver receipt binding mismatch");
404
+ if (existing.phase === "complete")
405
+ return { state: "driven" };
406
+ }
407
+ const projection = currentInteractiveProposal(approvals(path.join(options.agentRoot, "state/approvals/approvals.sqlite"), scenarioHandleDigest), label);
408
+ if (!projection)
409
+ return { state: "waiting" };
410
+ const { approval } = projection;
411
+ if (!approval.suspendedSessionRevision || !SHA256.test(approval.checkpointDigest))
412
+ throw new Error("interactive acceptance checkpoint is invalid");
413
+ const operation = label === "unit-16k-timeout-stale" ? "drive_timeout_stale"
414
+ : label === "unit-16l-duplicate-callback" ? "drive_duplicate_callbacks" : "drive_restart_continuation";
415
+ const response = object(await options.hostRequest({ operation, targetId: TARGET_ID, label, scenarioHandleDigest }), "interactive scenario driver response");
416
+ if (label === "unit-16k-timeout-stale" && response.state === "waiting") {
417
+ exactKeys(response, ["state"], "interactive scenario driver response");
418
+ return { state: "waiting" };
419
+ }
420
+ if (label === "unit-16m-restart-continuation") {
421
+ if (response.state === "waiting") {
422
+ exactKeys(response, ["state"], "interactive scenario driver response");
423
+ return { state: "waiting" };
424
+ }
425
+ if (response.state === "failed") {
426
+ exactKeys(response, ["state", "errorDigest"], "interactive scenario driver response");
427
+ if (typeof response.errorDigest !== "string" || !SHA256.test(response.errorDigest))
428
+ throw new Error("interactive scenario driver failure is invalid");
429
+ throw new Error(`interactive scenario driver failed (${response.errorDigest})`);
430
+ }
431
+ exactKeys(response, ["state", "receipt"], "interactive scenario driver response");
432
+ if (response.state !== "complete" || !parseInteractiveDriverReceipt(JSON.stringify(response.receipt), label, scenarioHandleDigest)) {
433
+ throw new Error("interactive scenario driver receipt is invalid");
434
+ }
435
+ return { state: "driven" };
436
+ }
437
+ parseInteractiveDriverReceipt(JSON.stringify(response), label, scenarioHandleDigest);
438
+ return { state: "driven" };
439
+ },
440
+ complete(label, scenarioHandleDigest) {
441
+ if (!isInteractiveLabel(label))
442
+ return "complete";
443
+ const filePath = receiptPath(scenarioHandleDigest);
444
+ if ((0, node_fs_1.existsSync)(filePath)) {
445
+ const existing = object(JSON.parse((0, node_fs_1.readFileSync)(filePath, "utf8")), "interactive driver receipt");
446
+ if (existing.label !== label || existing.scenarioHandleDigest !== scenarioHandleDigest)
447
+ throw new Error("interactive driver receipt binding mismatch");
448
+ if (existing.phase !== "complete")
449
+ return "preserve";
450
+ }
451
+ return "complete";
452
+ },
453
+ cleanup(label, scenarioHandleDigest) {
454
+ if (!isInteractiveLabel(label))
455
+ return;
456
+ const filePath = receiptPath(scenarioHandleDigest);
457
+ if (!(0, node_fs_1.existsSync)(filePath))
458
+ return;
459
+ const existing = object(JSON.parse((0, node_fs_1.readFileSync)(filePath, "utf8")), "interactive driver receipt");
460
+ if (existing.label !== label || existing.scenarioHandleDigest !== scenarioHandleDigest || existing.phase !== "complete") {
461
+ throw new Error("interactive driver receipt is not cleanup-ready");
462
+ }
463
+ (0, node_fs_1.unlinkSync)(filePath);
464
+ const directory = (0, node_fs_1.openSync)(receiptRoot, "r");
465
+ try {
466
+ (0, node_fs_1.fsyncSync)(directory);
467
+ }
468
+ finally {
469
+ (0, node_fs_1.closeSync)(directory);
470
+ }
471
+ },
472
+ };
473
+ }
474
+ function createSanctuaryAcceptanceScenarioFinalizer(dependencies) {
475
+ const appendErrorLeaves = (errors, error) => {
476
+ if (error instanceof AggregateError) {
477
+ for (const nested of error.errors)
478
+ appendErrorLeaves(errors, nested);
479
+ }
480
+ else
481
+ errors.push(error);
482
+ };
483
+ return async () => {
484
+ const errors = [];
485
+ let active = null;
486
+ try {
487
+ active = dependencies.readActiveScenario();
488
+ }
489
+ catch (error) {
490
+ appendErrorLeaves(errors, error);
491
+ }
492
+ if (active && HEALTH_ACCEPTANCE_LABELS.has(active.label)) {
493
+ try {
494
+ await dependencies.recoverHealthScenario(active.label, active.scenarioHandleDigest);
495
+ }
496
+ catch (error) {
497
+ appendErrorLeaves(errors, error);
498
+ }
499
+ }
500
+ let preserveInteractive = false;
501
+ if (active && isInteractiveLabel(active.label) && dependencies.finalizeInteractiveScenario) {
502
+ preserveInteractive = true;
503
+ try {
504
+ await dependencies.finalizeInteractiveScenario(active.label, active.scenarioHandleDigest);
505
+ }
506
+ catch (error) {
507
+ appendErrorLeaves(errors, error);
508
+ }
509
+ }
510
+ if (preserveInteractive)
511
+ errors.push(new Error("interactive scenario requires inspect-before-retry"));
512
+ else
513
+ try {
514
+ dependencies.finalizeLocal();
515
+ }
516
+ catch (error) {
517
+ appendErrorLeaves(errors, error);
518
+ }
519
+ if (errors.length > 0)
520
+ throw new AggregateError(errors, preserveInteractive
521
+ ? "Sanctuary scenario recovery and finalization failed: interactive scenario requires inspect-before-retry"
522
+ : "Sanctuary scenario recovery and finalization failed");
523
+ };
524
+ }
525
+ function optionalFixedFile(deps, filePath) {
526
+ try {
527
+ return deps.readFixedFile ? deps.readFixedFile(filePath) : (0, node_fs_1.readFileSync)(filePath, "utf8");
528
+ }
529
+ catch (error) {
530
+ if (error.code === "ENOENT")
531
+ return null;
532
+ throw error;
533
+ }
534
+ }
535
+ function parsedJson(raw) {
536
+ if (raw === null)
537
+ return null;
538
+ return object(JSON.parse(raw), "scenario source");
539
+ }
540
+ async function projectTelegramAdmissions(agentRoot, credentials, identityKey) {
541
+ const admissionsRoot = path.join(agentRoot, "state", "senses", "telegram", "admissions");
542
+ const effectsRoot = path.join(agentRoot, "state", "telegram", "effects");
543
+ if (!(0, node_fs_1.existsSync)(admissionsRoot) || !(0, node_fs_1.existsSync)(effectsRoot) || !identityKey || !/^[A-Za-z0-9_-]{43}$/u.test(identityKey))
544
+ return [];
545
+ const admissionStore = new telegram_admission_1.FileTelegramAdmissionStore(admissionsRoot);
546
+ const effectStore = new telegram_effect_adapter_1.FileTelegramEffectJournal(effectsRoot);
547
+ try {
548
+ const effects = new Map(effectStore.list().map((artifact) => [artifact.id, artifact]));
549
+ const botId = credentials.botId;
550
+ const ownerSessionKey = `telegram:${(0, telegram_1.opaqueTelegramSubject)(identityKey, botId, credentials.authorizedUserId, credentials.authorizedChatId)}`;
551
+ const friendsRoot = path.join(agentRoot, "friends");
552
+ const friendStore = (0, node_fs_1.existsSync)(friendsRoot) ? new friends_1.FileFriendStore(friendsRoot) : null;
553
+ const owner = friendStore ? await friendStore.findByExternalId("telegram-user", credentials.authorizedUserId, botId) : null;
554
+ const exactSameBotIdentity = (friend, userId) => {
555
+ const bindings = friend.externalIds.filter((external) => external.provider === "telegram-user" && external.tenantId === botId);
556
+ return bindings.length === 1 && bindings[0].externalId === userId;
557
+ };
558
+ const ownerExact = Boolean(owner && exactSameBotIdentity(owner, credentials.authorizedUserId) && owner.trustLevel === "family" && owner.admissionState === "active"
559
+ && owner.initiativePolicy === "proactive" && owner.capabilityProfileId === "sanctuary-owner");
560
+ const projections = await Promise.all(admissionStore.list().map(async (record) => {
561
+ const acknowledgement = record.acknowledgementArtifactId ? effects.get(record.acknowledgementArtifactId) : undefined;
562
+ const ownerCard = record.ownerCardArtifactId ? effects.get(record.ownerCardArtifactId) : undefined;
563
+ const expectedButtons = [[
564
+ { text: "Allow", callbackData: `admit:${record.id}:allow` },
565
+ { text: "Deny", callbackData: `admit:${record.id}:deny` },
566
+ { text: "Block", callbackData: `admit:${record.id}:block` },
567
+ ]];
568
+ const admittedFriend = friendStore ? await friendStore.findByExternalId("telegram-user", record.userId, record.botId) : null;
569
+ const acknowledgementExact = Boolean(acknowledgement?.idempotencyKey === `ack:${record.id}`
570
+ && acknowledgement.authorClass === "control" && acknowledgement.target.kind === "admission_gate"
571
+ && acknowledgement.target.admissionId === record.id && acknowledgement.target.botId === record.botId
572
+ && acknowledgement.target.userId === record.userId && acknowledgement.target.chatId === record.chatId
573
+ && acknowledgement.effect.kind === "admission_ack" && acknowledgement.effect.text === telegram_effect_adapter_1.FIXED_ADMISSION_ACKNOWLEDGEMENT
574
+ && acknowledgement.parts.every((part) => part.state === "accepted" || part.state === "session_recorded"));
575
+ const ownerCardExact = Boolean(ownerExact && owner && ownerCard?.idempotencyKey === `owner-card:${record.id}`
576
+ && ownerCard.authorClass === "control" && ownerCard.target.kind === "approved_relationship"
577
+ && ownerCard.target.friendId === owner.id && ownerCard.target.sessionKey === ownerSessionKey && ownerCard.target.requestId === record.id
578
+ && ownerCard.effect.kind === "card" && ownerCard.effect.text.startsWith("Unknown Telegram contact\n")
579
+ && ownerCard.effect.text.includes("The message is quarantined and has not been processed.")
580
+ && JSON.stringify(ownerCard.effect.buttons) === JSON.stringify(expectedButtons)
581
+ && ownerCard.parts.every((part) => part.state === "session_recorded"));
582
+ return {
583
+ admissionDigest: (0, telegram_1.sanctuaryTelegramTurnReceiptDigest)(identityKey, "sanctuary-telegram-turn-receipt-v3", "admission", `${record.id}\0${record.botId}\0${record.userId}\0${record.chatId}\0${record.updateId}\0${record.messageId}`),
584
+ status: record.status,
585
+ identityExact: record.botId === botId && record.userId === record.chatId && record.userId !== credentials.authorizedUserId,
586
+ acknowledgementExact,
587
+ ownerCardExact,
588
+ contentQuarantined: typeof record.quarantinedText === "string" && (0, node_crypto_1.createHash)("sha256").update(record.quarantinedText).digest("hex") === record.contentDigest,
589
+ relationshipAbsent: record.friendId === null && admittedFriend === null,
590
+ capturedAt: record.createdAt,
591
+ updatedAt: record.updatedAt,
592
+ };
593
+ }));
594
+ return projections.sort((left, right) => left.admissionDigest.localeCompare(right.admissionDigest));
595
+ }
596
+ finally {
597
+ admissionStore.close();
598
+ effectStore.close();
599
+ }
600
+ }
601
+ function canonicalIso(value) {
602
+ return typeof value === "string" && value.length <= 30 && Number.isFinite(Date.parse(value)) && new Date(Date.parse(value)).toISOString() === value;
603
+ }
604
+ function boundedLines(raw, label, limits) {
605
+ if (Buffer.byteLength(raw) > limits.bytes)
606
+ throw new Error(`${label} exceeds its bound`);
607
+ const lines = raw.split("\n").filter(Boolean);
608
+ if (lines.length > limits.rows || lines.some((line) => Buffer.byteLength(line) > limits.rowBytes))
609
+ throw new Error(`${label} exceeds its bound`);
610
+ return lines;
611
+ }
612
+ function parseRestartAttempts(raw, scenarioHandleDigest) {
613
+ const exactKeys = ["actionDigest", "afterState", "approvalId", "argumentDigest", "attemptId", "beforeState", "container", "mutationAcknowledged", "observedAt", "scenarioHandleDigest", "state"].sort();
614
+ return boundedLines(raw, "restart attempt ledger", { bytes: 4 * 1024 * 1024, rows: 500, rowBytes: 8 * 1024 }).flatMap((line) => {
615
+ const attempt = object(JSON.parse(line), "restart attempt");
616
+ const containerRecord = object(attempt.container, "restart target");
617
+ if (JSON.stringify(Object.keys(attempt).sort()) !== JSON.stringify(exactKeys)
618
+ || JSON.stringify(Object.keys(containerRecord).sort()) !== JSON.stringify(["id", "name"])
619
+ || typeof containerRecord.id !== "string" || containerRecord.id.length < 1 || containerRecord.id.length > 128
620
+ || typeof containerRecord.name !== "string" || containerRecord.name.length < 1 || containerRecord.name.length > 128
621
+ || !["attempt_not_started", "attempting", "succeeded", "attempted_or_indeterminate"].includes(String(attempt.state))
622
+ || !canonicalIso(attempt.observedAt) || !SHA256.test(String(attempt.actionDigest)) || !SHA256.test(String(attempt.argumentDigest))
623
+ || typeof attempt.scenarioHandleDigest !== "string" || !SHA256.test(attempt.scenarioHandleDigest)
624
+ || typeof attempt.approvalId !== "string" || attempt.approvalId.length < 1 || attempt.approvalId.length > 128
625
+ || typeof attempt.attemptId !== "string" || attempt.attemptId.length < 1 || attempt.attemptId.length > 128
626
+ || typeof attempt.beforeState !== "string" || attempt.beforeState.length > 64 || typeof attempt.mutationAcknowledged !== "boolean"
627
+ || (attempt.afterState !== null && (typeof attempt.afterState !== "string" || attempt.afterState.length > 64)))
628
+ throw new Error("restart attempt ledger row is invalid");
629
+ if (scenarioHandleDigest !== null && attempt.scenarioHandleDigest !== scenarioHandleDigest)
630
+ return [];
631
+ return [{ state: attempt.state, actionDigest: String(attempt.actionDigest), argumentDigest: String(attempt.argumentDigest), target: containerRecord.name, targetId: containerRecord.id, beforeState: attempt.beforeState, scenarioHandleDigest: attempt.scenarioHandleDigest, approvalId: attempt.approvalId, attemptId: attempt.attemptId, observedAt: Date.parse(attempt.observedAt), mutationAcknowledged: attempt.mutationAcknowledged, afterState: attempt.afterState }];
632
+ });
633
+ }
634
+ function parseHealthAcceptanceState(raw) {
635
+ if (raw === null)
636
+ return null;
637
+ if (Buffer.byteLength(raw) > 4 * 1024 * 1024)
638
+ throw new Error("Sanctuary health state exceeds its bound");
639
+ const health = object(JSON.parse(raw), "Sanctuary health state");
640
+ const rootKeys = ["deliveredReceipts", "incidents", "indeterminateDeliveries", "lastDigestDay", "outbox", "sweepReceipts", "updatedAt"].sort();
641
+ if (JSON.stringify(Object.keys(health).sort()) !== JSON.stringify(rootKeys)
642
+ || !health.incidents || typeof health.incidents !== "object" || Array.isArray(health.incidents)
643
+ || (health.lastDigestDay !== null && typeof health.lastDigestDay !== "string") || !canonicalIso(health.updatedAt)
644
+ || (health.outbox !== null && (typeof health.outbox !== "object" || Array.isArray(health.outbox)))
645
+ || !Array.isArray(health.indeterminateDeliveries))
646
+ throw new Error("Sanctuary health state schema is invalid");
647
+ if (!Array.isArray(health.deliveredReceipts) || health.deliveredReceipts.length > 100 || !health.deliveredReceipts.every((rawReceipt) => {
648
+ const receipt = object(rawReceipt, "health delivery receipt");
649
+ return JSON.stringify(Object.keys(receipt).sort()) === JSON.stringify(["deliveredAt", "deliveryId", "kind", "messageIds"])
650
+ && typeof receipt.deliveryId === "string" && receipt.deliveryId.length > 0 && receipt.deliveryId.length <= 128
651
+ && ["transition", "digest", "transition_and_digest", "legacy_unknown"].includes(String(receipt.kind)) && canonicalIso(receipt.deliveredAt)
652
+ && Array.isArray(receipt.messageIds) && receipt.messageIds.length > 0 && receipt.messageIds.length <= 100 && receipt.messageIds.every((id) => Number.isSafeInteger(id) && Number(id) > 0);
653
+ }))
654
+ throw new Error("Sanctuary health delivery receipts are invalid");
655
+ if (!Array.isArray(health.sweepReceipts) || health.sweepReceipts.length > 500 || !health.sweepReceipts.every((rawReceipt) => {
656
+ const receipt = object(rawReceipt, "health sweep receipt");
657
+ const expectedKeys = ["completedAt", "digestDue", "incidentDigest", "opened", "recovered", "startedAt", "sweepId", ...(receipt.deliveryId === undefined ? [] : ["deliveryId"]), ...(receipt.scenarioHandleDigest === undefined ? [] : ["scenarioHandleDigest"])].sort();
658
+ return JSON.stringify(Object.keys(receipt).sort()) === JSON.stringify(expectedKeys)
659
+ && typeof receipt.sweepId === "string" && /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/u.test(receipt.sweepId)
660
+ && canonicalIso(receipt.startedAt) && canonicalIso(receipt.completedAt) && typeof receipt.incidentDigest === "string" && SHA256.test(receipt.incidentDigest)
661
+ && Number.isSafeInteger(receipt.opened) && Number(receipt.opened) >= 0 && Number.isSafeInteger(receipt.recovered) && Number(receipt.recovered) >= 0 && typeof receipt.digestDue === "boolean"
662
+ && (receipt.deliveryId === undefined || (typeof receipt.deliveryId === "string" && receipt.deliveryId.length > 0 && receipt.deliveryId.length <= 128))
663
+ && (receipt.scenarioHandleDigest === undefined || (typeof receipt.scenarioHandleDigest === "string" && SHA256.test(receipt.scenarioHandleDigest)));
664
+ }))
665
+ throw new Error("Sanctuary health sweep receipts are invalid");
666
+ return health;
667
+ }
668
+ function buildPostbootIntegritySnapshot(input) {
669
+ const canonical = (value) => {
670
+ if (Array.isArray(value))
671
+ return `[${value.map(canonical).join(",")}]`;
672
+ if (value && typeof value === "object")
673
+ return `{${Object.keys(value).sort().map((key) => `${JSON.stringify(key)}:${canonical(value[key])}`).join(",")}}`;
674
+ return JSON.stringify(value);
675
+ };
676
+ const digest = (value) => (0, node_crypto_1.createHash)("sha256").update(canonical(value)).digest("hex");
677
+ const checkpoints = input.checkpointsRaw === null ? {} : object(JSON.parse(input.checkpointsRaw), "approval checkpoint state");
678
+ const approvalCheckpoints = Object.entries(checkpoints).map(([id, record]) => ({ idDigest: digest(id), recordDigest: digest(record) }))
679
+ .sort((left, right) => left.idDigest.localeCompare(right.idDigest));
680
+ const sweeps = (input.health?.sweepReceipts ?? []).map((row) => ({
681
+ idDigest: digest(row.sweepId), recordDigest: digest(row), scenarioHandleDigest: typeof row.scenarioHandleDigest === "string" ? row.scenarioHandleDigest : null,
682
+ deliveryIdDigest: typeof row.deliveryId === "string" ? digest(row.deliveryId) : null,
683
+ }));
684
+ const deliveries = (input.health?.deliveredReceipts ?? []).map((row) => ({ idDigest: digest(row.deliveryId), recordDigest: digest(row) }));
685
+ const scenarioRelevantEvents = new Set(["telegram.callback_settled", "telegram.update_dropped", "approval.acceptance_transition", "senses.sanctuary_read_receipt", "senses.telegram_approved_restart_end"]);
686
+ const restartAttempts = input.restartAttempts.map((row) => ({
687
+ idDigest: digest(row.attemptId), recordDigest: digest(row), state: row.state,
688
+ }));
689
+ return {
690
+ schemaVersion: "sanctuary-postboot-integrity-v2", activeScenarioHandleDigest: input.activeScenarioHandleDigest,
691
+ telegramNextUpdateId: input.telegramNextUpdateId, approvalCheckpoints,
692
+ approvalExecutionCount: new Set(restartAttempts.filter((row) => row.state !== "attempt_not_started").map((row) => row.idDigest)).size, restartAttempts,
693
+ fingerprintDigest: digest(input.cronRaw), sweeps, deliveries,
694
+ audits: input.auditLedgerEntries.map((row) => ({ idDigest: digest(row), recordDigest: digest(row), scenarioHandleDigest: typeof row.meta.scenarioHandleDigest === "string" ? row.meta.scenarioHandleDigest : null, scenarioRelevant: scenarioRelevantEvents.has(row.event) })),
695
+ };
696
+ }
697
+ function parseHealthProbeReceipt(raw, label, scenarioHandleDigest, identityKey) {
698
+ if (raw === null)
699
+ return undefined;
700
+ if (Buffer.byteLength(raw) > 128 * 1024)
701
+ throw new Error("Sanctuary health probe receipt exceeds its bound");
702
+ const receipt = object(JSON.parse(raw), "Sanctuary health probe receipt");
703
+ const exactKeys = ["acceptanceOnly", "beforeStateDigest", "clockMode", "cronDegradedAfter", "cronDegradedBefore", "cronFingerprintAfter", "cronFingerprintBefore", "cronRegisteredAfter", "cronRegisteredBefore", "deliveryCount", "effectiveNow", "fixtureSequenceDigest", "label", "ownerContainerDigestAfter", "ownerContainerDigestBefore", "ownerImageDigestAfter", "ownerImageDigestBefore", "phases", "privateTurnCount", "productionRestored", "productionScheduleChanged", "providerInvocationCount", "realCheckEquivalent", "restoredStateDigest", "scenarioHandleDigest", "schedulerReceipt", "schemaVersion", "snapshotAbsent", "socketAbsent", "workspaceAbsent"].sort();
704
+ const supportedLabel = label === "unit-16f-cron-fingerprint" || label === "unit-16g-health-transition" || label === "unit-16h-acceptance-delivery-probe";
705
+ const digestFields = ["ownerImageDigestBefore", "ownerImageDigestAfter", "ownerContainerDigestBefore", "ownerContainerDigestAfter", "beforeStateDigest", "restoredStateDigest", "cronFingerprintBefore", "cronFingerprintAfter", "fixtureSequenceDigest"];
706
+ const booleanFields = ["acceptanceOnly", "productionScheduleChanged", "cronRegisteredBefore", "cronRegisteredAfter", "cronDegradedBefore", "cronDegradedAfter", "workspaceAbsent", "socketAbsent", "snapshotAbsent", "realCheckEquivalent", "productionRestored"];
707
+ if (!supportedLabel || JSON.stringify(Object.keys(receipt).sort()) !== JSON.stringify(exactKeys) || receipt.schemaVersion !== "sanctuary-health-probe-receipt-v1"
708
+ || receipt.label !== label || receipt.scenarioHandleDigest !== scenarioHandleDigest || !SHA256.test(scenarioHandleDigest)
709
+ || !digestFields.every((field) => typeof receipt[field] === "string" && SHA256.test(receipt[field]))
710
+ || !booleanFields.every((field) => typeof receipt[field] === "boolean") || !canonicalIso(receipt.effectiveNow)
711
+ || (receipt.clockMode !== "ambient" && receipt.clockMode !== "local-daily-boundary")
712
+ || !Number.isSafeInteger(receipt.privateTurnCount) || Number(receipt.privateTurnCount) < 0 || Number(receipt.privateTurnCount) > 1_000
713
+ || !Number.isSafeInteger(receipt.providerInvocationCount) || Number(receipt.providerInvocationCount) < Number(receipt.privateTurnCount) || Number(receipt.providerInvocationCount) > 1_000
714
+ || !Number.isSafeInteger(receipt.deliveryCount) || Number(receipt.deliveryCount) < 0 || Number(receipt.deliveryCount) > 1_000
715
+ || !Array.isArray(receipt.phases) || receipt.phases.length < 1 || receipt.phases.length > 8)
716
+ throw new Error("Sanctuary health probe receipt schema is invalid");
717
+ const phases = receipt.phases.map((rawPhase, index) => {
718
+ const phase = object(rawPhase, "Sanctuary health probe phase");
719
+ const phaseKeys = ["deliveryKind", "deliveryReceiptDigest", "digestDue", "fixtureStatus", "name", "opened", "ordinal", "recovered", "sweepReceiptDigest", "trigger"].sort();
720
+ if (JSON.stringify(Object.keys(phase).sort()) !== JSON.stringify(phaseKeys) || phase.ordinal !== index + 1
721
+ || typeof phase.name !== "string" || phase.name.length < 1 || phase.name.length > 64 || (phase.trigger !== "cron" && phase.trigger !== "acceptance")
722
+ || (phase.fixtureStatus !== null && phase.fixtureStatus !== 200 && phase.fixtureStatus !== 503)
723
+ || !Number.isSafeInteger(phase.opened) || Number(phase.opened) < 0 || !Number.isSafeInteger(phase.recovered) || Number(phase.recovered) < 0
724
+ || typeof phase.digestDue !== "boolean" || (phase.deliveryKind !== null && !["transition", "digest", "transition_and_digest"].includes(String(phase.deliveryKind)))
725
+ || typeof phase.sweepReceiptDigest !== "string" || !SHA256.test(phase.sweepReceiptDigest)
726
+ || (phase.deliveryReceiptDigest !== null && (typeof phase.deliveryReceiptDigest !== "string" || !SHA256.test(phase.deliveryReceiptDigest))))
727
+ throw new Error("Sanctuary health probe phase is invalid");
728
+ return phase;
729
+ });
730
+ const fixtureSequence = phases.flatMap((phase) => phase.fixtureStatus === null ? [] : [phase.fixtureStatus]);
731
+ const expectedSequenceDigest = (0, node_crypto_1.createHash)("sha256").update(JSON.stringify(fixtureSequence)).digest("hex");
732
+ const deliveryCount = phases.filter((phase) => phase.deliveryReceiptDigest !== null).length;
733
+ if (receipt.fixtureSequenceDigest !== expectedSequenceDigest || receipt.deliveryCount !== deliveryCount)
734
+ throw new Error("Sanctuary health probe receipt counters are invalid");
735
+ if (label === "unit-16f-cron-fingerprint") {
736
+ const scheduler = object(receipt.schedulerReceipt, "Sanctuary scheduler liveness receipt");
737
+ const schedulerKeys = ["after", "before", "deliveryDelta", "label", "nonReplay", "occurrenceId", "privateTurnCount", "providerInvocationCount", "receiptMac", "recordedAt", "runnerId", "scenarioHandleDigest", "schedulerOrigin", "schemaVersion", "supervisor", "sweep", "sweepDelta", "trigger"].sort();
738
+ const supervisor = object(scheduler.supervisor, "Sanctuary scheduler supervisor snapshot");
739
+ const supervisorKeys = ["args", "binaryPath", "childCount", "childPid", "crontabPath", "daemonPid", "healthy", "manifest", "namespace", "renderedCrontab", "schemaVersion"].sort();
740
+ const before = object(scheduler.before, "Sanctuary scheduler before cursor");
741
+ const after = object(scheduler.after, "Sanctuary scheduler after cursor");
742
+ const sweep = object(scheduler.sweep, "Sanctuary scheduler sweep");
743
+ if (!Array.isArray(supervisor.manifest) || supervisor.manifest.length !== 1)
744
+ throw new Error("Sanctuary scheduler liveness receipt is invalid");
745
+ const manifest = supervisor.manifest;
746
+ const manifestJob = object(manifest[0], "Sanctuary scheduler manifest job");
747
+ const origin = object(scheduler.schedulerOrigin, "Sanctuary scheduler origin");
748
+ const manifestKeys = ["agent", "command", "id", "lastRun", "schedule", "taskId", "taskPath"].sort();
749
+ if (!identityKey || !(0, sanctuary_scheduler_liveness_1.verifySanctuarySchedulerLivenessReceiptMac)(identityKey, scheduler)
750
+ || JSON.stringify(Object.keys(scheduler).sort()) !== JSON.stringify(schedulerKeys)
751
+ || scheduler.schemaVersion !== "sanctuary-scheduler-liveness-receipt-v1" || scheduler.label !== label || scheduler.scenarioHandleDigest !== scenarioHandleDigest
752
+ || scheduler.trigger !== "cron" || typeof scheduler.occurrenceId !== "string" || scheduler.occurrenceId.length === 0
753
+ || typeof scheduler.runnerId !== "string" || !/^[0-9a-f-]{36}$/u.test(scheduler.runnerId) || !canonicalIso(scheduler.recordedAt)
754
+ || JSON.stringify(Object.keys(before).sort()) !== JSON.stringify(["deliveryCount", "sweepCount"])
755
+ || JSON.stringify(Object.keys(after).sort()) !== JSON.stringify(["deliveryCount", "sweepCount"])
756
+ || !Number.isSafeInteger(before.sweepCount) || !Number.isSafeInteger(before.deliveryCount)
757
+ || after.sweepCount !== Number(before.sweepCount) + 1 || after.deliveryCount !== before.deliveryCount
758
+ || scheduler.sweepDelta !== 1 || scheduler.deliveryDelta !== 0 || scheduler.providerInvocationCount !== 0 || scheduler.privateTurnCount !== 0 || scheduler.nonReplay !== true
759
+ || JSON.stringify(Object.keys(sweep).sort()) !== JSON.stringify(["deliveryId", "digestDue", "opened", "recordDigest", "recovered"])
760
+ || typeof sweep.recordDigest !== "string" || !SHA256.test(sweep.recordDigest) || sweep.opened !== 0 || sweep.recovered !== 0 || sweep.digestDue !== false || sweep.deliveryId !== null
761
+ || JSON.stringify(Object.keys(supervisor).sort()) !== JSON.stringify(supervisorKeys) || supervisor.schemaVersion !== "supercronic-supervisor-snapshot-v1"
762
+ || !Number.isSafeInteger(supervisor.daemonPid) || Number(supervisor.daemonPid) !== 1 || supervisor.childCount !== 1 || !Number.isSafeInteger(supervisor.childPid) || Number(supervisor.childPid) <= 1 || supervisor.healthy !== true
763
+ || supervisor.binaryPath !== "/usr/local/bin/supercronic" || supervisor.crontabPath !== "/home/ouro/.ouro-cli/scheduler/sanctuary.crontab"
764
+ || JSON.stringify(supervisor.args) !== JSON.stringify(["-split-logs", "-inotify", "/home/ouro/.ouro-cli/scheduler/sanctuary.crontab"])
765
+ || supervisor.namespace !== "habit:sanctuary" || manifestJob.id !== "sanctuary:sanctuary-health" || manifestJob.agent !== "sanctuary"
766
+ || JSON.stringify(Object.keys(manifestJob).sort()) !== JSON.stringify(manifestKeys)
767
+ || manifestJob.taskId !== "sanctuary-health" || manifestJob.schedule !== "*/15 * * * *" || sweep.recordDigest !== phases[0]?.sweepReceiptDigest
768
+ || manifestJob.taskPath !== "/home/ouro/AgentBundles/sanctuary.ouro/habits/sanctuary-health.md"
769
+ || manifestJob.command !== "/usr/local/bin/node /opt/ouro/dist/heart/daemon/ouro-entry.js poke sanctuary --habit sanctuary-health --trigger cron"
770
+ || JSON.stringify(Object.keys(origin).sort()) !== JSON.stringify(["invocationPid", "invocationStartTime", "occurrenceId", "parentPid", "parentStartTime", "proofMac", "scenarioHandleDigest", "schedulerRunId", "slot"].sort())
771
+ || origin.parentPid !== supervisor.childPid || typeof origin.slot !== "string" || scheduler.occurrenceId !== `cron:${origin.slot}` || origin.occurrenceId !== scheduler.occurrenceId
772
+ || origin.scenarioHandleDigest !== scenarioHandleDigest
773
+ || typeof origin.schedulerRunId !== "string" || !/^[0-9a-f-]{36}$/u.test(origin.schedulerRunId) || typeof origin.proofMac !== "string" || !SHA256.test(origin.proofMac))
774
+ throw new Error("Sanctuary scheduler liveness receipt is invalid");
775
+ }
776
+ else if (receipt.schedulerReceipt !== null)
777
+ throw new Error("Sanctuary health probe has an unexpected scheduler receipt");
778
+ if (receipt.ownerImageDigestBefore !== receipt.ownerImageDigestAfter || receipt.ownerContainerDigestBefore !== receipt.ownerContainerDigestAfter
779
+ || (label !== "unit-16f-cron-fingerprint" && receipt.beforeStateDigest !== receipt.restoredStateDigest) || receipt.cronFingerprintBefore !== receipt.cronFingerprintAfter
780
+ || receipt.cronRegisteredBefore !== true || receipt.cronRegisteredAfter !== true || receipt.cronDegradedBefore !== false || receipt.cronDegradedAfter !== false
781
+ || receipt.acceptanceOnly !== true || receipt.productionScheduleChanged !== false || receipt.workspaceAbsent !== true || receipt.socketAbsent !== true || receipt.snapshotAbsent !== true || receipt.realCheckEquivalent !== true || receipt.productionRestored !== true) {
782
+ throw new Error("Sanctuary health probe did not restore its exact production owner and state");
783
+ }
784
+ if (label === "unit-16h-acceptance-delivery-probe") {
785
+ const effective = new Date(receipt.effectiveNow);
786
+ const parts = new Intl.DateTimeFormat("en-US", { timeZone: "America/Los_Angeles", hour: "2-digit", minute: "2-digit", second: "2-digit", hourCycle: "h23" }).formatToParts(effective);
787
+ const part = (type) => Number(parts.find((entry) => entry.type === type).value);
788
+ if (receipt.clockMode !== "local-daily-boundary" || part("hour") !== 9 || part("minute") !== 0 || part("second") !== 0 || effective.getUTCMilliseconds() !== 0)
789
+ throw new Error("Sanctuary health digest clock is not the exact local daily boundary");
790
+ }
791
+ else if (receipt.clockMode !== "ambient")
792
+ throw new Error("Sanctuary health probe clock mode is invalid");
793
+ const { schemaVersion: _schemaVersion, ...validated } = receipt;
794
+ return { ...validated, phases };
795
+ }
796
+ function auditContainsSensitiveMaterial(raw, credentials) {
797
+ const knownValues = credentials ? [credentials.botToken ?? "", credentials.authorizedUserId, credentials.authorizedChatId] : [];
798
+ return knownValues.some((value) => value.length > 0 && raw.includes(value))
799
+ || /\b\d{5,16}:[A-Za-z0-9_-]{20,}\b/u.test(raw)
800
+ || /"(?:authorized_?user_?id|authorized_?chat_?id|transport_?user_?id|transport_?chat_?id|user_?id|chat_?id|update_?id|message_?id)"\s*:\s*"?\d{5,16}"?/iu.test(raw)
801
+ || /"(?:botToken|apiKey|password|secret)"\s*:\s*"(?!\[REDACTED\])[^"\n]+"/u.test(raw)
802
+ || /\b(?:bearer\s+|sk-|AIza|xox[baprs]-)[A-Za-z0-9_-]{10,}\b/iu.test(raw)
803
+ || /\b(?:api[_ -]?key|token|secret|password)\s*[:=]\s*(?!\[REDACTED\])\S{8,}/iu.test(raw);
804
+ }
805
+ function millisecondsAfterLocalNine(timestamp) {
806
+ const parts = new Intl.DateTimeFormat("en-US", { timeZone: "America/Los_Angeles", hour: "2-digit", minute: "2-digit", second: "2-digit", hourCycle: "h23" }).formatToParts(new Date(timestamp));
807
+ const value = (type) => Number(parts.find((part) => part.type === type).value);
808
+ const milliseconds = ((value("hour") - 9) * 60 * 60 + value("minute") * 60 + value("second")) * 1_000;
809
+ return milliseconds >= 0 ? milliseconds : null;
810
+ }
811
+ function canonicalSanctuaryHealthCronRegistered(raw) {
812
+ const lines = raw.split(/\r?\n/u);
813
+ const marker = "# ouro:habit:sanctuary:sanctuary:sanctuary-health";
814
+ const command = "*/15 * * * * /usr/local/bin/node /opt/ouro/dist/heart/daemon/ouro-entry.js poke sanctuary --habit sanctuary-health --trigger cron";
815
+ const index = lines.indexOf(marker);
816
+ return index >= 0 && lines[index + 1] === command;
817
+ }
818
+ function readBoundedIdentitySurfaces(agentRoot) {
819
+ const roots = [
820
+ pathFor(agentRoot, "state/senses/telegram/identity-subjects.json"),
821
+ pathFor(agentRoot, "friends"),
822
+ pathFor(agentRoot, "state/sessions"),
823
+ pathFor(agentRoot, "state/pending"),
824
+ pathFor(agentRoot, "state/pending-returns"),
825
+ pathFor(agentRoot, "state/daemon/logs"),
826
+ ];
827
+ const records = [];
828
+ let totalBytes = 0;
829
+ let visitedFiles = 0;
830
+ const visit = (target, depth) => {
831
+ if (depth > 6)
832
+ throw new Error("identity surface audit exceeds its depth bound");
833
+ try {
834
+ const entries = (0, node_fs_1.readdirSync)(target, { withFileTypes: true }).sort((left, right) => left.name.localeCompare(right.name));
835
+ for (const entry of entries) {
836
+ if (entry.isSymbolicLink())
837
+ throw new Error("identity surface audit refuses symbolic links");
838
+ const child = pathFor(target, entry.name);
839
+ if (entry.isDirectory())
840
+ visit(child, depth + 1);
841
+ else if (entry.isFile()) {
842
+ visitedFiles += 1;
843
+ if (visitedFiles > 2_000)
844
+ throw new Error("identity surface audit exceeds its file-count bound");
845
+ const raw = (0, node_fs_1.readFileSync)(child, "utf8");
846
+ totalBytes += Buffer.byteLength(raw);
847
+ if (totalBytes > 16 * 1024 * 1024 || Buffer.byteLength(raw) > 1024 * 1024)
848
+ throw new Error("identity surface audit exceeds its bound");
849
+ if (entry.name.endsWith(".json"))
850
+ JSON.parse(raw);
851
+ records.push(path.relative(agentRoot, child), raw);
852
+ }
853
+ else
854
+ throw new Error("identity surface audit found an unsupported filesystem entry");
855
+ }
856
+ }
857
+ catch (error) {
858
+ if (error.code === "ENOENT" || error.code === "ENOTDIR") {
859
+ try {
860
+ const raw = (0, node_fs_1.readFileSync)(target, "utf8");
861
+ totalBytes += Buffer.byteLength(raw);
862
+ if (totalBytes > 16 * 1024 * 1024 || Buffer.byteLength(raw) > 1024 * 1024)
863
+ throw new Error("identity surface audit exceeds its bound");
864
+ JSON.parse(raw);
865
+ records.push(path.relative(agentRoot, target), raw);
866
+ }
867
+ catch (fileError) {
868
+ if (fileError.code !== "ENOENT")
869
+ throw fileError;
870
+ }
871
+ }
872
+ else
873
+ throw error;
874
+ }
875
+ };
876
+ for (const root of roots)
877
+ visit(root, 0);
878
+ return records;
879
+ }
880
+ function parseTelegramTurnReceipts(raw, scenarioHandleDigest, identityKey) {
881
+ if (Buffer.byteLength(raw) > 4 * 1024 * 1024)
882
+ throw new Error("Telegram turn receipt ledger exceeds its bound");
883
+ const lines = raw.split("\n").filter(Boolean);
884
+ if (lines.length > 500 || lines.some((line) => Buffer.byteLength(line) > 16 * 1024))
885
+ throw new Error("Telegram turn receipt ledger exceeds its bound");
886
+ return lines.flatMap((line) => {
887
+ const receipt = object(JSON.parse(line), "Telegram turn receipt");
888
+ const grounded = receipt.schemaVersion === "sanctuary-telegram-turn-receipt-v4";
889
+ const exactKeys = ["completedAt", "deliveries", "deliveryCount", "errorCategory", "providerInvocationCount", "responseDigest", "scenarioHandleDigest", "schemaVersion", "sequenceDigest", "status", "toolInvocationCount", "toolResultDigests", "updateDigest", ...(grounded ? ["receiptMac", "toolGroundings"] : [])].sort();
890
+ const deliveries = receipt.deliveries;
891
+ const authenticatedDeliveries = grounded && typeof identityKey === "string" && /^[A-Za-z0-9_-]{43}$/u.test(identityKey) && Array.isArray(deliveries)
892
+ && deliveries.every((value) => {
893
+ const delivery = object(value, "Telegram authenticated delivery receipt");
894
+ return typeof delivery.redactedText === "string" && delivery.chunkDigest === (0, telegram_1.sanctuaryTelegramTurnReceiptDigest)(identityKey, "sanctuary-telegram-turn-receipt-v4", "chunk", delivery.redactedText);
895
+ })
896
+ && receipt.responseDigest === (0, telegram_1.sanctuaryTelegramTurnReceiptDigest)(identityKey, "sanctuary-telegram-turn-receipt-v4", "response", JSON.stringify(deliveries))
897
+ && receipt.receiptMac === (0, telegram_1.sanctuaryTelegramTurnReceiptMac)(identityKey, receipt);
898
+ if (JSON.stringify(Object.keys(receipt).sort()) !== JSON.stringify(exactKeys)
899
+ || (!grounded && receipt.schemaVersion !== "sanctuary-telegram-turn-receipt-v3")
900
+ || typeof receipt.scenarioHandleDigest !== "string" || !SHA256.test(receipt.scenarioHandleDigest)
901
+ || (receipt.status !== "success" && receipt.status !== "error")
902
+ || (receipt.status === "success" ? receipt.errorCategory !== null : typeof receipt.errorCategory !== "string" || receipt.errorCategory.length < 1 || receipt.errorCategory.length > 128)
903
+ || ![receipt.updateDigest, receipt.sequenceDigest, receipt.responseDigest].every((value) => typeof value === "string" && SHA256.test(value))
904
+ || !Array.isArray(receipt.toolResultDigests) || receipt.toolResultDigests.length > 100 || !receipt.toolResultDigests.every((value) => typeof value === "string" && SHA256.test(value))
905
+ || !Array.isArray(deliveries) || deliveries.length > 100 || !deliveries.every((value) => {
906
+ const delivery = object(value, "Telegram delivery receipt");
907
+ return JSON.stringify(Object.keys(delivery).sort()) === JSON.stringify(grounded ? ["chunkDigest", "messageIdDigest", "redactedText", "utf16Units"] : ["chunkDigest", "messageIdDigest"])
908
+ && typeof delivery.messageIdDigest === "string" && SHA256.test(delivery.messageIdDigest)
909
+ && typeof delivery.chunkDigest === "string" && SHA256.test(delivery.chunkDigest)
910
+ && (!grounded || (typeof delivery.redactedText === "string" && delivery.redactedText.length === delivery.utf16Units && Number(delivery.utf16Units) <= 1_200));
911
+ })
912
+ || (grounded && !authenticatedDeliveries)
913
+ || (grounded && (typeof receipt.receiptMac !== "string" || !SHA256.test(receipt.receiptMac)))
914
+ || (grounded && (!Array.isArray(receipt.toolGroundings) || receipt.toolGroundings.length !== 1 || !receipt.toolGroundings.every((raw) => {
915
+ const grounding = object(raw, "Telegram tool grounding");
916
+ if (JSON.stringify(Object.keys(grounding).sort()) !== JSON.stringify(["facts", "groundingDigest", "observedAt", "resultDigest", "sourceIdentityDigest", "toolName"]) || (grounding.toolName !== "unraid_get_system" && grounding.toolName !== "unraid_get_storage")
917
+ || typeof grounding.resultDigest !== "string" || !SHA256.test(grounding.resultDigest) || typeof grounding.groundingDigest !== "string" || !SHA256.test(grounding.groundingDigest)
918
+ || typeof grounding.sourceIdentityDigest !== "string" || !SHA256.test(grounding.sourceIdentityDigest) || typeof grounding.observedAt !== "string" || !Number.isFinite(Date.parse(grounding.observedAt)) || new Date(Date.parse(grounding.observedAt)).toISOString() !== grounding.observedAt
919
+ || !grounding.facts || typeof grounding.facts !== "object" || Array.isArray(grounding.facts))
920
+ return false;
921
+ return (0, sanctuary_grounding_1.sanctuaryGroundingDigest)(grounding.facts) === grounding.groundingDigest && receipt.toolResultDigests.includes(grounding.resultDigest);
922
+ })))
923
+ || ![receipt.providerInvocationCount, receipt.toolInvocationCount].every((value) => Number.isSafeInteger(value) && Number(value) >= 0 && Number(value) <= 1_000)
924
+ || !Number.isSafeInteger(receipt.deliveryCount) || receipt.deliveryCount !== deliveries.length
925
+ || typeof receipt.completedAt !== "string" || receipt.completedAt.length > 30 || !Number.isFinite(Date.parse(receipt.completedAt)) || new Date(Date.parse(receipt.completedAt)).toISOString() !== receipt.completedAt) {
926
+ throw new Error("Telegram turn receipt ledger row is invalid");
927
+ }
928
+ if (receipt.scenarioHandleDigest !== scenarioHandleDigest)
929
+ return [];
930
+ const responseText = grounded ? deliveries.map((delivery) => String(delivery.redactedText)).join("") : undefined;
931
+ const toolGroundings = grounded ? structuredClone(receipt.toolGroundings) : undefined;
932
+ return [{
933
+ status: receipt.status, updateDigest: String(receipt.updateDigest), sequenceDigest: String(receipt.sequenceDigest), responseDigest: String(receipt.responseDigest),
934
+ toolResultDigests: receipt.toolResultDigests, providerTurnCount: Number(receipt.providerInvocationCount),
935
+ toolInvocationCount: Number(receipt.toolInvocationCount), deliveryCount: Number(receipt.deliveryCount),
936
+ telegramMessageIdDigests: deliveries.map((delivery) => String(delivery.messageIdDigest)), completedAt: Date.parse(receipt.completedAt),
937
+ ...(grounded ? { responseText, responseUtf16Units: responseText.length, toolGroundings } : {}),
938
+ }];
939
+ });
940
+ }
941
+ async function readIndependentSanctuaryGrounding(toolName) {
942
+ const sanctuary = (0, sanctuary_runtime_1.createSanctuaryToolContext)(TARGET_ID).sanctuary;
943
+ const result = toolName === "unraid_get_system" ? await sanctuary.getSystem() : await sanctuary.getStorage();
944
+ const facts = (0, sanctuary_grounding_1.projectSanctuaryGrounding)(toolName, result);
945
+ const sourceIdentityDigest = result.data?.sourceIdentityDigest;
946
+ if (typeof sourceIdentityDigest !== "string" || !SHA256.test(sourceIdentityDigest))
947
+ throw new Error("independent Sanctuary source identity is unavailable");
948
+ return { toolName, groundingDigest: (0, sanctuary_grounding_1.sanctuaryGroundingDigest)(facts), sourceIdentityDigest, observedAt: new Date().toISOString(), facts };
949
+ }
950
+ function canonicalDockerIdFromUnraidPrefixedId(value) {
951
+ if (typeof value !== "string" || !/^[0-9a-f]{64}:[0-9a-f]{64}$/u.test(value))
952
+ throw new Error("Unraid Docker PrefixedID is invalid");
953
+ return value.slice(65);
954
+ }
955
+ function containmentToolContext(agentRoot, profileId) {
956
+ const profile = (0, relationship_authorization_1.loadRelationshipCapabilityRegistry)(agentRoot).profiles[profileId];
957
+ if (!profile)
958
+ throw new Error("Sanctuary containment profile is missing");
959
+ return {
960
+ signin: async () => undefined, agentName: TARGET_ID, agentRoot,
961
+ relationshipAuthorization: {
962
+ profileId, authorizedContextScopes: profile.contextScopes, advertisedToolNames: profile.toolNames,
963
+ // This request-bound subject audits the profile contract; it is not a persisted Friend or grant.
964
+ authorizeTool: async (name) => (0, relationship_authorization_1.authorizeRelationshipAccess)({
965
+ relationship: { friendId: "sanctuary-containment-probe", trustLevel: profileId === "sanctuary-household" ? "friend" : "family", admissionState: "active", initiativePolicy: "reactive_only", capabilityProfileId: profileId },
966
+ profiles: Object.values((0, relationship_authorization_1.loadRelationshipCapabilityRegistry)(agentRoot).profiles),
967
+ request: { kind: "tool", name, requestId: "sanctuary-containment-probe", returnTargetFriendId: "sanctuary-containment-probe" },
968
+ activeRequestId: "sanctuary-containment-probe", requestPhase: "inbound",
969
+ }),
970
+ },
971
+ };
972
+ }
973
+ async function runSanctuaryProductionBoundaryProbe(input) {
974
+ const excludedNames = ["vault_get", "mcp_call", "exec", "credential_get", ...(input.profileId === "sanctuary-owner" ? [] : tools_1.SANCTUARY_OWNER_ADDITIONS)];
975
+ const inner = input.profileId === "sanctuary-event";
976
+ const terminal = inner ? "rest" : "settle";
977
+ const turns = [
978
+ { content: "", toolCalls: excludedNames.map((name, index) => ({ id: `sanctuary-excluded-${index}`, name, arguments: "{}" })), outputItems: [] },
979
+ { content: "", toolCalls: [{ id: "sanctuary-valid-system", name: "unraid_get_system", arguments: "{}" }], outputItems: [] },
980
+ { content: "", toolCalls: [{ id: "sanctuary-boundary-terminal", name: terminal, arguments: JSON.stringify(inner ? { note: "boundary complete" } : { answer: "boundary complete", intent: "complete" }) }], outputItems: [] },
981
+ ];
982
+ let turn = 0;
983
+ const controlOutputs = [];
984
+ const providerRuntime = {
985
+ ...input.providerRuntime, client: null,
986
+ streamTurn: async () => turns[turn++] ?? (() => { throw new Error("production boundary probe exceeded its turn budget"); })(),
987
+ appendToolOutput: (callId, output) => { if (callId === "sanctuary-valid-system")
988
+ controlOutputs.push(output); }, resetTurnState: () => undefined, ping: async () => undefined, classifyError: () => "unknown",
989
+ };
990
+ const receipts = [];
991
+ const sanctuary = (0, sanctuary_runtime_1.createSanctuaryToolContext)(TARGET_ID).sanctuary;
992
+ const context = containmentToolContext(input.agentRoot, input.profileId);
993
+ const relationship = context.relationshipAuthorization;
994
+ const poisonedContext = { ...context, sanctuary, relationshipAuthorization: {
995
+ ...relationship, advertisedToolNames: [...relationship.advertisedToolNames, ...excludedNames, "mcp__containment_poison"],
996
+ } };
997
+ const observed = await (0, sanctuary_runtime_1.runWithSanctuaryToolReceiptCollection)(() => (0, core_1.runAgent)([{ role: "user", content: "Run the bounded production tool authorization probe." }], {
998
+ onModelStart: () => undefined, onModelStreamStart: () => undefined, onTextChunk: () => undefined, onReasoningChunk: () => undefined,
999
+ onToolStart: () => undefined, onToolEnd: () => undefined, onError: () => undefined, onClearText: () => undefined,
1000
+ }, inner ? "inner" : "telegram", undefined, {
1001
+ skipKeptNotes: true, providerRuntimeOverride: providerRuntime, toolBoundaryObserver: (receipt) => receipts.push(receipt),
1002
+ toolContext: poisonedContext,
1003
+ }));
1004
+ const controlReceipts = receipts.filter((receipt) => receipt.name === "unraid_get_system");
1005
+ if (observed.result.outcome !== (inner ? "rested" : "settled") || controlReceipts.length !== 1
1006
+ || controlReceipts[0].reason !== "dispatched" || !controlReceipts[0].invoked || controlReceipts[0].sideEffect
1007
+ || observed.toolResultDigests.length !== 1 || controlOutputs.length !== 1
1008
+ || (0, node_crypto_1.createHash)("sha256").update(controlOutputs[0]).digest("hex") !== observed.toolResultDigests[0]) {
1009
+ throw new Error("production boundary valid control did not dispatch with one live Sanctuary read receipt");
1010
+ }
1011
+ const controlResult = object(JSON.parse(controlOutputs[0]), "production boundary control result");
1012
+ const controlData = object(controlResult.data, "production boundary control data");
1013
+ if (controlResult.ok !== true || typeof controlData.sourceIdentityDigest !== "string" || !SHA256.test(controlData.sourceIdentityDigest)) {
1014
+ throw new Error("production boundary valid control result is invalid");
1015
+ }
1016
+ return receipts.filter((receipt) => receipt.name !== terminal);
1017
+ }
1018
+ function parseInteractiveDriverReceipt(raw, label, scenarioHandleDigest) {
1019
+ if (raw === null)
1020
+ return undefined;
1021
+ const receipt = object(JSON.parse(raw), "interactive driver receipt");
1022
+ const expectedSchema = label === "unit-16k-timeout-stale" ? "sanctuary-timeout-stale-driver-receipt-v1" : "sanctuary-interactive-driver-receipt-v2";
1023
+ if (receipt.schemaVersion !== expectedSchema || receipt.phase !== "complete"
1024
+ || receipt.label !== label || receipt.scenarioHandleDigest !== scenarioHandleDigest)
1025
+ throw new Error("interactive driver receipt binding is invalid");
1026
+ const common = ["schemaVersion", "phase", "label", "scenarioHandleDigest", "approvalIdDigest", "checkpointDigest", "suspendedSessionRevisionDigest", "approvalEpochBefore"];
1027
+ const digests = [receipt.approvalIdDigest, receipt.checkpointDigest, receipt.suspendedSessionRevisionDigest];
1028
+ if (!digests.every((value) => typeof value === "string" && SHA256.test(value)) || !Number.isSafeInteger(receipt.approvalEpochBefore) || Number(receipt.approvalEpochBefore) < 0) {
1029
+ throw new Error("interactive driver receipt common coordinates are invalid");
1030
+ }
1031
+ if (label === "unit-16k-timeout-stale") {
1032
+ exactKeys(receipt, [...common, "callbackAttempts", "distinctQueryCount", "callbackDataDigest", "settledCount", "claimCount", "mutationCount", "staleAcknowledged", "promptTerminal"], "timeout stale driver receipt");
1033
+ if (receipt.callbackAttempts !== 1 || receipt.distinctQueryCount !== 1 || typeof receipt.callbackDataDigest !== "string" || !SHA256.test(receipt.callbackDataDigest)
1034
+ || receipt.settledCount !== 1 || receipt.claimCount !== 0 || receipt.mutationCount !== 0 || receipt.staleAcknowledged !== true || receipt.promptTerminal !== true) {
1035
+ throw new Error("timeout stale driver receipt is invalid");
1036
+ }
1037
+ }
1038
+ else if (label === "unit-16l-duplicate-callback") {
1039
+ exactKeys(receipt, [...common, "callbackAttempts", "distinctQueryCount", "callbackDataDigest", "barrierObserved", "settledCount", "claimCount", "mutationCount", "staleReplayAttempts", "staleReplaySettled", "staleReplayMutationCount", "promptTerminal", "writeCredentialObserved"], "duplicate callback driver receipt");
1040
+ if (receipt.callbackAttempts !== 2 || receipt.distinctQueryCount !== 2 || typeof receipt.callbackDataDigest !== "string" || !SHA256.test(receipt.callbackDataDigest)
1041
+ || receipt.barrierObserved !== true || receipt.settledCount !== 2 || receipt.claimCount !== 1 || receipt.mutationCount !== 1
1042
+ || receipt.staleReplayAttempts !== 1 || receipt.staleReplaySettled !== true || receipt.staleReplayMutationCount !== 0
1043
+ || receipt.promptTerminal !== true || receipt.writeCredentialObserved !== false)
1044
+ throw new Error("duplicate callback driver receipt is invalid");
1045
+ }
1046
+ else if (label === "unit-16m-restart-continuation") {
1047
+ exactKeys(receipt, [...common, "approvalEpochAfterRestart", "continuationEpochAfter", "ownerImageDigest", "ownerContainerDigest", "restartCountBefore", "restartCountAfter", "pendingDigestBefore", "pendingDigestAfter", "pendingRestored", "callbackAttempts", "mutationCount", "indeterminateRecoveryObserved", "attemptedRecoveryReopened", "attemptedRecordDigest", "recoveredRecordDigest", "indeterminateRetryCount"], "restart continuation driver receipt");
1048
+ if (![receipt.ownerImageDigest, receipt.ownerContainerDigest, receipt.pendingDigestBefore, receipt.pendingDigestAfter, receipt.attemptedRecordDigest, receipt.recoveredRecordDigest].every((value) => typeof value === "string" && SHA256.test(value))
1049
+ || ![receipt.approvalEpochAfterRestart, receipt.continuationEpochAfter, receipt.restartCountBefore, receipt.restartCountAfter].every((value) => Number.isSafeInteger(value) && Number(value) >= 0)
1050
+ || receipt.pendingRestored !== true || receipt.callbackAttempts !== 1 || receipt.mutationCount !== 1
1051
+ || receipt.indeterminateRecoveryObserved !== true || receipt.attemptedRecoveryReopened !== true
1052
+ || receipt.attemptedRecordDigest === receipt.recoveredRecordDigest || receipt.indeterminateRetryCount !== 0)
1053
+ throw new Error("restart continuation driver receipt is invalid");
1054
+ }
1055
+ else
1056
+ return undefined;
1057
+ const { phase: _phase, ...validated } = receipt;
1058
+ return validated;
1059
+ }
1060
+ function parseReadOnlyDenialReceipt(raw, label, scenarioHandleDigest) {
1061
+ if (raw === null)
1062
+ return undefined;
1063
+ const receipt = object(JSON.parse(raw), "read-only denial receipt");
1064
+ exactKeys(receipt, ["schemaVersion", "phase", "label", "scenarioHandleDigest", "operation", "targetDigest", "attemptCount", "httpStatus", "errorCode", "before", "after"], "read-only denial receipt");
1065
+ const expectedOperation = label === "unit-16e-1-stop-denial" ? "stop" : label === "unit-16e-2-restart-denial" ? "restart" : null;
1066
+ if (receipt.schemaVersion !== "sanctuary-read-only-denial-receipt-v1" || receipt.phase !== "complete" || receipt.label !== label
1067
+ || receipt.scenarioHandleDigest !== scenarioHandleDigest || expectedOperation === null || receipt.operation !== expectedOperation
1068
+ || typeof receipt.targetDigest !== "string" || !SHA256.test(receipt.targetDigest) || receipt.attemptCount !== 1
1069
+ || ![200, 401, 403].includes(Number(receipt.httpStatus)) || (receipt.errorCode !== "FORBIDDEN" && receipt.errorCode !== "PERMISSION_DENIED")) {
1070
+ throw new Error("read-only denial receipt binding is invalid");
1071
+ }
1072
+ const boundaryKeys = ["ownerSnapshotDigest", "targetSnapshotDigest", "targetRestartCount", "targetContainerIdDigest", "auditCursorDigest", "providerUsageCursorDigest", "sessionCursorDigest", "toolActionCursorDigest"];
1073
+ const parseBoundary = (rawBoundary, boundaryLabel) => {
1074
+ const boundary = object(rawBoundary, boundaryLabel);
1075
+ exactKeys(boundary, boundaryKeys, boundaryLabel);
1076
+ if (![boundary.ownerSnapshotDigest, boundary.targetSnapshotDigest, boundary.targetContainerIdDigest, boundary.auditCursorDigest, boundary.providerUsageCursorDigest, boundary.sessionCursorDigest, boundary.toolActionCursorDigest]
1077
+ .every((value) => typeof value === "string" && SHA256.test(value)) || !Number.isSafeInteger(boundary.targetRestartCount) || Number(boundary.targetRestartCount) < 0) {
1078
+ throw new Error(`${boundaryLabel} is invalid`);
1079
+ }
1080
+ return boundary;
1081
+ };
1082
+ const before = parseBoundary(receipt.before, "read-only denial before boundary");
1083
+ const after = parseBoundary(receipt.after, "read-only denial after boundary");
1084
+ if (receipt.targetDigest !== before.targetContainerIdDigest || receipt.targetDigest !== after.targetContainerIdDigest)
1085
+ throw new Error("read-only denial target binding is invalid");
1086
+ if (JSON.stringify(before) !== JSON.stringify(after))
1087
+ throw new Error("read-only denial boundary drift was observed");
1088
+ return receipt;
1089
+ }
1090
+ function durablePrivateJson(filePath, value) {
1091
+ const directory = path.dirname(filePath);
1092
+ (0, node_fs_1.mkdirSync)(directory, { recursive: true, mode: 0o700 });
1093
+ const directoryFd = (0, node_fs_1.openSync)(directory, node_fs_1.constants.O_RDONLY | node_fs_1.constants.O_DIRECTORY | node_fs_1.constants.O_NOFOLLOW);
1094
+ const temporary = `${filePath}.${process.pid}.${Date.now()}.tmp`;
1095
+ try {
1096
+ const fileFd = (0, node_fs_1.openSync)(temporary, node_fs_1.constants.O_WRONLY | node_fs_1.constants.O_CREAT | node_fs_1.constants.O_EXCL | node_fs_1.constants.O_NOFOLLOW, 0o600);
1097
+ try {
1098
+ (0, node_fs_1.writeFileSync)(fileFd, `${JSON.stringify(value)}\n`);
1099
+ (0, node_fs_1.fsyncSync)(fileFd);
1100
+ }
1101
+ finally {
1102
+ (0, node_fs_1.closeSync)(fileFd);
1103
+ }
1104
+ (0, node_fs_1.renameSync)(temporary, filePath);
1105
+ (0, node_fs_1.fsyncSync)(directoryFd);
1106
+ }
1107
+ finally {
1108
+ (0, node_fs_1.rmSync)(temporary, { force: true });
1109
+ (0, node_fs_1.closeSync)(directoryFd);
1110
+ }
1111
+ }
1112
+ function createSanctuaryReadOnlyDenialScenarioDriver(options) {
1113
+ const receiptPath = (scenarioHandleDigest) => pathFor(options.agentRoot, `state/acceptance/denial-receipts/${scenarioHandleDigest}.json`);
1114
+ const attemptPath = (scenarioHandleDigest) => pathFor(options.agentRoot, `state/acceptance/denial-attempts/${scenarioHandleDigest}.json`);
1115
+ const attemptsRoot = pathFor(options.agentRoot, "state/acceptance/denial-attempts");
1116
+ const assertNoIndeterminateAttempt = (label) => {
1117
+ try {
1118
+ const entries = (0, node_fs_1.readdirSync)(attemptsRoot, { withFileTypes: true });
1119
+ if (entries.length > 100)
1120
+ throw new Error("read-only denial attempt inventory exceeds its bound");
1121
+ for (const entry of entries) {
1122
+ if (!entry.isFile() || !entry.name.endsWith(".json"))
1123
+ throw new Error("read-only denial attempt inventory is invalid");
1124
+ const attempt = object(JSON.parse((0, node_fs_1.readFileSync)(pathFor(attemptsRoot, entry.name), "utf8")), "read-only denial attempt");
1125
+ exactKeys(attempt, ["schemaVersion", "phase", "label", "scenarioHandleDigest"], "read-only denial attempt");
1126
+ if (attempt.schemaVersion !== "sanctuary-read-only-denial-attempt-v1" || attempt.phase !== "attempting"
1127
+ || typeof attempt.scenarioHandleDigest !== "string" || !SHA256.test(attempt.scenarioHandleDigest))
1128
+ throw new Error("read-only denial attempt is invalid");
1129
+ if (attempt.label === label)
1130
+ throw new Error("read-only denial attempt is attempted or indeterminate; inspect-before-retry is required");
1131
+ }
1132
+ }
1133
+ catch (error) {
1134
+ if (error.code !== "ENOENT")
1135
+ throw error;
1136
+ }
1137
+ };
1138
+ return {
1139
+ async poll(rawLabel, scenarioHandleDigest) {
1140
+ if (rawLabel !== "unit-16e-1-stop-denial" && rawLabel !== "unit-16e-2-restart-denial")
1141
+ throw new Error("read-only denial label is invalid");
1142
+ const label = rawLabel;
1143
+ const existingReceipt = receiptPath(scenarioHandleDigest);
1144
+ if ((0, node_fs_1.existsSync)(existingReceipt)) {
1145
+ parseReadOnlyDenialReceipt((0, node_fs_1.readFileSync)(existingReceipt, "utf8"), label, scenarioHandleDigest);
1146
+ return { state: "driven" };
1147
+ }
1148
+ assertNoIndeterminateAttempt(label);
1149
+ const attempt = attemptPath(scenarioHandleDigest);
1150
+ durablePrivateJson(attempt, { schemaVersion: "sanctuary-read-only-denial-attempt-v1", phase: "attempting", label, scenarioHandleDigest });
1151
+ const receipt = await options.runProbe(label, scenarioHandleDigest);
1152
+ parseReadOnlyDenialReceipt(JSON.stringify(receipt), label, scenarioHandleDigest);
1153
+ durablePrivateJson(existingReceipt, receipt);
1154
+ return { state: "driven" };
1155
+ },
1156
+ complete(_label, scenarioHandleDigest) {
1157
+ for (const filePath of [receiptPath(scenarioHandleDigest), attemptPath(scenarioHandleDigest)])
1158
+ if ((0, node_fs_1.existsSync)(filePath))
1159
+ (0, node_fs_1.unlinkSync)(filePath);
1160
+ },
1161
+ };
1162
+ }
1163
+ function digestOptionalFiles(filePaths) {
1164
+ const digest = (0, node_crypto_1.createHash)("sha256");
1165
+ for (const filePath of filePaths) {
1166
+ digest.update(filePath);
1167
+ try {
1168
+ digest.update((0, node_fs_1.readFileSync)(filePath));
1169
+ }
1170
+ catch (error) {
1171
+ if (error.code !== "ENOENT")
1172
+ throw error;
1173
+ digest.update("[absent]");
1174
+ }
1175
+ }
1176
+ return digest.digest("hex");
1177
+ }
1178
+ async function captureReadOnlyDenialBoundary(agentRoot, deps) {
1179
+ const ownerSnapshot = object(await dependency(deps.hostRequest, "Sanctuary host broker")({ operation: "container_snapshot", targetId: TARGET_ID }), "denial owner snapshot");
1180
+ const targetSnapshot = object(await dependency(deps.hostRequest, "Sanctuary host broker")({ operation: "denial_target_snapshot", targetId: TARGET_ID }), "denial target snapshot");
1181
+ exactKeys(targetSnapshot, ["containerIdDigest", "imageDigest", "running", "status", "restartCount", "startedAtDigest"], "denial target snapshot");
1182
+ if (![targetSnapshot.containerIdDigest, targetSnapshot.imageDigest, targetSnapshot.startedAtDigest].every((value) => typeof value === "string" && SHA256.test(value))
1183
+ || typeof targetSnapshot.running !== "boolean" || typeof targetSnapshot.status !== "string"
1184
+ || !Number.isSafeInteger(targetSnapshot.restartCount) || Number(targetSnapshot.restartCount) < 0)
1185
+ throw new Error("denial target snapshot is invalid");
1186
+ const auditPath = TELEGRAM_AUDIT;
1187
+ const providerPath = pathFor(agentRoot, "state/acceptance/telegram-turns.ndjson");
1188
+ const toolPath = pathFor(agentRoot, "state/acceptance/restart-attempts.ndjson");
1189
+ const approvalDatabase = pathFor(agentRoot, "state/approvals/approvals.sqlite");
1190
+ return {
1191
+ ownerSnapshotDigest: (0, node_crypto_1.createHash)("sha256").update(JSON.stringify(ownerSnapshot)).digest("hex"),
1192
+ targetSnapshotDigest: (0, node_crypto_1.createHash)("sha256").update(JSON.stringify(targetSnapshot)).digest("hex"),
1193
+ targetRestartCount: Number(targetSnapshot.restartCount),
1194
+ targetContainerIdDigest: String(targetSnapshot.containerIdDigest),
1195
+ auditCursorDigest: digestOptionalFiles([auditPath, TELEGRAM_AUDIT_HEAD]),
1196
+ providerUsageCursorDigest: digestOptionalFiles([providerPath]),
1197
+ sessionCursorDigest: (0, node_crypto_1.createHash)("sha256").update(JSON.stringify(readBoundedIdentitySurfaces(agentRoot))).digest("hex"),
1198
+ toolActionCursorDigest: digestOptionalFiles([toolPath, approvalDatabase, `${approvalDatabase}-wal`, `${approvalDatabase}-shm`]),
1199
+ };
1200
+ }
1201
+ async function executeSanctuaryReadOnlyDenialProbe(label, scenarioHandleDigest, agentRoot, deps) {
1202
+ const runtime = (0, runtime_credentials_1.readMachineRuntimeCredentialConfig)(TARGET_ID);
1203
+ if (!runtime.ok)
1204
+ throw new Error("read-only denial probe requires an unlocked machine runtime credential");
1205
+ const endpoint = exactLoopbackGraphqlEndpoint(runtime.config.unraidGraphqlUrl);
1206
+ const readKey = text(runtime.config.unraidReadApiKey, "read-only Unraid credential");
1207
+ const topologyQuery = "query AcceptanceDenialTarget { docker { containers(skipCache: true) { id names state status } } }";
1208
+ const topologyResponse = await deps.fetch(endpoint.href, { method: "POST", headers: { "content-type": "application/json", "x-api-key": readKey }, body: JSON.stringify({ query: topologyQuery, variables: {} }), signal: AbortSignal.timeout(NETWORK_TIMEOUT_MS) });
1209
+ const topologyEnvelope = object(await topologyResponse.json(), "read-only denial target response");
1210
+ const docker = object(object(topologyEnvelope.data, "read-only denial target data").docker, "read-only denial target docker");
1211
+ if (!Array.isArray(docker.containers))
1212
+ throw new Error("read-only denial target list is invalid");
1213
+ const matches = docker.containers.map((entry) => object(entry, "read-only denial target"))
1214
+ .filter((entry) => Array.isArray(entry.names) && entry.names.some((name) => name === "calibre-web" || name === "/calibre-web"));
1215
+ if (matches.length !== 1 || typeof matches[0].id !== "string")
1216
+ throw new Error("read-only denial target is absent or ambiguous");
1217
+ const targetId = text(matches[0].id, "read-only denial target id");
1218
+ const canonicalTargetId = canonicalDockerIdFromUnraidPrefixedId(targetId);
1219
+ const before = await captureReadOnlyDenialBoundary(agentRoot, deps);
1220
+ if (before.targetContainerIdDigest !== (0, node_crypto_1.createHash)("sha256").update(canonicalTargetId).digest("hex"))
1221
+ throw new Error("read-only denial target identity is invalid");
1222
+ const query = label === "unit-16e-1-stop-denial"
1223
+ ? "mutation AcceptanceStopDenial($id: PrefixedID!) { docker { stop(id: $id) { id } } }"
1224
+ : WRITE_PROBE;
1225
+ const response = await deps.fetch(endpoint.href, { method: "POST", headers: { "content-type": "application/json", "x-api-key": readKey }, body: JSON.stringify({ query, variables: { id: targetId } }), signal: AbortSignal.timeout(NETWORK_TIMEOUT_MS) });
1226
+ const envelope = object(await response.json(), "read-only mutation denial response");
1227
+ const codes = Array.isArray(envelope.errors) ? envelope.errors.map((raw) => object(raw, "read-only denial error")).flatMap((error) => {
1228
+ const extensions = error.extensions && typeof error.extensions === "object" && !Array.isArray(error.extensions) ? error.extensions : {};
1229
+ return typeof extensions.code === "string" ? [extensions.code] : [];
1230
+ }) : [];
1231
+ const errorCode = codes.includes("FORBIDDEN") ? "FORBIDDEN" : codes.includes("PERMISSION_DENIED") ? "PERMISSION_DENIED" : "UNCLASSIFIED";
1232
+ const after = await captureReadOnlyDenialBoundary(agentRoot, deps);
1233
+ if (after.targetContainerIdDigest !== (0, node_crypto_1.createHash)("sha256").update(canonicalTargetId).digest("hex"))
1234
+ throw new Error("read-only denial target identity drifted");
1235
+ return {
1236
+ schemaVersion: "sanctuary-read-only-denial-receipt-v1", phase: "complete", label, scenarioHandleDigest,
1237
+ operation: label === "unit-16e-1-stop-denial" ? "stop" : "restart",
1238
+ targetDigest: (0, node_crypto_1.createHash)("sha256").update(canonicalTargetId).digest("hex"), attemptCount: 1,
1239
+ httpStatus: response.status, errorCode, before, after,
1240
+ };
1241
+ }
1242
+ async function readDefaultSanctuaryScenarioFacts(label, scenarioHandleDigest, deps, configuredAgentRoot = (0, identity_1.getAgentRoot)(TARGET_ID), options = {}) {
1243
+ if (options.skipContainerSnapshot === true && options.containerSnapshot !== undefined)
1244
+ throw new Error("container snapshot options are mutually exclusive");
1245
+ const agentRoot = configuredAgentRoot;
1246
+ const identityRaw = optionalFixedFile(deps, pathFor(agentRoot, "state/senses/telegram/identity.key"));
1247
+ const auditRaw = optionalFixedFile(deps, TELEGRAM_AUDIT);
1248
+ const auditHeadRaw = optionalFixedFile(deps, TELEGRAM_AUDIT_HEAD);
1249
+ const gatewayCursor = await readGatewayCursor(deps);
1250
+ const checkpointsRaw = optionalFixedFile(deps, pathFor(agentRoot, "state/approvals/checkpoints.json"));
1251
+ const restartAttemptsRaw = optionalFixedFile(deps, pathFor(agentRoot, "state/acceptance/restart-attempts.ndjson")) ?? "";
1252
+ const telegramTurnsRaw = optionalFixedFile(deps, pathFor(agentRoot, "state/acceptance/telegram-turns.ndjson")) ?? "";
1253
+ const cronRaw = optionalFixedFile(deps, "/home/ouro/.ouro-cli/scheduler/sanctuary.crontab");
1254
+ const healthRaw = optionalFixedFile(deps, pathFor(agentRoot, "state/health/sanctuary-health.json"));
1255
+ const healthProbeRaw = optionalFixedFile(deps, pathFor(agentRoot, `state/acceptance/health-probe-receipts/${scenarioHandleDigest}.json`));
1256
+ const interactiveDriverRaw = optionalFixedFile(deps, pathFor(agentRoot, `state/acceptance/interactive-driver-receipts/${scenarioHandleDigest}.json`));
1257
+ const denialReceiptRaw = optionalFixedFile(deps, pathFor(agentRoot, `state/acceptance/denial-receipts/${scenarioHandleDigest}.json`));
1258
+ const agentConfig = parsedJson(optionalFixedFile(deps, pathFor(agentRoot, "agent.json")));
1259
+ const readinessPolicy = parsedJson(optionalFixedFile(deps, pathFor(agentRoot, "provider-readiness.json")));
1260
+ const rebootRaw = optionalFixedFile(deps, "/evidence/reboot.json");
1261
+ const expectedImageId = optionalFixedFile(deps, IMAGE_DIGEST_FILE)?.trim();
1262
+ let stopDenied = false;
1263
+ let restartDenied = false;
1264
+ let denialAuditCount = 0;
1265
+ let denialStateUnchanged = false;
1266
+ let denialProbeCompleted = false;
1267
+ const denialReceipt = parseReadOnlyDenialReceipt(denialReceiptRaw, label, scenarioHandleDigest);
1268
+ if (denialReceipt) {
1269
+ denialStateUnchanged = JSON.stringify(denialReceipt.before) === JSON.stringify(denialReceipt.after);
1270
+ denialProbeCompleted = true;
1271
+ denialAuditCount = denialReceipt.attemptCount;
1272
+ if (label === "unit-16e-1-stop-denial")
1273
+ stopDenied = true;
1274
+ else
1275
+ restartDenied = true;
1276
+ }
1277
+ if ((auditRaw === null) !== (auditHeadRaw === null))
1278
+ throw new Error("Telegram audit ledger/head presence mismatch");
1279
+ if (auditRaw !== null && (!identityRaw || !/^[A-Za-z0-9_-]{43}\n?$/u.test(identityRaw)))
1280
+ throw new Error("Telegram audit ledger identity key is unavailable");
1281
+ const auditLedgerEntries = auditRaw === null ? [] : (0, telegram_audit_ledger_1.verifyTelegramAuditLedger)({
1282
+ ledgerRaw: auditRaw,
1283
+ headRaw: auditHeadRaw,
1284
+ identityKey: identityRaw.trim(),
1285
+ });
1286
+ const auditEntries = auditLedgerEntries.filter((entry) => entry.meta.scenarioHandleDigest === scenarioHandleDigest);
1287
+ let approvalRecords = [];
1288
+ const approvalDatabasePath = pathFor(agentRoot, "state/approvals/approvals.sqlite");
1289
+ if ((0, node_fs_1.existsSync)(approvalDatabasePath))
1290
+ approvalRecords = (0, approval_store_1.readApprovalsByScenarioHandleDigest)(approvalDatabasePath, scenarioHandleDigest);
1291
+ const approvals = approvalRecords.map(({ approval: record, continuation }) => {
1292
+ const boundEvents = auditEntries.filter((entry) => entry.meta.approvalId === record.approvalId);
1293
+ const terminalized = boundEvents.some((entry) => entry.event === "telegram.approval_prompt_terminalized" && entry.meta.buttonsRemoved === true);
1294
+ const callbackEvents = boundEvents.filter((entry) => entry.event === "telegram.callback_settled" || entry.event === "telegram.callback_recovery_settled");
1295
+ const staleCallbackEvents = boundEvents.filter((entry) => entry.event === "telegram.approval_stale_callback_settled");
1296
+ const claimCount = boundEvents.filter((entry) => entry.event === "approval.acceptance_transition" && entry.meta.state === "claimed").length;
1297
+ const restartExecutionCount = boundEvents.filter((entry) => entry.event === "senses.telegram_approved_restart_end").length;
1298
+ const resultDigest = (0, node_crypto_1.createHash)("sha256").update(JSON.stringify({ state: record.state, result: record.result })).digest("hex");
1299
+ let resultTargetId = null;
1300
+ if (record.toolName === "unraid_restart_container" && record.state === "succeeded") {
1301
+ let parsed;
1302
+ try {
1303
+ parsed = JSON.parse(text(record.result, "approved restart result"));
1304
+ }
1305
+ catch {
1306
+ throw new Error("approved restart result binding is invalid");
1307
+ }
1308
+ const result = object(parsed, "approved restart result");
1309
+ const data = object(result.data, "approved restart result data");
1310
+ const container = object(data.container, "approved restart result container");
1311
+ if (result.ok !== true || container.name !== "calibre-web" || typeof data.beforeState !== "string" || typeof data.afterState !== "string"
1312
+ || data.observedRestart !== true || data.degraded !== false)
1313
+ throw new Error("approved restart result binding is invalid");
1314
+ resultTargetId = text(container.id, "approved restart result target id");
1315
+ if (resultTargetId.length > 128)
1316
+ throw new Error("approved restart result binding is invalid");
1317
+ }
1318
+ return ({
1319
+ approvalId: record.approvalId,
1320
+ state: record.state,
1321
+ toolName: record.toolName,
1322
+ createdAt: Date.parse(record.createdAt),
1323
+ expiresAt: Date.parse(record.expiresAt),
1324
+ updatedAt: Date.parse(record.updatedAt),
1325
+ attempted: record.attemptedAt !== null,
1326
+ continuationCompleted: continuation?.continuationState === "completed",
1327
+ buttonsRemoved: terminalized,
1328
+ terminalPrompt: terminalized,
1329
+ callbackCount: callbackEvents.length,
1330
+ settledCount: callbackEvents.length,
1331
+ claimCount,
1332
+ replayMutationCount: Math.max(0, restartExecutionCount - (record.state === "succeeded" ? 1 : 0)),
1333
+ staleAcknowledged: [...callbackEvents, ...staleCallbackEvents].some((entry) => entry.meta.acknowledged === true && (entry.meta.reason === "stale_callback" || entry.meta.reason === "expired")),
1334
+ argumentDigest: record.argumentDigest,
1335
+ target: typeof record.arguments.container === "string" ? record.arguments.container : null,
1336
+ resultDigest,
1337
+ resultTargetId,
1338
+ checkpointDigest: record.checkpointDigest,
1339
+ approvalEpoch: record.epoch,
1340
+ continuationEpoch: continuation?.continuationEpoch ?? null,
1341
+ continuationState: continuation?.continuationState ?? null,
1342
+ suspendedSessionRevision: record.suspendedSessionRevision,
1343
+ });
1344
+ });
1345
+ const restartAttempts = parseRestartAttempts(restartAttemptsRaw, scenarioHandleDigest);
1346
+ const telegramTurns = parseTelegramTurnReceipts(telegramTurnsRaw, scenarioHandleDigest, identityRaw?.trim() ?? null);
1347
+ const telegramNextUpdateId = gatewayCursor.nextUpdateId;
1348
+ const groundingTool = label === "unit-16d-whats-up" ? "unraid_get_system" : label === "unit-16d-1-space" ? "unraid_get_storage" : null;
1349
+ const liveGrounding = groundingTool && deps.readLiveGrounding ? await deps.readLiveGrounding(groundingTool) : undefined;
1350
+ let identity;
1351
+ if (APPROVAL_EVIDENCE_LABELS.has(label) && (!identityRaw || !/^[A-Za-z0-9_-]{43}\n?$/u.test(identityRaw))) {
1352
+ throw new Error("Telegram approval identity key is missing or malformed");
1353
+ }
1354
+ if (identityRaw && /^[A-Za-z0-9_-]{43}\n?$/u.test(identityRaw)) {
1355
+ const credentials = deps.telegramCredentials ? deps.telegramCredentials() : acceptanceTelegramIdentity();
1356
+ const identityKey = identityRaw.trim();
1357
+ const auditSchema = label === "unit-16d-whats-up" || label === "unit-16d-1-space" ? "sanctuary-telegram-turn-receipt-v4" : "sanctuary-telegram-turn-receipt-v3";
1358
+ for (const entry of auditEntries.filter((candidate) => candidate.event === "senses.telegram_turn_start" || candidate.event === "senses.telegram_turn_end" || candidate.event === "senses.telegram_turn_error")) {
1359
+ if (typeof entry.meta.lifecycleMac !== "string" || !SHA256.test(entry.meta.lifecycleMac)
1360
+ || entry.meta.lifecycleMac !== (0, telegram_1.sanctuaryTelegramAuditLifecycleMac)(identityKey, auditSchema, entry.event, entry.meta)) {
1361
+ throw new Error("Telegram audit lifecycle MAC is invalid");
1362
+ }
1363
+ }
1364
+ const approvalEvidenceKeys = {
1365
+ "senses.telegram_approval_prompt_bound": [["actionDigest", "approvalId", "boundAt", "checkpointDigest", "evidenceMac", "messageIdDigest", "scenarioHandleDigest", "suspendedSessionRevisionDigest", "targetDigest"]],
1366
+ "telegram.callback_settled": [["accepted", "acknowledged", "acknowledgementState", "actionDigest", "approvalId", "boundAt", "callbackAt", "checkpointDigest", "decisionAttemptDigest", "evidenceMac", "messageIdDigest", "reason", "scenarioHandleDigest", "suspendedSessionRevisionDigest", "targetDigest"]],
1367
+ "telegram.callback_recovery_settled": [["accepted", "acknowledgementState", "actionDigest", "approvalId", "boundAt", "callbackAt", "checkpointDigest", "decisionAttemptDigest", "evidenceMac", "messageIdDigest", "reason", "recoveredAt", "scenarioHandleDigest", "suspendedSessionRevisionDigest", "targetDigest"]],
1368
+ "telegram.approval_stale_callback_settled": [["accepted", "acknowledged", "actionDigest", "approvalId", "boundAt", "checkpointDigest", "evidenceMac", "messageIdDigest", "reason", "scenarioHandleDigest", "staleAt", "suspendedSessionRevisionDigest", "targetDigest"]],
1369
+ "telegram.approval_expiry_observed": [["actionDigest", "approvalId", "boundAt", "checkpointDigest", "evidenceMac", "expiryDeadlineAt", "expiryObservationSchemaVersion", "expiryObservedAt", "messageIdDigest", "scenarioHandleDigest", "suspendedSessionRevisionDigest", "targetDigest"]],
1370
+ "telegram.approval_prompt_terminalized": [
1371
+ ["actionDigest", "approvalId", "boundAt", "buttonsRemoved", "checkpointDigest", "evidenceMac", "messageIdDigest", "scenarioHandleDigest", "suspendedSessionRevisionDigest", "targetDigest", "terminalEditStartedAt", "terminalizedAt"],
1372
+ ["actionDigest", "approvalId", "boundAt", "buttonsRemoved", "checkpointDigest", "evidenceMac", "expiryDeadlineAt", "expiryObservedAt", "messageIdDigest", "scenarioHandleDigest", "suspendedSessionRevisionDigest", "targetDigest", "terminalEditStartedAt", "terminalizedAt"],
1373
+ ],
1374
+ "senses.telegram_approval_continuation_delivered": [["actionDigest", "approvalId", "boundAt", "checkpointDigest", "deliveredAt", "deliveryDigest", "deliveryMessageIdDigest", "evidenceMac", "messageIdDigest", "resultDigest", "scenarioHandleDigest", "suspendedSessionRevisionDigest", "targetDigest"]],
1375
+ };
1376
+ for (const entry of auditEntries.filter((candidate) => candidate.event in approvalEvidenceKeys
1377
+ && candidate.meta.scenarioHandleDigest === scenarioHandleDigest && typeof candidate.meta.approvalId === "string")) {
1378
+ if (!approvalEvidenceKeys[entry.event].some((keys) => JSON.stringify(Object.keys(entry.meta).sort()) === JSON.stringify([...keys].sort()))
1379
+ || typeof entry.meta.evidenceMac !== "string" || !SHA256.test(entry.meta.evidenceMac)
1380
+ || entry.meta.evidenceMac !== (0, telegram_1.sanctuaryTelegramApprovalEvidenceMac)(identityKey, entry.event, entry.meta)) {
1381
+ throw new Error("Telegram approval evidence MAC is invalid");
1382
+ }
1383
+ }
1384
+ // Unknown admission needs the same surfaces for its zero-work digest.
1385
+ if (sanctuary_acceptance_harness_1.SANCTUARY_SCENARIO_SOURCES[label].includes("identity-surface-audit") || label === "unit-16d-2-unknown-admission") {
1386
+ const expectedSubject = (0, telegram_1.opaqueTelegramSubject)(identityKey, credentials.botId, credentials.authorizedUserId, credentials.authorizedChatId);
1387
+ const observedSubjects = auditEntries.flatMap((entry) => typeof entry.meta.subject === "string" ? [entry.meta.subject] : []);
1388
+ const approvalSubjects = approvalRecords.map((projection) => projection.approval).filter((record) => record.transport === "telegram").map((record) => record.requesterId);
1389
+ const rawValues = [...new Set([credentials.authorizedUserId, credentials.authorizedChatId])];
1390
+ const persistedIdentitySurfaces = readBoundedIdentitySurfaces(agentRoot);
1391
+ const surfacePairs = Array.from({ length: Math.floor(persistedIdentitySurfaces.length / 2) }, (_, index) => ({
1392
+ relativePath: persistedIdentitySurfaces[index * 2],
1393
+ raw: persistedIdentitySurfaces[index * 2 + 1],
1394
+ }));
1395
+ const canonicalSessionPath = path.join("state", "sessions", `telegram-user:${expectedSubject}`, "telegram", `telegram_${expectedSubject}.json`);
1396
+ const canonicalSessionCount = surfacePairs.filter(({ relativePath, raw }) => {
1397
+ if (relativePath !== canonicalSessionPath)
1398
+ return false;
1399
+ object(JSON.parse(raw), "canonical Telegram session");
1400
+ return true;
1401
+ }).length;
1402
+ const sessionSurfaceDigest = (0, node_crypto_1.createHash)("sha256").update(JSON.stringify(surfacePairs.filter(({ relativePath }) => relativePath === path.join("state", "senses", "telegram", "identity-subjects.json")
1403
+ || relativePath.startsWith(`state${path.sep}sessions${path.sep}`)
1404
+ || relativePath.startsWith(`state${path.sep}pending${path.sep}`)
1405
+ || relativePath.startsWith(`state${path.sep}pending-returns${path.sep}`)))).digest("hex");
1406
+ const friendSurfaceDigest = (0, node_crypto_1.createHash)("sha256").update(JSON.stringify(surfacePairs.filter(({ relativePath }) => relativePath.startsWith(`friends${path.sep}`)))).digest("hex");
1407
+ const canonicalFriendCount = surfacePairs.reduce((count, { relativePath, raw }) => {
1408
+ if (!relativePath.startsWith(`friends${path.sep}`) || !relativePath.endsWith(".json"))
1409
+ return count;
1410
+ const record = object(JSON.parse(raw), "Telegram Friend identity");
1411
+ if (!Array.isArray(record.externalIds))
1412
+ return count;
1413
+ return count + record.externalIds.filter((value) => {
1414
+ const identity = object(value, "Telegram Friend external identity");
1415
+ return identity.provider === "telegram-user" && identity.externalId === expectedSubject;
1416
+ }).length;
1417
+ }, 0);
1418
+ const surfaceRecords = [...persistedIdentitySurfaces, auditRaw ?? "", auditHeadRaw ?? "", JSON.stringify(approvalRecords)];
1419
+ const surfaceSubjects = surfaceRecords.flatMap((raw) => raw.match(/tg_[A-Za-z0-9_-]{43}/gu) ?? []);
1420
+ const structuredRawId = /"(?:authorizedUserId|authorizedChatId|transportUserId|transportChatId|userId|chatId|updateId|messageId)"\s*:\s*"?\d{1,20}"?/gu;
1421
+ const rawLeakCount = surfaceRecords.reduce((count, raw) => count + rawValues.filter((value) => raw.includes(value)).length + (raw.match(structuredRawId)?.length ?? 0) + Number(auditContainsSensitiveMaterial(raw)), 0);
1422
+ const mismatchCount = [...surfaceSubjects, ...observedSubjects, ...approvalSubjects].filter((subject) => subject !== expectedSubject).length;
1423
+ identity = {
1424
+ keyPresent: true,
1425
+ subjectOpaque: /^tg_[A-Za-z0-9_-]{43}$/u.test(expectedSubject)
1426
+ && mismatchCount === 0,
1427
+ rawIdentityAbsent: rawLeakCount === 0,
1428
+ liveSubjectObserved: observedSubjects.includes(expectedSubject) && telegramTurns.some((turn) => turn.status === "success"),
1429
+ inspectedRecordCount: surfaceRecords.length,
1430
+ opaqueSubjectCount: [...surfaceSubjects, ...observedSubjects, ...approvalSubjects].filter((subject) => subject === expectedSubject).length,
1431
+ mismatchCount,
1432
+ rawLeakCount,
1433
+ canonicalSessionCount,
1434
+ canonicalFriendCount,
1435
+ sessionSurfaceDigest,
1436
+ friendSurfaceDigest,
1437
+ surfaceDigest: (0, node_crypto_1.createHash)("sha256").update(JSON.stringify(surfaceRecords)).digest("hex"),
1438
+ };
1439
+ }
1440
+ }
1441
+ const container = options.skipContainerSnapshot === true
1442
+ ? null
1443
+ : options.containerSnapshot !== undefined
1444
+ ? ownerContainerSnapshot(options.containerSnapshot)
1445
+ : deps.hostRequest ? object(await deps.hostRequest({ operation: "container_snapshot", targetId: TARGET_ID }), "container snapshot") : null;
1446
+ const rebootCheckpoint = parsedJson(rebootRaw);
1447
+ let reboot;
1448
+ if (rebootCheckpoint?.operation === "reboot" && rebootCheckpoint.phase === "preflight" && rebootCheckpoint.targetId === TARGET_ID
1449
+ && typeof rebootCheckpoint.idempotencyDigest === "string" && SHA256.test(rebootCheckpoint.idempotencyDigest)
1450
+ && typeof rebootCheckpoint.processBindingDigest === "string" && SHA256.test(rebootCheckpoint.processBindingDigest)
1451
+ && Number.isSafeInteger(rebootCheckpoint.unrelatedHostOperations) && Number(rebootCheckpoint.unrelatedHostOperations) >= 0) {
1452
+ reboot = {
1453
+ phase: "preflight", requestDigest: rebootCheckpoint.idempotencyDigest, processBindingDigest: rebootCheckpoint.processBindingDigest, requestCount: 0, checkpointPersisted: true, unrelatedHostOperations: Number(rebootCheckpoint.unrelatedHostOperations),
1454
+ bootIdentityChanged: false, hostReady: false, arrayReady: false, dockerReady: false, butlerReady: false, tailscaleReady: false, sshReady: false,
1455
+ };
1456
+ }
1457
+ else if (rebootCheckpoint?.operation === "reboot" && ["requested", "complete"].includes(String(rebootCheckpoint.phase))
1458
+ && typeof rebootCheckpoint.requestId === "string" && SHA256.test(rebootCheckpoint.requestId)
1459
+ && typeof rebootCheckpoint.prebootDigest === "string" && SHA256.test(rebootCheckpoint.prebootDigest)
1460
+ && typeof rebootCheckpoint.processBindingDigest === "string" && SHA256.test(rebootCheckpoint.processBindingDigest)
1461
+ && Number.isSafeInteger(rebootCheckpoint.unrelatedHostOperations) && Number(rebootCheckpoint.unrelatedHostOperations) >= 0) {
1462
+ const milestones = container && container.recoveryMilestones && typeof container.recoveryMilestones === "object" && !Array.isArray(container.recoveryMilestones)
1463
+ ? container.recoveryMilestones : {};
1464
+ const complete = rebootCheckpoint.phase === "complete" && typeof rebootCheckpoint.postbootDigest === "string" && SHA256.test(rebootCheckpoint.postbootDigest);
1465
+ reboot = {
1466
+ phase: rebootCheckpoint.phase, requestDigest: (0, node_crypto_1.createHash)("sha256").update(rebootCheckpoint.requestId).digest("hex"), processBindingDigest: rebootCheckpoint.processBindingDigest, requestCount: 1, checkpointPersisted: true, unrelatedHostOperations: Number(rebootCheckpoint.unrelatedHostOperations),
1467
+ bootIdentityChanged: complete && rebootCheckpoint.postbootDigest !== rebootCheckpoint.prebootDigest,
1468
+ hostReady: milestones.hostReady === true, arrayReady: milestones.arrayReady === true, dockerReady: milestones.dockerReady === true,
1469
+ butlerReady: milestones.butlerReady === true, tailscaleReady: milestones.tailscaleReady === true, sshReady: milestones.sshReady === true,
1470
+ };
1471
+ }
1472
+ const health = parseHealthAcceptanceState(healthRaw);
1473
+ const activeMarker = (0, sanctuary_acceptance_marker_1.readSanctuaryAcceptanceMarker)(TARGET_ID, agentRoot);
1474
+ const postbootIntegrity = buildPostbootIntegritySnapshot({ telegramNextUpdateId, checkpointsRaw, restartAttempts: parseRestartAttempts(restartAttemptsRaw, null), cronRaw, health, auditLedgerEntries, activeScenarioHandleDigest: activeMarker?.scenarioHandleDigest ?? null });
1475
+ const prebootIntegrity = rebootCheckpoint && typeof rebootCheckpoint.prebootIntegrity === "object" && !Array.isArray(rebootCheckpoint.prebootIntegrity)
1476
+ ? rebootCheckpoint.prebootIntegrity : undefined;
1477
+ const healthProbe = parseHealthProbeReceipt(healthProbeRaw, label, scenarioHandleDigest, identityRaw?.trim() ?? null);
1478
+ const interactiveDriver = parseInteractiveDriverReceipt(interactiveDriverRaw, label, scenarioHandleDigest);
1479
+ const healthSweeps = health ? health.sweepReceipts.filter((receipt) => receipt.scenarioHandleDigest === scenarioHandleDigest) : [];
1480
+ const healthDeliveries = health ? health.deliveredReceipts : [];
1481
+ const scenarioDeliveryIds = new Set(healthSweeps.flatMap((receipt) => typeof receipt.deliveryId === "string" ? [receipt.deliveryId] : []));
1482
+ const scenarioDeliveries = healthDeliveries.filter((receipt) => typeof receipt.deliveryId === "string" && scenarioDeliveryIds.has(receipt.deliveryId));
1483
+ const digestSweep = healthSweeps.findLast((receipt) => receipt.digestDue === true && typeof receipt.completedAt === "string");
1484
+ const digestFiredWithinMs = digestSweep ? millisecondsAfterLocalNine(Date.parse(String(digestSweep.completedAt))) : null;
1485
+ let liveProvider;
1486
+ if (agentConfig && readinessPolicy && Array.isArray(readinessPolicy.providers)) {
1487
+ const outwardConfig = object(agentConfig.humanFacing, "outward provider");
1488
+ const innerConfig = object(agentConfig.agentFacing, "inner provider");
1489
+ const readinessProviders = readinessPolicy.providers.map((entry) => object(entry, "provider readiness candidate"));
1490
+ const minimaxReadiness = readinessProviders.find((entry) => entry.provider === "minimax");
1491
+ if (minimaxReadiness && outwardConfig.provider === "minimax" && innerConfig.provider === "minimax") {
1492
+ const readCredential = deps.readProviderCredential ?? provider_credentials_1.readProviderCredentialRecord;
1493
+ const checkProvider = deps.providerPing ?? provider_ping_1.pingProvider;
1494
+ const minimaxRecord = await readCredential(TARGET_ID, "minimax", { refreshIfMissing: false });
1495
+ const outwardModel = text(outwardConfig.model, "outward model");
1496
+ const innerModel = text(innerConfig.model, "inner model");
1497
+ const minimaxConfig = minimaxRecord.ok ? { ...minimaxRecord.record.credentials, ...minimaxRecord.record.config } : null;
1498
+ const unavailablePing = { ok: false, classification: "auth-failure", message: "credential unavailable", attempts: [] };
1499
+ const outwardPing = minimaxConfig ? await checkProvider("minimax", minimaxConfig, { model: outwardModel, timeoutMs: 10_000 }) : unavailablePing;
1500
+ const innerPing = minimaxConfig ? await checkProvider("minimax", minimaxConfig, { model: innerModel, timeoutMs: 10_000 }) : unavailablePing;
1501
+ const pingReceipts = [
1502
+ { lane: "outward", provider: "minimax", model: outwardModel, credentialRevision: minimaxRecord.ok ? minimaxRecord.record.revision : null, ok: outwardPing.ok, attempts: outwardPing.attempts?.map(({ provider, model, operation, ok }) => ({ provider, model, operation, ok })) ?? [] },
1503
+ { lane: "inner", provider: "minimax", model: innerModel, credentialRevision: minimaxRecord.ok ? minimaxRecord.record.revision : null, ok: innerPing.ok, attempts: innerPing.attempts?.map(({ provider, model, operation, ok }) => ({ provider, model, operation, ok })) ?? [] },
1504
+ ];
1505
+ const requestSemanticsExact = pingReceipts.every((receipt) => receipt.ok && receipt.attempts.length >= 1 && receipt.attempts.length <= 3
1506
+ && receipt.attempts.every((attempt) => attempt.provider === receipt.provider && attempt.model === receipt.model && attempt.operation === "ping" && typeof attempt.ok === "boolean")
1507
+ && receipt.attempts.at(-1)?.ok === true);
1508
+ const fallbackAttemptCount = pingReceipts.reduce((count, receipt) => count + receipt.attempts.filter((attempt) => attempt.provider !== receipt.provider || attempt.model !== receipt.model).length, 0);
1509
+ const minimaxCredentials = minimaxRecord.ok ? minimaxRecord.record.credentials : {};
1510
+ let exactContract = { laneSelectionExact: false, vaultCoordinatesExact: false, singleCredentialExact: false };
1511
+ if (typeof minimaxCredentials.apiKey === "string") {
1512
+ const input = {
1513
+ outward: { provider: text(outwardConfig.provider, "outward provider"), model: outwardModel },
1514
+ inner: { provider: text(innerConfig.provider, "inner provider"), model: innerModel },
1515
+ minimax: { vaultItem: typeof minimaxReadiness?.vaultItem === "string" ? minimaxReadiness.vaultItem : "", apiKey: minimaxCredentials.apiKey },
1516
+ selectionPolicy: text(readinessPolicy.selectionPolicy, "provider selection policy"),
1517
+ };
1518
+ exactContract = evaluateSanctuaryProviderReadinessContract(input);
1519
+ }
1520
+ liveProvider = {
1521
+ outwardReady: outwardPing.ok,
1522
+ innerReady: innerPing.ok,
1523
+ silentFallback: readinessPolicy.selectionPolicy !== "explicit-same-lane-only",
1524
+ credentialRevisionsPresent: minimaxRecord.ok && Boolean(minimaxRecord.record.revision),
1525
+ requestSemanticsExact,
1526
+ fallbackAttemptCount,
1527
+ ...exactContract,
1528
+ pingReceipts,
1529
+ };
1530
+ }
1531
+ }
1532
+ let containment;
1533
+ if (label === "unit-16e-containment-audit" || label === "unit-16d-2-unknown-admission") {
1534
+ const inventoryEnvelope = object(await dependency(deps.hostRequest, "Sanctuary host broker")({ operation: "inventory_keys", targetServerId: TARGET_SERVER_ID }), "containment key inventory");
1535
+ if (!Array.isArray(inventoryEnvelope.keys))
1536
+ throw new Error("containment key inventory is invalid");
1537
+ const rawInventory = inventoryEnvelope.keys.map((entry) => object(entry, "containment key record"));
1538
+ const inventory = rawInventory.map(normalizeKey);
1539
+ const redactedInventory = inventory.map((record) => ({
1540
+ idDigest: (0, node_crypto_1.createHash)("sha256").update(record.id).digest("hex"),
1541
+ name: record.name,
1542
+ permissions: flattenedPermissions(record),
1543
+ roles: [...record.roles].sort(),
1544
+ })).sort((left, right) => left.name.localeCompare(right.name));
1545
+ const readRecord = inventory.find((record) => record.name === "Butler RO");
1546
+ const writeRecord = inventory.find((record) => record.name === "Butler RW");
1547
+ const rawProfiles = (0, node_fs_1.readFileSync)(path.join(agentRoot, "tool-profiles.json"), "utf8");
1548
+ const packagedProfiles = (0, node_fs_1.readFileSync)(path.resolve(__dirname, "../../../deploy/unraid/sanctuary.ouro/tool-profiles.json"), "utf8");
1549
+ if (!(0, node_util_1.isDeepStrictEqual)(JSON.parse(rawProfiles), JSON.parse(packagedProfiles)))
1550
+ throw new Error("Sanctuary containment profiles do not match the verified package");
1551
+ const relationshipRegistry = (0, relationship_authorization_1.loadRelationshipCapabilityRegistry)(agentRoot);
1552
+ const digest = (value) => (0, node_crypto_1.createHash)("sha256").update(JSON.stringify(value)).digest("hex");
1553
+ const boundaryFor = async (profileId) => {
1554
+ const profile = relationshipRegistry.profiles[profileId];
1555
+ const inner = profileId === "sanctuary-event";
1556
+ const runtime = await dependency(deps.providerRuntime, "current provider runtime")(inner ? "agent" : "human", { agentName: TARGET_ID, agentRoot });
1557
+ const context = containmentToolContext(agentRoot, profileId);
1558
+ const select = (toolContext) => (0, tools_1.selectToolsForChannel)((0, friends_1.getChannelCapabilities)(inner ? "inner" : "telegram"), undefined, undefined, runtime.capabilities, undefined, undefined, toolContext);
1559
+ const selection = select(context);
1560
+ const schemas = (0, tools_1.toolSelectionSchemas)(selection);
1561
+ const schemaToolNames = schemas.map((tool) => tool.function.name);
1562
+ const expectedNames = profile.toolNames.filter((name) => name !== "sanctuary_host_execute" && !tools_1.SANCTUARY_MCP_PROVIDED_TOOLS.has(name) && (inner || name !== "rest") && (name !== "set_reasoning_effort" || runtime.capabilities.has("reasoning-effort")));
1563
+ const excludedNames = ["vault_get", "mcp_call", "exec", "credential_get", ...(profileId === "sanctuary-owner" ? [] : tools_1.SANCTUARY_OWNER_ADDITIONS)];
1564
+ const poisoned = select({ ...context, relationshipAuthorization: {
1565
+ ...context.relationshipAuthorization, advertisedToolNames: [...profile.toolNames, ...excludedNames, "mcp__containment_poison"],
1566
+ } });
1567
+ const productionBoundaryReceipts = await dependency(deps.runProductionBoundaryProbe, "production tool boundary probe")({ agentRoot, profileId, providerRuntime: runtime });
1568
+ const excludedAttempts = productionBoundaryReceipts.filter((receipt) => receipt.name !== "unraid_get_system");
1569
+ return {
1570
+ profileId, profileVersion: profile.version, profileDigest: digest(profile), profileToolNames: profile.toolNames,
1571
+ providerCapabilities: [...runtime.capabilities], schemaDigest: digest(schemas), schemaToolNames,
1572
+ ordinaryDefinitionCount: selection.ordinary.length, engineSchemaCount: selection.engine.length,
1573
+ profileExact: true, schemasExact: (0, node_util_1.isDeepStrictEqual)([...schemaToolNames].sort(), [...expectedNames].sort()),
1574
+ handlersExact: selection.ordinary.every((definition) => typeof definition.handler === "function") && selection.engine.every((schema) => [tools_1.ponderTool, tools_1.settleTool, tools_1.speakTool, tools_1.restTool].includes(schema)),
1575
+ poisonedSchemaIntersectionCount: (0, tools_1.toolSelectionSchemas)(poisoned).filter((schema) => !schemaToolNames.includes(schema.function.name)).length,
1576
+ excludedToolNames: excludedAttempts.map((receipt) => receipt.name),
1577
+ excludedSchemaIntersectionCount: schemaToolNames.filter((name) => excludedNames.includes(name)).length,
1578
+ fabricatedHandlerInvocationCount: excludedAttempts.filter((receipt) => receipt.invoked).length,
1579
+ excludedToolAttemptCount: excludedAttempts.length,
1580
+ excludedToolRejectedCount: excludedAttempts.filter((receipt) => receipt.reason === "profile_excluded").length,
1581
+ excludedToolInvokedCount: excludedAttempts.filter((receipt) => receipt.invoked).length,
1582
+ excludedToolSideEffectCount: excludedAttempts.filter((receipt) => receipt.sideEffect).length,
1583
+ globallyResolvableExcludedToolCount: excludedAttempts.filter((receipt) => receipt.globallyResolvable).length,
1584
+ };
1585
+ };
1586
+ const profileBoundaries = {
1587
+ "sanctuary-owner": await boundaryFor("sanctuary-owner"),
1588
+ "sanctuary-household": await boundaryFor("sanctuary-household"),
1589
+ "sanctuary-event": await boundaryFor("sanctuary-event"),
1590
+ };
1591
+ if ((0, node_fs_1.readFileSync)(path.join(agentRoot, "tool-profiles.json"), "utf8") !== rawProfiles)
1592
+ throw new Error("Sanctuary containment profiles changed during the audit");
1593
+ const restartDefinition = (0, tools_1.resolveToolDefinition)("unraid_restart_container");
1594
+ const writeApprovalPolicy = restartDefinition.approvalPolicy({ container: "calibre-web" });
1595
+ const writeApprovalPolicyExact = writeApprovalPolicy.kind === "required"
1596
+ && writeApprovalPolicy.policyId === "sanctuary.unraid.restart.v1"
1597
+ && writeApprovalPolicy.actionClass === "unraid.container.restart"
1598
+ && writeApprovalPolicy.requiresSoleCall === true;
1599
+ let lifecycleBalance = 0;
1600
+ let lifecyclePairs = 0;
1601
+ for (const entry of auditLedgerEntries) {
1602
+ if (entry.event === "senses.telegram_turn_start")
1603
+ lifecycleBalance += 1;
1604
+ if (entry.event === "senses.telegram_turn_end" && lifecycleBalance > 0) {
1605
+ lifecycleBalance -= 1;
1606
+ lifecyclePairs += 1;
1607
+ }
1608
+ }
1609
+ const rawWriteMaterialFieldCount = rawInventory.reduce((count, record) => count
1610
+ + Object.keys(record).filter((field) => /^(?:key|credential|secret|token)$/iu.test(field)).length, 0);
1611
+ if (container && typeof container.readOnlyRoot !== "boolean")
1612
+ throw new Error("containment root-mode observation must be boolean");
1613
+ containment = {
1614
+ schemaVersion: "sanctuary-containment-audit-v2",
1615
+ keyCount: inventory.length,
1616
+ keyInventoryDigest: (0, node_crypto_1.createHash)("sha256").update(JSON.stringify(redactedInventory)).digest("hex"),
1617
+ readScopeDigest: (0, node_crypto_1.createHash)("sha256").update(JSON.stringify(readRecord ? flattenedPermissions(readRecord) : [])).digest("hex"),
1618
+ writeScopeDigest: (0, node_crypto_1.createHash)("sha256").update(JSON.stringify(writeRecord ? flattenedPermissions(writeRecord) : [])).digest("hex"),
1619
+ keyRoleAssignmentCount: inventory.reduce((count, record) => count + record.roles.length, 0),
1620
+ profileBoundaries,
1621
+ auditPathDigest: (0, node_crypto_1.createHash)("sha256").update(TELEGRAM_AUDIT).digest("hex"),
1622
+ auditLedgerDigest: (0, node_crypto_1.createHash)("sha256").update(JSON.stringify(auditLedgerEntries)).digest("hex"),
1623
+ auditRecordCount: auditLedgerEntries.length,
1624
+ auditLifecyclePairCount: lifecyclePairs,
1625
+ containerUser: typeof container?.user === "string" ? container.user : "",
1626
+ liveProcessUser: typeof container?.liveProcessUser === "string" ? container.liveProcessUser : "",
1627
+ mountCount: Number(container?.mountCount ?? -1),
1628
+ publishedPortCount: Number(container?.publishedPortCount ?? -1),
1629
+ networkMode: typeof container?.networkMode === "string" ? container.networkMode : "",
1630
+ readOnlyRoot: container?.readOnlyRoot === true,
1631
+ mountsExact: container?.mountsExact === true,
1632
+ securityExact: container?.securityExact === true,
1633
+ updaterDisabled: container?.updaterDisabled === true,
1634
+ writableKeyExposure: container?.writableKeyExposure !== false,
1635
+ rawWriteMaterialFieldCount,
1636
+ typedWriteExecutorCount: relationshipRegistry.profiles["sanctuary-owner"].toolNames.filter((name) => name === "unraid_restart_container" && writeApprovalPolicy.kind === "required").length,
1637
+ writeApprovalPolicyDigest: (0, node_crypto_1.createHash)("sha256").update(JSON.stringify(writeApprovalPolicy)).digest("hex"),
1638
+ writeApprovalPolicyExact,
1639
+ sensitiveMaterialObserved: auditContainsSensitiveMaterial(auditRaw ?? "", deps.telegramCredentials?.()) || rawWriteMaterialFieldCount > 0 || container?.writableKeyExposure === true,
1640
+ stopDenied, restartDenied, denialAuditCount, denialStateUnchanged, denialProbeCompleted,
1641
+ };
1642
+ }
1643
+ const telegramAdmissions = label === "unit-16d-2-unknown-admission"
1644
+ ? await projectTelegramAdmissions(agentRoot, dependency(deps.telegramCredentials, "Telegram credentials")(), identityRaw?.trim() ?? null)
1645
+ : [];
1646
+ const sourceValues = {
1647
+ "identity-key": identityRaw, "telegram-audit": auditEntries, "telegram-offset": gatewayCursor.progressDigest,
1648
+ "approval-journal": approvals, "approval-checkpoints": checkpointsRaw, "container-inspect": container,
1649
+ "provider-live-check": liveProvider ?? null, "cron-runtime": cronRaw, "health-runtime": health, "restart-attempt-ledger": restartAttempts,
1650
+ "digest-runtime": health, "reboot-checkpoint": rebootCheckpoint, "telegram-turn-receipts": telegramTurns, "read-only-denial-receipt": denialReceipt ?? null,
1651
+ "identity-surface-audit": identity ? { inspectedRecordCount: identity.inspectedRecordCount, opaqueSubjectCount: identity.opaqueSubjectCount, mismatchCount: identity.mismatchCount, rawLeakCount: identity.rawLeakCount, canonicalSessionCount: identity.canonicalSessionCount, canonicalFriendCount: identity.canonicalFriendCount, sessionSurfaceDigest: identity.sessionSurfaceDigest, friendSurfaceDigest: identity.friendSurfaceDigest, surfaceDigest: identity.surfaceDigest } : null,
1652
+ "containment-audit": containment ?? null,
1653
+ "telegram-admission-evidence": telegramAdmissions,
1654
+ "health-probe-receipt": healthProbe ?? null,
1655
+ "scheduler-liveness-receipt": healthProbe?.schedulerReceipt ?? null,
1656
+ "interactive-driver-receipt": parsedJson(interactiveDriverRaw),
1657
+ "live-grounding-read": liveGrounding ?? null,
1658
+ };
1659
+ const zeroWork = {
1660
+ providerToolDigest: (0, node_crypto_1.createHash)("sha256").update(JSON.stringify({ telegramTurnsRaw, restartAttemptsRaw })).digest("hex"),
1661
+ outwardDigest: (0, node_crypto_1.createHash)("sha256").update(JSON.stringify({
1662
+ deliveredReceipts: health?.deliveredReceipts ?? [],
1663
+ outwardEvents: auditLedgerEntries.filter((entry) => /(?:delivered|send|outbound)/u.test(entry.event)),
1664
+ })).digest("hex"),
1665
+ approvalMutationDigest: (0, node_crypto_1.createHash)("sha256").update(JSON.stringify({
1666
+ checkpointsRaw,
1667
+ approvalStoreDigest: digestOptionalFiles([approvalDatabasePath, `${approvalDatabasePath}-wal`, `${approvalDatabasePath}-shm`]),
1668
+ restartAttemptsRaw,
1669
+ restartCount: container?.restartCount ?? null,
1670
+ })).digest("hex"),
1671
+ sessionFriendDigest: (0, node_crypto_1.createHash)("sha256").update(JSON.stringify({
1672
+ sessionSurfaceDigest: identity?.sessionSurfaceDigest ?? null,
1673
+ friendSurfaceDigest: identity?.friendSurfaceDigest ?? null,
1674
+ })).digest("hex"),
1675
+ };
1676
+ return {
1677
+ capturedAt: deps.now?.() ?? Date.now(),
1678
+ sourceValues,
1679
+ postbootIntegrity,
1680
+ prebootIntegrity,
1681
+ events: auditEntries,
1682
+ approvals,
1683
+ restartAttempts,
1684
+ telegramTurns,
1685
+ telegramAdmissions,
1686
+ telegramNextUpdateId,
1687
+ zeroWork,
1688
+ liveGrounding,
1689
+ identity,
1690
+ container: container ? {
1691
+ exactImage: typeof expectedImageId === "string" && SHA256.test(expectedImageId) && container.imageId === `sha256:${expectedImageId}`, running: container.running === true,
1692
+ healthy: container.health === "healthy", user: text(container.user, "container user"), readOnlyRoot: container.readOnlyRoot === true,
1693
+ mountCount: Number(container.mountCount), publishedPortCount: Number(container.publishedPortCount), restartPolicy: text(container.restartPolicy, "restart policy"), restartCount: Number(container.restartCount),
1694
+ autostartExact: container.autostartExact === true, mountsExact: container.mountsExact === true, updaterDisabled: container.updaterDisabled === true,
1695
+ vaultUnlocked: container.vaultUnlocked === true, manualAuthRequired: container.manualAuthRequired === true,
1696
+ } : undefined,
1697
+ provider: liveProvider,
1698
+ healthProbe,
1699
+ interactiveDriver,
1700
+ denial: denialReceipt,
1701
+ cron: cronRaw ? { registered: canonicalSanctuaryHealthCronRegistered(cronRaw), fingerprint: (0, node_crypto_1.createHash)("sha256").update(cronRaw).digest("hex"), receiptDigest: (0, node_crypto_1.createHash)("sha256").update(JSON.stringify(health ? health.deliveredReceipts : null)).digest("hex"), sweepCount: healthSweeps.length } : undefined,
1702
+ health: health ? { transitionCount: healthSweeps.filter((receipt) => Number(receipt.opened) > 0 || Number(receipt.recovered) > 0).length, alertCount: scenarioDeliveries.filter((receipt) => receipt.kind === "transition" || receipt.kind === "transition_and_digest").length, productionRestored: container?.running === true && container.health === "healthy" } : undefined,
1703
+ digest: health && digestFiredWithinMs !== null ? { scheduleObserved: Boolean(cronRaw && canonicalSanctuaryHealthCronRegistered(cronRaw)), messageCount: scenarioDeliveries.filter((receipt) => receipt.kind === "digest" || receipt.kind === "transition_and_digest").length, firedWithinMs: digestFiredWithinMs, productionRestored: container?.running === true && container.health === "healthy" } : undefined,
1704
+ reboot,
1705
+ containment: containment ?? {
1706
+ schemaVersion: "sanctuary-containment-audit-v2", keyCount: 0, keyInventoryDigest: "", readScopeDigest: "", writeScopeDigest: "", keyRoleAssignmentCount: 0,
1707
+ profileBoundaries: null, auditPathDigest: "", auditLedgerDigest: "", auditRecordCount: 0, auditLifecyclePairCount: 0,
1708
+ containerUser: "", liveProcessUser: "", mountCount: 0, publishedPortCount: 0, networkMode: "", readOnlyRoot: false, mountsExact: false, securityExact: false, updaterDisabled: false,
1709
+ writableKeyExposure: container?.writableKeyExposure === true, rawWriteMaterialFieldCount: 0, typedWriteExecutorCount: 0, writeApprovalPolicyDigest: "", writeApprovalPolicyExact: false,
1710
+ sensitiveMaterialObserved: auditContainsSensitiveMaterial(auditRaw ?? "") || container?.writableKeyExposure === true,
1711
+ stopDenied, restartDenied, denialAuditCount, denialStateUnchanged, denialProbeCompleted,
1712
+ },
1713
+ };
1714
+ }
1715
+ async function postbootIntegritySnapshot(deps) {
1716
+ const agentRoot = (0, identity_1.getAgentRoot)(TARGET_ID);
1717
+ const identityRaw = optionalFixedFile(deps, pathFor(agentRoot, "state/senses/telegram/identity.key"));
1718
+ const auditRaw = optionalFixedFile(deps, TELEGRAM_AUDIT);
1719
+ const auditHeadRaw = optionalFixedFile(deps, TELEGRAM_AUDIT_HEAD);
1720
+ if ((auditRaw === null) !== (auditHeadRaw === null))
1721
+ throw new Error("Telegram audit ledger/head presence mismatch");
1722
+ if (auditRaw !== null && (!identityRaw || !/^[A-Za-z0-9_-]{43}\n?$/u.test(identityRaw)))
1723
+ throw new Error("Telegram audit ledger identity key is unavailable");
1724
+ const auditLedgerEntries = auditRaw === null ? [] : (0, telegram_audit_ledger_1.verifyTelegramAuditLedger)({
1725
+ ledgerRaw: auditRaw,
1726
+ headRaw: auditHeadRaw,
1727
+ identityKey: identityRaw.trim(),
1728
+ });
1729
+ const restartAttemptsRaw = optionalFixedFile(deps, pathFor(agentRoot, "state/acceptance/restart-attempts.ndjson")) ?? "";
1730
+ return buildPostbootIntegritySnapshot({
1731
+ telegramNextUpdateId: (await readGatewayCursor(deps)).nextUpdateId, checkpointsRaw: optionalFixedFile(deps, pathFor(agentRoot, "state/approvals/checkpoints.json")),
1732
+ restartAttempts: parseRestartAttempts(restartAttemptsRaw, null), cronRaw: optionalFixedFile(deps, "/home/ouro/.ouro-cli/scheduler/sanctuary.crontab"),
1733
+ health: parseHealthAcceptanceState(optionalFixedFile(deps, pathFor(agentRoot, "state/health/sanctuary-health.json"))), auditLedgerEntries,
1734
+ activeScenarioHandleDigest: (0, sanctuary_acceptance_marker_1.readSanctuaryAcceptanceMarker)(TARGET_ID)?.scenarioHandleDigest ?? null,
1735
+ });
1736
+ }
1737
+ function pathFor(root, suffix) { return `${root}/${suffix}`; }
1738
+ async function captureAcceptanceScenario(payload, deps) {
1739
+ const phase = text(payload.phase, "scenario phase");
1740
+ if (phase !== "begin" && phase !== "poll")
1741
+ throw new Error("scenario phase is invalid");
1742
+ exactKeys(payload, phase === "begin" ? ["operation", "phase", "label", "externalGate", "sources"] : ["operation", "phase", "label", "externalGate", "sources", "checkpointDigest"], "scenario payload");
1743
+ const label = text(payload.label, "scenario label");
1744
+ if (!sanctuary_acceptance_harness_1.SANCTUARY_UNIT_16_EVIDENCE_LABELS.includes(label))
1745
+ throw new Error("scenario label is invalid");
1746
+ const externalGate = text(payload.externalGate, "scenario external gate");
1747
+ if (externalGate !== sanctuary_acceptance_harness_1.SANCTUARY_SCENARIO_GATES[label])
1748
+ throw new Error("scenario external gate is invalid");
1749
+ if (!Array.isArray(payload.sources) || JSON.stringify(payload.sources) !== JSON.stringify(sanctuary_acceptance_harness_1.SANCTUARY_SCENARIO_SOURCES[label]))
1750
+ throw new Error("scenario sources are invalid");
1751
+ if (!deps.captureScenario)
1752
+ throw new Error("scenario capture is unavailable");
1753
+ return deps.captureScenario({ phase, label, externalGate, sources: payload.sources, ...(phase === "poll" ? { checkpointDigest: text(payload.checkpointDigest, "scenario checkpoint digest") } : {}) });
1754
+ }
1755
+ function normalizePermission(value) {
1756
+ const permission = object(value, "Unraid key permission");
1757
+ if (JSON.stringify(Object.keys(permission).sort()) !== JSON.stringify(["actions", "resource"]))
1758
+ throw new Error("Unraid key permission fields are invalid");
1759
+ const resource = text(permission.resource, "Unraid key permission resource");
1760
+ if (!PERMISSION_RESOURCES.has(resource))
1761
+ throw new Error("Unraid key permission resource is invalid");
1762
+ if (!Array.isArray(permission.actions) || permission.actions.length === 0)
1763
+ throw new Error("Unraid key permission actions are invalid");
1764
+ const actions = permission.actions.map((action) => text(action, "Unraid key permission action"));
1765
+ if (new Set(actions).size !== actions.length || actions.some((action) => !PERMISSION_ACTIONS.has(action)))
1766
+ throw new Error("Unraid key permission actions are invalid");
1767
+ return { resource, actions: [...actions].sort() };
1768
+ }
1769
+ function normalizeKey(value) {
1770
+ const key = value;
1771
+ if (!Array.isArray(key.permissions))
1772
+ throw new Error("Unraid key permissions are invalid");
1773
+ if (!Array.isArray(key.roles) || !key.roles.every((role) => typeof role === "string"))
1774
+ throw new Error("Unraid key roles are invalid");
1775
+ return {
1776
+ id: keyId(key.id),
1777
+ name: text(key.name, "Unraid key name"),
1778
+ permissions: key.permissions.map(normalizePermission),
1779
+ roles: [...key.roles],
1780
+ };
1781
+ }
1782
+ function scope(key) {
1783
+ const flattened = key.permissions.flatMap((permission) => permission.actions.map((action) => `${permission.resource}:${action}`)).sort();
1784
+ if (JSON.stringify(flattened) === JSON.stringify(RO_PERMISSIONS))
1785
+ return "read-only";
1786
+ if (JSON.stringify(flattened) === JSON.stringify([...RO_PERMISSIONS, "DOCKER:UPDATE_ANY"].sort()))
1787
+ return "bounded-write";
1788
+ return "legacy-write";
1789
+ }
1790
+ function sameSecret(left, right) {
1791
+ const leftBytes = Buffer.from(left, "utf8");
1792
+ const rightBytes = Buffer.from(right, "utf8");
1793
+ return leftBytes.length === rightBytes.length && (0, node_crypto_1.timingSafeEqual)(leftBytes, rightBytes);
1794
+ }
1795
+ function exactLoopbackGraphqlEndpoint(value) {
1796
+ const endpoint = new URL(text(value, "endpoint"));
1797
+ if ((endpoint.protocol !== "http:" && endpoint.protocol !== "https:")
1798
+ || (endpoint.hostname !== "127.0.0.1" && endpoint.hostname !== "localhost" && endpoint.hostname !== "::1")
1799
+ || endpoint.pathname !== "/graphql" || endpoint.username || endpoint.password || endpoint.search || endpoint.hash) {
1800
+ throw new Error("probe endpoint must be an exact loopback GraphQL endpoint");
1801
+ }
1802
+ return endpoint;
1803
+ }
1804
+ function inventory(deps) {
1805
+ const keys = deps.readKeyFiles().map(normalizeKey);
1806
+ if (new Set(keys.map((key) => key.id)).size !== keys.length)
1807
+ throw new Error("Unraid key inventory contains duplicate IDs");
1808
+ if (new Set(keys.map((key) => key.name)).size !== keys.length)
1809
+ throw new Error("Unraid key inventory contains duplicate names");
1810
+ return keys;
1811
+ }
1812
+ async function vaultBackedCapabilityVerify(payload, deps) {
1813
+ const id = keyId(payload.keyId);
1814
+ const capability = text(payload.capability, "capability");
1815
+ if (capability !== "read-only" && capability !== "bounded-write")
1816
+ throw new Error("capability is invalid");
1817
+ const args = [
1818
+ "exec", "-i", "ouro-butler-staging",
1819
+ "/opt/ouro/deploy/unraid/sanctuary-acceptance-adapter.sh",
1820
+ "vault-probe", id, capability,
1821
+ ];
1822
+ const result = await deps.execFile("/usr/bin/docker", args);
1823
+ const response = object(JSON.parse(result.stdout), "vault probe result");
1824
+ if (response.valid !== true || response.keyId !== id || response.capability !== capability)
1825
+ throw new Error("vault-backed capability verification failed");
1826
+ return { verified: true, keyId: id, capability };
1827
+ }
1828
+ function closedInventory(deps) {
1829
+ return {
1830
+ keys: inventory(deps).map((key) => ({
1831
+ id: key.id,
1832
+ scope: scope(key),
1833
+ roles: key.roles.length === 0 ? "none" : "present",
1834
+ })).sort((left, right) => left.id.localeCompare(right.id)),
1835
+ };
1836
+ }
1837
+ async function exactIdRevoke(payload, deps) {
1838
+ const id = keyId(payload.keyId);
1839
+ const matches = inventory(deps).filter((key) => key.id === id);
1840
+ if (matches.length !== 1)
1841
+ throw new Error("exact Unraid key ID is absent or ambiguous");
1842
+ const target = matches[0];
1843
+ const result = await deps.execFile("/usr/local/sbin/unraid-api", ["apikey", "--name", target.name, "--delete", "--json"]);
1844
+ const response = object(JSON.parse(result.stdout), "Unraid revoke result");
1845
+ if (response.deleted !== 1 || !Array.isArray(response.keys) || response.keys.length !== 1)
1846
+ throw new Error("Unraid revoke result is invalid");
1847
+ const deleted = object(response.keys[0], "deleted Unraid key");
1848
+ if (deleted.id !== id || deleted.name !== target.name)
1849
+ throw new Error("Unraid revoke did not return the exact key ID");
1850
+ return { revoked: true, id };
1851
+ }
1852
+ async function revokedKeyAuthRejection(payload, deps) {
1853
+ const id = keyId(payload.keyId);
1854
+ const endpoint = exactLoopbackGraphqlEndpoint(payload.endpoint);
1855
+ const descriptorPayload = object(JSON.parse(deps.readDescriptor()), "revoked-key descriptor");
1856
+ if (JSON.stringify(Object.keys(descriptorPayload).sort()) !== JSON.stringify(["descriptor", "keyId"]))
1857
+ throw new Error("revoked-key descriptor shape is invalid");
1858
+ if (keyId(descriptorPayload.keyId) !== id)
1859
+ throw new Error("revoked-key descriptor ID mismatch");
1860
+ const descriptor = text(descriptorPayload.descriptor, "revoked-key descriptor value");
1861
+ const signal = AbortSignal.timeout(NETWORK_TIMEOUT_MS);
1862
+ const response = await deps.fetch(endpoint.href, {
1863
+ method: "POST",
1864
+ headers: { "content-type": "application/json", "x-api-key": descriptor },
1865
+ body: JSON.stringify({ query: AUTH_PROBE, variables: {} }),
1866
+ signal,
1867
+ });
1868
+ if (response.status !== 401 && response.status !== 403)
1869
+ throw new Error("revoked Unraid key did not receive an authentication rejection");
1870
+ return { rejected: true, id, status: response.status };
1871
+ }
1872
+ function dependency(value, label) {
1873
+ if (!value)
1874
+ throw new Error(`${label} is unavailable`);
1875
+ return value;
1876
+ }
1877
+ function exactKeys(value, keys, label) {
1878
+ if (JSON.stringify(Object.keys(value).sort()) !== JSON.stringify([...keys].sort()))
1879
+ throw new Error(`${label} shape is invalid`);
1880
+ }
1881
+ function sha256(value) {
1882
+ return (0, node_crypto_1.createHash)("sha256").update(value, "utf8").digest("hex");
1883
+ }
1884
+ function fixedFile(deps, filePath) {
1885
+ return dependency(deps.readFixedFile, "fixed file reader")(filePath);
1886
+ }
1887
+ async function runtimeConfig(reader, label, ...args) {
1888
+ const result = await dependency(reader, label)(...args);
1889
+ if (!result.ok)
1890
+ throw new Error(`${label} is unavailable`);
1891
+ return result.config;
1892
+ }
1893
+ async function readGatewayCursor(deps) {
1894
+ const cursor = await dependency(deps.gatewayCursor, "signed gateway cursor")();
1895
+ if (!cursor || !Number.isSafeInteger(cursor.nextUpdateId) || cursor.nextUpdateId < 0 || typeof cursor.progressDigest !== "string" || !/^sha256:[a-f0-9]{64}$/u.test(cursor.progressDigest))
1896
+ throw new Error("signed gateway cursor is invalid");
1897
+ return cursor;
1898
+ }
1899
+ async function cursorSnapshot(payload, deps) {
1900
+ exactKeys(payload, ["allowGenesis", "operation", "schema"], "Telegram cursor snapshot request");
1901
+ if (payload.schema !== "telegram-cursor-v1" || typeof payload.allowGenesis !== "boolean")
1902
+ throw new Error("Telegram cursor snapshot request is invalid");
1903
+ const cursor = await readGatewayCursor(deps);
1904
+ const identityKey = fixedFile(deps, TELEGRAM_IDENTITY_KEY).trim();
1905
+ if (!/^[A-Za-z0-9_-]{43}$/u.test(identityKey) || Buffer.from(identityKey, "base64url").length !== 32) {
1906
+ throw new Error("Telegram identity key is invalid");
1907
+ }
1908
+ let auditCursorDigest;
1909
+ let verificationFailure;
1910
+ let observedAuditState = false;
1911
+ for (let attempt = 0; attempt < 3; attempt += 1) {
1912
+ const headBefore = optionalFixedFile(deps, TELEGRAM_AUDIT_HEAD);
1913
+ const auditRaw = optionalFixedFile(deps, TELEGRAM_AUDIT);
1914
+ const headAfter = optionalFixedFile(deps, TELEGRAM_AUDIT_HEAD);
1915
+ if (headBefore === null && auditRaw === null && headAfter === null) {
1916
+ if (payload.allowGenesis !== true)
1917
+ throw new Error("Telegram audit genesis is not authorized for this snapshot");
1918
+ if (!observedAuditState) {
1919
+ auditCursorDigest = sha256("ouroboros.telegram.acceptance.cursor.v1\0genesis");
1920
+ break;
1921
+ }
1922
+ continue;
1923
+ }
1924
+ observedAuditState = true;
1925
+ if (headBefore !== null && auditRaw !== null && headAfter !== null && headBefore === headAfter) {
1926
+ try {
1927
+ (0, telegram_audit_ledger_1.verifyTelegramAuditLedger)({ ledgerRaw: auditRaw, headRaw: headAfter, identityKey });
1928
+ auditCursorDigest = sha256(`ouroboros.telegram.acceptance.cursor.v1\0present\0${auditRaw}\0${headAfter}`);
1929
+ break;
1930
+ }
1931
+ catch (error) {
1932
+ verificationFailure = error;
1933
+ }
1934
+ }
1935
+ }
1936
+ if (auditCursorDigest === undefined) {
1937
+ throw new Error(`Telegram acceptance audit state could not be captured consistently${verificationFailure ? `: ${verificationFailure.message}` : ""}`, verificationFailure ? { cause: verificationFailure } : undefined);
1938
+ }
1939
+ return {
1940
+ offsetDigest: cursor.progressDigest.slice("sha256:".length),
1941
+ auditCursorDigest,
1942
+ };
1943
+ }
1944
+ async function telegramPollerQuiescence(payload, deps) {
1945
+ if (payload.expectedState !== "stopped" || JSON.stringify(Object.keys(payload).sort()) !== JSON.stringify(["expectedState", "operation"])) {
1946
+ throw new Error("Telegram poller quiescence request is invalid");
1947
+ }
1948
+ const fact = object(await dependency(deps.hostRequest, "Sanctuary host broker")({ operation: "telegram_gateway_quiescence", targetId: TARGET_ID }), "Telegram root poller proof");
1949
+ exactKeys(fact, ["schemaVersion", "activePollers", "residentStopped", "processBindingDigest", "keyId", "publicKeyDigest", "botId", "observedAt"], "Telegram root poller proof");
1950
+ const gateway = dependency(deps.gateway, "Telegram root gateway")();
1951
+ try {
1952
+ if (!await gateway.authorityTransport.hostApproval?.refresh())
1953
+ throw new Error("Telegram root gateway health is unavailable");
1954
+ const cursor = await gateway.cursorSnapshot();
1955
+ const now = (deps.now ?? Date.now)();
1956
+ if (fact.schemaVersion !== 1 || fact.activePollers !== 1 || fact.residentStopped !== true
1957
+ || typeof fact.processBindingDigest !== "string" || !SHA256.test(fact.processBindingDigest)
1958
+ || fact.keyId !== cursor.keyId || fact.publicKeyDigest !== cursor.publicKeyDigest || fact.botId !== cursor.botId
1959
+ || typeof fact.observedAt !== "string" || !Number.isFinite(Date.parse(fact.observedAt))
1960
+ || Date.parse(fact.observedAt) > now || now - Date.parse(fact.observedAt) > 30_000)
1961
+ throw new Error("Telegram root poller proof is invalid or stale");
1962
+ return { quiesced: true, activePollers: 1 };
1963
+ }
1964
+ finally {
1965
+ gateway.authorityTransport.api.stop();
1966
+ }
1967
+ }
1968
+ function callbackUpdate(value) {
1969
+ const update = object(value, "callback update");
1970
+ object(update.callback_query, "callback update callback_query");
1971
+ return update;
1972
+ }
1973
+ function withCallbackPlaybackJournal(agentRoot, operation) {
1974
+ const databasePath = path.join(agentRoot, CALLBACK_PLAYBACK_JOURNAL);
1975
+ (0, node_fs_1.mkdirSync)(path.dirname(databasePath), { recursive: true, mode: 0o700 });
1976
+ const database = new better_sqlite3_1.default(databasePath);
1977
+ try {
1978
+ (0, node_fs_1.chmodSync)(databasePath, 0o600);
1979
+ database.pragma("journal_mode = DELETE");
1980
+ database.pragma("synchronous = FULL");
1981
+ database.exec(`
1982
+ CREATE TABLE IF NOT EXISTS callback_playback (
1983
+ coordinate_digest TEXT PRIMARY KEY NOT NULL CHECK(length(coordinate_digest) = 64),
1984
+ playback_count INTEGER NOT NULL CHECK(playback_count > 0)
1985
+ )
1986
+ `);
1987
+ return operation(database);
1988
+ }
1989
+ finally {
1990
+ database.close();
1991
+ }
1992
+ }
1993
+ function callbackPlaybackSnapshot(agentRoot, coordinateDigest) {
1994
+ if (!SHA256.test(coordinateDigest))
1995
+ throw new Error("callback coordinate digest is invalid");
1996
+ return withCallbackPlaybackJournal(agentRoot, (database) => {
1997
+ const rows = database.prepare("SELECT coordinate_digest, playback_count FROM callback_playback ORDER BY coordinate_digest")
1998
+ .all();
1999
+ if (rows.some((row) => !SHA256.test(row.coordinate_digest) || !Number.isSafeInteger(row.playback_count) || row.playback_count <= 0)) {
2000
+ throw new Error("callback playback journal is invalid");
2001
+ }
2002
+ const playbackCount = rows.find((row) => row.coordinate_digest === coordinateDigest)?.playback_count ?? 0;
2003
+ return { playbackCount, coordinateDigest, journalDigest: sha256(JSON.stringify(rows)) };
2004
+ });
2005
+ }
2006
+ function recordCallbackPlayback(agentRoot, coordinateDigest) {
2007
+ if (!SHA256.test(coordinateDigest))
2008
+ throw new Error("callback coordinate digest is invalid");
2009
+ withCallbackPlaybackJournal(agentRoot, (database) => {
2010
+ database.prepare(`
2011
+ INSERT INTO callback_playback (coordinate_digest, playback_count) VALUES (?, 1)
2012
+ ON CONFLICT(coordinate_digest) DO UPDATE SET playback_count = playback_count + 1
2013
+ `).run(coordinateDigest);
2014
+ });
2015
+ }
2016
+ function callbackCoordinate(update) {
2017
+ const updateId = update.update_id;
2018
+ if (!Number.isSafeInteger(updateId) || Number(updateId) < 0)
2019
+ throw new Error("callback update_id is invalid");
2020
+ const callback = object(update.callback_query, "callback update callback_query");
2021
+ const from = object(callback.from, "callback update sender");
2022
+ const message = object(callback.message, "callback update message");
2023
+ const chat = object(message.chat, "callback update chat");
2024
+ const callbackId = (value, label) => {
2025
+ if (!Number.isSafeInteger(value) || Number(value) <= 0)
2026
+ throw new Error(`${label} is invalid`);
2027
+ return String(value);
2028
+ };
2029
+ const coordinate = {
2030
+ updateId: Number(updateId),
2031
+ queryId: text(callback.id, "callback query id"),
2032
+ callbackData: text(callback.data, "callback data"),
2033
+ userId: callbackId(from.id, "callback user id"),
2034
+ chatId: callbackId(chat.id, "callback chat id"),
2035
+ messageId: callbackId(message.message_id, "callback message id"),
2036
+ };
2037
+ return {
2038
+ updateId: coordinate.updateId,
2039
+ digest: sha256(`ouroboros.sanctuary.callback-coordinate.v1\0${JSON.stringify(coordinate)}`),
2040
+ };
2041
+ }
2042
+ async function callbackPlaybackPreflight(payload, deps) {
2043
+ exactKeys(payload, ["operation", "update"], "callback playback preflight request");
2044
+ const coordinate = callbackCoordinate(callbackUpdate(payload.update));
2045
+ const offset = await readGatewayCursor(deps);
2046
+ const snapshot = object(dependency(deps.callbackPlaybackSnapshot, "callback playback journal")(coordinate.digest), "callback playback journal snapshot");
2047
+ exactKeys(snapshot, ["coordinateDigest", "journalDigest", "playbackCount"], "callback playback journal snapshot");
2048
+ if (snapshot.coordinateDigest !== coordinate.digest || typeof snapshot.journalDigest !== "string" || !SHA256.test(snapshot.journalDigest)
2049
+ || !Number.isSafeInteger(snapshot.playbackCount) || Number(snapshot.playbackCount) < 0) {
2050
+ throw new Error("callback playback journal snapshot is invalid");
2051
+ }
2052
+ return {
2053
+ playbackCount: Math.max(Number(snapshot.playbackCount), Number(offset.nextUpdateId) > coordinate.updateId ? 1 : 0),
2054
+ coordinateDigest: coordinate.digest,
2055
+ journalDigest: snapshot.journalDigest,
2056
+ };
2057
+ }
2058
+ async function concurrentCallbackProbe(payload, deps) {
2059
+ const update = callbackUpdate(payload.update);
2060
+ if (!Number.isSafeInteger(payload.concurrency) || payload.concurrency < 2 || payload.concurrency > 16) {
2061
+ throw new Error("callback concurrency is invalid");
2062
+ }
2063
+ const probe = dependency(deps.callbackProbe, "callback probe");
2064
+ const results = await Promise.all(Array.from({ length: payload.concurrency }, () => probe(update, false)));
2065
+ return { results };
2066
+ }
2067
+ async function callbackReplay(payload, deps) {
2068
+ return dependency(deps.callbackProbe, "callback probe")(callbackUpdate(payload.update), true);
2069
+ }
2070
+ async function interactiveRuntimeOperation(payload, deps) {
2071
+ exactKeys(payload, ["operation", "label", "scenarioHandleDigest"], "interactive runtime payload");
2072
+ const operation = text(payload.operation, "interactive runtime operation");
2073
+ const label = text(payload.label, "interactive runtime label");
2074
+ const scenarioHandleDigest = text(payload.scenarioHandleDigest, "interactive runtime scenario digest");
2075
+ if (!SHA256.test(scenarioHandleDigest))
2076
+ throw new Error("interactive runtime scenario digest is invalid");
2077
+ const expectedLabel = operation === "drive_timeout_stale" ? "unit-16k-timeout-stale"
2078
+ : operation === "drive_duplicate_callbacks" ? "unit-16l-duplicate-callback"
2079
+ : "unit-16m-restart-continuation";
2080
+ if (label !== expectedLabel)
2081
+ throw new Error("interactive runtime label is invalid");
2082
+ return dependency(deps.interactiveRuntime, "interactive production runtime")({ operation, label, scenarioHandleDigest });
2083
+ }
2084
+ async function interactiveRuntimeReady(payload) {
2085
+ exactKeys(payload, ["operation", "label", "scenarioHandleDigest"], "interactive readiness payload");
2086
+ if (payload.label !== "unit-16m-restart-continuation" || typeof payload.scenarioHandleDigest !== "string" || !SHA256.test(payload.scenarioHandleDigest)) {
2087
+ throw new Error("interactive readiness coordinates are invalid");
2088
+ }
2089
+ const agentRoot = (0, identity_1.getAgentRoot)(TARGET_ID);
2090
+ const socketPath = path.join(agentRoot, "state", "acceptance", "telegram-control.sock");
2091
+ try {
2092
+ const response = object(await defaultHostRequest(payload, socketPath, 2_000), "interactive readiness response");
2093
+ exactKeys(response, ["ready"], "interactive readiness response");
2094
+ return { ready: response.ready === true };
2095
+ }
2096
+ catch {
2097
+ return { ready: false };
2098
+ }
2099
+ }
2100
+ function requireTargetServer(payload) {
2101
+ if (text(payload.targetServerId, "targetServerId") !== TARGET_SERVER_ID)
2102
+ throw new Error("targetServerId is invalid");
2103
+ }
2104
+ function flattenedPermissions(record) {
2105
+ return record.permissions.flatMap((permission) => permission.actions.map((action) => `${permission.resource}:${action}`)).sort();
2106
+ }
2107
+ async function keyInventory(payload, deps) {
2108
+ requireTargetServer(payload);
2109
+ const response = object(await dependency(deps.hostRequest, "Sanctuary host broker")({
2110
+ operation: "inventory_keys", targetServerId: TARGET_SERVER_ID,
2111
+ }), "host key inventory");
2112
+ if (!Array.isArray(response.keys))
2113
+ throw new Error("host key inventory is invalid");
2114
+ const keys = response.keys.map(normalizeKey);
2115
+ if (new Set(keys.map(({ id }) => id)).size !== keys.length || new Set(keys.map(({ name }) => name)).size !== keys.length) {
2116
+ throw new Error("host key inventory is ambiguous");
2117
+ }
2118
+ return { keys: keys.map((record) => ({
2119
+ id: record.id, name: record.name, permissions: flattenedPermissions(record), roles: [...record.roles].sort(),
2120
+ })).sort((left, right) => left.id.localeCompare(right.id)) };
2121
+ }
2122
+ function permissionStrings(value) {
2123
+ if (!Array.isArray(value) || value.length === 0)
2124
+ throw new Error("permissions are invalid");
2125
+ const result = value.map((permission) => text(permission, "permission"));
2126
+ if (new Set(result).size !== result.length || result.some((permission) => !/^[A-Z_]+:(?:CREATE|READ|UPDATE|DELETE)_(?:ANY|OWN)$/u.test(permission))) {
2127
+ throw new Error("permissions are invalid");
2128
+ }
2129
+ return [...result].sort();
2130
+ }
2131
+ async function createKey(payload, deps) {
2132
+ requireTargetServer(payload);
2133
+ const name = text(payload.name, "Unraid key name");
2134
+ if (!/^Butler (?:RO|RW)(?: Rotation [0-9a-f]{16})?$/u.test(name))
2135
+ throw new Error("Unraid key name is invalid");
2136
+ const permissions = permissionStrings(payload.permissions);
2137
+ const field = name.startsWith("Butler RO") ? "unraidReadApiKey" : "unraidWriteApiKey";
2138
+ const expected = field === "unraidReadApiKey" ? RO_PERMISSIONS : [...RO_PERMISSIONS, "DOCKER:UPDATE_ANY"].sort();
2139
+ if (JSON.stringify(permissions) !== JSON.stringify(expected))
2140
+ throw new Error("Unraid key scope is invalid");
2141
+ const raw = object(await dependency(deps.hostRequest, "Sanctuary host broker")({
2142
+ operation: "create_key", targetServerId: TARGET_SERVER_ID, name, permissions,
2143
+ }), "created Unraid key");
2144
+ const created = normalizeKey(raw);
2145
+ const id = created.id;
2146
+ const key = text(raw.key, "created Unraid key credential");
2147
+ if (created.name !== name || JSON.stringify(flattenedPermissions(created)) !== JSON.stringify(permissions)
2148
+ || created.roles.length !== 0)
2149
+ throw new Error("created Unraid key scope mismatch");
2150
+ const stored = await dependency(deps.mergeMachine, "machine vault writer")(TARGET_ID, TARGET_ID, {
2151
+ [field]: key,
2152
+ sanctuaryAcceptanceKeyHandles: { [id]: key },
2153
+ });
2154
+ if (!stored.ok || stored.config[field] !== key)
2155
+ throw new Error("created Unraid key vault readback failed");
2156
+ return { id, name, key: `unraid-key:${id}:${field}`, permissions, roles: [] };
2157
+ }
2158
+ async function storeKey(payload, deps) {
2159
+ requireTargetServer(payload);
2160
+ const id = keyId(payload.keyId);
2161
+ const field = text(payload.vaultField, "vaultField");
2162
+ if (field !== "unraidReadApiKey" && field !== "unraidWriteApiKey")
2163
+ throw new Error("vaultField is invalid");
2164
+ const handle = text(payload.key, "Unraid key handle");
2165
+ if (handle !== `unraid-key:${id}:${field}`)
2166
+ throw new Error("Unraid key handle is invalid");
2167
+ const stored = await runtimeConfig(deps.refreshMachine, "Sanctuary machine runtime config", TARGET_ID, TARGET_ID);
2168
+ const handles = object(stored.sanctuaryAcceptanceKeyHandles, "vault-backed acceptance key handles");
2169
+ if (text(handles[id], "vault-backed Unraid credential") !== text(stored[field], "vault-backed Unraid credential")) {
2170
+ throw new Error("Unraid key handle does not bind to the active vault field");
2171
+ }
2172
+ const acknowledged = object(await dependency(deps.hostRequest, "Sanctuary host broker")({
2173
+ operation: "acknowledge_key_storage", targetServerId: TARGET_SERVER_ID, keyId: id,
2174
+ }), "Unraid key storage acknowledgement");
2175
+ if (acknowledged.acknowledged !== true || acknowledged.id !== id) {
2176
+ throw new Error("Unraid key storage acknowledgement failed");
2177
+ }
2178
+ return { stored: true, keyId: id };
2179
+ }
2180
+ async function probeKey(payload, deps) {
2181
+ requireTargetServer(payload);
2182
+ const id = keyId(payload.id);
2183
+ const handle = text(payload.key, "Unraid key handle");
2184
+ const config = await runtimeConfig(deps.refreshMachine, "Sanctuary machine runtime config", TARGET_ID, TARGET_ID);
2185
+ const handleMatch = /^unraid-key:([A-Za-z0-9._:-]+):(unraidReadApiKey|unraidWriteApiKey)$/u.exec(handle);
2186
+ if (!handleMatch || handleMatch[1] !== id)
2187
+ throw new Error("Unraid key handle is invalid");
2188
+ const handles = object(config.sanctuaryAcceptanceKeyHandles, "vault-backed acceptance key handles");
2189
+ const key = text(handles[id], "vault-backed Unraid credential");
2190
+ const endpoint = exactLoopbackGraphqlEndpoint(config.unraidGraphqlUrl);
2191
+ let response;
2192
+ try {
2193
+ response = await deps.fetch(endpoint.href, {
2194
+ method: "POST", headers: { "content-type": "application/json", "x-api-key": key },
2195
+ body: JSON.stringify({ query: AUTH_PROBE, variables: {} }), signal: AbortSignal.timeout(NETWORK_TIMEOUT_MS),
2196
+ });
2197
+ }
2198
+ catch {
2199
+ throw new Error("Unraid key readiness probe failed");
2200
+ }
2201
+ const envelope = object(await response.json(), "Unraid key readiness response");
2202
+ if (!response.ok || !envelope.data || envelope.errors)
2203
+ throw new Error("Unraid key readiness probe failed");
2204
+ return { valid: true };
2205
+ }
2206
+ async function readOldKey(payload, deps) {
2207
+ requireTargetServer(payload);
2208
+ const id = keyId(payload.id);
2209
+ const raw = object(await dependency(deps.hostRequest, "Sanctuary host broker")({
2210
+ operation: "read_key_record", targetServerId: TARGET_SERVER_ID, keyId: id,
2211
+ }), "old Unraid key record");
2212
+ const record = { ...normalizeKey(raw), key: text(raw.key, "old Unraid key credential") };
2213
+ if (record.id !== id)
2214
+ throw new Error("old Unraid key ID does not match the host record");
2215
+ const stored = await dependency(deps.mergeMachine, "machine vault writer")(TARGET_ID, TARGET_ID, { sanctuaryAcceptanceKeyHandles: { [id]: record.key } });
2216
+ if (!stored.ok)
2217
+ throw new Error("old Unraid key recovery storage failed");
2218
+ return { key: `unraid-key:${id}:legacy` };
2219
+ }
2220
+ async function revokeKey(payload, deps) {
2221
+ requireTargetServer(payload);
2222
+ const id = keyId(payload.id);
2223
+ const response = object(await dependency(deps.hostRequest, "Sanctuary host broker")({
2224
+ operation: "revoke_key", targetServerId: TARGET_SERVER_ID, keyId: id,
2225
+ }), "host key revoke");
2226
+ if (response.revoked !== true || response.id !== id)
2227
+ throw new Error("host key revoke did not attest the exact ID");
2228
+ return { revoked: true, id };
2229
+ }
2230
+ async function probeRevokedKey(payload, deps) {
2231
+ requireTargetServer(payload);
2232
+ const id = keyId(payload.id);
2233
+ const handle = text(payload.key, "revoked Unraid key handle");
2234
+ if (handle !== `unraid-key:${id}:legacy` && !handle.startsWith(`unraid-key:${id}:unraid`))
2235
+ throw new Error("revoked Unraid key handle is invalid");
2236
+ const response = object(await dependency(deps.hostRequest, "Sanctuary host broker")({
2237
+ operation: "probe_revoked_key", targetServerId: TARGET_SERVER_ID, keyId: id,
2238
+ }), "revoked key host proof");
2239
+ if (response.valid !== false || response.id !== id || (response.status !== 401 && response.status !== 403)) {
2240
+ throw new Error("revoked Unraid key still authenticates");
2241
+ }
2242
+ const cleared = await dependency(deps.mergeMachine, "machine vault writer")(TARGET_ID, TARGET_ID, { sanctuaryAcceptanceKeyHandles: { [id]: "" } });
2243
+ if (!cleared.ok)
2244
+ throw new Error("revoked Unraid key recovery cleanup failed");
2245
+ return { valid: false, status: response.status, id };
2246
+ }
2247
+ async function provenance(payload, deps) {
2248
+ if (payload.schema !== "sanctuary-unit-16-provenance-v1")
2249
+ throw new Error("provenance schema is invalid");
2250
+ const imageDigest = fixedFile(deps, IMAGE_DIGEST_FILE).trim();
2251
+ const containerDigest = fixedFile(deps, CONTAINER_DIGEST_FILE).trim();
2252
+ if (!SHA256.test(imageDigest) || !SHA256.test(containerDigest))
2253
+ throw new Error("live provenance digest is invalid");
2254
+ const cursor = await cursorSnapshot({ operation: "snapshot", schema: "telegram-cursor-v1", allowGenesis: false }, deps);
2255
+ return { imageDigest, containerDigest, cursorDigest: sha256(`${cursor.offsetDigest}\0${cursor.auditCursorDigest}`) };
2256
+ }
2257
+ async function evidenceSnapshot(payload, deps) {
2258
+ if (payload.schema !== "postboot-health-v1")
2259
+ throw new Error("evidence schema is invalid");
2260
+ const health = object(JSON.parse(fixedFile(deps, POSTBOOT_HEALTH_FILE)), "postboot health");
2261
+ exactKeys(health, ["healthy"], "postboot health");
2262
+ if (typeof health.healthy !== "boolean")
2263
+ throw new Error("postboot health is invalid");
2264
+ const imageDigest = fixedFile(deps, IMAGE_DIGEST_FILE).trim();
2265
+ if (!SHA256.test(imageDigest))
2266
+ throw new Error("container image digest is invalid");
2267
+ return {
2268
+ healthy: health.healthy,
2269
+ containerImageDigest: imageDigest,
2270
+ telegramOffsetDigest: (await cursorSnapshot({ operation: "snapshot", schema: "telegram-cursor-v1", allowGenesis: false }, deps)).offsetDigest,
2271
+ };
2272
+ }
2273
+ function bootId(deps) {
2274
+ const value = fixedFile(deps, BOOT_ID_FILE).trim();
2275
+ if (!/^[A-Za-z0-9-]{4,128}$/u.test(value))
2276
+ throw new Error("boot identity is invalid");
2277
+ return value;
2278
+ }
2279
+ async function requestReboot(payload, deps) {
2280
+ if (text(payload.targetId, "targetId") !== TARGET_ID)
2281
+ throw new Error("targetId is invalid");
2282
+ const idempotencyKey = text(payload.idempotencyKey, "idempotencyKey");
2283
+ const preflightDigest = text(payload.preflightDigest, "preflightDigest");
2284
+ const processBindingDigest = text(payload.processBindingDigest, "processBindingDigest");
2285
+ if (!/^[0-9a-f]{32}$/u.test(idempotencyKey))
2286
+ throw new Error("idempotencyKey is invalid");
2287
+ if (!SHA256.test(preflightDigest) || !SHA256.test(processBindingDigest))
2288
+ throw new Error("reboot binding digest is invalid");
2289
+ const response = object(await dependency(deps.hostRequest, "Sanctuary host broker")({
2290
+ operation: "request_reboot", targetId: TARGET_ID, idempotencyKey, preflightDigest, processBindingDigest,
2291
+ }), "host reboot staging");
2292
+ const requestId = text(response.requestId, "host reboot requestId");
2293
+ const reservationId = text(response.reservationId, "host reboot reservationId");
2294
+ const prebootId = text(response.prebootId, "host reboot prebootId");
2295
+ if (response.accepted !== true || response.staged !== true || response.targetId !== TARGET_ID || response.preflightDigest !== preflightDigest || response.processBindingDigest !== processBindingDigest
2296
+ || requestId !== sha256(`sanctuary-reboot\0${idempotencyKey}`)
2297
+ || reservationId !== sha256(`sanctuary-reboot-reservation\0${requestId}`))
2298
+ throw new Error("host reboot staging attestation is invalid");
2299
+ return { accepted: true, targetId: TARGET_ID, requestId, reservationId, prebootId, preflightDigest, processBindingDigest };
2300
+ }
2301
+ async function rebootPreflight(payload, deps) {
2302
+ if (text(payload.targetId, "targetId") !== TARGET_ID)
2303
+ throw new Error("targetId is invalid");
2304
+ const processBindingDigest = fixedFile(deps, PROCESS_BINDING_DIGEST_FILE).trim();
2305
+ if (!SHA256.test(processBindingDigest))
2306
+ throw new Error("process binding digest is invalid");
2307
+ const result = object(await dependency(deps.hostRequest, "Sanctuary host broker")({ operation: "reboot_preflight_snapshot", targetId: TARGET_ID, processBindingDigest }), "host reboot preflight");
2308
+ const digest = text(result.digest, "host reboot preflight digest");
2309
+ if (!SHA256.test(digest) || result.processBindingDigest !== processBindingDigest || result.safe !== true || result.arrayReady !== true || result.parityActive !== false || result.moverActive !== false || result.mutationActive !== false) {
2310
+ throw new Error("host reboot preflight attestation is invalid");
2311
+ }
2312
+ return result;
2313
+ }
2314
+ function pollReboot(payload, deps) {
2315
+ if (text(payload.targetId, "targetId") !== TARGET_ID)
2316
+ throw new Error("targetId is invalid");
2317
+ const requestId = text(payload.requestId, "requestId");
2318
+ if (!SHA256.test(requestId))
2319
+ throw new Error("requestId is invalid");
2320
+ return { targetId: TARGET_ID, requestId, state: "ready", bootId: bootId(deps) };
2321
+ }
2322
+ async function materializeConfig(payload, deps) {
2323
+ const command = text(payload.command, "materializer command");
2324
+ const contract = object(JSON.parse(fixedFile(deps, CONTRACT_FILE)), "acceptance contract");
2325
+ const templates = object(contract.configTemplates, "acceptance config templates");
2326
+ const template = object(templates[command], "acceptance config template");
2327
+ const config = { ...object(template.fixed, "acceptance fixed config") };
2328
+ const adapterPath = "/opt/ouro/deploy/unraid/sanctuary-acceptance-adapter.sh";
2329
+ if (command === "telegram-bootstrap") {
2330
+ const offset = await readGatewayCursor(deps);
2331
+ Object.assign(config, { expectedBotId: "8541786263", expectedUsername: "MendelowCloudButlerBot", currentOffset: offset.nextUpdateId });
2332
+ }
2333
+ else if (command === "cursor-snapshot") {
2334
+ const phase = text(payload.phase, "cursor snapshot phase");
2335
+ if (phase !== "before" && phase !== "after")
2336
+ throw new Error("cursor snapshot phase is invalid");
2337
+ config.evidencePath = `/evidence/cursor-${phase}.json`;
2338
+ config.adapters = config.adapters.map((adapter) => ({ ...adapter, allowGenesis: phase === "before" }));
2339
+ }
2340
+ else if (command === "unraid-key-rotate") {
2341
+ const closed = object(JSON.parse(fixedFile(deps, CLOSED_INVENTORY_FILE)), "closed Unraid inventory");
2342
+ if (!Array.isArray(closed.keys) || closed.keys.length === 0)
2343
+ throw new Error("closed Unraid inventory is empty");
2344
+ const ids = closed.keys.map((raw) => keyId(object(raw, "closed Unraid key").id));
2345
+ if (new Set(ids).size !== ids.length)
2346
+ throw new Error("closed Unraid inventory IDs are ambiguous");
2347
+ config.oldKeys = ids.sort().map((id) => ({ id, secretAdapter: adapterPath }));
2348
+ }
2349
+ else if (command === "evidence-bundle-index") {
2350
+ config.entries = sanctuary_acceptance_harness_1.SANCTUARY_UNIT_16_EVIDENCE_LABELS.map((label) => ({ label, path: `/evidence/${label}.json` }));
2351
+ }
2352
+ return config;
2353
+ }
2354
+ async function telegramReadiness(payload, deps) {
2355
+ exactKeys(payload, ["operation"], "Telegram readiness payload");
2356
+ let gateway;
2357
+ try {
2358
+ gateway = dependency(deps.gateway, "Telegram root gateway")();
2359
+ }
2360
+ catch {
2361
+ throw new Error("Telegram gateway inventory or pins are unavailable; actor: agent-runnable; inspect root migration");
2362
+ }
2363
+ const api = gateway.authorityTransport.api;
2364
+ let bot;
2365
+ try {
2366
+ if (!await gateway.authorityTransport.hostApproval?.refresh())
2367
+ throw new Error("root authority is unhealthy");
2368
+ await gateway.cursorSnapshot();
2369
+ bot = await api.request("getMe", {}, AbortSignal.timeout(30_000));
2370
+ }
2371
+ catch {
2372
+ throw new Error("Telegram gateway health or identity probe failed; actor: agent-runnable; inspect root authority");
2373
+ }
2374
+ finally {
2375
+ try {
2376
+ api.stop();
2377
+ }
2378
+ catch {
2379
+ throw new Error("Telegram client cleanup failed; actor: agent-runnable; retry Telegram readiness");
2380
+ }
2381
+ }
2382
+ if (!bot || typeof bot !== "object" || Array.isArray(bot)
2383
+ || String(bot.id) !== gateway.credentials.botId || gateway.credentials.botId !== "8541786263"
2384
+ || bot.username !== "MendelowCloudButlerBot") {
2385
+ throw new Error("Telegram bot identity mismatch; actor: human-required; repair root gateway identity");
2386
+ }
2387
+ return { ready: true, identityMatches: true };
2388
+ }
2389
+ async function executeSanctuaryAcceptanceAdapter(rawPayload, deps = createSanctuaryAcceptanceAdapterDependencies()) {
2390
+ const payload = object(rawPayload, "acceptance adapter payload");
2391
+ const operation = text(payload.operation, "operation");
2392
+ (0, runtime_1.emitNervesEvent)({ component: "daemon", event: "daemon.sanctuary_acceptance_adapter_start", message: "Sanctuary acceptance adapter started", meta: { operation } });
2393
+ try {
2394
+ let result;
2395
+ switch (operation) {
2396
+ case "vault-backed-capability-verify":
2397
+ result = await vaultBackedCapabilityVerify(payload, deps);
2398
+ break;
2399
+ case "closed-inventory":
2400
+ result = closedInventory(deps);
2401
+ break;
2402
+ case "exact-id-revoke":
2403
+ result = await exactIdRevoke(payload, deps);
2404
+ break;
2405
+ case "revoked-key-auth-rejection":
2406
+ result = await revokedKeyAuthRejection(payload, deps);
2407
+ break;
2408
+ case "quiesce_telegram_poller":
2409
+ result = await telegramPollerQuiescence(payload, deps);
2410
+ break;
2411
+ case "snapshot":
2412
+ result = await cursorSnapshot(payload, deps);
2413
+ break;
2414
+ case "callback_playback_preflight":
2415
+ result = await callbackPlaybackPreflight(payload, deps);
2416
+ break;
2417
+ case "inject_callbacks_concurrently":
2418
+ result = await concurrentCallbackProbe(payload, deps);
2419
+ break;
2420
+ case "inject_callback_replay":
2421
+ result = await callbackReplay(payload, deps);
2422
+ break;
2423
+ case "drive_timeout_stale":
2424
+ result = await interactiveRuntimeOperation(payload, deps);
2425
+ break;
2426
+ case "drive_duplicate_callbacks":
2427
+ result = await interactiveRuntimeOperation(payload, deps);
2428
+ break;
2429
+ case "prepare_restart_continuation":
2430
+ result = await interactiveRuntimeOperation(payload, deps);
2431
+ break;
2432
+ case "reconcile_restart_continuation":
2433
+ result = await interactiveRuntimeOperation(payload, deps);
2434
+ break;
2435
+ case "interactive_runtime_ready":
2436
+ result = await interactiveRuntimeReady(payload);
2437
+ break;
2438
+ case "inventory_keys":
2439
+ result = await keyInventory(payload, deps);
2440
+ break;
2441
+ case "create_key":
2442
+ result = await createKey(payload, deps);
2443
+ break;
2444
+ case "store_key":
2445
+ result = await storeKey(payload, deps);
2446
+ break;
2447
+ case "probe_new_key":
2448
+ result = await probeKey(payload, deps);
2449
+ break;
2450
+ case "read_old_key":
2451
+ result = await readOldKey(payload, deps);
2452
+ break;
2453
+ case "revoke_key":
2454
+ result = await revokeKey(payload, deps);
2455
+ break;
2456
+ case "probe_revoked_key":
2457
+ result = await probeRevokedKey(payload, deps);
2458
+ break;
2459
+ case "evidence_snapshot":
2460
+ result = await evidenceSnapshot(payload, deps);
2461
+ break;
2462
+ case "capture_evidence_provenance":
2463
+ result = await provenance(payload, deps);
2464
+ break;
2465
+ case "capture_acceptance_scenario":
2466
+ result = await captureAcceptanceScenario(payload, deps);
2467
+ break;
2468
+ case "finalize_acceptance_scenarios":
2469
+ exactKeys(payload, ["operation"], "scenario finalization payload");
2470
+ if (!deps.finalizeScenarios)
2471
+ throw new Error("scenario finalization is unavailable");
2472
+ await deps.finalizeScenarios();
2473
+ result = { finalized: true };
2474
+ break;
2475
+ case "reboot_preflight_snapshot":
2476
+ result = await rebootPreflight(payload, deps);
2477
+ break;
2478
+ case "postboot_integrity_snapshot":
2479
+ result = await postbootIntegritySnapshot(deps);
2480
+ break;
2481
+ case "request_reboot":
2482
+ result = await requestReboot(payload, deps);
2483
+ break;
2484
+ case "poll_reboot":
2485
+ result = pollReboot(payload, deps);
2486
+ break;
2487
+ case "materialize_config":
2488
+ result = await materializeConfig(payload, deps);
2489
+ break;
2490
+ case "telegram_readiness":
2491
+ result = await telegramReadiness(payload, deps);
2492
+ break;
2493
+ default: throw new Error("unknown Sanctuary acceptance adapter operation");
2494
+ }
2495
+ (0, runtime_1.emitNervesEvent)({ component: "daemon", event: "daemon.sanctuary_acceptance_adapter_end", message: "Sanctuary acceptance adapter completed", meta: { operation } });
2496
+ return result;
2497
+ }
2498
+ catch (error) {
2499
+ (0, runtime_1.emitNervesEvent)({ level: "error", component: "daemon", event: "daemon.sanctuary_acceptance_adapter_error", message: "Sanctuary acceptance adapter failed", meta: { operation, category: error instanceof Error ? error.name : "unknown" } });
2500
+ throw error;
2501
+ }
2502
+ }
2503
+ async function executeSanctuaryAcceptanceVaultProbe(keyIdValue, capabilityValue, deps = createSanctuaryAcceptanceVaultProbeDependencies({ keyRecordPath: SELECTED_KEY_RECORD })) {
2504
+ const id = keyId(keyIdValue);
2505
+ const capability = text(capabilityValue, "capability");
2506
+ if (capability !== "read-only" && capability !== "bounded-write")
2507
+ throw new Error("capability is invalid");
2508
+ const refreshed = await deps.refresh("sanctuary", "sanctuary");
2509
+ if (!refreshed.ok)
2510
+ throw new Error("Sanctuary machine runtime credentials are unavailable");
2511
+ const endpoint = exactLoopbackGraphqlEndpoint(refreshed.config.unraidGraphqlUrl);
2512
+ const field = capability === "read-only" ? "unraidReadApiKey" : "unraidWriteApiKey";
2513
+ const descriptor = text(refreshed.config[field], "vault-backed Unraid descriptor");
2514
+ const matches = deps.readKeyRecords().filter((record) => sameSecret(record.key, descriptor));
2515
+ if (matches.length !== 1 || matches[0].id !== id)
2516
+ throw new Error("vault descriptor does not bind to the exact key ID");
2517
+ const matched = matches[0];
2518
+ const expectedName = capability === "read-only" ? "Butler RO" : "Butler RW";
2519
+ if (matched.name !== expectedName || matched.roles.length !== 0 || scope(matched) !== capability) {
2520
+ throw new Error("vault-backed Unraid key metadata scope is invalid");
2521
+ }
2522
+ const headers = { "content-type": "application/json", "x-api-key": descriptor };
2523
+ const readResponse = await deps.fetch(endpoint.href, {
2524
+ method: "POST",
2525
+ headers,
2526
+ body: JSON.stringify({ query: AUTH_PROBE, variables: {} }),
2527
+ signal: AbortSignal.timeout(NETWORK_TIMEOUT_MS),
2528
+ });
2529
+ if (!readResponse.ok)
2530
+ throw new Error("vault-backed Unraid capability probe failed");
2531
+ const readEnvelope = object(await readResponse.json(), "vault-backed Unraid response");
2532
+ if (!readEnvelope.data || readEnvelope.errors)
2533
+ throw new Error("vault-backed Unraid capability probe was rejected");
2534
+ const writeResponse = await deps.fetch(endpoint.href, {
2535
+ method: "POST",
2536
+ headers,
2537
+ body: JSON.stringify({ query: WRITE_PROBE, variables: { id: MISSING_CONTAINER_ID } }),
2538
+ signal: AbortSignal.timeout(NETWORK_TIMEOUT_MS),
2539
+ });
2540
+ const writeEnvelope = object(await writeResponse.json(), "vault-backed Unraid write response");
2541
+ const errors = Array.isArray(writeEnvelope.errors) ? writeEnvelope.errors.map((value) => object(value, "GraphQL error")) : [];
2542
+ const codes = errors.map((error) => {
2543
+ const extensions = error.extensions && typeof error.extensions === "object" && !Array.isArray(error.extensions)
2544
+ ? error.extensions
2545
+ : {};
2546
+ return typeof extensions.code === "string" ? extensions.code : "";
2547
+ });
2548
+ if (capability === "read-only") {
2549
+ if (writeEnvelope.data || (writeResponse.status !== 403 && !codes.includes("FORBIDDEN") && !codes.includes("PERMISSION_DENIED"))) {
2550
+ throw new Error("read-only Unraid key did not prove write permission denial");
2551
+ }
2552
+ return { valid: true, keyId: id, capability, proof: "read-authorized-write-denied" };
2553
+ }
2554
+ if (writeEnvelope.data || !writeResponse.ok || !codes.includes("NOT_FOUND")) {
2555
+ throw new Error("bounded-write Unraid key did not reach deterministic not-found");
2556
+ }
2557
+ return { valid: true, keyId: id, capability, proof: "read-authorized-write-reached-not-found" };
2558
+ }
2559
+ async function executeSanctuaryAcceptanceRevokedProbe(keyIdValue, endpointValue, rawKeyFile, deps = { fetch }) {
2560
+ const id = keyId(keyIdValue);
2561
+ const raw = object(JSON.parse(rawKeyFile), "revoked Unraid key file");
2562
+ if (keyId(raw.id) !== id)
2563
+ throw new Error("revoked Unraid key file ID mismatch");
2564
+ const descriptor = text(raw.key, "revoked Unraid key descriptor");
2565
+ return revokedKeyAuthRejection({ keyId: id, endpoint: endpointValue }, {
2566
+ readDescriptor: () => JSON.stringify({ keyId: id, descriptor }),
2567
+ fetch: deps.fetch,
2568
+ });
2569
+ }
2570
+ function createAcceptanceProbeEffects(input) {
2571
+ const store = new telegram_effect_adapter_1.FileTelegramEffectJournal(path.join(input.agentRoot, "state", "telegram", "effects"));
2572
+ const target = { kind: "approved_relationship", friendId: `telegram-user:${input.subject}`, sessionKey: `telegram:${input.subject}` };
2573
+ const execute = (0, telegram_effect_adapter_1.createTelegramAuthorizedEffectExecutor)({
2574
+ store,
2575
+ api: input.api,
2576
+ authorize: ({ target: candidate }) => JSON.stringify(candidate) === JSON.stringify(target)
2577
+ ? { allowed: true, receiptId: `configured-owner:${input.subject}`, expiresAt: new Date(Date.now() + 5 * 60_000).toISOString(), transport: { chatId: input.chatId } }
2578
+ : { allowed: false, reason: "effect target is not the configured owner relationship" },
2579
+ });
2580
+ const sessionPath = (0, shared_turn_1.getSenseSessionPath)(TARGET_ID, target.friendId, "telegram", target.sessionKey, input.agentRoot);
2581
+ const port = (0, telegram_effect_adapter_1.createTelegramApprovalEffectPort)({
2582
+ target,
2583
+ chatId: input.chatId,
2584
+ execute,
2585
+ record: async (artifact) => { await (0, telegram_effect_adapter_1.recordTelegramEffectsInSession)({ store, sessionPath, artifacts: [artifact] }); },
2586
+ });
2587
+ return { port, close: () => store.close() };
2588
+ }
2589
+ exports.proveAttemptedRecoveryWithoutRetry = sanctuary_interactive_control_1.proveSanctuaryAttemptedRecoveryWithoutRetry;
2590
+ async function executeSanctuaryInteractiveRuntimeOperation(rawPayload, supplied) {
2591
+ exactKeys(rawPayload, ["operation", "label", "scenarioHandleDigest"], "interactive runtime payload");
2592
+ const agentRoot = supplied?.agentRoot ?? (0, identity_1.getAgentRoot)(TARGET_ID);
2593
+ if (!supplied)
2594
+ return defaultHostRequest(rawPayload, path.join(agentRoot, "state", "acceptance", "telegram-control.sock"), ADAPTER_TIMEOUT_MS);
2595
+ const deps = {
2596
+ agentRoot,
2597
+ readApprovals: supplied.readApprovals ?? ((digest) => (0, approval_store_1.readApprovalsByScenarioHandleDigest)(path.join(agentRoot, "state", "approvals", "approvals.sqlite"), digest)),
2598
+ readPending: dependency(supplied.readPending, "daemon-owned pending approval reader"),
2599
+ createSession: supplied.createSession ?? (async () => { throw new Error("daemon-owned interactive callback session is unavailable"); }),
2600
+ proveIndeterminateRecovery: supplied.proveIndeterminateRecovery
2601
+ ?? ((approval, scenarioHandleDigest) => (0, sanctuary_interactive_control_1.proveSanctuaryAttemptedRecoveryWithoutRetry)(agentRoot, scenarioHandleDigest, approval)),
2602
+ writeCredentialObserved: supplied.writeCredentialObserved ?? (() => /credential|api[_-]?key|token|secret/iu.test(JSON.stringify(rawPayload))),
2603
+ };
2604
+ return (0, sanctuary_interactive_control_1.executeSanctuaryInteractiveEngine)(rawPayload, deps);
2605
+ }
2606
+ async function executeSanctuaryAcceptanceCallbackProbe(rawUpdate, replay, deps = {
2607
+ gateway: () => (0, sanctuary_authority_resident_1.openSanctuaryResidentAuthority)({}, {}),
2608
+ identityKey: telegram_1.readOrCreateTelegramIdentityKey,
2609
+ createRuntime: telegram_approval_runtime_1.createTelegramApprovalRuntime,
2610
+ toolContext: sanctuary_runtime_1.createSanctuaryToolContext,
2611
+ effects: createAcceptanceProbeEffects,
2612
+ recordCallbackPlayback: (coordinateDigest) => recordCallbackPlayback((0, identity_1.getAgentRoot)(TARGET_ID), coordinateDigest),
2613
+ }) {
2614
+ const update = callbackUpdate(rawUpdate);
2615
+ const gateway = deps.gateway();
2616
+ const credentials = gateway.credentials;
2617
+ const identityKey = deps.identityKey((0, identity_1.getAgentRoot)(TARGET_ID));
2618
+ const subject = (0, telegram_1.opaqueTelegramSubject)(identityKey, credentials.botId, credentials.authorizedUserId, credentials.authorizedChatId);
2619
+ const api = gateway.authorityTransport.api;
2620
+ const effectBoundary = deps.effects?.({ agentRoot: (0, identity_1.getAgentRoot)(TARGET_ID), api, subject, chatId: credentials.authorizedChatId });
2621
+ const unavailableEffects = {
2622
+ sendText: async () => { throw new Error("Telegram acceptance probe effect boundary is unavailable"); },
2623
+ sendCard: async () => { throw new Error("Telegram acceptance probe effect boundary is unavailable"); },
2624
+ edit: async () => { throw new Error("Telegram acceptance probe effect boundary is unavailable"); },
2625
+ acknowledge: async () => { throw new Error("Telegram acceptance probe effect boundary is unavailable"); },
2626
+ };
2627
+ const runtime = deps.createRuntime({
2628
+ agentName: TARGET_ID,
2629
+ api,
2630
+ authorizedUserId: credentials.authorizedUserId,
2631
+ authorizedChatId: credentials.authorizedChatId,
2632
+ subject,
2633
+ identityKey,
2634
+ toolContext: deps.toolContext(TARGET_ID),
2635
+ effects: effectBoundary?.port ?? unavailableEffects,
2636
+ });
2637
+ try {
2638
+ deps.recordCallbackPlayback(callbackCoordinate(update).digest);
2639
+ const result = await runtime.transport.handleUpdate(update);
2640
+ if (replay && (result.handled !== true || result.accepted !== false || result.reason !== "stale_callback")) {
2641
+ throw new Error("Telegram callback replay did not settle as stale");
2642
+ }
2643
+ return {
2644
+ settled: result.handled,
2645
+ claimed: result.reason === "accepted" || result.reason === "decision_refused",
2646
+ mutated: result.accepted,
2647
+ };
2648
+ }
2649
+ finally {
2650
+ runtime.close();
2651
+ effectBoundary?.close();
2652
+ api.stop();
2653
+ }
2654
+ }