@ouro.bot/cli 0.1.0-alpha.74 → 0.1.0-alpha.741

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 (587) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +154 -23
  3. package/RepairGuide.ouro/agent.json +5 -0
  4. package/RepairGuide.ouro/psyche/IDENTITY.md +19 -0
  5. package/RepairGuide.ouro/psyche/SOUL.md +55 -0
  6. package/RepairGuide.ouro/skills/diagnose-broken-remote.md +63 -0
  7. package/RepairGuide.ouro/skills/diagnose-stacked-typed-issues.md +35 -0
  8. package/RepairGuide.ouro/skills/diagnose-sync-blocked.md +54 -0
  9. package/RepairGuide.ouro/skills/diagnose-vault-expired.md +60 -0
  10. package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/agent.json +4 -2
  11. package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/SOUL.md +2 -2
  12. package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/the-serpent.md +1 -1
  13. package/assets/bluebubbles-host +264 -0
  14. package/changelog.json +4730 -16
  15. package/deploy/unraid/Dockerfile +43 -0
  16. package/deploy/unraid/README.txt +1623 -0
  17. package/deploy/unraid/audit-container-spec.sh +4 -0
  18. package/deploy/unraid/container-runtime.json +1 -0
  19. package/deploy/unraid/sanctuary-acceptance-adapter.sh +37 -0
  20. package/deploy/unraid/sanctuary-acceptance-contract.json +200 -0
  21. package/deploy/unraid/sanctuary-acceptance-harness.sh +23 -0
  22. package/deploy/unraid/sanctuary-deployment-target.mjs +709 -0
  23. package/deploy/unraid/sanctuary-safe-rename.py +24 -0
  24. package/deploy/unraid/sanctuary-unit16-host-broker.mjs +1796 -0
  25. package/deploy/unraid/sanctuary-unit16-run.sh +520 -0
  26. package/deploy/unraid/sanctuary-unit18-target-audit.sh +27 -0
  27. package/deploy/unraid/sanctuary.ouro/agent.json +24 -0
  28. package/deploy/unraid/sanctuary.ouro/arc/README.md +3 -0
  29. package/deploy/unraid/sanctuary.ouro/bundle-meta.json +5 -0
  30. package/deploy/unraid/sanctuary.ouro/habits/sanctuary-health.md +8 -0
  31. package/deploy/unraid/sanctuary.ouro/provider-readiness.json +18 -0
  32. package/deploy/unraid/sanctuary.ouro/psyche/ASPIRATIONS.md +3 -0
  33. package/deploy/unraid/sanctuary.ouro/psyche/IDENTITY.md +3 -0
  34. package/deploy/unraid/sanctuary.ouro/psyche/LORE.md +3 -0
  35. package/deploy/unraid/sanctuary.ouro/psyche/SOUL.md +5 -0
  36. package/deploy/unraid/sanctuary.ouro/psyche/TACIT.md +7 -0
  37. package/deploy/unraid/sanctuary.ouro/tool-profiles.json +21 -0
  38. package/deploy/unraid/sanctuary.xml +25 -0
  39. package/dist/a2a/card.js +57 -0
  40. package/dist/a2a/client.js +180 -0
  41. package/dist/a2a/config.js +50 -0
  42. package/dist/a2a/delegation-stores.js +56 -0
  43. package/dist/a2a/did-resolution.js +93 -0
  44. package/dist/a2a/identity.js +170 -0
  45. package/dist/a2a/inbound-share.js +107 -0
  46. package/dist/a2a/mission-result-wire.js +196 -0
  47. package/dist/a2a/onboarding.js +115 -0
  48. package/dist/a2a/pin-store.js +132 -0
  49. package/dist/a2a/seen-ledger.js +120 -0
  50. package/dist/a2a/server.js +604 -0
  51. package/dist/a2a/task-store.js +69 -0
  52. package/dist/a2a/transport.js +45 -0
  53. package/dist/a2a/types.js +3 -0
  54. package/dist/arc/attention-types.js +8 -0
  55. package/dist/arc/cares.js +144 -0
  56. package/dist/arc/episodes.js +118 -0
  57. package/dist/arc/evolution.js +490 -0
  58. package/dist/arc/flight-recorder.js +1034 -0
  59. package/dist/arc/intentions.js +134 -0
  60. package/dist/arc/json-store.js +117 -0
  61. package/dist/arc/obligations.js +348 -0
  62. package/dist/arc/packets.js +336 -0
  63. package/dist/arc/presence.js +185 -0
  64. package/dist/arc/task-lifecycle.js +57 -0
  65. package/dist/commerce/store.js +755 -0
  66. package/dist/commerce/types.js +3 -0
  67. package/dist/heart/active-work.js +874 -43
  68. package/dist/heart/agent-entry.js +190 -3
  69. package/dist/heart/approval-files.js +113 -0
  70. package/dist/heart/approval-store.js +950 -0
  71. package/dist/heart/attachments/image-normalize.js +194 -0
  72. package/dist/heart/attachments/materialize.js +97 -0
  73. package/dist/heart/attachments/originals.js +88 -0
  74. package/dist/heart/attachments/render.js +29 -0
  75. package/dist/heart/attachments/sources/bluebubbles.js +156 -0
  76. package/dist/heart/attachments/sources/cli-local-file.js +78 -0
  77. package/dist/heart/attachments/sources/index.js +16 -0
  78. package/dist/heart/attachments/store.js +103 -0
  79. package/dist/heart/attachments/types.js +93 -0
  80. package/dist/heart/auth/auth-flow.js +495 -0
  81. package/dist/heart/autonomy-budget.js +477 -0
  82. package/dist/heart/awaiting/await-alert.js +146 -0
  83. package/dist/heart/awaiting/await-expiry.js +108 -0
  84. package/dist/heart/awaiting/await-loader.js +91 -0
  85. package/dist/heart/awaiting/await-parser.js +141 -0
  86. package/dist/heart/awaiting/await-runtime-state.js +100 -0
  87. package/dist/heart/awaiting/await-scheduler.js +382 -0
  88. package/dist/heart/background-operations.js +281 -0
  89. package/dist/heart/bridges/manager.js +137 -17
  90. package/dist/heart/bridges/store.js +14 -2
  91. package/dist/heart/bundle-state.js +168 -0
  92. package/dist/heart/commitments.js +135 -0
  93. package/dist/heart/config-registry.js +331 -0
  94. package/dist/heart/config.js +128 -129
  95. package/dist/heart/context-loss-gauntlet.js +354 -0
  96. package/dist/heart/context-loss-sentinel.js +1155 -0
  97. package/dist/heart/core.js +1759 -331
  98. package/dist/heart/cross-chat-delivery.js +8 -22
  99. package/dist/heart/daemon/agent-config-check.js +451 -0
  100. package/dist/heart/daemon/agent-discovery.js +185 -3
  101. package/dist/heart/daemon/agent-service.js +542 -0
  102. package/dist/heart/daemon/agentic-repair.js +547 -0
  103. package/dist/heart/daemon/await-private-wake.js +59 -0
  104. package/dist/heart/daemon/bluebubbles-cli-integration.js +71 -0
  105. package/dist/heart/daemon/bluebubbles-health-diagnostics.js +122 -0
  106. package/dist/heart/daemon/bluebubbles-host-protocol.js +433 -0
  107. package/dist/heart/daemon/bluebubbles-host.js +273 -0
  108. package/dist/heart/daemon/boot-sync-probe.js +197 -0
  109. package/dist/heart/daemon/cadence.js +180 -0
  110. package/dist/heart/daemon/cli-defaults.js +832 -0
  111. package/dist/heart/daemon/cli-desk.js +322 -0
  112. package/dist/heart/daemon/cli-exec.js +8692 -0
  113. package/dist/heart/daemon/cli-help.js +710 -0
  114. package/dist/heart/daemon/cli-parse.js +2714 -0
  115. package/dist/heart/daemon/cli-render-doctor.js +57 -0
  116. package/dist/heart/daemon/cli-render.js +777 -0
  117. package/dist/heart/daemon/cli-types.js +8 -0
  118. package/dist/heart/daemon/connect-bay.js +323 -0
  119. package/dist/heart/daemon/container-credential-bootstrap.js +273 -0
  120. package/dist/heart/daemon/container-healthcheck.js +94 -0
  121. package/dist/heart/daemon/container-runtime.js +84 -0
  122. package/dist/heart/daemon/container-spec-auditor-main.js +156 -0
  123. package/dist/heart/daemon/container-spec-auditor.js +156 -0
  124. package/dist/heart/daemon/daemon-bootstrap-startup.js +41 -0
  125. package/dist/heart/daemon/daemon-cli.js +29 -2144
  126. package/dist/heart/daemon/daemon-entry.js +930 -10
  127. package/dist/heart/daemon/daemon-health.js +186 -0
  128. package/dist/heart/daemon/daemon-rollup.js +57 -0
  129. package/dist/heart/daemon/daemon-runtime-sync.js +88 -13
  130. package/dist/heart/daemon/daemon-tombstone.js +236 -0
  131. package/dist/heart/daemon/daemon.js +1698 -88
  132. package/dist/heart/daemon/dns-workflow.js +394 -0
  133. package/dist/heart/daemon/doctor-types.js +8 -0
  134. package/dist/heart/daemon/doctor.js +1755 -0
  135. package/dist/heart/daemon/freshness.js +345 -0
  136. package/dist/heart/daemon/habit-private-wake.js +61 -0
  137. package/dist/heart/daemon/health-monitor.js +142 -11
  138. package/dist/heart/daemon/hooks/agent-config-v2.js +33 -0
  139. package/dist/heart/daemon/hooks/bundle-meta.js +135 -1
  140. package/dist/heart/daemon/http-health-probe.js +80 -0
  141. package/dist/heart/daemon/human-command-screens.js +234 -0
  142. package/dist/heart/daemon/human-readiness.js +114 -0
  143. package/dist/heart/daemon/inner-status.js +78 -0
  144. package/dist/heart/daemon/interactive-repair.js +394 -0
  145. package/dist/heart/daemon/launchd.js +37 -8
  146. package/dist/heart/daemon/log-tailer.js +79 -10
  147. package/dist/heart/daemon/logs-prune.js +132 -0
  148. package/dist/heart/daemon/mcp-canary.js +524 -0
  149. package/dist/heart/daemon/message-router.js +45 -5
  150. package/dist/heart/daemon/migrate-to-desk.js +848 -0
  151. package/dist/heart/daemon/os-cron-deps.js +135 -0
  152. package/dist/heart/daemon/os-cron.js +22 -14
  153. package/dist/heart/daemon/ouro-bot-entry.js +4 -2
  154. package/dist/heart/daemon/ouro-entry.js +3 -1
  155. package/dist/heart/daemon/plugin-cli.js +432 -0
  156. package/dist/heart/daemon/process-manager.js +624 -61
  157. package/dist/heart/daemon/provider-discovery.js +137 -0
  158. package/dist/heart/daemon/provider-ping-progress.js +83 -0
  159. package/dist/heart/daemon/prunable-bundle.js +144 -0
  160. package/dist/heart/daemon/pulse.js +511 -0
  161. package/dist/heart/daemon/readiness-repair.js +365 -0
  162. package/dist/heart/daemon/run-hooks.js +2 -0
  163. package/dist/heart/daemon/runtime-logging.js +47 -14
  164. package/dist/heart/daemon/runtime-metadata.js +2 -30
  165. package/dist/heart/daemon/runtime-mode.js +13 -2
  166. package/dist/heart/daemon/safe-mode.js +161 -0
  167. package/dist/heart/daemon/sanctuary-acceptance-adapter.js +2365 -0
  168. package/dist/heart/daemon/sanctuary-acceptance-harness.js +1624 -0
  169. package/dist/heart/daemon/sanctuary-acceptance-marker.js +267 -0
  170. package/dist/heart/daemon/sanctuary-acceptance-scenarios.js +985 -0
  171. package/dist/heart/daemon/sanctuary-scheduler-liveness.js +227 -0
  172. package/dist/heart/daemon/sanctuary-scheduler-origin.js +126 -0
  173. package/dist/heart/daemon/sense-manager.js +689 -48
  174. package/dist/heart/daemon/session-id-resolver.js +131 -0
  175. package/dist/heart/daemon/skill-management-installer.js +1 -1
  176. package/dist/heart/daemon/socket-client.js +180 -12
  177. package/dist/heart/daemon/stale-bundle-prune.js +113 -0
  178. package/dist/heart/daemon/startup-tui.js +338 -0
  179. package/dist/heart/daemon/supercronic-supervisor.js +267 -0
  180. package/dist/heart/daemon/task-scheduler.js +117 -39
  181. package/dist/heart/daemon/terminal-ui.js +499 -0
  182. package/dist/heart/daemon/thoughts.js +255 -43
  183. package/dist/heart/daemon/up-progress.js +366 -0
  184. package/dist/heart/daemon/vault-items.js +56 -0
  185. package/dist/heart/delegation.js +1 -4
  186. package/dist/heart/external-events/router.js +121 -0
  187. package/dist/heart/habits/habit-cancel.js +810 -0
  188. package/dist/heart/habits/habit-lifecycle.js +1327 -0
  189. package/dist/heart/habits/habit-migration.js +194 -0
  190. package/dist/heart/habits/habit-parser.js +278 -0
  191. package/dist/heart/habits/habit-runtime-state.js +93 -0
  192. package/dist/heart/habits/habit-scheduler.js +653 -0
  193. package/dist/heart/habits/habit-session-summary.js +318 -0
  194. package/dist/heart/habits/habit-session.js +636 -0
  195. package/dist/heart/{daemon → hatch}/hatch-flow.js +55 -62
  196. package/dist/heart/{daemon → hatch}/hatch-specialist.js +6 -8
  197. package/dist/heart/{daemon → hatch}/specialist-prompt.js +12 -9
  198. package/dist/heart/{daemon → hatch}/specialist-tools.js +53 -18
  199. package/dist/heart/identity.js +184 -59
  200. package/dist/heart/kept-notes.js +289 -0
  201. package/dist/heart/kicks.js +1 -1
  202. package/dist/heart/machine-identity.js +161 -0
  203. package/dist/heart/mail-import-discovery.js +353 -0
  204. package/dist/heart/mailbox/mailbox-http-hooks.js +98 -0
  205. package/dist/heart/mailbox/mailbox-http-response.js +7 -0
  206. package/dist/heart/mailbox/mailbox-http-routes.js +398 -0
  207. package/dist/heart/mailbox/mailbox-http-static.js +103 -0
  208. package/dist/heart/mailbox/mailbox-http-transport.js +116 -0
  209. package/dist/heart/mailbox/mailbox-http.js +99 -0
  210. package/dist/heart/mailbox/mailbox-read.js +39 -0
  211. package/dist/heart/mailbox/mailbox-types.js +27 -0
  212. package/dist/heart/mailbox/mailbox-view.js +197 -0
  213. package/dist/heart/mailbox/readers/agent-machine.js +428 -0
  214. package/dist/heart/mailbox/readers/continuity-readers.js +329 -0
  215. package/dist/heart/mailbox/readers/mail.js +375 -0
  216. package/dist/heart/mailbox/readers/runtime-readers.js +823 -0
  217. package/dist/heart/mailbox/readers/sessions.js +232 -0
  218. package/dist/heart/mailbox/readers/shared.js +111 -0
  219. package/dist/heart/mcp/mcp-server.js +696 -0
  220. package/dist/heart/migrate-config.js +100 -0
  221. package/dist/heart/model-capabilities.js +11 -0
  222. package/dist/heart/orientation-frame.js +345 -0
  223. package/dist/heart/platform.js +81 -0
  224. package/dist/heart/primitives.js +1 -1
  225. package/dist/heart/private-runtime/decision-renderer.js +156 -0
  226. package/dist/heart/private-runtime/index.js +18 -0
  227. package/dist/heart/private-runtime/ledger.js +218 -0
  228. package/dist/heart/private-runtime/policy.js +214 -0
  229. package/dist/heart/private-runtime/types.js +2 -0
  230. package/dist/heart/progress-story.js +1 -1
  231. package/dist/heart/provider-attempt.js +137 -0
  232. package/dist/heart/provider-binding-resolver.js +273 -0
  233. package/dist/heart/provider-credentials.js +449 -0
  234. package/dist/heart/provider-failover.js +311 -0
  235. package/dist/heart/provider-models.js +89 -0
  236. package/dist/heart/provider-ping.js +265 -0
  237. package/dist/heart/provider-readiness-cache.js +119 -0
  238. package/dist/heart/provider-visibility.js +188 -0
  239. package/dist/heart/providers/anthropic-token.js +131 -0
  240. package/dist/heart/providers/anthropic.js +173 -60
  241. package/dist/heart/providers/azure.js +23 -11
  242. package/dist/heart/providers/error-classification.js +127 -0
  243. package/dist/heart/providers/github-copilot.js +34 -38
  244. package/dist/heart/providers/minimax-vlm.js +189 -0
  245. package/dist/heart/providers/minimax.js +26 -8
  246. package/dist/heart/providers/openai-codex-token.js +349 -0
  247. package/dist/heart/providers/openai-codex.js +55 -40
  248. package/dist/heart/providers/openai-compatible.js +177 -0
  249. package/dist/heart/run-ledger.js +247 -0
  250. package/dist/heart/runtime-capability-check.js +170 -0
  251. package/dist/heart/runtime-credentials.js +607 -0
  252. package/dist/heart/runtime-cwd.js +87 -0
  253. package/dist/heart/sense-truth.js +22 -4
  254. package/dist/heart/session-activity.js +90 -24
  255. package/dist/heart/session-events.js +1300 -0
  256. package/dist/heart/session-playback-cli-main.js +5 -0
  257. package/dist/heart/session-playback-cli.js +36 -0
  258. package/dist/heart/session-playback.js +231 -0
  259. package/dist/heart/session-stats-cli-main.js +5 -0
  260. package/dist/heart/session-stats.js +182 -0
  261. package/dist/heart/session-transcript.js +133 -0
  262. package/dist/heart/start-of-turn-packet.js +368 -0
  263. package/dist/heart/streaming.js +604 -207
  264. package/dist/heart/structured-output.js +196 -0
  265. package/dist/heart/sync-classification.js +176 -0
  266. package/dist/heart/sync.js +449 -0
  267. package/dist/heart/target-resolution.js +11 -7
  268. package/dist/heart/tempo.js +93 -0
  269. package/dist/heart/temporal-view.js +41 -0
  270. package/dist/heart/timeouts.js +101 -0
  271. package/dist/heart/tool-activity-callbacks.js +59 -0
  272. package/dist/heart/tool-approval.js +340 -0
  273. package/dist/heart/tool-description.js +155 -0
  274. package/dist/heart/tool-friction.js +55 -0
  275. package/dist/heart/tool-loop.js +200 -0
  276. package/dist/heart/turn-context.js +480 -0
  277. package/dist/heart/{daemon → versioning}/ouro-bot-global-installer.js +6 -5
  278. package/dist/heart/{daemon → versioning}/ouro-bot-wrapper.js +1 -1
  279. package/dist/heart/versioning/ouro-path-installer.js +432 -0
  280. package/dist/heart/versioning/ouro-recovery-launcher.js +76 -0
  281. package/dist/heart/versioning/ouro-version-manager.js +409 -0
  282. package/dist/heart/{daemon → versioning}/staged-restart.js +40 -8
  283. package/dist/heart/{daemon → versioning}/update-checker.js +6 -1
  284. package/dist/heart/versioning/update-hooks.js +154 -0
  285. package/dist/heart/versioning/version-intent.js +114 -0
  286. package/dist/heart/work-card.js +380 -0
  287. package/dist/mailbox-ui/assets/index-Du_9G9WO.css +1 -0
  288. package/dist/mailbox-ui/assets/index-J8zDwHmE.js +1 -0
  289. package/dist/mailbox-ui/assets/vendor-CcN1XpQ9.js +61 -0
  290. package/dist/mailbox-ui/index.html +16 -0
  291. package/dist/mailroom/attention.js +167 -0
  292. package/dist/mailroom/autonomy.js +209 -0
  293. package/dist/mailroom/blob-store.js +889 -0
  294. package/dist/mailroom/body-cache.js +61 -0
  295. package/dist/mailroom/cache-sync-cli.js +58 -0
  296. package/dist/mailroom/core.js +803 -0
  297. package/dist/mailroom/entry.js +160 -0
  298. package/dist/mailroom/file-store.js +568 -0
  299. package/dist/mailroom/hosted-cache-sync.js +384 -0
  300. package/dist/mailroom/mbox-import.js +393 -0
  301. package/dist/mailroom/migration.js +164 -0
  302. package/dist/mailroom/outbound.js +380 -0
  303. package/dist/mailroom/policy.js +263 -0
  304. package/dist/mailroom/reader.js +292 -0
  305. package/dist/mailroom/search-cache.js +513 -0
  306. package/dist/mailroom/search-relevance.js +319 -0
  307. package/dist/mailroom/smtp-ingress.js +176 -0
  308. package/dist/mailroom/source-state.js +176 -0
  309. package/dist/mailroom/thread.js +109 -0
  310. package/dist/mailroom/travel-extract.js +89 -0
  311. package/dist/mind/bundle-manifest.js +24 -2
  312. package/dist/mind/context.js +273 -111
  313. package/dist/mind/desk-section.js +362 -0
  314. package/dist/mind/diary-integrity.js +60 -0
  315. package/dist/mind/{memory.js → diary.js} +68 -77
  316. package/dist/mind/embedding-provider.js +60 -0
  317. package/dist/mind/file-state.js +179 -0
  318. package/dist/mind/{associative-recall.js → note-search.js} +47 -58
  319. package/dist/mind/obligation-steering.js +227 -0
  320. package/dist/mind/pending.js +13 -8
  321. package/dist/mind/prompt-budget.js +479 -0
  322. package/dist/mind/prompt-refresh.js +3 -2
  323. package/dist/mind/prompt.js +1067 -172
  324. package/dist/mind/provenance-trust.js +26 -0
  325. package/dist/mind/record-paths.js +312 -0
  326. package/dist/mind/scrutiny.js +173 -0
  327. package/dist/mind/session-transaction.js +379 -0
  328. package/dist/nerves/cli-logging.js +7 -1
  329. package/dist/nerves/coverage/audit-rules.js +15 -6
  330. package/dist/nerves/coverage/audit.js +28 -2
  331. package/dist/nerves/coverage/cli.js +1 -1
  332. package/dist/nerves/coverage/contract.js +5 -5
  333. package/dist/nerves/coverage/file-completeness.js +139 -5
  334. package/dist/nerves/coverage/run-artifacts.js +55 -35
  335. package/dist/nerves/coverage/source-scanner.js +4 -4
  336. package/dist/nerves/event-buffer.js +111 -0
  337. package/dist/nerves/index.js +293 -9
  338. package/dist/nerves/observation.js +20 -0
  339. package/dist/nerves/redact.js +79 -0
  340. package/dist/nerves/review/cli-main.js +5 -0
  341. package/dist/nerves/review/cli.js +156 -0
  342. package/dist/nerves/review/core.js +159 -0
  343. package/dist/nerves/runtime.js +17 -1
  344. package/dist/repertoire/ado-client.js +15 -56
  345. package/dist/repertoire/ado-semantic.js +16 -10
  346. package/dist/repertoire/api-client.js +97 -0
  347. package/dist/repertoire/bitwarden-store.js +1045 -0
  348. package/dist/repertoire/bundle-templates.js +71 -0
  349. package/dist/repertoire/bw-installer.js +180 -0
  350. package/dist/repertoire/coding/codex-jsonl.js +64 -0
  351. package/dist/repertoire/coding/context-pack.js +331 -0
  352. package/dist/repertoire/coding/feedback.js +218 -30
  353. package/dist/repertoire/coding/index.js +15 -3
  354. package/dist/repertoire/coding/manager.js +166 -10
  355. package/dist/repertoire/coding/spawner.js +58 -11
  356. package/dist/repertoire/coding/tools.js +242 -14
  357. package/dist/repertoire/coding/workbench-client.js +272 -0
  358. package/dist/repertoire/coding/workbench-manager.js +346 -0
  359. package/dist/repertoire/commerce-errors.js +109 -0
  360. package/dist/repertoire/commerce-self-test.js +156 -0
  361. package/dist/repertoire/credential-access.js +178 -0
  362. package/dist/repertoire/desk/classifier.js +362 -0
  363. package/dist/repertoire/duffel-client.js +185 -0
  364. package/dist/repertoire/github-client.js +14 -55
  365. package/dist/repertoire/graph-client.js +11 -52
  366. package/dist/repertoire/guardrails.js +812 -18
  367. package/dist/repertoire/mcp-client.js +44 -3
  368. package/dist/repertoire/mcp-manager.js +259 -20
  369. package/dist/repertoire/mcp-tools.js +83 -0
  370. package/dist/repertoire/plugin-mcp.js +175 -0
  371. package/dist/repertoire/plugins.js +253 -0
  372. package/dist/repertoire/shell-sessions.js +133 -0
  373. package/dist/repertoire/skills.js +48 -4
  374. package/dist/repertoire/stripe-client.js +131 -0
  375. package/dist/repertoire/tool-arguments.js +143 -0
  376. package/dist/repertoire/tool-results.js +29 -0
  377. package/dist/repertoire/tools-a2a.js +699 -0
  378. package/dist/repertoire/tools-attachments.js +317 -0
  379. package/dist/repertoire/tools-awaiting.js +372 -0
  380. package/dist/repertoire/tools-base.js +67 -1095
  381. package/dist/repertoire/tools-bluebubbles.js +2 -0
  382. package/dist/repertoire/tools-bridge.js +144 -0
  383. package/dist/repertoire/tools-bundle.js +993 -0
  384. package/dist/repertoire/tools-commerce.js +253 -0
  385. package/dist/repertoire/tools-config.js +186 -0
  386. package/dist/repertoire/tools-continuity.js +252 -0
  387. package/dist/repertoire/tools-credential.js +383 -0
  388. package/dist/repertoire/tools-evolution.js +527 -0
  389. package/dist/repertoire/tools-files.js +344 -0
  390. package/dist/repertoire/tools-flight.js +290 -0
  391. package/dist/repertoire/tools-flow.js +119 -0
  392. package/dist/repertoire/tools-github.js +3 -8
  393. package/dist/repertoire/tools-habits.js +103 -0
  394. package/dist/repertoire/tools-mail.js +1828 -0
  395. package/dist/repertoire/tools-notes.js +459 -0
  396. package/dist/repertoire/tools-obligations.js +182 -0
  397. package/dist/repertoire/tools-orientation.js +31 -0
  398. package/dist/repertoire/tools-record.js +482 -0
  399. package/dist/repertoire/tools-rsvp.js +139 -0
  400. package/dist/repertoire/tools-runtime.js +150 -0
  401. package/dist/repertoire/tools-session.js +881 -0
  402. package/dist/repertoire/tools-shell.js +133 -0
  403. package/dist/repertoire/tools-stripe.js +224 -0
  404. package/dist/repertoire/tools-surface.js +355 -0
  405. package/dist/repertoire/tools-teams.js +12 -39
  406. package/dist/repertoire/tools-travel.js +125 -0
  407. package/dist/repertoire/tools-trip.js +982 -0
  408. package/dist/repertoire/tools-unraid.js +353 -0
  409. package/dist/repertoire/tools-user-profile.js +146 -0
  410. package/dist/repertoire/tools-vault.js +40 -0
  411. package/dist/repertoire/tools-voice.js +145 -0
  412. package/dist/repertoire/tools.js +276 -79
  413. package/dist/repertoire/travel-api-client.js +360 -0
  414. package/dist/repertoire/unraid-client.js +155 -0
  415. package/dist/repertoire/unraid-restart.js +130 -0
  416. package/dist/repertoire/user-profile.js +131 -0
  417. package/dist/repertoire/vault-setup.js +246 -0
  418. package/dist/repertoire/vault-unlock.js +628 -0
  419. package/dist/rsvp/aisleplanner-client.js +354 -0
  420. package/dist/rsvp/cli.js +926 -0
  421. package/dist/rsvp/config.js +327 -0
  422. package/dist/rsvp/cutover.js +433 -0
  423. package/dist/rsvp/diagnostics.js +232 -0
  424. package/dist/rsvp/diff-renderer.js +176 -0
  425. package/dist/rsvp/habit-policy.js +110 -0
  426. package/dist/rsvp/habit-stage.js +123 -0
  427. package/dist/rsvp/incident-bundle.js +97 -0
  428. package/dist/rsvp/migration.js +204 -0
  429. package/dist/rsvp/native-habit-runner.js +630 -0
  430. package/dist/rsvp/outbound-state.js +628 -0
  431. package/dist/rsvp/query.js +118 -0
  432. package/dist/rsvp/replay.js +233 -0
  433. package/dist/rsvp/snapshot.js +232 -0
  434. package/dist/rsvp/spend-ledger.js +175 -0
  435. package/dist/scripts/claude-code-hook.js +41 -0
  436. package/dist/scripts/claude-code-stop-hook.js +47 -0
  437. package/dist/senses/a2a-entry.js +115 -0
  438. package/dist/senses/attention-queue.js +186 -0
  439. package/dist/senses/await-turn-message.js +58 -0
  440. package/dist/senses/bluebubbles/active-turns.js +216 -0
  441. package/dist/senses/bluebubbles/attachment-cache.js +53 -0
  442. package/dist/senses/bluebubbles/attachment-download.js +137 -0
  443. package/dist/senses/{bluebubbles-client.js → bluebubbles/client.js} +355 -40
  444. package/dist/senses/bluebubbles/context-packet.js +207 -0
  445. package/dist/senses/bluebubbles/context-smoke.js +144 -0
  446. package/dist/senses/bluebubbles/entry.js +90 -0
  447. package/dist/senses/{bluebubbles-inbound-log.js → bluebubbles/inbound-log.js} +21 -3
  448. package/dist/senses/bluebubbles/index.js +3839 -0
  449. package/dist/senses/bluebubbles/latest-turn.js +311 -0
  450. package/dist/senses/{bluebubbles-media.js → bluebubbles/media.js} +121 -71
  451. package/dist/senses/{bluebubbles-model.js → bluebubbles/model.js} +159 -28
  452. package/dist/senses/{bluebubbles-mutation-log.js → bluebubbles/mutation-log.js} +24 -13
  453. package/dist/senses/bluebubbles/outbound-state.js +308 -0
  454. package/dist/senses/bluebubbles/processed-log.js +133 -0
  455. package/dist/senses/bluebubbles/reaction-policy.js +47 -0
  456. package/dist/senses/bluebubbles/replay.js +284 -0
  457. package/dist/senses/{bluebubbles-runtime-state.js → bluebubbles/runtime-state.js} +30 -2
  458. package/dist/senses/bluebubbles/semantic-receipts.js +1986 -0
  459. package/dist/senses/{bluebubbles-session-cleanup.js → bluebubbles/session-cleanup.js} +1 -1
  460. package/dist/senses/bluebubbles/webhook-registration.js +228 -0
  461. package/dist/senses/bluebubbles-meta-guard.js +39 -0
  462. package/dist/senses/cli/bracketed-paste.js +82 -0
  463. package/dist/senses/cli/image-paste.js +287 -0
  464. package/dist/senses/cli/image-ref-navigation.js +75 -0
  465. package/dist/senses/cli/ink-app.js +156 -0
  466. package/dist/senses/cli/inline-diff.js +64 -0
  467. package/dist/senses/cli/input-keys.js +174 -0
  468. package/dist/senses/cli/kill-ring.js +86 -0
  469. package/dist/senses/cli/message-list.js +51 -0
  470. package/dist/senses/cli/ouro-tui.js +607 -0
  471. package/dist/senses/cli/spinner-imperative.js +135 -0
  472. package/dist/senses/cli/spinner.js +101 -0
  473. package/dist/senses/cli/status-line.js +60 -0
  474. package/dist/senses/cli/streaming-markdown.js +526 -0
  475. package/dist/senses/cli/tool-display.js +85 -0
  476. package/dist/senses/cli/tool-render.js +85 -0
  477. package/dist/senses/cli/tui-store.js +241 -0
  478. package/dist/senses/cli/virtual-list.js +35 -0
  479. package/dist/senses/cli-entry.js +60 -8
  480. package/dist/senses/cli-layout.js +100 -0
  481. package/dist/senses/cli.js +621 -321
  482. package/dist/senses/commands.js +66 -3
  483. package/dist/senses/context-packet-ledger.js +371 -0
  484. package/dist/senses/context-packets.js +154 -0
  485. package/dist/senses/habit-lifecycle-guard.js +24 -0
  486. package/dist/senses/habit-turn-message.js +160 -0
  487. package/dist/senses/ingress-evidence.js +2 -0
  488. package/dist/senses/mail-entry.js +66 -0
  489. package/dist/senses/mail.js +524 -0
  490. package/dist/senses/pipeline.js +1075 -176
  491. package/dist/senses/private-runtime-worker.js +741 -0
  492. package/dist/senses/private-runtime.js +1212 -0
  493. package/dist/senses/proactive-content-guard.js +51 -0
  494. package/dist/senses/sanctuary-grounding.js +118 -0
  495. package/dist/senses/sanctuary-health-acceptance-probe-entry.js +12 -0
  496. package/dist/senses/sanctuary-health-acceptance-probe.js +772 -0
  497. package/dist/senses/sanctuary-health-runner.js +146 -0
  498. package/dist/senses/sanctuary-health.js +447 -0
  499. package/dist/senses/sanctuary-interactive-control.js +306 -0
  500. package/dist/senses/sanctuary-runtime.js +206 -0
  501. package/dist/senses/shared-turn.js +432 -0
  502. package/dist/senses/surface-tool.js +109 -0
  503. package/dist/senses/teams-entry.js +60 -8
  504. package/dist/senses/teams.js +515 -217
  505. package/dist/senses/telegram-approval-runtime.js +526 -0
  506. package/dist/senses/telegram-audit-ledger.js +220 -0
  507. package/dist/senses/telegram-client.js +1471 -0
  508. package/dist/senses/telegram-entry.js +80 -0
  509. package/dist/senses/telegram.js +1232 -0
  510. package/dist/senses/trust-gate.js +104 -9
  511. package/dist/senses/voice/audio-playback.js +237 -0
  512. package/dist/senses/voice/audio-routing.js +119 -0
  513. package/dist/senses/voice/elevenlabs.js +202 -0
  514. package/dist/senses/voice/floor-control.js +431 -0
  515. package/dist/senses/voice/floor-controller.js +115 -0
  516. package/dist/senses/voice/golden-path.js +116 -0
  517. package/dist/senses/voice/index.js +29 -0
  518. package/dist/senses/voice/meeting.js +113 -0
  519. package/dist/senses/voice/outbound.js +190 -0
  520. package/dist/senses/voice/phone.js +33 -0
  521. package/dist/senses/voice/playback.js +139 -0
  522. package/dist/senses/voice/realtime-eval.js +496 -0
  523. package/dist/senses/voice/realtime-trace.js +531 -0
  524. package/dist/senses/voice/transcript.js +70 -0
  525. package/dist/senses/voice/turn.js +191 -0
  526. package/dist/senses/voice/twilio-phone-runtime.js +807 -0
  527. package/dist/senses/voice/twilio-phone.js +5081 -0
  528. package/dist/senses/voice/types.js +2 -0
  529. package/dist/senses/voice/whisper.js +161 -0
  530. package/dist/senses/voice-entry.js +81 -0
  531. package/dist/senses/voice-realtime-eval-command.js +99 -0
  532. package/dist/senses/voice-realtime-eval-entry.js +21 -0
  533. package/dist/senses/voice-twilio-entry.js +87 -0
  534. package/dist/trips/core.js +138 -0
  535. package/dist/trips/store.js +265 -0
  536. package/dist/util/frontmatter.js +69 -0
  537. package/npm-shrinkwrap.json +8129 -0
  538. package/package.json +71 -12
  539. package/skills/agent-commerce.md +113 -0
  540. package/skills/browser-navigation.md +117 -0
  541. package/skills/commerce-setup-guide.md +116 -0
  542. package/skills/commerce-setup.md +84 -0
  543. package/skills/configure-dev-tools.md +99 -0
  544. package/skills/travel-planning.md +138 -0
  545. package/dist/heart/daemon/auth-flow.js +0 -430
  546. package/dist/heart/daemon/ouro-path-installer.js +0 -178
  547. package/dist/heart/daemon/update-hooks.js +0 -138
  548. package/dist/heart/safe-workspace.js +0 -228
  549. package/dist/heart/session-recall.js +0 -116
  550. package/dist/mind/friends/channel.js +0 -84
  551. package/dist/mind/friends/group-context.js +0 -144
  552. package/dist/mind/friends/resolver.js +0 -84
  553. package/dist/mind/friends/store-file.js +0 -190
  554. package/dist/mind/friends/store.js +0 -4
  555. package/dist/mind/friends/tokens.js +0 -26
  556. package/dist/mind/friends/trust-explanation.js +0 -74
  557. package/dist/mind/friends/types.js +0 -29
  558. package/dist/repertoire/tasks/board.js +0 -134
  559. package/dist/repertoire/tasks/index.js +0 -224
  560. package/dist/repertoire/tasks/lifecycle.js +0 -80
  561. package/dist/repertoire/tasks/middleware.js +0 -65
  562. package/dist/repertoire/tasks/parser.js +0 -173
  563. package/dist/repertoire/tasks/scanner.js +0 -132
  564. package/dist/repertoire/tasks/transitions.js +0 -144
  565. package/dist/senses/bluebubbles-entry.js +0 -13
  566. package/dist/senses/bluebubbles.js +0 -1180
  567. package/dist/senses/debug-activity.js +0 -148
  568. package/dist/senses/inner-dialog-worker.js +0 -90
  569. package/dist/senses/inner-dialog.js +0 -480
  570. package/subagents/README.md +0 -7
  571. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/basilisk.md +0 -0
  572. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/jafar.md +0 -0
  573. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/jormungandr.md +0 -0
  574. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/kaa.md +0 -0
  575. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/medusa.md +0 -0
  576. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/monty.md +0 -0
  577. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/nagini.md +0 -0
  578. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/ouroboros.md +0 -0
  579. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/python.md +0 -0
  580. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/quetzalcoatl.md +0 -0
  581. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/sir-hiss.md +0 -0
  582. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/the-snake.md +0 -0
  583. /package/dist/{repertoire/tasks/types.js → heart/attachments/sources/adapter.js} +0 -0
  584. /package/dist/heart/{daemon → hatch}/hatch-animation.js +0 -0
  585. /package/dist/heart/{daemon → hatch}/specialist-orchestrator.js +0 -0
  586. /package/dist/heart/{daemon → versioning}/ouro-uti.js +0 -0
  587. /package/dist/heart/{daemon → versioning}/wrapper-publish-guard.js +0 -0
@@ -0,0 +1,985 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.SANCTUARY_APPROVAL_TERMINAL_EDIT_TIMEOUT_MS = exports.SANCTUARY_APPROVAL_RECONCILIATION_JITTER_MS = exports.SANCTUARY_APPROVAL_TTL_MS = void 0;
37
+ exports.verifySanctuaryPostbootIntegrity = verifySanctuaryPostbootIntegrity;
38
+ exports.deriveSanctuaryScenarioAssertions = deriveSanctuaryScenarioAssertions;
39
+ exports.createSanctuaryScenarioCapture = createSanctuaryScenarioCapture;
40
+ exports.finalizeSanctuaryScenarioCapture = finalizeSanctuaryScenarioCapture;
41
+ const node_crypto_1 = require("node:crypto");
42
+ const fs = __importStar(require("node:fs"));
43
+ const path = __importStar(require("node:path"));
44
+ const runtime_1 = require("../../nerves/runtime");
45
+ const identity_1 = require("../identity");
46
+ const sanctuary_acceptance_marker_1 = require("./sanctuary-acceptance-marker");
47
+ const sanctuary_acceptance_harness_1 = require("./sanctuary-acceptance-harness");
48
+ const sanctuary_grounding_1 = require("../../senses/sanctuary-grounding");
49
+ const telegram_client_1 = require("../../senses/telegram-client");
50
+ const SHA256 = /^[0-9a-f]{64}$/u;
51
+ exports.SANCTUARY_APPROVAL_TTL_MS = telegram_client_1.TELEGRAM_APPROVAL_TTL_MS;
52
+ exports.SANCTUARY_APPROVAL_RECONCILIATION_JITTER_MS = 1_000;
53
+ exports.SANCTUARY_APPROVAL_TERMINAL_EDIT_TIMEOUT_MS = telegram_client_1.TELEGRAM_APPROVAL_TERMINAL_EDIT_TIMEOUT_MS;
54
+ const CANONICAL_RESTART_TARGET = "calibre-web";
55
+ function uniqueDigests(rows) {
56
+ return rows.every((row) => SHA256.test(row.idDigest)) && new Set(rows.map((row) => row.idDigest)).size === rows.length;
57
+ }
58
+ function preservedPrefix(before, after) {
59
+ return before.length <= after.length && before.every((row, index) => row.recordDigest === after[index]?.recordDigest);
60
+ }
61
+ function verifySanctuaryPostbootIntegrity(before, after, scenarioHandleDigest, options = {}) {
62
+ if (before.schemaVersion !== "sanctuary-postboot-integrity-v2" || after.schemaVersion !== before.schemaVersion || !SHA256.test(scenarioHandleDigest)
63
+ || before.activeScenarioHandleDigest !== null || after.activeScenarioHandleDigest !== scenarioHandleDigest)
64
+ return null;
65
+ if (!Number.isSafeInteger(before.telegramNextUpdateId) || !Number.isSafeInteger(after.telegramNextUpdateId)
66
+ || before.telegramNextUpdateId < 0 || after.telegramNextUpdateId < before.telegramNextUpdateId)
67
+ return null;
68
+ if (options.preserveCursors !== false && (JSON.stringify(before.approvalCheckpoints) !== JSON.stringify(after.approvalCheckpoints)
69
+ || before.approvalExecutionCount !== after.approvalExecutionCount || before.fingerprintDigest !== after.fingerprintDigest))
70
+ return null;
71
+ if (![before.approvalCheckpoints, after.approvalCheckpoints, before.sweeps, after.sweeps, before.deliveries, after.deliveries, before.audits, after.audits].every(uniqueDigests))
72
+ return null;
73
+ const rows = [...before.approvalCheckpoints, ...after.approvalCheckpoints, ...before.restartAttempts, ...after.restartAttempts,
74
+ ...before.sweeps, ...after.sweeps, ...before.deliveries, ...after.deliveries, ...before.audits, ...after.audits];
75
+ if (!rows.every((row) => SHA256.test(row.recordDigest)))
76
+ return null;
77
+ if (JSON.stringify(before.restartAttempts) !== JSON.stringify(after.restartAttempts) || !preservedPrefix(before.sweeps, after.sweeps)
78
+ || !preservedPrefix(before.deliveries, after.deliveries) || !preservedPrefix(before.audits, after.audits))
79
+ return null;
80
+ const executionCount = (snapshot) => new Set(snapshot.restartAttempts
81
+ .filter((row) => row.state !== "attempt_not_started").map((row) => row.idDigest)).size;
82
+ const validAttemptLifecycles = (snapshot) => {
83
+ const states = new Map();
84
+ for (const row of snapshot.restartAttempts)
85
+ states.set(row.idDigest, [...(states.get(row.idDigest) ?? []), row.state]);
86
+ return [...states.values()].every((sequence) => sequence.length === 3 && sequence[0] === "attempt_not_started"
87
+ && sequence[1] === "attempting" && (sequence[2] === "succeeded" || sequence[2] === "attempted_or_indeterminate"));
88
+ };
89
+ if (!validAttemptLifecycles(before) || !validAttemptLifecycles(after))
90
+ return null;
91
+ if (before.approvalExecutionCount !== executionCount(before) || after.approvalExecutionCount !== executionCount(after)
92
+ || after.approvalExecutionCount !== before.approvalExecutionCount)
93
+ return null;
94
+ const newSweeps = after.sweeps.slice(before.sweeps.length);
95
+ const newDeliveries = after.deliveries.slice(before.deliveries.length);
96
+ const newAudits = after.audits.slice(before.audits.length);
97
+ if (newSweeps.some((row) => row.scenarioHandleDigest !== scenarioHandleDigest))
98
+ return null;
99
+ const boundDeliveryIds = new Set(newSweeps.flatMap((row) => row.deliveryIdDigest ? [row.deliveryIdDigest] : []));
100
+ if (newDeliveries.some((row) => !boundDeliveryIds.has(row.idDigest)))
101
+ return null;
102
+ if (newAudits.some((row) => row.scenarioRelevant && row.scenarioHandleDigest !== scenarioHandleDigest))
103
+ return null;
104
+ return { preserved: true, sweepDeltaCount: newSweeps.length, deliveryDeltaCount: newDeliveries.length, auditDeltaCount: newAudits.length };
105
+ }
106
+ const HEALTH_SCENARIO_LABELS = new Set([
107
+ "unit-16f-cron-fingerprint",
108
+ "unit-16g-health-transition",
109
+ "unit-16h-daily-digest",
110
+ ]);
111
+ const INTERACTIVE_DRIVER_LABELS = new Set([
112
+ "unit-16l-duplicate-callback",
113
+ "unit-16m-restart-continuation",
114
+ ]);
115
+ const READ_ONLY_DENIAL_LABELS = new Set([
116
+ "unit-16e-1-stop-denial",
117
+ "unit-16e-2-restart-denial",
118
+ ]);
119
+ function hash(value) {
120
+ return (0, node_crypto_1.createHash)("sha256").update(JSON.stringify(value), "utf8").digest("hex");
121
+ }
122
+ const CONTAINMENT_READ_SCOPE = ["ARRAY", "DASHBOARD", "DISK", "DOCKER", "INFO", "LOGS", "NOTIFICATIONS", "SHARE", "VARS"]
123
+ .map((resource) => `${resource}:READ_ANY`).sort();
124
+ const CONTAINMENT_TELEGRAM_PROFILE = ["unraid_list_containers", "unraid_get_container_logs", "unraid_get_storage", "unraid_get_disks", "unraid_get_notifications", "unraid_get_system", "unraid_restart_container", "ponder", "settle", "speak"];
125
+ const CONTAINMENT_PRIVATE_PROFILE = ["send_message", "rest"];
126
+ const CONTAINMENT_EXCLUDED_TOOLS = ["shell", "read_file", "edit_file", "vault_get", "mcp_call", "exec", "credential_get"];
127
+ const CONTAINMENT_AUDIT_PATH = "/home/ouro/AgentBundles/sanctuary.ouro/state/acceptance/telegram-audit-chain.ndjson";
128
+ const CONTAINMENT_WRITE_POLICY = { kind: "required", policyId: "sanctuary.unraid.restart.v1", actionClass: "unraid.container.restart", requiresSoleCall: true };
129
+ const CONTAINMENT_TELEGRAM_SCHEMA_DIGEST = "3c66299a5f70ec82f8795cae47659284e6dbc691ef49002c2fb22edba76c59b6";
130
+ const CONTAINMENT_PRIVATE_SCHEMA_DIGEST = "61b137b2467acbcf22ca7443ee01e71ed970a62728c42aabffbdcb562f4a6a70";
131
+ function exactContainmentAudit(evidence) {
132
+ const digestFields = [evidence.keyInventoryDigest, evidence.telegramSchemaDigest, evidence.privateSchemaDigest, evidence.auditLedgerDigest];
133
+ return evidence.schemaVersion === "sanctuary-containment-audit-v1"
134
+ && evidence.keyCount === 2
135
+ && evidence.readScopeDigest === hash(CONTAINMENT_READ_SCOPE)
136
+ && evidence.writeScopeDigest === hash([...CONTAINMENT_READ_SCOPE, "DOCKER:UPDATE_ANY"].sort())
137
+ && evidence.keyRoleAssignmentCount === 0
138
+ && evidence.telegramToolCount === CONTAINMENT_TELEGRAM_PROFILE.length
139
+ && evidence.telegramProfileDigest === hash(CONTAINMENT_TELEGRAM_PROFILE)
140
+ && evidence.telegramSchemaDigest === CONTAINMENT_TELEGRAM_SCHEMA_DIGEST
141
+ && evidence.privateToolCount === CONTAINMENT_PRIVATE_PROFILE.length
142
+ && evidence.privateProfileDigest === hash(CONTAINMENT_PRIVATE_PROFILE)
143
+ && evidence.privateSchemaDigest === CONTAINMENT_PRIVATE_SCHEMA_DIGEST
144
+ && evidence.resolvedHandlerCount === CONTAINMENT_TELEGRAM_PROFILE.length + CONTAINMENT_PRIVATE_PROFILE.length
145
+ && evidence.excludedToolCount === CONTAINMENT_EXCLUDED_TOOLS.length
146
+ && evidence.excludedSchemaIntersectionCount === 0
147
+ && evidence.fabricatedHandlerInvocationCount === 0
148
+ && evidence.excludedToolAttemptCount === CONTAINMENT_EXCLUDED_TOOLS.length
149
+ && evidence.excludedToolRejectedCount === CONTAINMENT_EXCLUDED_TOOLS.length
150
+ && evidence.excludedToolInvokedCount === 0 && evidence.excludedToolSideEffectCount === 0
151
+ && evidence.globallyResolvableExcludedToolCount >= 1
152
+ && evidence.auditPathDigest === (0, node_crypto_1.createHash)("sha256").update(CONTAINMENT_AUDIT_PATH).digest("hex")
153
+ && evidence.auditRecordCount >= 2 && evidence.auditLifecyclePairCount >= 1
154
+ && evidence.containerUser === "10001:10001" && evidence.liveProcessUser === "10001:10001" && evidence.mountCount === 2 && evidence.publishedPortCount === 0
155
+ && evidence.networkMode === "host" && evidence.readOnlyRoot && evidence.mountsExact && evidence.securityExact && evidence.updaterDisabled
156
+ && !evidence.writableKeyExposure && evidence.rawWriteMaterialFieldCount === 0 && evidence.typedWriteExecutorCount === 1
157
+ && evidence.writeApprovalPolicyDigest === hash(CONTAINMENT_WRITE_POLICY) && !evidence.sensitiveMaterialObserved
158
+ && digestFields.every((value) => SHA256.test(value));
159
+ }
160
+ function atomicPrivateJson(filePath, value) {
161
+ fs.mkdirSync(path.dirname(filePath), { recursive: true, mode: 0o700 });
162
+ const temporary = `${filePath}.${process.pid}.tmp`;
163
+ fs.writeFileSync(temporary, `${JSON.stringify(value)}\n`, { encoding: "utf8", mode: 0o600, flag: "wx" });
164
+ fs.renameSync(temporary, filePath);
165
+ fs.chmodSync(filePath, 0o600);
166
+ }
167
+ function eventCount(facts, event) {
168
+ return facts.events.filter((entry) => entry.event === event).length;
169
+ }
170
+ function delta(after, before, event) {
171
+ return Math.max(0, eventCount(after, event) - eventCount(before, event));
172
+ }
173
+ function turnHasGroundedRead(after, turn, toolName) {
174
+ const resultDigests = new Set(after.events.flatMap((entry) => entry.event === "senses.sanctuary_read_receipt" && entry.meta.toolName === toolName && entry.meta.success === true && typeof entry.meta.resultDigest === "string" ? [entry.meta.resultDigest] : []));
175
+ return turn.toolResultDigests.some((digest) => resultDigests.has(digest));
176
+ }
177
+ function exactGroundedResponse(after, turn, toolName) {
178
+ const grounding = turn.toolGroundings?.length === 1 ? turn.toolGroundings[0] : undefined;
179
+ const live = after.liveGrounding;
180
+ const audit = after.events.filter((entry) => entry.event === "senses.sanctuary_read_receipt" && entry.meta.toolName === toolName && entry.meta.success === true
181
+ && entry.meta.resultDigest === grounding?.resultDigest && entry.meta.groundingDigest === grounding?.groundingDigest
182
+ && entry.meta.sourceIdentityDigest === grounding?.sourceIdentityDigest && entry.meta.observedAt === grounding?.observedAt);
183
+ const turnObservedAt = Date.parse(grounding?.observedAt ?? "");
184
+ const liveObservedAt = Date.parse(live?.observedAt ?? "");
185
+ const timeBound = Number.isFinite(turnObservedAt) && Number.isFinite(liveObservedAt)
186
+ && turnObservedAt <= turn.completedAt && turn.completedAt - turnObservedAt <= 300_000
187
+ && liveObservedAt >= turnObservedAt && liveObservedAt - turnObservedAt <= 300_000;
188
+ const liveFactsBound = toolName === "unraid_get_storage"
189
+ ? live?.groundingDigest === (0, sanctuary_grounding_1.sanctuaryGroundingDigest)(live?.facts ?? {})
190
+ : grounding?.groundingDigest === live?.groundingDigest && JSON.stringify(grounding?.facts) === JSON.stringify(live?.facts);
191
+ return Boolean(grounding && live && grounding.toolName === toolName && live.toolName === toolName
192
+ && grounding.groundingDigest === (0, sanctuary_grounding_1.sanctuaryGroundingDigest)(grounding.facts) && live.groundingDigest === (0, sanctuary_grounding_1.sanctuaryGroundingDigest)(live.facts)
193
+ && grounding.sourceIdentityDigest === live.sourceIdentityDigest && SHA256.test(grounding.sourceIdentityDigest) && timeBound && liveFactsBound
194
+ && audit.length === 1 && typeof turn.responseText === "string" && turn.responseText.length > 0
195
+ && turn.responseUtf16Units === turn.responseText.length && turn.responseUtf16Units <= 1_200
196
+ && (0, sanctuary_grounding_1.sanctuaryGroundedResponseAccurate)(toolName, grounding.facts, turn.responseText));
197
+ }
198
+ function recordsAdded(before, after, key) {
199
+ const remaining = new Map();
200
+ for (const value of before)
201
+ remaining.set(key(value), (remaining.get(key(value)) ?? 0) + 1);
202
+ return after.filter((value) => {
203
+ const digest = key(value);
204
+ const count = remaining.get(digest) ?? 0;
205
+ if (count === 0)
206
+ return true;
207
+ remaining.set(digest, count - 1);
208
+ return false;
209
+ });
210
+ }
211
+ function exactFreshTelegramLifecycle(before, after, expectedUpdateDigest) {
212
+ if (before.events.length > after.events.length || before.events.some((entry, index) => hash(entry) !== hash(after.events[index])))
213
+ return false;
214
+ const fresh = recordsAdded(before.events, after.events, (entry) => hash(entry));
215
+ const starts = fresh.filter((entry) => entry.event === "senses.telegram_turn_start");
216
+ const terminals = fresh.filter((entry) => entry.event === "senses.telegram_turn_end" || entry.event === "senses.telegram_turn_error");
217
+ if (starts.length !== 1 || terminals.length !== 1)
218
+ return false;
219
+ const start = starts[0];
220
+ const terminal = terminals[0];
221
+ const coordinateKeys = ["scenarioHandleDigest", "turnDigest", "updateDigest", "subject", "identityDigest", "sessionDigest", "argumentDigest"];
222
+ if (coordinateKeys.some((key) => start.meta[key] !== terminal.meta[key]))
223
+ return false;
224
+ if (!SHA256.test(String(start.meta.scenarioHandleDigest)) || !SHA256.test(String(start.meta.turnDigest))
225
+ || !SHA256.test(String(start.meta.updateDigest)) || (expectedUpdateDigest !== undefined && start.meta.updateDigest !== expectedUpdateDigest)
226
+ || typeof start.meta.subject !== "string" || !/^tg_[A-Za-z0-9_-]{43}$/u.test(start.meta.subject)
227
+ || ![start.meta.identityDigest, start.meta.sessionDigest, start.meta.argumentDigest, start.meta.lifecycleMac, terminal.meta.lifecycleMac].every((value) => typeof value === "string" && SHA256.test(value)))
228
+ return false;
229
+ if (!Number.isSafeInteger(terminal.meta.deliveryCount) || Number(terminal.meta.deliveryCount) < 0)
230
+ return false;
231
+ if (!Number.isSafeInteger(start.meta.lifecycleAt) || !Number.isSafeInteger(terminal.meta.lifecycleAt)
232
+ || Number(start.meta.lifecycleAt) < 0 || Number(start.meta.lifecycleAt) >= Number(terminal.meta.lifecycleAt))
233
+ return false;
234
+ return terminal.event === "senses.telegram_turn_end" && terminal.meta.outcome === "success" && terminal.meta.errorDigest === null;
235
+ }
236
+ function intendedApproval(before, after) {
237
+ const previous = new Set(before.approvals.map((record) => record.approvalId));
238
+ const candidates = after.approvals.filter((record) => !previous.has(record.approvalId) && record.createdAt >= before.capturedAt);
239
+ return candidates.length === 1 ? candidates[0] : null;
240
+ }
241
+ function intendedRestartApproval(approval) {
242
+ const argumentDigest = (0, node_crypto_1.createHash)("sha256").update(JSON.stringify({ container: CANONICAL_RESTART_TARGET })).digest("hex");
243
+ return approval?.toolName === "unraid_restart_container"
244
+ && approval.target === CANONICAL_RESTART_TARGET
245
+ && approval.argumentDigest === argumentDigest;
246
+ }
247
+ function approvalEvidenceCoordinates(approval) {
248
+ return {
249
+ actionDigest: (0, node_crypto_1.createHash)("sha256").update(JSON.stringify({ toolName: approval.toolName, argumentDigest: approval.argumentDigest })).digest("hex"),
250
+ targetDigest: (0, node_crypto_1.createHash)("sha256").update(JSON.stringify({ container: CANONICAL_RESTART_TARGET })).digest("hex"),
251
+ };
252
+ }
253
+ function exactApprovalEvidence(before, after, approval) {
254
+ const fresh = recordsAdded(before.events, after.events, (entry) => hash(entry));
255
+ const prompt = fresh.filter((entry) => entry.event === "senses.telegram_approval_prompt_bound" && entry.meta.approvalId === approval.approvalId);
256
+ const terminals = fresh.filter((entry) => entry.event === "telegram.approval_prompt_terminalized" && entry.meta.approvalId === approval.approvalId);
257
+ const callbackCandidates = fresh.filter((entry) => ["telegram.callback_settled", "telegram.callback_recovery_settled"].includes(entry.event) && entry.meta.approvalId === approval.approvalId);
258
+ const callbacks = [...new Map(callbackCandidates.map((entry) => [hash({ event: entry.event, meta: entry.meta }), entry])).values()];
259
+ const continuations = fresh.filter((entry) => entry.event === "senses.telegram_approval_continuation_delivered" && entry.meta.approvalId === approval.approvalId);
260
+ const staleCallbacks = fresh.filter((entry) => entry.event === "telegram.approval_stale_callback_settled" && entry.meta.approvalId === approval.approvalId);
261
+ const expiryObservations = fresh.filter((entry) => entry.event === "telegram.approval_expiry_observed" && entry.meta.approvalId === approval.approvalId);
262
+ if (prompt.length !== 1 || terminals.length !== 1 || callbacks.length > 1 || continuations.length > 1 || staleCallbacks.length > 1)
263
+ return null;
264
+ if ((approval.state === "expired" && (expiryObservations.length < 1
265
+ || new Set(expiryObservations.map((entry) => hash({ event: entry.event, meta: entry.meta }))).size !== 1))
266
+ || (approval.state !== "expired" && expiryObservations.length !== 0))
267
+ return null;
268
+ const expected = approvalEvidenceCoordinates(approval);
269
+ const coordinatesMatch = (entry) => entry.meta.scenarioHandleDigest !== undefined
270
+ && typeof entry.meta.scenarioHandleDigest === "string" && SHA256.test(entry.meta.scenarioHandleDigest)
271
+ && entry.meta.actionDigest === expected.actionDigest && entry.meta.targetDigest === expected.targetDigest
272
+ && entry.meta.checkpointDigest === approval.checkpointDigest
273
+ && typeof approval.suspendedSessionRevision === "string"
274
+ && entry.meta.suspendedSessionRevisionDigest === (0, node_crypto_1.createHash)("sha256").update(approval.suspendedSessionRevision, "utf8").digest("hex")
275
+ && typeof entry.meta.messageIdDigest === "string" && SHA256.test(entry.meta.messageIdDigest)
276
+ && typeof entry.meta.evidenceMac === "string" && SHA256.test(entry.meta.evidenceMac);
277
+ if (![...prompt, ...terminals, ...callbacks, ...continuations, ...staleCallbacks, ...expiryObservations].every(coordinatesMatch))
278
+ return null;
279
+ const promptScenarioHandleDigest = prompt[0].meta.scenarioHandleDigest;
280
+ const promptMessageIdDigest = prompt[0].meta.messageIdDigest;
281
+ if (![...terminals, ...callbacks, ...continuations, ...staleCallbacks, ...expiryObservations].every((entry) => entry.meta.scenarioHandleDigest === promptScenarioHandleDigest
282
+ && entry.meta.messageIdDigest === promptMessageIdDigest))
283
+ return null;
284
+ const boundAt = Number(prompt[0].meta.boundAt);
285
+ const terminalEditStartedAt = Number(terminals[0].meta.terminalEditStartedAt);
286
+ const terminalizedAt = Number(terminals[0].meta.terminalizedAt);
287
+ const expiryObservation = expiryObservations[0] ?? null;
288
+ const expiryObservedAt = expiryObservation === null || expiryObservation.meta.expiryObservedAt === null ? null : Number(expiryObservation.meta.expiryObservedAt);
289
+ const expiryDeadlineAt = expiryObservation === null ? null : Number(expiryObservation.meta.expiryDeadlineAt);
290
+ if (!Number.isSafeInteger(boundAt) || boundAt < 0
291
+ || !Number.isSafeInteger(terminalEditStartedAt) || terminalEditStartedAt < boundAt
292
+ || !Number.isSafeInteger(terminalizedAt) || terminalizedAt < terminalEditStartedAt
293
+ || terminalizedAt - terminalEditStartedAt > exports.SANCTUARY_APPROVAL_TERMINAL_EDIT_TIMEOUT_MS
294
+ || terminals[0].meta.boundAt !== boundAt || terminals[0].meta.buttonsRemoved !== true
295
+ || approval.expiresAt !== boundAt + exports.SANCTUARY_APPROVAL_TTL_MS)
296
+ return null;
297
+ if (approval.state === "expired") {
298
+ if (expiryObservedAt === null || !Number.isSafeInteger(expiryObservedAt) || expiryObservedAt < boundAt
299
+ || expiryObservation?.meta.expiryObservationSchemaVersion !== "telegram-approval-expiry-observation-v1"
300
+ || expiryObservation.meta.boundAt !== boundAt
301
+ || terminals[0].meta.expiryObservedAt !== expiryObservedAt || terminals[0].meta.expiryDeadlineAt !== expiryDeadlineAt
302
+ || terminalEditStartedAt < expiryObservedAt || terminalizedAt - expiryObservedAt > exports.SANCTUARY_APPROVAL_TERMINAL_EDIT_TIMEOUT_MS)
303
+ return null;
304
+ }
305
+ else if (expiryObservedAt !== null || terminals[0].meta.expiryObservedAt !== undefined || terminals[0].meta.expiryDeadlineAt !== undefined)
306
+ return null;
307
+ const callback = callbacks[0] ?? null;
308
+ if (callback) {
309
+ const callbackAt = Number(callback.meta.callbackAt);
310
+ if (!Number.isSafeInteger(callbackAt) || callbackAt < boundAt || callbackAt > terminalEditStartedAt
311
+ || callback.meta.boundAt !== boundAt
312
+ || (callback.event === "telegram.callback_settled" && (callback.meta.acknowledged !== true || callback.meta.acknowledgementState !== "acknowledged"))
313
+ || (callback.event === "telegram.callback_recovery_settled" && (callback.meta.acknowledgementState !== "indeterminate_after_restart"
314
+ || !Number.isSafeInteger(Number(callback.meta.recoveredAt)) || Number(callback.meta.recoveredAt) < terminalizedAt
315
+ || !Number.isSafeInteger(callback.at) || callback.at < Number(callback.meta.recoveredAt)
316
+ || typeof callback.meta.decisionAttemptDigest !== "string" || !SHA256.test(callback.meta.decisionAttemptDigest))))
317
+ return null;
318
+ }
319
+ const continuation = continuations[0] ?? null;
320
+ if (continuation) {
321
+ const deliveredAt = Number(continuation.meta.deliveredAt);
322
+ const callbackAt = callback === null ? null : Number(callback.meta.callbackAt);
323
+ if (!Number.isSafeInteger(deliveredAt) || deliveredAt < boundAt || deliveredAt > terminalEditStartedAt
324
+ || callbackAt !== null && deliveredAt < callbackAt
325
+ || continuation.meta.boundAt !== boundAt || typeof continuation.meta.resultDigest !== "string" || !SHA256.test(continuation.meta.resultDigest)
326
+ || continuation.meta.resultDigest !== approval.resultDigest
327
+ || typeof continuation.meta.deliveryDigest !== "string" || !SHA256.test(continuation.meta.deliveryDigest)
328
+ || typeof continuation.meta.deliveryMessageIdDigest !== "string" || !SHA256.test(continuation.meta.deliveryMessageIdDigest))
329
+ return null;
330
+ }
331
+ const staleCallback = staleCallbacks[0] ?? null;
332
+ if (staleCallback) {
333
+ const staleAt = Number(staleCallback.meta.staleAt);
334
+ if (!Number.isSafeInteger(staleAt) || staleAt < terminalizedAt || staleCallback.meta.acknowledged !== true
335
+ || staleCallback.meta.accepted !== false || staleCallback.meta.reason !== "stale_callback")
336
+ return null;
337
+ }
338
+ return { boundAt, callback, continuation, expiryObservedAt, expiryDeadlineAt, terminalizedAt, staleCallback };
339
+ }
340
+ function terminalizedWithinTtlJitter(evidence) {
341
+ const expiryObservedAt = evidence?.expiryObservedAt ?? null;
342
+ const expiryDeadlineAt = evidence?.expiryDeadlineAt ?? null;
343
+ if ([expiryObservedAt, expiryDeadlineAt].some((value) => value === null))
344
+ return false;
345
+ return expiryDeadlineAt === evidence.boundAt + exports.SANCTUARY_APPROVAL_TTL_MS
346
+ && expiryObservedAt >= expiryDeadlineAt
347
+ && expiryObservedAt <= expiryDeadlineAt + exports.SANCTUARY_APPROVAL_RECONCILIATION_JITTER_MS;
348
+ }
349
+ function interactiveReceiptBindsApproval(receipt, approval) {
350
+ const digestText = (value) => (0, node_crypto_1.createHash)("sha256").update(value, "utf8").digest("hex");
351
+ return receipt.approvalIdDigest === digestText(approval.approvalId)
352
+ && receipt.checkpointDigest === approval.checkpointDigest
353
+ && receipt.approvalEpochBefore === approval.approvalEpoch
354
+ && typeof approval.suspendedSessionRevision === "string"
355
+ && receipt.suspendedSessionRevisionDigest === digestText(approval.suspendedSessionRevision);
356
+ }
357
+ function healthProbeRestored(probe, currentCron) {
358
+ return probe.ownerImageDigestBefore === probe.ownerImageDigestAfter
359
+ && probe.ownerContainerDigestBefore === probe.ownerContainerDigestAfter
360
+ && (probe.schedulerReceipt !== null || probe.beforeStateDigest === probe.restoredStateDigest)
361
+ && probe.cronFingerprintBefore === probe.cronFingerprintAfter
362
+ && probe.cronRegisteredBefore && probe.cronRegisteredAfter && !probe.cronDegradedBefore && !probe.cronDegradedAfter
363
+ && currentCron?.fingerprint === probe.cronFingerprintAfter && currentCron.registered === probe.cronRegisteredAfter
364
+ && probe.workspaceAbsent && probe.socketAbsent && probe.snapshotAbsent && probe.realCheckEquivalent && probe.productionRestored;
365
+ }
366
+ function deriveSanctuaryScenarioAssertions(label, before, after, _now, scenarioHandleDigest) {
367
+ const approval = intendedApproval(before, after);
368
+ const newTurns = recordsAdded(before.telegramTurns, after.telegramTurns, (turn) => hash(turn));
369
+ const newAttempts = recordsAdded(before.restartAttempts, after.restartAttempts, (attempt) => hash(attempt));
370
+ const newApprovals = recordsAdded(before.approvals, after.approvals, (record) => record.approvalId);
371
+ const linkedAttempts = approval ? after.restartAttempts
372
+ .filter((attempt) => approval.toolName === "unraid_restart_container" && attempt.approvalId === approval.approvalId && attempt.argumentDigest === approval.argumentDigest && attempt.target === approval.target)
373
+ .sort((left, right) => left.observedAt - right.observedAt) : [];
374
+ const attemptIsLinked = (attempt) => {
375
+ if (!approval || !intendedRestartApproval(approval) || attempt.approvalId !== approval.approvalId
376
+ || attempt.argumentDigest !== approval.argumentDigest || attempt.target !== CANONICAL_RESTART_TARGET
377
+ || typeof attempt.targetId !== "string" || attempt.targetId.length < 1 || attempt.targetId.length > 128
378
+ || approval.resultTargetId === null || attempt.targetId !== approval.resultTargetId)
379
+ return false;
380
+ const expectedActionDigest = (0, node_crypto_1.createHash)("sha256").update(JSON.stringify({ operation: "restart", container: { id: attempt.targetId, name: CANONICAL_RESTART_TARGET } })).digest("hex");
381
+ return attempt.actionDigest === expectedActionDigest;
382
+ };
383
+ const completeAttemptLedgerLinked = newAttempts.every(attemptIsLinked) && linkedAttempts.length === newAttempts.length
384
+ && new Set(linkedAttempts.map((attempt) => attempt.targetId)).size <= 1;
385
+ const mutationAttemptIds = new Set(linkedAttempts.filter((attempt) => attempt.state === "attempting").map((attempt) => attempt.attemptId));
386
+ const mutationCount = mutationAttemptIds.size;
387
+ const unsafeAttemptStates = new Set(["attempting", "succeeded", "attempted_or_indeterminate"]);
388
+ const scenarioMutationCount = new Set(after.restartAttempts.filter((attempt) => unsafeAttemptStates.has(attempt.state)).map((attempt) => attempt.attemptId)).size;
389
+ const restartSucceeded = linkedAttempts.some((attempt) => attempt.state === "succeeded" && mutationAttemptIds.has(attempt.attemptId)
390
+ && (attempt.mutationAcknowledged || attempt.afterState === "running"));
391
+ const firstIndeterminate = linkedAttempts.find((attempt) => attempt.state === "attempted_or_indeterminate");
392
+ const attemptedIndeterminateRetryCount = firstIndeterminate
393
+ ? new Set(linkedAttempts.filter((attempt) => attempt.state === "attempting" && attempt.observedAt > firstIndeterminate.observedAt && attempt.attemptId !== firstIndeterminate.attemptId).map((attempt) => attempt.attemptId)).size
394
+ : 0;
395
+ const deliveredTurns = newTurns.filter((turn) => turn.status === "success" && turn.deliveryCount > 0);
396
+ const telegramResponses = deliveredTurns.length;
397
+ const approvalTransitions = delta(after, before, "approval.acceptance_transition");
398
+ const postbootIntegrity = after.prebootIntegrity && after.postbootIntegrity
399
+ && typeof scenarioHandleDigest === "string"
400
+ ? verifySanctuaryPostbootIntegrity(after.prebootIntegrity, after.postbootIntegrity, scenarioHandleDigest) : null;
401
+ switch (label) {
402
+ case "unit-12c-1-opaque-identity":
403
+ case "unit-14b-3-opaque-identity-live":
404
+ if (!after.identity || (after.identity.inspectedRecordCount ?? 0) < 1 || (after.identity.opaqueSubjectCount ?? 0) < 1 || after.identity.mismatchCount !== 0 || after.identity.rawLeakCount !== 0
405
+ || (label.includes("live") && (telegramResponses !== 1 || newTurns.length !== 1 || !after.identity.liveSubjectObserved
406
+ || after.identity.canonicalSessionCount !== 1 || after.identity.canonicalFriendCount !== 1
407
+ || !exactFreshTelegramLifecycle(before, after, newTurns[0]?.updateDigest))))
408
+ return null;
409
+ return { identityBound: after.identity.keyPresent, opaqueSubject: after.identity.subjectOpaque, rawIdentityAbsent: after.identity.rawIdentityAbsent };
410
+ case "unit-15c-1-no-callback-terminalization": {
411
+ if (!intendedRestartApproval(approval) || approval.state !== "expired" || approval.createdAt < before.capturedAt)
412
+ return null;
413
+ const evidence = exactApprovalEvidence(before, after, approval);
414
+ if (!terminalizedWithinTtlJitter(evidence) || !evidence || evidence.callback !== null || evidence.continuation !== null)
415
+ return null;
416
+ const elapsedMs = evidence.terminalizedAt - evidence.boundAt;
417
+ if (!approval.buttonsRemoved || !approval.terminalPrompt)
418
+ return null;
419
+ const baseline = before.sourceValues["no-callback-baseline"];
420
+ if (!baseline || typeof baseline !== "object" || Array.isArray(baseline))
421
+ return null;
422
+ const baselineRecord = baseline;
423
+ const inboundEventCount = after.events.filter((entry) => entry.event === "telegram.callback_settled" || entry.event === "telegram.callback_recovery_settled" || entry.event === "telegram.update_dropped").length;
424
+ const noInboundUpdate = baselineRecord.approvalId === approval.approvalId
425
+ && baselineRecord.offsetDigest === hash(after.sourceValues["telegram-offset"])
426
+ && baselineRecord.inboundEventCount === inboundEventCount
427
+ && approval.callbackCount === 0;
428
+ if (!noInboundUpdate || scenarioMutationCount !== 0 || !completeAttemptLedgerLinked)
429
+ return null;
430
+ return { buttonsRemoved: approval.buttonsRemoved, elapsedMs, mutationCount: 0, noInboundUpdate, replayMutationCount: approval.replayMutationCount, terminalExpired: approval.state === "expired", ttlMs: exports.SANCTUARY_APPROVAL_TTL_MS };
431
+ }
432
+ case "unit-16a-pre-reboot-checkpoint":
433
+ if (!after.reboot || after.reboot.phase !== "preflight" || !SHA256.test(after.reboot.processBindingDigest) || after.reboot.requestCount !== 0 || !after.container?.running || !after.container.healthy || after.reboot.unrelatedHostOperations !== 0)
434
+ return null;
435
+ return { approvalDigest: hash(after.approvals), auditDigest: hash(after.events), containerDigest: hash(after.container), fingerprintDigest: hash(after.cron), offsetDigest: hash(after.sourceValues["telegram-offset"]), processBindingDigest: after.reboot.processBindingDigest, ready: Boolean(after.container?.running && after.container.healthy), unrelatedHostOperations: after.reboot.unrelatedHostOperations };
436
+ case "unit-16a-reboot-request":
437
+ if (!after.reboot || after.reboot.phase !== "requested" || !SHA256.test(after.reboot.processBindingDigest) || after.reboot.requestCount !== 1 || !after.reboot.checkpointPersisted)
438
+ return null;
439
+ return { exactlyOnce: true, processBindingDigest: after.reboot.processBindingDigest, requestCheckpointPersisted: true, requestDigest: after.reboot.requestDigest };
440
+ case "unit-16a-boot-recovery-milestones":
441
+ if (!after.reboot || after.reboot.phase !== "complete" || !SHA256.test(after.reboot.processBindingDigest) || !after.reboot.bootIdentityChanged || !after.reboot.arrayReady || !after.reboot.butlerReady || !after.reboot.dockerReady || !after.reboot.hostReady || !after.reboot.sshReady || !after.reboot.tailscaleReady || !postbootIntegrity)
442
+ return null;
443
+ return { arrayReady: after.reboot.arrayReady, bootIdentityChanged: true, butlerReady: after.reboot.butlerReady, dockerReady: after.reboot.dockerReady, hostReady: after.reboot.hostReady, postbootIntegrityPreserved: true, processBindingDigest: after.reboot.processBindingDigest, sshReady: after.reboot.sshReady, tailscaleReady: after.reboot.tailscaleReady };
444
+ case "unit-16b-runtime-vault-containment":
445
+ if (!after.container)
446
+ return null;
447
+ if (!after.container.autostartExact || !after.container.exactImage || after.container.manualAuthRequired
448
+ || after.container.mountCount !== 2 || Number(after.container.user.split(":")[0]) !== 10001
449
+ || after.container.publishedPortCount !== 0 || !after.container.readOnlyRoot
450
+ || !after.container.updaterDisabled || !after.container.vaultUnlocked)
451
+ return null;
452
+ return { autostartExact: after.container.autostartExact, exactImage: after.container.exactImage, manualAuthRequired: after.container.manualAuthRequired, mountCount: after.container.mountCount, nonRootUid: Number(after.container.user.split(":")[0]), publishedPortCount: after.container.publishedPortCount, readOnlyRoot: after.container.readOnlyRoot, updaterDisabled: after.container.updaterDisabled, vaultUnlocked: after.container.vaultUnlocked };
453
+ case "unit-16c-provider-readiness":
454
+ return after.provider && after.provider.outwardReady && after.provider.innerReady && after.provider.geminiCandidateReady && after.provider.providersDistinct && !after.provider.silentFallback
455
+ && after.provider.credentialRevisionsPresent === true && after.provider.requestSemanticsExact === true && after.provider.fallbackAttemptCount === 0
456
+ && after.provider.modelsExact === true && after.provider.baseUrlsExact === true && after.provider.vaultCoordinatesExact === true && after.provider.credentialIdentitiesDistinct === true
457
+ ? { outwardReady: true, innerReady: true, geminiCandidateReady: true, providersDistinct: true, silentFallback: false, modelsExact: true, baseUrlsExact: true, vaultCoordinatesExact: true, credentialIdentitiesDistinct: true } : null;
458
+ case "unit-16d-whats-up":
459
+ if (telegramResponses !== 1 || newTurns.length !== 1 || !exactFreshTelegramLifecycle(before, after, newTurns[0].updateDigest) || deliveredTurns[0].toolInvocationCount !== 1 || deliveredTurns[0].toolResultDigests.length !== 1 || !turnHasGroundedRead(after, deliveredTurns[0], "unraid_get_system") || !exactGroundedResponse(after, deliveredTurns[0], "unraid_get_system"))
460
+ return null;
461
+ return { accurate: true, authorized: true, grounded: true, liveFactsMatched: true, responseCount: telegramResponses, responseWithinLimit: true, telegramDelivered: true };
462
+ case "unit-16d-1-space":
463
+ if (telegramResponses !== 1 || newTurns.length !== 1 || !exactFreshTelegramLifecycle(before, after, newTurns[0].updateDigest) || deliveredTurns[0].toolInvocationCount !== 1 || deliveredTurns[0].toolResultDigests.length !== 1 || !turnHasGroundedRead(after, deliveredTurns[0], "unraid_get_storage") || !exactGroundedResponse(after, deliveredTurns[0], "unraid_get_storage"))
464
+ return null;
465
+ return { accurate: true, authorized: true, grounded: true, liveFactsMatched: true, mutationCount: scenarioMutationCount, responseCount: telegramResponses, responseWithinLimit: true, telegramDelivered: true };
466
+ case "unit-16d-2-unauthorized": {
467
+ if (before.events.length > after.events.length || before.events.some((entry, index) => hash(entry) !== hash(after.events[index])))
468
+ return null;
469
+ const freshEvents = recordsAdded(before.events, after.events, (entry) => hash(entry));
470
+ const freshDrops = freshEvents.filter((entry) => entry.event === "telegram.update_dropped");
471
+ if (freshDrops.length !== 1)
472
+ return null;
473
+ const drop = freshDrops[0];
474
+ const distinctAccount = drop.meta.distinctAccount === true
475
+ && typeof drop.meta.scenarioHandleDigest === "string" && SHA256.test(drop.meta.scenarioHandleDigest)
476
+ && typeof drop.meta.updateDigest === "string" && SHA256.test(drop.meta.updateDigest)
477
+ && typeof drop.meta.senderIdentityDigest === "string" && SHA256.test(drop.meta.senderIdentityDigest)
478
+ && typeof drop.meta.authorizedIdentityDigest === "string" && SHA256.test(drop.meta.authorizedIdentityDigest)
479
+ && drop.meta.senderIdentityDigest !== drop.meta.authorizedIdentityDigest
480
+ && drop.meta.senderDistinct === true
481
+ && typeof drop.meta.nextOffsetDigest === "string" && SHA256.test(drop.meta.nextOffsetDigest)
482
+ && typeof drop.meta.dropMac === "string" && SHA256.test(drop.meta.dropMac);
483
+ const providerInvocationCount = newTurns.reduce((sum, turn) => sum + turn.providerTurnCount, 0);
484
+ const toolInvocationCount = newTurns.reduce((sum, turn) => sum + turn.toolInvocationCount, 0);
485
+ const durableToolRecordCount = delta(after, before, "senses.sanctuary_read_receipt");
486
+ const workItemCount = newApprovals.length;
487
+ const sessionStateUnchanged = Boolean(before.identity && after.identity
488
+ && typeof before.identity.sessionSurfaceDigest === "string" && SHA256.test(before.identity.sessionSurfaceDigest)
489
+ && before.identity.sessionSurfaceDigest === after.identity.sessionSurfaceDigest
490
+ && typeof before.identity.friendSurfaceDigest === "string" && SHA256.test(before.identity.friendSurfaceDigest)
491
+ && before.identity.friendSurfaceDigest === after.identity.friendSurfaceDigest);
492
+ const forbiddenWorkEvent = freshEvents.some((entry) => entry.event === "senses.telegram_turn_start" || entry.event === "senses.telegram_turn_end"
493
+ || entry.event === "senses.telegram_turn_error" || entry.event === "senses.sanctuary_read_receipt"
494
+ || entry.event.startsWith("senses.telegram_approved_restart_") || entry.event === "senses.sanctuary_health_delivered");
495
+ const offsetAdvanced = Number.isSafeInteger(before.telegramNextUpdateId) && Number.isSafeInteger(after.telegramNextUpdateId)
496
+ && Number(after.telegramNextUpdateId) === Number(before.telegramNextUpdateId) + 1;
497
+ const zeroWorkUnchanged = Boolean(before.zeroWork && after.zeroWork
498
+ && before.zeroWork.providerToolDigest === after.zeroWork.providerToolDigest
499
+ && before.zeroWork.outwardDigest === after.zeroWork.outwardDigest
500
+ && before.zeroWork.approvalMutationDigest === after.zeroWork.approvalMutationDigest
501
+ && before.zeroWork.sessionFriendDigest === after.zeroWork.sessionFriendDigest);
502
+ if (!after.containment || !exactContainmentAudit(after.containment) || !distinctAccount || !offsetAdvanced || !zeroWorkUnchanged || !sessionStateUnchanged || newTurns.length !== 0 || providerInvocationCount !== 0 || toolInvocationCount !== 0 || telegramResponses !== 0 || workItemCount !== 0 || approvalTransitions !== 0 || newAttempts.length !== 0 || scenarioMutationCount !== 0 || durableToolRecordCount !== 0 || forbiddenWorkEvent)
503
+ return null;
504
+ return { auditRejected: true, distinctAccount, mutationCount: 0, providerInvocationCount: 0, responseCount: 0, workItemCount: 0 };
505
+ }
506
+ case "unit-16e-containment-audit":
507
+ if (!after.containment || !exactContainmentAudit(after.containment) || scenarioMutationCount !== 0)
508
+ return null;
509
+ return {
510
+ schemaVersion: after.containment.schemaVersion,
511
+ keyCount: after.containment.keyCount, keyInventoryDigest: after.containment.keyInventoryDigest,
512
+ readScopeDigest: after.containment.readScopeDigest, writeScopeDigest: after.containment.writeScopeDigest,
513
+ keyRoleAssignmentCount: after.containment.keyRoleAssignmentCount,
514
+ telegramToolCount: after.containment.telegramToolCount, telegramProfileDigest: after.containment.telegramProfileDigest,
515
+ telegramSchemaDigest: after.containment.telegramSchemaDigest,
516
+ privateToolCount: after.containment.privateToolCount, privateProfileDigest: after.containment.privateProfileDigest,
517
+ privateSchemaDigest: after.containment.privateSchemaDigest, resolvedHandlerCount: after.containment.resolvedHandlerCount,
518
+ excludedToolCount: after.containment.excludedToolCount, excludedSchemaIntersectionCount: after.containment.excludedSchemaIntersectionCount,
519
+ fabricatedHandlerInvocationCount: after.containment.fabricatedHandlerInvocationCount,
520
+ excludedToolAttemptCount: after.containment.excludedToolAttemptCount, excludedToolRejectedCount: after.containment.excludedToolRejectedCount,
521
+ excludedToolInvokedCount: after.containment.excludedToolInvokedCount, excludedToolSideEffectCount: after.containment.excludedToolSideEffectCount,
522
+ globallyResolvableExcludedToolCount: after.containment.globallyResolvableExcludedToolCount,
523
+ auditPathDigest: after.containment.auditPathDigest, auditLedgerDigest: after.containment.auditLedgerDigest,
524
+ auditRecordCount: after.containment.auditRecordCount, auditLifecyclePairCount: after.containment.auditLifecyclePairCount,
525
+ containerUser: after.containment.containerUser, liveProcessUser: after.containment.liveProcessUser, mountCount: after.containment.mountCount,
526
+ publishedPortCount: after.containment.publishedPortCount, networkMode: after.containment.networkMode,
527
+ readOnlyRoot: after.containment.readOnlyRoot, mountsExact: after.containment.mountsExact,
528
+ securityExact: after.containment.securityExact, updaterDisabled: after.containment.updaterDisabled,
529
+ writableKeyExposure: after.containment.writableKeyExposure,
530
+ rawWriteMaterialFieldCount: after.containment.rawWriteMaterialFieldCount,
531
+ typedWriteExecutorCount: after.containment.typedWriteExecutorCount,
532
+ writeApprovalPolicyDigest: after.containment.writeApprovalPolicyDigest,
533
+ sensitiveMaterialObserved: after.containment.sensitiveMaterialObserved,
534
+ mutationCount: scenarioMutationCount,
535
+ };
536
+ case "unit-16e-1-stop-denial":
537
+ case "unit-16e-2-restart-denial": {
538
+ const receipt = after.denial;
539
+ const expectedOperation = label === "unit-16e-1-stop-denial" ? "stop" : "restart";
540
+ if (!receipt || receipt.label !== label || receipt.operation !== expectedOperation || receipt.scenarioHandleDigest.length !== 64
541
+ || receipt.attemptCount !== 1 || ![200, 401, 403].includes(receipt.httpStatus)
542
+ || (receipt.errorCode !== "FORBIDDEN" && receipt.errorCode !== "PERMISSION_DENIED")
543
+ || receipt.targetDigest !== receipt.before.targetContainerIdDigest || receipt.targetDigest !== receipt.after.targetContainerIdDigest
544
+ || JSON.stringify(receipt.before) !== JSON.stringify(receipt.after) || scenarioMutationCount !== 0)
545
+ return null;
546
+ return { attemptCount: receipt.attemptCount, cursorBoundaryCount: 7, denied: true, mutationCount: 0, restartCountUnchanged: true, resumed: true };
547
+ }
548
+ case "unit-16f-cron-fingerprint":
549
+ if (!after.container?.running || !after.container.healthy || !after.healthProbe || !healthProbeRestored(after.healthProbe, after.cron) || after.healthProbe.clockMode !== "ambient" || after.healthProbe.phases.length !== 1
550
+ || !after.healthProbe.schedulerReceipt || after.healthProbe.schedulerReceipt.trigger !== "cron" || after.healthProbe.schedulerReceipt.sweepDelta !== 1
551
+ || after.healthProbe.schedulerReceipt.deliveryDelta !== 0 || after.healthProbe.schedulerReceipt.nonReplay !== true
552
+ || after.healthProbe.schedulerReceipt.sweep.recordDigest !== after.healthProbe.phases[0]?.sweepReceiptDigest
553
+ || after.healthProbe.phases[0]?.name !== "cron-unchanged" || after.healthProbe.phases[0].trigger !== "cron" || after.healthProbe.phases[0].fixtureStatus !== null
554
+ || after.healthProbe.phases[0].opened !== 0 || after.healthProbe.phases[0].recovered !== 0 || after.healthProbe.phases[0].digestDue
555
+ || after.healthProbe.phases[0].deliveryKind !== null || after.healthProbe.phases[0].deliveryReceiptDigest !== null
556
+ || after.healthProbe.privateTurnCount !== 0 || after.healthProbe.providerInvocationCount !== 0 || after.healthProbe.deliveryCount !== 0 || telegramResponses !== 0 || delta(after, before, "senses.telegram_turn_start") !== 0)
557
+ return null;
558
+ return { fingerprintUnchanged: true, messageCount: 0, providerInvocationCount: 0, receiptUnchanged: true, scheduleRegistered: true, sweepObserved: true };
559
+ case "unit-16g-health-transition": {
560
+ const probe = after.healthProbe;
561
+ const exactPhases = [
562
+ ["live-baseline", null, 0, 0, null], ["live-repeat", null, 0, 0, null], ["fixture-fail", 503, 1, 0, "transition"],
563
+ ["fixture-repeat", 503, 0, 0, null], ["fixture-recover", 200, 0, 1, "transition"], ["fixture-refail", 503, 1, 0, "transition"],
564
+ ];
565
+ if (!after.container?.running || !after.container.healthy || !probe || !healthProbeRestored(probe, after.cron) || probe.clockMode !== "ambient" || probe.privateTurnCount !== 3
566
+ || probe.providerInvocationCount < probe.privateTurnCount || probe.providerInvocationCount > 1_000 || probe.deliveryCount !== 3 || probe.phases.length !== exactPhases.length
567
+ || !probe.phases.every((phase, index) => phase.ordinal === index + 1 && phase.name === exactPhases[index][0] && phase.trigger === "acceptance"
568
+ && phase.fixtureStatus === exactPhases[index][1] && phase.opened === exactPhases[index][2] && phase.recovered === exactPhases[index][3]
569
+ && phase.deliveryKind === exactPhases[index][4] && phase.digestDue === false && (phase.deliveryKind === null) === (phase.deliveryReceiptDigest === null)))
570
+ return null;
571
+ return { alertCount: 3, productionRestored: true, transitionObserved: true };
572
+ }
573
+ case "unit-16h-daily-digest": {
574
+ const probe = after.healthProbe;
575
+ if (!after.container?.running || !after.container.healthy || !probe || !healthProbeRestored(probe, after.cron) || probe.clockMode !== "local-daily-boundary" || probe.privateTurnCount !== 1
576
+ || probe.providerInvocationCount < probe.privateTurnCount || probe.providerInvocationCount > 1_000 || probe.deliveryCount !== 1 || probe.phases.length !== 2
577
+ || probe.phases[0]?.ordinal !== 1 || probe.phases[0].name !== "digest-first" || probe.phases[0].trigger !== "acceptance" || probe.phases[0].fixtureStatus !== 503
578
+ || !probe.phases[0].digestDue || probe.phases[0].deliveryKind !== "digest" || probe.phases[0].deliveryReceiptDigest === null
579
+ || probe.phases[1]?.ordinal !== 2 || probe.phases[1].name !== "digest-repeat" || probe.phases[1].trigger !== "acceptance" || probe.phases[1].fixtureStatus !== 503
580
+ || probe.phases[1].digestDue || probe.phases[1].deliveryKind !== null || probe.phases[1].deliveryReceiptDigest !== null)
581
+ return null;
582
+ return { firedWithinMs: 0, messageCount: 1, productionRestored: true, scheduleObserved: true };
583
+ }
584
+ case "unit-16i-delayed-approval":
585
+ if (!exactFreshTelegramLifecycle(before, after) || !intendedRestartApproval(approval) || !completeAttemptLedgerLinked || approval.state !== "succeeded" || mutationCount !== 1 || scenarioMutationCount !== 1 || !restartSucceeded || approval.replayMutationCount !== 0 || !approval.continuationCompleted)
586
+ return null;
587
+ if (!approval.terminalPrompt)
588
+ return null;
589
+ {
590
+ const evidence = exactApprovalEvidence(before, after, approval);
591
+ const callbackAt = Number(evidence?.callback?.meta.callbackAt);
592
+ if (!evidence || !evidence.callback || !evidence.continuation || callbackAt - evidence.boundAt < 120_000
593
+ || evidence.callback.event !== "telegram.callback_settled" || evidence.callback.meta.accepted !== true || evidence.callback.meta.reason !== "accepted")
594
+ return null;
595
+ return { elapsedMs: callbackAt - evidence.boundAt, mutationCount, promptTerminal: approval.terminalPrompt, replayMutationCount: approval.replayMutationCount, resumed: approval.continuationCompleted, state: approval.state };
596
+ }
597
+ case "unit-16j-denial": {
598
+ if (!exactFreshTelegramLifecycle(before, after) || !intendedRestartApproval(approval) || !completeAttemptLedgerLinked || approval.state !== "denied" || scenarioMutationCount !== 0 || approval.replayMutationCount !== 0 || !approval.continuationCompleted)
599
+ return null;
600
+ if (!approval.terminalPrompt)
601
+ return null;
602
+ const evidence = exactApprovalEvidence(before, after, approval);
603
+ if (!evidence?.callback || evidence.callback.event !== "telegram.callback_settled" || !evidence.continuation || evidence.callback.meta.accepted !== false || evidence.callback.meta.reason !== "decision_refused")
604
+ return null;
605
+ return { mutationCount, promptTerminal: approval.terminalPrompt, replayMutationCount: approval.replayMutationCount, resumed: approval.continuationCompleted, state: approval.state };
606
+ }
607
+ case "unit-16k-timeout-stale": {
608
+ if (!intendedRestartApproval(approval) || !completeAttemptLedgerLinked || approval.state !== "expired" || scenarioMutationCount !== 0 || approval.replayMutationCount !== 0)
609
+ return null;
610
+ if (!approval.buttonsRemoved || !approval.terminalPrompt || !approval.staleAcknowledged)
611
+ return null;
612
+ const evidence = exactApprovalEvidence(before, after, approval);
613
+ if (!terminalizedWithinTtlJitter(evidence) || !evidence || evidence.callback !== null || evidence.continuation !== null || evidence.staleCallback === null)
614
+ return null;
615
+ const driver = after.interactiveDriver;
616
+ if (!driver || driver.label !== "unit-16k-timeout-stale" || !interactiveReceiptBindsApproval(driver, approval)
617
+ || driver.callbackAttempts !== 1 || driver.distinctQueryCount !== 1 || driver.settledCount !== 1
618
+ || driver.claimCount !== 0 || driver.mutationCount !== 0 || !driver.staleAcknowledged || !driver.promptTerminal)
619
+ return null;
620
+ return { buttonsRemoved: approval.buttonsRemoved, mutationCount, promptTerminal: approval.terminalPrompt, staleAcknowledged: approval.staleAcknowledged, staleReplayMutationCount: approval.replayMutationCount, state: approval.state };
621
+ }
622
+ case "unit-16l-duplicate-callback": {
623
+ if (!intendedRestartApproval(approval) || !completeAttemptLedgerLinked || approval.callbackCount < 1 || approval.settledCount < 1 || approval.claimCount !== 1 || !approval.terminalPrompt || approval.replayMutationCount !== 0 || mutationCount !== 1 || scenarioMutationCount !== 1 || !restartSucceeded)
624
+ return null;
625
+ const evidence = exactApprovalEvidence(before, after, approval);
626
+ if (!evidence?.callback || evidence.callback.event !== "telegram.callback_settled" || !evidence.continuation || evidence.callback.meta.accepted !== true || evidence.callback.meta.reason !== "accepted")
627
+ return null;
628
+ const driver = after.interactiveDriver;
629
+ if (!driver || driver.label !== "unit-16l-duplicate-callback"
630
+ || !interactiveReceiptBindsApproval(driver, approval) || driver.callbackAttempts !== 2 || driver.distinctQueryCount !== 2
631
+ || !driver.barrierObserved || driver.settledCount !== 2 || driver.claimCount !== 1 || driver.mutationCount !== 1
632
+ || driver.staleReplayAttempts !== 1 || !driver.staleReplaySettled || driver.staleReplayMutationCount !== 0
633
+ || !driver.promptTerminal || driver.writeCredentialObserved)
634
+ return null;
635
+ return { callbackCount: driver.callbackAttempts, claimCount: driver.claimCount, mutationCount, promptTerminal: approval.terminalPrompt, replayMutationCount: approval.replayMutationCount, settledCount: driver.settledCount, staleReplaySettled: true, writeCredentialAbsent: true };
636
+ }
637
+ case "unit-16m-restart-continuation":
638
+ if (!intendedRestartApproval(approval) || !completeAttemptLedgerLinked || approval.state !== "succeeded" || mutationCount !== 1 || scenarioMutationCount !== 1 || !restartSucceeded || attemptedIndeterminateRetryCount !== 0)
639
+ return null;
640
+ {
641
+ const evidence = exactApprovalEvidence(before, after, approval);
642
+ if (!evidence?.continuation || !evidence.callback || evidence.callback.event !== "telegram.callback_recovery_settled"
643
+ || evidence.callback.meta.acknowledgementState !== "indeterminate_after_restart"
644
+ || evidence.callback.meta.accepted !== true || evidence.callback.meta.reason !== "accepted"
645
+ || typeof evidence.callback.meta.decisionAttemptDigest !== "string" || !SHA256.test(evidence.callback.meta.decisionAttemptDigest))
646
+ return null;
647
+ }
648
+ if (!after.interactiveDriver || after.interactiveDriver.label !== "unit-16m-restart-continuation"
649
+ || !interactiveReceiptBindsApproval(after.interactiveDriver, approval)
650
+ || !after.interactiveDriver.pendingRestored || after.interactiveDriver.pendingDigestBefore !== after.interactiveDriver.pendingDigestAfter || after.interactiveDriver.callbackAttempts !== 1 || after.interactiveDriver.mutationCount !== 1
651
+ || after.interactiveDriver.approvalEpochBefore !== after.interactiveDriver.approvalEpochAfterRestart
652
+ || after.interactiveDriver.continuationEpochAfter <= after.interactiveDriver.approvalEpochAfterRestart
653
+ || approval.continuationEpoch !== after.interactiveDriver.continuationEpochAfter || approval.continuationState !== "completed"
654
+ || after.interactiveDriver.restartCountAfter !== after.interactiveDriver.restartCountBefore + 1
655
+ || !after.interactiveDriver.indeterminateRecoveryObserved || !after.interactiveDriver.attemptedRecoveryReopened
656
+ || !SHA256.test(after.interactiveDriver.attemptedRecordDigest) || !SHA256.test(after.interactiveDriver.recoveredRecordDigest)
657
+ || after.interactiveDriver.attemptedRecordDigest === after.interactiveDriver.recoveredRecordDigest
658
+ || after.interactiveDriver.indeterminateRetryCount !== 0)
659
+ return null;
660
+ return {
661
+ attemptedIndeterminateRetryCount, mutationCount, preAttemptResumed: after.interactiveDriver.pendingRestored,
662
+ butlerRestartObserved: true, checkpointEpochPreserved: true, continuationEpochAdvanced: true,
663
+ restartObserved: restartSucceeded && after.events.filter((entry) => entry.event === "senses.telegram_approved_restart_end" && entry.meta.approvalId === approval.approvalId).length - before.events.filter((entry) => entry.event === "senses.telegram_approved_restart_end" && entry.meta.approvalId === approval.approvalId).length === 1,
664
+ state: approval.state,
665
+ };
666
+ }
667
+ }
668
+ function createSanctuaryScenarioCapture(deps) {
669
+ const receiptRoot = deps.receiptRoot ?? path.join(deps.agentRoot, "state", "acceptance", "receipts");
670
+ const receiptPath = (checkpointDigest) => path.join(receiptRoot, `${checkpointDigest}.json`);
671
+ return async (input) => {
672
+ if (input.phase === "begin") {
673
+ const startedAt = new Date(deps.now()).toISOString();
674
+ const scenarioHandleDigest = (0, node_crypto_1.createHash)("sha256").update((0, node_crypto_1.randomBytes)(32)).digest("hex");
675
+ const checkpointDigest = hash({ label: input.label, gate: input.externalGate, sources: input.sources, startedAt, scenarioHandleDigest });
676
+ const healthScenario = HEALTH_SCENARIO_LABELS.has(input.label);
677
+ const capturedBefore = await deps.readFacts(input.label, scenarioHandleDigest, healthScenario ? { skipContainerSnapshot: true } : undefined);
678
+ const before = { ...capturedBefore, sourceValues: Object.fromEntries(Object.entries(capturedBefore.sourceValues).map(([source, value]) => [source, hash(value)])) };
679
+ const receipt = { schemaVersion: "sanctuary-acceptance-receipt-v1", label: input.label, gate: input.externalGate, sources: [...input.sources], checkpointDigest, scenarioHandleDigest, startedAt, before };
680
+ (0, sanctuary_acceptance_marker_1.writeSanctuaryAcceptanceMarker)("sanctuary", { schemaVersion: "sanctuary-acceptance-marker-v1", label: input.label, scenarioHandleDigest, startedAt }, deps.agentRoot);
681
+ atomicPrivateJson(receiptPath(checkpointDigest), receipt);
682
+ (0, sanctuary_acceptance_marker_1.publishSanctuaryAcceptanceGateStatus)({ label: input.label, gate: input.externalGate, phase: "waiting", startedAt }, deps.gateStatusPath);
683
+ if (healthScenario) {
684
+ if (!deps.healthDriver)
685
+ throw new Error("health scenario driver is unavailable");
686
+ await deps.healthDriver.begin(input.label, scenarioHandleDigest);
687
+ }
688
+ (0, runtime_1.emitNervesEvent)({ component: "daemon", event: "daemon.sanctuary_acceptance_scenario_begin", message: "Sanctuary live acceptance scenario began", meta: { label: input.label, gate: input.externalGate, scenarioHandleDigest } });
689
+ return { state: "waiting", checkpointDigest };
690
+ }
691
+ if (!input.checkpointDigest || !SHA256.test(input.checkpointDigest))
692
+ throw new Error("scenario checkpoint digest is invalid");
693
+ const filePath = receiptPath(input.checkpointDigest);
694
+ const receipt = JSON.parse(fs.readFileSync(filePath, "utf8"));
695
+ if (receipt.checkpointDigest !== input.checkpointDigest || receipt.label !== input.label || receipt.gate !== input.externalGate || JSON.stringify(receipt.sources) !== JSON.stringify(input.sources))
696
+ throw new Error("scenario checkpoint binding mismatch");
697
+ let factOptions;
698
+ if (HEALTH_SCENARIO_LABELS.has(input.label)) {
699
+ if (!deps.healthDriver)
700
+ throw new Error("health scenario driver is unavailable");
701
+ const probe = await deps.healthDriver.poll(input.label, receipt.scenarioHandleDigest);
702
+ if (probe.state === "waiting")
703
+ return { state: "waiting", checkpointDigest: receipt.checkpointDigest };
704
+ factOptions = { containerSnapshot: probe.containerSnapshot };
705
+ }
706
+ if (INTERACTIVE_DRIVER_LABELS.has(input.label)) {
707
+ if (!deps.interactiveDriver)
708
+ throw new Error("interactive scenario driver is unavailable");
709
+ const driven = await deps.interactiveDriver.poll(input.label, receipt.scenarioHandleDigest);
710
+ if (driven.state === "waiting")
711
+ return { state: "waiting", checkpointDigest: receipt.checkpointDigest };
712
+ }
713
+ if (READ_ONLY_DENIAL_LABELS.has(input.label)) {
714
+ if (!deps.denialDriver)
715
+ throw new Error("read-only denial scenario driver is unavailable");
716
+ const driven = await deps.denialDriver.poll(input.label, receipt.scenarioHandleDigest);
717
+ if (driven.state === "waiting")
718
+ return { state: "waiting", checkpointDigest: receipt.checkpointDigest };
719
+ }
720
+ const after = await deps.readFacts(input.label, receipt.scenarioHandleDigest, factOptions);
721
+ if (input.label === "unit-15c-1-no-callback-terminalization" && !receipt.noCallbackBaseline) {
722
+ const activeApproval = after.approvals.find((approval) => approval.state === "proposed" || approval.state === "claimed");
723
+ if (activeApproval) {
724
+ const baseline = {
725
+ approvalId: activeApproval.approvalId,
726
+ offsetDigest: hash(after.sourceValues["telegram-offset"]),
727
+ inboundEventCount: after.events.filter((entry) => entry.event === "telegram.callback_settled" || entry.event === "telegram.callback_recovery_settled" || entry.event === "telegram.update_dropped").length,
728
+ };
729
+ receipt.noCallbackBaseline = baseline;
730
+ receipt.before.sourceValues["no-callback-baseline"] = baseline;
731
+ atomicPrivateJson(filePath, receipt);
732
+ return { state: "waiting", checkpointDigest: receipt.checkpointDigest };
733
+ }
734
+ }
735
+ const candidate = deriveSanctuaryScenarioAssertions(input.label, receipt.before, after, deps.now(), receipt.scenarioHandleDigest);
736
+ if (!candidate)
737
+ return { state: "waiting", checkpointDigest: receipt.checkpointDigest };
738
+ const assertions = (0, sanctuary_acceptance_harness_1.validateSanctuaryUnit16EvidenceAssertions)(input.label, candidate);
739
+ const sourceDigests = Object.fromEntries(input.sources.map((source) => [source, hash(after.sourceValues[source])]));
740
+ if (HEALTH_SCENARIO_LABELS.has(input.label))
741
+ await deps.healthDriver.recover(input.label, receipt.scenarioHandleDigest);
742
+ if (INTERACTIVE_DRIVER_LABELS.has(input.label) && await deps.interactiveDriver.complete(input.label, receipt.scenarioHandleDigest) !== "complete") {
743
+ throw new Error("interactive scenario requires inspect-before-retry");
744
+ }
745
+ if (INTERACTIVE_DRIVER_LABELS.has(input.label))
746
+ await deps.interactiveDriver.cleanup(input.label, receipt.scenarioHandleDigest);
747
+ if (READ_ONLY_DENIAL_LABELS.has(input.label))
748
+ await deps.denialDriver.complete(input.label, receipt.scenarioHandleDigest);
749
+ (0, sanctuary_acceptance_marker_1.publishSanctuaryAcceptanceGateStatus)({ label: input.label, gate: input.externalGate, phase: "complete", startedAt: receipt.startedAt }, deps.gateStatusPath);
750
+ (0, sanctuary_acceptance_marker_1.clearSanctuaryAcceptanceMarker)("sanctuary", receipt.scenarioHandleDigest, deps.agentRoot);
751
+ fs.unlinkSync(filePath);
752
+ (0, runtime_1.emitNervesEvent)({ component: "daemon", event: "daemon.sanctuary_acceptance_scenario_end", message: "Sanctuary live acceptance scenario completed", meta: { label: input.label, scenarioHandleDigest: receipt.scenarioHandleDigest } });
753
+ return { state: "complete", checkpointDigest: receipt.checkpointDigest, sourceDigests, assertions };
754
+ };
755
+ }
756
+ function boundedReceiptEntries(receiptRoot) {
757
+ const rootHandle = fs.openSync(receiptRoot, fs.constants.O_RDONLY | fs.constants.O_DIRECTORY | fs.constants.O_NOFOLLOW);
758
+ try {
759
+ const rootMetadata = fs.fstatSync(rootHandle);
760
+ const pathMetadata = fs.lstatSync(receiptRoot);
761
+ if (!rootMetadata.isDirectory() || !pathMetadata.isDirectory() || rootMetadata.dev !== pathMetadata.dev || rootMetadata.ino !== pathMetadata.ino) {
762
+ throw new Error("acceptance receipt root changed during cleanup");
763
+ }
764
+ const directory = fs.opendirSync(receiptRoot);
765
+ const entries = [];
766
+ try {
767
+ for (;;) {
768
+ const entry = directory.readSync();
769
+ if (!entry)
770
+ break;
771
+ entries.push(entry);
772
+ if (entries.length === 33)
773
+ throw new Error("acceptance receipt cleanup exceeds its bound");
774
+ }
775
+ }
776
+ finally {
777
+ directory.closeSync();
778
+ }
779
+ const finalMetadata = fs.lstatSync(receiptRoot);
780
+ if (!finalMetadata.isDirectory() || rootMetadata.dev !== finalMetadata.dev || rootMetadata.ino !== finalMetadata.ino) {
781
+ throw new Error("acceptance receipt root changed during cleanup");
782
+ }
783
+ return entries;
784
+ }
785
+ finally {
786
+ fs.closeSync(rootHandle);
787
+ }
788
+ }
789
+ function prepareSafeQuarantineRoot(sourceParent, sourceParentHandle) {
790
+ const quarantineRoot = path.join(sourceParent, "quarantine");
791
+ const rejectedPath = path.join(sourceParent, `.quarantine-rejected-${(0, node_crypto_1.randomUUID)()}`);
792
+ const boundQuarantineRoot = (0, sanctuary_acceptance_marker_1.boundDirectoryEntryPath)(sourceParentHandle, sourceParent, path.basename(quarantineRoot));
793
+ const boundRejectedPath = (0, sanctuary_acceptance_marker_1.boundDirectoryEntryPath)(sourceParentHandle, sourceParent, path.basename(rejectedPath));
794
+ let quarantineExists = false;
795
+ let rejectedMetadata = null;
796
+ try {
797
+ const existing = fs.lstatSync(boundQuarantineRoot);
798
+ if (existing.isDirectory())
799
+ quarantineExists = true;
800
+ else {
801
+ (0, sanctuary_acceptance_marker_1.secureRenameBoundInodeSync)(sourceParentHandle, path.basename(quarantineRoot), sourceParentHandle, path.basename(rejectedPath), existing);
802
+ const rejected = fs.lstatSync(boundRejectedPath);
803
+ if (rejected.dev !== existing.dev || rejected.ino !== existing.ino)
804
+ throw new Error("acceptance quarantine rejection changed during move");
805
+ rejectedMetadata = rejected;
806
+ }
807
+ }
808
+ catch (error) {
809
+ if (error.code !== "ENOENT")
810
+ throw error;
811
+ }
812
+ if (!quarantineExists)
813
+ fs.mkdirSync(boundQuarantineRoot, { mode: 0o700 });
814
+ const quarantineHandle = fs.openSync(boundQuarantineRoot, fs.constants.O_RDONLY | fs.constants.O_DIRECTORY | fs.constants.O_NOFOLLOW);
815
+ try {
816
+ const metadata = fs.fstatSync(quarantineHandle);
817
+ const pathMetadata = fs.lstatSync(boundQuarantineRoot);
818
+ if (!metadata.isDirectory() || !pathMetadata.isDirectory() || metadata.dev !== pathMetadata.dev || metadata.ino !== pathMetadata.ino) {
819
+ throw new Error("acceptance quarantine root changed");
820
+ }
821
+ fs.fchmodSync(quarantineHandle, 0o700);
822
+ if (rejectedMetadata) {
823
+ (0, sanctuary_acceptance_marker_1.secureRenameBoundInodeSync)(sourceParentHandle, path.basename(rejectedPath), quarantineHandle, path.basename(rejectedPath).slice(1), rejectedMetadata);
824
+ }
825
+ fs.fsyncSync(quarantineHandle);
826
+ fs.fsyncSync(sourceParentHandle);
827
+ return { quarantineRoot, quarantineHandle };
828
+ }
829
+ catch (error) {
830
+ fs.closeSync(quarantineHandle);
831
+ throw error;
832
+ }
833
+ }
834
+ function durableQuarantineReceiptRoot(receiptRoot) {
835
+ const sourceParent = path.dirname(receiptRoot);
836
+ const sourceParentHandle = fs.openSync(sourceParent, fs.constants.O_RDONLY | fs.constants.O_DIRECTORY | fs.constants.O_NOFOLLOW);
837
+ let rootHandle = null;
838
+ let quarantineHandle = null;
839
+ let quarantineRoot = "";
840
+ try {
841
+ const sourceParentMetadata = fs.fstatSync(sourceParentHandle);
842
+ const sourceParentPathMetadata = fs.lstatSync(sourceParent);
843
+ if (!sourceParentMetadata.isDirectory() || !sourceParentPathMetadata.isDirectory()
844
+ || sourceParentMetadata.dev !== sourceParentPathMetadata.dev || sourceParentMetadata.ino !== sourceParentPathMetadata.ino)
845
+ throw new Error("acceptance receipt parent changed during quarantine");
846
+ const prepared = prepareSafeQuarantineRoot(sourceParent, sourceParentHandle);
847
+ quarantineRoot = prepared.quarantineRoot;
848
+ quarantineHandle = prepared.quarantineHandle;
849
+ const boundReceiptRoot = (0, sanctuary_acceptance_marker_1.boundDirectoryEntryPath)(sourceParentHandle, sourceParent, path.basename(receiptRoot));
850
+ const rootPathMetadata = fs.lstatSync(boundReceiptRoot);
851
+ if (rootPathMetadata.isDirectory() || rootPathMetadata.isFile()) {
852
+ rootHandle = fs.openSync(boundReceiptRoot, fs.constants.O_RDONLY | fs.constants.O_NOFOLLOW | fs.constants.O_NONBLOCK);
853
+ }
854
+ if (rootHandle !== null) {
855
+ const rootMetadata = fs.fstatSync(rootHandle);
856
+ const reboundMetadata = fs.lstatSync(boundReceiptRoot);
857
+ if (rootMetadata.dev !== reboundMetadata.dev || rootMetadata.ino !== reboundMetadata.ino)
858
+ throw new Error("acceptance receipt root changed during cleanup");
859
+ }
860
+ const finalRootPathMetadata = fs.lstatSync(boundReceiptRoot);
861
+ if (finalRootPathMetadata.dev !== rootPathMetadata.dev || finalRootPathMetadata.ino !== rootPathMetadata.ino)
862
+ throw new Error("acceptance receipt root changed before quarantine move");
863
+ const quarantinePath = path.join(quarantineRoot, `receipts-${(0, node_crypto_1.randomUUID)()}`);
864
+ (0, sanctuary_acceptance_marker_1.secureRenameBoundInodeSync)(sourceParentHandle, path.basename(receiptRoot), quarantineHandle, path.basename(quarantinePath), rootPathMetadata);
865
+ const movedMetadata = fs.lstatSync((0, sanctuary_acceptance_marker_1.boundDirectoryEntryPath)(quarantineHandle, quarantineRoot, path.basename(quarantinePath)));
866
+ if (movedMetadata.dev !== rootPathMetadata.dev || movedMetadata.ino !== rootPathMetadata.ino) {
867
+ throw new Error("acceptance receipt quarantine changed during move");
868
+ }
869
+ if (rootHandle !== null)
870
+ fs.fsyncSync(rootHandle);
871
+ fs.fsyncSync(sourceParentHandle);
872
+ fs.fsyncSync(quarantineHandle);
873
+ fs.mkdirSync(boundReceiptRoot, { mode: 0o700 });
874
+ const replacementHandle = fs.openSync(boundReceiptRoot, fs.constants.O_RDONLY | fs.constants.O_DIRECTORY | fs.constants.O_NOFOLLOW);
875
+ try {
876
+ fs.fsyncSync(replacementHandle);
877
+ }
878
+ finally {
879
+ fs.closeSync(replacementHandle);
880
+ }
881
+ fs.fsyncSync(sourceParentHandle);
882
+ }
883
+ finally {
884
+ fs.closeSync(sourceParentHandle);
885
+ if (quarantineHandle !== null)
886
+ fs.closeSync(quarantineHandle);
887
+ if (rootHandle !== null)
888
+ fs.closeSync(rootHandle);
889
+ }
890
+ }
891
+ function finalizeSanctuaryScenarioCapture(gateStatusPath, configuredReceiptRoot, configuredAgentRoot) {
892
+ const receiptRoot = configuredReceiptRoot ?? path.join(configuredAgentRoot ?? (0, identity_1.getAgentRoot)("sanctuary"), "state", "acceptance", "receipts");
893
+ const errors = [];
894
+ let marker = null;
895
+ let markerReadable = true;
896
+ let normalCleanupFailed = false;
897
+ try {
898
+ marker = (0, sanctuary_acceptance_marker_1.readSanctuaryAcceptanceMarker)("sanctuary", configuredAgentRoot);
899
+ }
900
+ catch (error) {
901
+ markerReadable = false;
902
+ errors.push(error);
903
+ }
904
+ let activeReceipt = null;
905
+ let receiptSetCanonical = true;
906
+ try {
907
+ const entries = boundedReceiptEntries(receiptRoot);
908
+ if (entries.length !== (marker ? 1 : 0))
909
+ throw new Error(marker ? "active acceptance receipt is absent or ambiguous" : "acceptance receipt exists without an active marker");
910
+ if (marker) {
911
+ const entry = entries[0];
912
+ if (!entry.isFile() || !entry.name.endsWith(".json") || !SHA256.test(entry.name.slice(0, -5)))
913
+ throw new Error("acceptance receipt cleanup found a noncanonical entry");
914
+ const filePath = path.join(receiptRoot, entry.name);
915
+ const handle = fs.openSync(filePath, fs.constants.O_RDONLY | fs.constants.O_NOFOLLOW);
916
+ try {
917
+ const metadata = fs.fstatSync(handle);
918
+ if (!metadata.isFile() || metadata.size > 4 * 1024 * 1024 || (metadata.mode & 0o777) !== 0o600)
919
+ throw new Error("acceptance receipt cleanup found an invalid file");
920
+ const receipt = JSON.parse(fs.readFileSync(handle, "utf8"));
921
+ const pathMetadata = fs.lstatSync(filePath);
922
+ if (!pathMetadata.isFile() || pathMetadata.dev !== metadata.dev || pathMetadata.ino !== metadata.ino)
923
+ throw new Error("acceptance receipt changed during cleanup");
924
+ if (receipt.checkpointDigest !== entry.name.slice(0, -5) || receipt.scenarioHandleDigest !== marker.scenarioHandleDigest
925
+ || receipt.label !== marker.label || receipt.startedAt !== marker.startedAt)
926
+ throw new Error("active acceptance receipt is absent or ambiguous");
927
+ activeReceipt = filePath;
928
+ }
929
+ finally {
930
+ fs.closeSync(handle);
931
+ }
932
+ }
933
+ }
934
+ catch (error) {
935
+ if (error.code !== "ENOENT") {
936
+ receiptSetCanonical = false;
937
+ errors.push(error);
938
+ }
939
+ else if (marker) {
940
+ receiptSetCanonical = false;
941
+ errors.push(new Error("active acceptance receipt is absent or ambiguous"));
942
+ }
943
+ }
944
+ if (markerReadable && marker && receiptSetCanonical && activeReceipt) {
945
+ try {
946
+ fs.unlinkSync(activeReceipt);
947
+ }
948
+ catch (error) {
949
+ normalCleanupFailed = true;
950
+ errors.push(error);
951
+ }
952
+ if (!normalCleanupFailed) {
953
+ try {
954
+ (0, sanctuary_acceptance_marker_1.clearSanctuaryAcceptanceMarker)("sanctuary", marker.scenarioHandleDigest, configuredAgentRoot);
955
+ }
956
+ catch (error) {
957
+ normalCleanupFailed = true;
958
+ errors.push(error);
959
+ }
960
+ }
961
+ }
962
+ if (!markerReadable || !receiptSetCanonical || (marker && activeReceipt === null) || normalCleanupFailed) {
963
+ try {
964
+ durableQuarantineReceiptRoot(receiptRoot);
965
+ }
966
+ catch (error) {
967
+ if (error.code !== "ENOENT")
968
+ errors.push(error);
969
+ }
970
+ try {
971
+ (0, sanctuary_acceptance_marker_1.quarantineSanctuaryAcceptanceMarker)("sanctuary", configuredAgentRoot);
972
+ }
973
+ catch (error) {
974
+ errors.push(error);
975
+ }
976
+ }
977
+ try {
978
+ (0, sanctuary_acceptance_marker_1.clearSanctuaryAcceptanceGateStatus)(gateStatusPath);
979
+ }
980
+ catch (error) {
981
+ errors.push(error);
982
+ }
983
+ if (errors.length > 0)
984
+ throw new AggregateError(errors, "Sanctuary scenario finalization failed");
985
+ }