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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (647) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +154 -23
  3. package/RepairGuide.ouro/agent.json +5 -0
  4. package/RepairGuide.ouro/psyche/IDENTITY.md +19 -0
  5. package/RepairGuide.ouro/psyche/SOUL.md +55 -0
  6. package/RepairGuide.ouro/skills/diagnose-broken-remote.md +63 -0
  7. package/RepairGuide.ouro/skills/diagnose-stacked-typed-issues.md +35 -0
  8. package/RepairGuide.ouro/skills/diagnose-sync-blocked.md +54 -0
  9. package/RepairGuide.ouro/skills/diagnose-vault-expired.md +60 -0
  10. package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/agent.json +4 -2
  11. package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/SOUL.md +2 -2
  12. package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/the-serpent.md +1 -1
  13. package/assets/bluebubbles-host +264 -0
  14. package/assets/sanctuary-host-launcher.sh +16 -0
  15. package/changelog.json +5302 -16
  16. package/deploy/unraid/Dockerfile +54 -0
  17. package/deploy/unraid/README.txt +2983 -0
  18. package/deploy/unraid/audit-container-spec.sh +4 -0
  19. package/deploy/unraid/container-runtime.json +1 -0
  20. package/deploy/unraid/docker-man-template-transaction.mjs +711 -0
  21. package/deploy/unraid/docker-man-template-xml.cjs +105 -0
  22. package/deploy/unraid/migrate-sanctuary-bundle.mjs +60 -0
  23. package/deploy/unraid/ouro-events/bootstrap-spool.sh +92 -0
  24. package/deploy/unraid/ouro-events/emit-event.mjs +310 -0
  25. package/deploy/unraid/ouro-events/emit-usenet-event.sh +54 -0
  26. package/deploy/unraid/ouro-events/install-usenet-guard.sh +435 -0
  27. package/deploy/unraid/ouro-events/usenet-health.sh +176 -0
  28. package/deploy/unraid/sanctuary-acceptance-adapter.sh +43 -0
  29. package/deploy/unraid/sanctuary-acceptance-contract.json +198 -0
  30. package/deploy/unraid/sanctuary-acceptance-harness.sh +23 -0
  31. package/deploy/unraid/sanctuary-authority-installation.json +36 -0
  32. package/deploy/unraid/sanctuary-authority-service.sh +30 -0
  33. package/deploy/unraid/sanctuary-deployment-target.mjs +710 -0
  34. package/deploy/unraid/sanctuary-safe-rename.py +24 -0
  35. package/deploy/unraid/sanctuary-unit16-host-broker.mjs +2084 -0
  36. package/deploy/unraid/sanctuary-unit16-run.sh +560 -0
  37. package/deploy/unraid/sanctuary-unit18-target-audit.sh +27 -0
  38. package/deploy/unraid/sanctuary.ouro/agent.json +66 -0
  39. package/deploy/unraid/sanctuary.ouro/arc/README.md +3 -0
  40. package/deploy/unraid/sanctuary.ouro/bundle-meta.json +5 -0
  41. package/deploy/unraid/sanctuary.ouro/habits/sanctuary-health.md +8 -0
  42. package/deploy/unraid/sanctuary.ouro/mcp/media-mcp.mjs +795 -0
  43. package/deploy/unraid/sanctuary.ouro/provider-readiness.json +11 -0
  44. package/deploy/unraid/sanctuary.ouro/psyche/ASPIRATIONS.md +5 -0
  45. package/deploy/unraid/sanctuary.ouro/psyche/IDENTITY.md +5 -0
  46. package/deploy/unraid/sanctuary.ouro/psyche/LORE.md +15 -0
  47. package/deploy/unraid/sanctuary.ouro/psyche/SOUL.md +25 -0
  48. package/deploy/unraid/sanctuary.ouro/psyche/TACIT.md +15 -0
  49. package/deploy/unraid/sanctuary.ouro/state/policy/steward.json +7 -0
  50. package/deploy/unraid/sanctuary.ouro/tool-profiles.json +135 -0
  51. package/deploy/unraid/sanctuary.xml +28 -0
  52. package/dist/a2a/card.js +57 -0
  53. package/dist/a2a/client.js +180 -0
  54. package/dist/a2a/config.js +50 -0
  55. package/dist/a2a/delegation-stores.js +56 -0
  56. package/dist/a2a/did-resolution.js +93 -0
  57. package/dist/a2a/identity.js +170 -0
  58. package/dist/a2a/inbound-share.js +107 -0
  59. package/dist/a2a/mission-result-wire.js +196 -0
  60. package/dist/a2a/onboarding.js +115 -0
  61. package/dist/a2a/pin-store.js +132 -0
  62. package/dist/a2a/seen-ledger.js +120 -0
  63. package/dist/a2a/server.js +604 -0
  64. package/dist/a2a/task-store.js +69 -0
  65. package/dist/a2a/transport.js +45 -0
  66. package/dist/a2a/types.js +3 -0
  67. package/dist/arc/attention-types.js +8 -0
  68. package/dist/arc/cares.js +360 -0
  69. package/dist/arc/episodes.js +118 -0
  70. package/dist/arc/evolution.js +490 -0
  71. package/dist/arc/flight-recorder.js +1034 -0
  72. package/dist/arc/intentions.js +134 -0
  73. package/dist/arc/json-store.js +117 -0
  74. package/dist/arc/obligations.js +386 -0
  75. package/dist/arc/packets.js +336 -0
  76. package/dist/arc/presence.js +185 -0
  77. package/dist/arc/task-lifecycle.js +57 -0
  78. package/dist/commerce/store.js +755 -0
  79. package/dist/commerce/types.js +3 -0
  80. package/dist/heart/active-work.js +798 -40
  81. package/dist/heart/agent-entry.js +252 -3
  82. package/dist/heart/approval-files.js +113 -0
  83. package/dist/heart/approval-store.js +960 -0
  84. package/dist/heart/attachments/image-normalize.js +194 -0
  85. package/dist/heart/attachments/materialize.js +97 -0
  86. package/dist/heart/attachments/originals.js +88 -0
  87. package/dist/heart/attachments/render.js +29 -0
  88. package/dist/heart/attachments/sources/bluebubbles.js +156 -0
  89. package/dist/heart/attachments/sources/cli-local-file.js +78 -0
  90. package/dist/heart/attachments/sources/index.js +18 -0
  91. package/dist/heart/attachments/sources/telegram.js +71 -0
  92. package/dist/heart/attachments/store.js +132 -0
  93. package/dist/heart/attachments/types.js +93 -0
  94. package/dist/heart/auth/auth-flow.js +495 -0
  95. package/dist/heart/autonomy-budget.js +478 -0
  96. package/dist/heart/awaiting/await-alert.js +148 -0
  97. package/dist/heart/awaiting/await-expiry.js +143 -0
  98. package/dist/heart/awaiting/await-loader.js +91 -0
  99. package/dist/heart/awaiting/await-parser.js +149 -0
  100. package/dist/heart/awaiting/await-runtime-state.js +111 -0
  101. package/dist/heart/awaiting/await-scheduler.js +382 -0
  102. package/dist/heart/background-operations.js +281 -0
  103. package/dist/heart/bridges/manager.js +137 -17
  104. package/dist/heart/bridges/store.js +14 -2
  105. package/dist/heart/bundle-state.js +168 -0
  106. package/dist/heart/commitments.js +44 -18
  107. package/dist/heart/config-registry.js +331 -0
  108. package/dist/heart/config.js +174 -131
  109. package/dist/heart/context-loss-gauntlet.js +387 -0
  110. package/dist/heart/context-loss-sentinel.js +1157 -0
  111. package/dist/heart/core.js +2169 -581
  112. package/dist/heart/cross-chat-delivery.js +9 -22
  113. package/dist/heart/daemon/agent-config-check.js +451 -0
  114. package/dist/heart/daemon/agent-discovery.js +185 -3
  115. package/dist/heart/daemon/agent-service.js +542 -0
  116. package/dist/heart/daemon/agentic-repair.js +547 -0
  117. package/dist/heart/daemon/await-private-wake.js +59 -0
  118. package/dist/heart/daemon/bluebubbles-cli-integration.js +71 -0
  119. package/dist/heart/daemon/bluebubbles-health-diagnostics.js +122 -0
  120. package/dist/heart/daemon/bluebubbles-host-protocol.js +433 -0
  121. package/dist/heart/daemon/bluebubbles-host.js +273 -0
  122. package/dist/heart/daemon/boot-sync-probe.js +197 -0
  123. package/dist/heart/daemon/cadence.js +180 -0
  124. package/dist/heart/daemon/cli-defaults.js +854 -0
  125. package/dist/heart/daemon/cli-desk.js +322 -0
  126. package/dist/heart/daemon/cli-exec.js +8822 -0
  127. package/dist/heart/daemon/cli-help.js +716 -0
  128. package/dist/heart/daemon/cli-parse.js +2771 -0
  129. package/dist/heart/daemon/cli-render-doctor.js +57 -0
  130. package/dist/heart/daemon/cli-render.js +777 -0
  131. package/dist/heart/daemon/cli-types.js +8 -0
  132. package/dist/heart/daemon/connect-bay.js +323 -0
  133. package/dist/heart/daemon/container-credential-bootstrap.js +273 -0
  134. package/dist/heart/daemon/container-healthcheck.js +112 -0
  135. package/dist/heart/daemon/container-runtime.js +97 -0
  136. package/dist/heart/daemon/container-spec-auditor-main.js +171 -0
  137. package/dist/heart/daemon/container-spec-auditor.js +229 -0
  138. package/dist/heart/daemon/daemon-bootstrap-startup.js +115 -0
  139. package/dist/heart/daemon/daemon-cli.js +29 -2349
  140. package/dist/heart/daemon/daemon-entry.js +930 -11
  141. package/dist/heart/daemon/daemon-health.js +186 -0
  142. package/dist/heart/daemon/daemon-rollup.js +57 -0
  143. package/dist/heart/daemon/daemon-runtime-sync.js +88 -13
  144. package/dist/heart/daemon/daemon-tombstone.js +236 -0
  145. package/dist/heart/daemon/daemon.js +2084 -105
  146. package/dist/heart/daemon/dns-workflow.js +394 -0
  147. package/dist/heart/daemon/doctor-types.js +8 -0
  148. package/dist/heart/daemon/doctor.js +1755 -0
  149. package/dist/heart/daemon/freshness.js +345 -0
  150. package/dist/heart/daemon/habit-private-wake.js +61 -0
  151. package/dist/heart/daemon/health-monitor.js +142 -11
  152. package/dist/heart/daemon/hooks/agent-config-v2.js +33 -0
  153. package/dist/heart/daemon/hooks/bundle-meta.js +135 -1
  154. package/dist/heart/daemon/http-health-probe.js +80 -0
  155. package/dist/heart/daemon/human-command-screens.js +234 -0
  156. package/dist/heart/daemon/human-readiness.js +114 -0
  157. package/dist/heart/daemon/inner-status.js +78 -0
  158. package/dist/heart/daemon/interactive-repair.js +394 -0
  159. package/dist/heart/daemon/launchd.js +37 -8
  160. package/dist/heart/daemon/log-tailer.js +79 -10
  161. package/dist/heart/daemon/logs-prune.js +132 -0
  162. package/dist/heart/daemon/mcp-canary.js +524 -0
  163. package/dist/heart/daemon/message-router.js +65 -5
  164. package/dist/heart/daemon/migrate-to-desk.js +848 -0
  165. package/dist/heart/daemon/os-cron-deps.js +135 -0
  166. package/dist/heart/daemon/os-cron.js +22 -14
  167. package/dist/heart/daemon/ouro-bot-entry.js +4 -2
  168. package/dist/heart/daemon/ouro-entry.js +3 -1
  169. package/dist/heart/daemon/plugin-cli.js +432 -0
  170. package/dist/heart/daemon/process-manager.js +733 -61
  171. package/dist/heart/daemon/provider-discovery.js +137 -0
  172. package/dist/heart/daemon/provider-ping-progress.js +83 -0
  173. package/dist/heart/daemon/prunable-bundle.js +144 -0
  174. package/dist/heart/daemon/pulse.js +511 -0
  175. package/dist/heart/daemon/readiness-repair.js +365 -0
  176. package/dist/heart/daemon/run-hooks.js +2 -0
  177. package/dist/heart/daemon/runtime-logging.js +47 -14
  178. package/dist/heart/daemon/runtime-metadata.js +2 -30
  179. package/dist/heart/daemon/runtime-mode.js +13 -2
  180. package/dist/heart/daemon/safe-mode.js +161 -0
  181. package/dist/heart/daemon/sanctuary-acceptance-adapter.js +2654 -0
  182. package/dist/heart/daemon/sanctuary-acceptance-harness.js +1830 -0
  183. package/dist/heart/daemon/sanctuary-acceptance-marker.js +267 -0
  184. package/dist/heart/daemon/sanctuary-acceptance-scenarios.js +955 -0
  185. package/dist/heart/daemon/sanctuary-authority-codec.js +86 -0
  186. package/dist/heart/daemon/sanctuary-authority-epoch.js +256 -0
  187. package/dist/heart/daemon/sanctuary-authority-installation.js +140 -0
  188. package/dist/heart/daemon/sanctuary-authority-ledger.js +241 -0
  189. package/dist/heart/daemon/sanctuary-authority-root-lifecycle.js +819 -0
  190. package/dist/heart/daemon/sanctuary-authority-vault-migration.js +79 -0
  191. package/dist/heart/daemon/sanctuary-bundle-migration.js +729 -0
  192. package/dist/heart/daemon/sanctuary-host-authority.js +1008 -0
  193. package/dist/heart/daemon/sanctuary-host-detached-supervisor.js +494 -0
  194. package/dist/heart/daemon/sanctuary-host-executor.js +670 -0
  195. package/dist/heart/daemon/sanctuary-host-linux-kernel.js +334 -0
  196. package/dist/heart/daemon/sanctuary-host-supervisor-entry.js +207 -0
  197. package/dist/heart/daemon/sanctuary-host-supervisor.js +95 -0
  198. package/dist/heart/daemon/sanctuary-package-management.js +103 -0
  199. package/dist/heart/daemon/sanctuary-scheduler-liveness.js +227 -0
  200. package/dist/heart/daemon/sanctuary-scheduler-origin.js +132 -0
  201. package/dist/heart/daemon/sanctuary-telegram-authority-entry.js +445 -0
  202. package/dist/heart/daemon/sanctuary-telegram-authority-gateway.js +585 -0
  203. package/dist/heart/daemon/sanctuary-telegram-authority-service.js +630 -0
  204. package/dist/heart/daemon/sense-manager.js +699 -48
  205. package/dist/heart/daemon/session-id-resolver.js +131 -0
  206. package/dist/heart/daemon/skill-management-installer.js +1 -1
  207. package/dist/heart/daemon/socket-client.js +180 -12
  208. package/dist/heart/daemon/stale-bundle-prune.js +113 -0
  209. package/dist/heart/daemon/startup-tui.js +339 -0
  210. package/dist/heart/daemon/supercronic-supervisor.js +282 -0
  211. package/dist/heart/daemon/task-scheduler.js +117 -39
  212. package/dist/heart/daemon/terminal-ui.js +499 -0
  213. package/dist/heart/daemon/thoughts.js +138 -54
  214. package/dist/heart/daemon/up-progress.js +366 -0
  215. package/dist/heart/daemon/vault-items.js +56 -0
  216. package/dist/heart/delegation.js +1 -4
  217. package/dist/heart/external-events/router.js +1430 -0
  218. package/dist/heart/frontend-approval-runtime.js +506 -0
  219. package/dist/heart/frontend-journal.js +215 -0
  220. package/dist/heart/frontend-session-service.js +237 -0
  221. package/dist/heart/frontend-socket-client.js +154 -0
  222. package/dist/heart/frontend-socket.js +400 -0
  223. package/dist/heart/habits/habit-cancel.js +810 -0
  224. package/dist/heart/habits/habit-lifecycle.js +1327 -0
  225. package/dist/heart/habits/habit-migration.js +194 -0
  226. package/dist/heart/habits/habit-parser.js +278 -0
  227. package/dist/heart/habits/habit-runtime-state.js +93 -0
  228. package/dist/heart/habits/habit-scheduler.js +653 -0
  229. package/dist/heart/habits/habit-session-summary.js +318 -0
  230. package/dist/heart/habits/habit-session.js +636 -0
  231. package/dist/heart/{daemon → hatch}/hatch-flow.js +55 -62
  232. package/dist/heart/{daemon → hatch}/hatch-specialist.js +6 -8
  233. package/dist/heart/{daemon → hatch}/specialist-prompt.js +12 -9
  234. package/dist/heart/{daemon → hatch}/specialist-tools.js +53 -18
  235. package/dist/heart/identity.js +196 -59
  236. package/dist/heart/kept-notes.js +289 -0
  237. package/dist/heart/kicks.js +1 -1
  238. package/dist/heart/machine-identity.js +161 -0
  239. package/dist/heart/mail-import-discovery.js +356 -0
  240. package/dist/heart/mailbox/mailbox-http-hooks.js +98 -0
  241. package/dist/heart/mailbox/mailbox-http-response.js +7 -0
  242. package/dist/heart/mailbox/mailbox-http-routes.js +398 -0
  243. package/dist/heart/mailbox/mailbox-http-static.js +103 -0
  244. package/dist/heart/mailbox/mailbox-http-transport.js +129 -0
  245. package/dist/heart/mailbox/mailbox-http.js +99 -0
  246. package/dist/heart/mailbox/mailbox-read.js +39 -0
  247. package/dist/heart/mailbox/mailbox-types.js +27 -0
  248. package/dist/heart/mailbox/mailbox-view.js +197 -0
  249. package/dist/heart/mailbox/readers/agent-machine.js +428 -0
  250. package/dist/heart/mailbox/readers/continuity-readers.js +329 -0
  251. package/dist/heart/mailbox/readers/mail.js +375 -0
  252. package/dist/heart/mailbox/readers/runtime-readers.js +823 -0
  253. package/dist/heart/mailbox/readers/sessions.js +231 -0
  254. package/dist/heart/mailbox/readers/shared.js +111 -0
  255. package/dist/heart/mcp/mcp-server.js +696 -0
  256. package/dist/heart/migrate-config.js +100 -0
  257. package/dist/heart/model-capabilities.js +11 -0
  258. package/dist/heart/orientation-frame.js +345 -0
  259. package/dist/heart/platform.js +81 -0
  260. package/dist/heart/primitives.js +1 -1
  261. package/dist/heart/private-runtime/decision-renderer.js +158 -0
  262. package/dist/heart/private-runtime/index.js +18 -0
  263. package/dist/heart/private-runtime/ledger.js +221 -0
  264. package/dist/heart/private-runtime/policy.js +252 -0
  265. package/dist/heart/private-runtime/types.js +2 -0
  266. package/dist/heart/progress-story.js +1 -1
  267. package/dist/heart/provider-attempt.js +137 -0
  268. package/dist/heart/provider-binding-resolver.js +273 -0
  269. package/dist/heart/provider-credentials.js +449 -0
  270. package/dist/heart/provider-failover.js +311 -0
  271. package/dist/heart/provider-models.js +96 -0
  272. package/dist/heart/provider-ping.js +265 -0
  273. package/dist/heart/provider-readiness-cache.js +119 -0
  274. package/dist/heart/provider-visibility.js +188 -0
  275. package/dist/heart/providers/anthropic-token.js +131 -0
  276. package/dist/heart/providers/anthropic.js +173 -60
  277. package/dist/heart/providers/azure.js +23 -11
  278. package/dist/heart/providers/error-classification.js +127 -0
  279. package/dist/heart/providers/github-copilot.js +34 -38
  280. package/dist/heart/providers/minimax-vlm.js +189 -0
  281. package/dist/heart/providers/minimax.js +26 -8
  282. package/dist/heart/providers/openai-codex-token.js +349 -0
  283. package/dist/heart/providers/openai-codex.js +55 -40
  284. package/dist/heart/providers/openai-compatible.js +177 -0
  285. package/dist/heart/run-ledger.js +247 -0
  286. package/dist/heart/runtime-capability-check.js +170 -0
  287. package/dist/heart/runtime-credentials.js +607 -0
  288. package/dist/heart/runtime-cwd.js +87 -0
  289. package/dist/heart/sense-truth.js +22 -4
  290. package/dist/heart/session-activity.js +92 -24
  291. package/dist/heart/session-events.js +1504 -0
  292. package/dist/heart/session-playback-cli-main.js +5 -0
  293. package/dist/heart/session-playback-cli.js +36 -0
  294. package/dist/heart/session-playback.js +231 -0
  295. package/dist/heart/session-redaction-repair-cli-main.js +31 -0
  296. package/dist/heart/session-redaction-repair.js +552 -0
  297. package/dist/heart/session-stats-cli-main.js +5 -0
  298. package/dist/heart/session-stats.js +183 -0
  299. package/dist/heart/session-transcript.js +133 -0
  300. package/dist/heart/start-of-turn-packet.js +372 -0
  301. package/dist/heart/steward-policy.js +618 -0
  302. package/dist/heart/streaming.js +604 -207
  303. package/dist/heart/structured-output.js +196 -0
  304. package/dist/heart/sync-classification.js +176 -0
  305. package/dist/heart/sync.js +449 -0
  306. package/dist/heart/target-resolution.js +11 -7
  307. package/dist/heart/tempo.js +93 -0
  308. package/dist/heart/temporal-view.js +41 -0
  309. package/dist/heart/timeouts.js +101 -0
  310. package/dist/heart/tool-activity-callbacks.js +59 -0
  311. package/dist/heart/tool-approval.js +382 -0
  312. package/dist/heart/tool-description.js +155 -0
  313. package/dist/heart/tool-friction.js +55 -0
  314. package/dist/heart/tool-loop.js +200 -0
  315. package/dist/heart/turn-context.js +482 -0
  316. package/dist/heart/turn-execution-lease.js +30 -0
  317. package/dist/heart/{daemon → versioning}/ouro-bot-global-installer.js +6 -5
  318. package/dist/heart/{daemon → versioning}/ouro-bot-wrapper.js +1 -1
  319. package/dist/heart/versioning/ouro-path-installer.js +432 -0
  320. package/dist/heart/versioning/ouro-recovery-launcher.js +76 -0
  321. package/dist/heart/versioning/ouro-version-manager.js +409 -0
  322. package/dist/heart/{daemon → versioning}/staged-restart.js +40 -8
  323. package/dist/heart/{daemon → versioning}/update-checker.js +6 -1
  324. package/dist/heart/versioning/update-hooks.js +154 -0
  325. package/dist/heart/versioning/version-intent.js +114 -0
  326. package/dist/heart/work-card.js +380 -0
  327. package/dist/mailbox-ui/assets/index-Cyv4mw3Y.js +1 -0
  328. package/dist/mailbox-ui/assets/index-Du_9G9WO.css +1 -0
  329. package/dist/mailbox-ui/assets/vendor-CcN1XpQ9.js +61 -0
  330. package/dist/mailbox-ui/index.html +16 -0
  331. package/dist/mailroom/attention.js +167 -0
  332. package/dist/mailroom/autonomy.js +209 -0
  333. package/dist/mailroom/blob-store.js +889 -0
  334. package/dist/mailroom/body-cache.js +61 -0
  335. package/dist/mailroom/cache-sync-cli.js +58 -0
  336. package/dist/mailroom/core.js +803 -0
  337. package/dist/mailroom/entry.js +160 -0
  338. package/dist/mailroom/file-store.js +568 -0
  339. package/dist/mailroom/hosted-cache-sync.js +384 -0
  340. package/dist/mailroom/mbox-import.js +393 -0
  341. package/dist/mailroom/migration.js +164 -0
  342. package/dist/mailroom/outbound.js +380 -0
  343. package/dist/mailroom/policy.js +263 -0
  344. package/dist/mailroom/reader.js +292 -0
  345. package/dist/mailroom/search-cache.js +513 -0
  346. package/dist/mailroom/search-relevance.js +319 -0
  347. package/dist/mailroom/smtp-ingress.js +176 -0
  348. package/dist/mailroom/source-state.js +176 -0
  349. package/dist/mailroom/thread.js +109 -0
  350. package/dist/mailroom/travel-extract.js +89 -0
  351. package/dist/mind/bundle-manifest.js +24 -2
  352. package/dist/mind/context.js +309 -111
  353. package/dist/mind/desk-section.js +362 -0
  354. package/dist/mind/diary-integrity.js +60 -0
  355. package/dist/mind/{memory.js → diary.js} +68 -77
  356. package/dist/mind/embedding-provider.js +60 -0
  357. package/dist/mind/file-state.js +179 -0
  358. package/dist/mind/{associative-recall.js → note-search.js} +47 -58
  359. package/dist/mind/obligation-steering.js +199 -3
  360. package/dist/mind/pending.js +36 -10
  361. package/dist/mind/prompt-budget.js +479 -0
  362. package/dist/mind/prompt-refresh.js +3 -2
  363. package/dist/mind/prompt.js +1050 -189
  364. package/dist/mind/provenance-trust.js +26 -0
  365. package/dist/mind/record-paths.js +312 -0
  366. package/dist/mind/scrutiny.js +173 -0
  367. package/dist/mind/session-transaction.js +564 -0
  368. package/dist/nerves/cli-logging.js +7 -1
  369. package/dist/nerves/coverage/audit-rules.js +15 -6
  370. package/dist/nerves/coverage/audit.js +28 -2
  371. package/dist/nerves/coverage/cli.js +1 -1
  372. package/dist/nerves/coverage/contract.js +5 -5
  373. package/dist/nerves/coverage/file-completeness.js +139 -5
  374. package/dist/nerves/coverage/run-artifacts.js +55 -35
  375. package/dist/nerves/coverage/source-scanner.js +4 -4
  376. package/dist/nerves/event-buffer.js +111 -0
  377. package/dist/nerves/index.js +293 -9
  378. package/dist/nerves/observation.js +20 -0
  379. package/dist/nerves/redact.js +79 -0
  380. package/dist/nerves/review/cli-main.js +5 -0
  381. package/dist/nerves/review/cli.js +156 -0
  382. package/dist/nerves/review/core.js +159 -0
  383. package/dist/nerves/runtime.js +17 -1
  384. package/dist/repertoire/ado-client.js +15 -56
  385. package/dist/repertoire/ado-semantic.js +16 -10
  386. package/dist/repertoire/api-client.js +97 -0
  387. package/dist/repertoire/bitwarden-store.js +1045 -0
  388. package/dist/repertoire/bundle-templates.js +71 -0
  389. package/dist/repertoire/bw-installer.js +180 -0
  390. package/dist/repertoire/coding/codex-jsonl.js +64 -0
  391. package/dist/repertoire/coding/context-pack.js +331 -0
  392. package/dist/repertoire/coding/feedback.js +174 -32
  393. package/dist/repertoire/coding/index.js +15 -3
  394. package/dist/repertoire/coding/manager.js +159 -10
  395. package/dist/repertoire/coding/spawner.js +58 -11
  396. package/dist/repertoire/coding/tools.js +221 -15
  397. package/dist/repertoire/coding/workbench-client.js +272 -0
  398. package/dist/repertoire/coding/workbench-manager.js +346 -0
  399. package/dist/repertoire/commerce-errors.js +109 -0
  400. package/dist/repertoire/commerce-self-test.js +156 -0
  401. package/dist/repertoire/credential-access.js +178 -0
  402. package/dist/repertoire/desk/classifier.js +362 -0
  403. package/dist/repertoire/duffel-client.js +185 -0
  404. package/dist/repertoire/github-client.js +14 -55
  405. package/dist/repertoire/graph-client.js +11 -52
  406. package/dist/repertoire/guardrails.js +811 -18
  407. package/dist/repertoire/mcp-client.js +44 -3
  408. package/dist/repertoire/mcp-manager.js +427 -139
  409. package/dist/repertoire/mcp-tools.js +124 -0
  410. package/dist/repertoire/plugin-mcp.js +175 -0
  411. package/dist/repertoire/plugins.js +253 -0
  412. package/dist/repertoire/relationship-authorization.js +322 -0
  413. package/dist/repertoire/shell-sessions.js +134 -0
  414. package/dist/repertoire/skills.js +48 -4
  415. package/dist/repertoire/stripe-client.js +131 -0
  416. package/dist/repertoire/tool-arguments.js +170 -0
  417. package/dist/repertoire/tool-results.js +29 -0
  418. package/dist/repertoire/tools-a2a.js +699 -0
  419. package/dist/repertoire/tools-attachments.js +322 -0
  420. package/dist/repertoire/tools-awaiting.js +575 -0
  421. package/dist/repertoire/tools-base.js +77 -1138
  422. package/dist/repertoire/tools-bluebubbles.js +2 -0
  423. package/dist/repertoire/tools-bridge.js +144 -0
  424. package/dist/repertoire/tools-bundle.js +993 -0
  425. package/dist/repertoire/tools-commerce.js +253 -0
  426. package/dist/repertoire/tools-config.js +186 -0
  427. package/dist/repertoire/tools-continuity.js +575 -0
  428. package/dist/repertoire/tools-credential.js +383 -0
  429. package/dist/repertoire/tools-evolution.js +527 -0
  430. package/dist/repertoire/tools-files.js +344 -0
  431. package/dist/repertoire/tools-flight.js +290 -0
  432. package/dist/repertoire/tools-flow.js +119 -0
  433. package/dist/repertoire/tools-github.js +3 -8
  434. package/dist/repertoire/tools-habits.js +103 -0
  435. package/dist/repertoire/tools-mail.js +1828 -0
  436. package/dist/repertoire/tools-notes.js +485 -0
  437. package/dist/repertoire/tools-obligations.js +182 -0
  438. package/dist/repertoire/tools-orientation.js +31 -0
  439. package/dist/repertoire/tools-record.js +482 -0
  440. package/dist/repertoire/tools-rsvp.js +139 -0
  441. package/dist/repertoire/tools-runtime.js +150 -0
  442. package/dist/repertoire/tools-sanctuary-host.js +202 -0
  443. package/dist/repertoire/tools-session.js +1133 -0
  444. package/dist/repertoire/tools-shell.js +149 -0
  445. package/dist/repertoire/tools-steward-policy.js +119 -0
  446. package/dist/repertoire/tools-stripe.js +224 -0
  447. package/dist/repertoire/tools-surface.js +369 -0
  448. package/dist/repertoire/tools-teams.js +12 -39
  449. package/dist/repertoire/tools-telegram-contacts.js +37 -0
  450. package/dist/repertoire/tools-travel.js +125 -0
  451. package/dist/repertoire/tools-trip.js +982 -0
  452. package/dist/repertoire/tools-unraid.js +531 -0
  453. package/dist/repertoire/tools-user-profile.js +146 -0
  454. package/dist/repertoire/tools-vault.js +40 -0
  455. package/dist/repertoire/tools-voice.js +145 -0
  456. package/dist/repertoire/tools.js +494 -148
  457. package/dist/repertoire/travel-api-client.js +360 -0
  458. package/dist/repertoire/unraid-client.js +155 -0
  459. package/dist/repertoire/unraid-restart.js +339 -0
  460. package/dist/repertoire/user-profile.js +131 -0
  461. package/dist/repertoire/vault-setup.js +246 -0
  462. package/dist/repertoire/vault-unlock.js +628 -0
  463. package/dist/rsvp/aisleplanner-client.js +354 -0
  464. package/dist/rsvp/cli.js +926 -0
  465. package/dist/rsvp/config.js +327 -0
  466. package/dist/rsvp/cutover.js +433 -0
  467. package/dist/rsvp/diagnostics.js +232 -0
  468. package/dist/rsvp/diff-renderer.js +176 -0
  469. package/dist/rsvp/habit-policy.js +110 -0
  470. package/dist/rsvp/habit-stage.js +123 -0
  471. package/dist/rsvp/incident-bundle.js +97 -0
  472. package/dist/rsvp/migration.js +204 -0
  473. package/dist/rsvp/native-habit-runner.js +630 -0
  474. package/dist/rsvp/outbound-state.js +628 -0
  475. package/dist/rsvp/query.js +118 -0
  476. package/dist/rsvp/replay.js +233 -0
  477. package/dist/rsvp/snapshot.js +232 -0
  478. package/dist/rsvp/spend-ledger.js +175 -0
  479. package/dist/scripts/claude-code-hook.js +41 -0
  480. package/dist/scripts/claude-code-stop-hook.js +47 -0
  481. package/dist/senses/a2a-entry.js +115 -0
  482. package/dist/senses/acp-server.js +386 -0
  483. package/dist/senses/attention-queue.js +186 -0
  484. package/dist/senses/await-turn-message.js +58 -0
  485. package/dist/senses/bluebubbles/active-turns.js +216 -0
  486. package/dist/senses/bluebubbles/attachment-cache.js +53 -0
  487. package/dist/senses/bluebubbles/attachment-download.js +137 -0
  488. package/dist/senses/{bluebubbles-client.js → bluebubbles/client.js} +355 -40
  489. package/dist/senses/bluebubbles/context-packet.js +207 -0
  490. package/dist/senses/bluebubbles/context-smoke.js +144 -0
  491. package/dist/senses/bluebubbles/entry.js +90 -0
  492. package/dist/senses/{bluebubbles-inbound-log.js → bluebubbles/inbound-log.js} +21 -3
  493. package/dist/senses/bluebubbles/index.js +3841 -0
  494. package/dist/senses/bluebubbles/latest-turn.js +311 -0
  495. package/dist/senses/{bluebubbles-media.js → bluebubbles/media.js} +121 -71
  496. package/dist/senses/{bluebubbles-model.js → bluebubbles/model.js} +159 -28
  497. package/dist/senses/{bluebubbles-mutation-log.js → bluebubbles/mutation-log.js} +24 -13
  498. package/dist/senses/bluebubbles/outbound-state.js +308 -0
  499. package/dist/senses/bluebubbles/processed-log.js +133 -0
  500. package/dist/senses/bluebubbles/reaction-policy.js +47 -0
  501. package/dist/senses/bluebubbles/replay.js +284 -0
  502. package/dist/senses/{bluebubbles-runtime-state.js → bluebubbles/runtime-state.js} +30 -2
  503. package/dist/senses/bluebubbles/semantic-receipts.js +1986 -0
  504. package/dist/senses/{bluebubbles-session-cleanup.js → bluebubbles/session-cleanup.js} +1 -1
  505. package/dist/senses/bluebubbles/webhook-registration.js +228 -0
  506. package/dist/senses/bluebubbles-meta-guard.js +39 -0
  507. package/dist/senses/cli/bracketed-paste.js +82 -0
  508. package/dist/senses/cli/image-paste.js +287 -0
  509. package/dist/senses/cli/image-ref-navigation.js +75 -0
  510. package/dist/senses/cli/ink-app.js +156 -0
  511. package/dist/senses/cli/inline-diff.js +64 -0
  512. package/dist/senses/cli/input-keys.js +174 -0
  513. package/dist/senses/cli/kill-ring.js +86 -0
  514. package/dist/senses/cli/message-list.js +51 -0
  515. package/dist/senses/cli/ouro-tui.js +607 -0
  516. package/dist/senses/cli/spinner-imperative.js +135 -0
  517. package/dist/senses/cli/spinner.js +101 -0
  518. package/dist/senses/cli/status-line.js +60 -0
  519. package/dist/senses/cli/streaming-markdown.js +526 -0
  520. package/dist/senses/cli/tool-display.js +85 -0
  521. package/dist/senses/cli/tool-render.js +85 -0
  522. package/dist/senses/cli/tui-store.js +241 -0
  523. package/dist/senses/cli/virtual-list.js +35 -0
  524. package/dist/senses/cli-entry.js +60 -8
  525. package/dist/senses/cli.js +604 -320
  526. package/dist/senses/commands.js +66 -3
  527. package/dist/senses/context-packet-ledger.js +371 -0
  528. package/dist/senses/context-packets.js +154 -0
  529. package/dist/senses/habit-lifecycle-guard.js +24 -0
  530. package/dist/senses/habit-turn-message.js +160 -0
  531. package/dist/senses/ingress-evidence.js +2 -0
  532. package/dist/senses/mail-entry.js +66 -0
  533. package/dist/senses/mail.js +524 -0
  534. package/dist/senses/pipeline.js +1115 -204
  535. package/dist/senses/private-runtime-worker.js +859 -0
  536. package/dist/senses/private-runtime.js +1517 -0
  537. package/dist/senses/proactive-content-guard.js +51 -0
  538. package/dist/senses/root-host-approval-port.js +345 -0
  539. package/dist/senses/root-host-approval-runtime.js +393 -0
  540. package/dist/senses/sanctuary-authority-resident.js +102 -0
  541. package/dist/senses/sanctuary-download-credit-presentation.js +31 -0
  542. package/dist/senses/sanctuary-full-visibility-contract.js +302 -0
  543. package/dist/senses/sanctuary-grounding.js +118 -0
  544. package/dist/senses/sanctuary-health-acceptance-probe-entry.js +12 -0
  545. package/dist/senses/sanctuary-health-acceptance-probe.js +774 -0
  546. package/dist/senses/sanctuary-health-runner.js +100 -0
  547. package/dist/senses/sanctuary-health.js +408 -0
  548. package/dist/senses/sanctuary-install-state-contract.js +126 -0
  549. package/dist/senses/sanctuary-interactive-control.js +410 -0
  550. package/dist/senses/sanctuary-media-catalog-contract.js +297 -0
  551. package/dist/senses/sanctuary-media-optimization.js +514 -0
  552. package/dist/senses/sanctuary-runtime.js +340 -0
  553. package/dist/senses/sanctuary-sab.js +85 -0
  554. package/dist/senses/sanctuary-storage-optimization-contract.js +32 -0
  555. package/dist/senses/shared-turn.js +772 -0
  556. package/dist/senses/surface-tool.js +109 -0
  557. package/dist/senses/teams-entry.js +60 -8
  558. package/dist/senses/teams.js +519 -218
  559. package/dist/senses/telegram-admission.js +718 -0
  560. package/dist/senses/telegram-approval-runtime.js +793 -0
  561. package/dist/senses/telegram-attachments.js +186 -0
  562. package/dist/senses/telegram-audit-ledger.js +220 -0
  563. package/dist/senses/telegram-authority-transport.js +231 -0
  564. package/dist/senses/telegram-client.js +1717 -0
  565. package/dist/senses/telegram-effect-adapter.js +896 -0
  566. package/dist/senses/telegram-entry.js +83 -0
  567. package/dist/senses/telegram.js +2324 -0
  568. package/dist/senses/trust-gate.js +104 -9
  569. package/dist/senses/voice/audio-playback.js +237 -0
  570. package/dist/senses/voice/audio-routing.js +119 -0
  571. package/dist/senses/voice/elevenlabs.js +202 -0
  572. package/dist/senses/voice/floor-control.js +431 -0
  573. package/dist/senses/voice/floor-controller.js +115 -0
  574. package/dist/senses/voice/golden-path.js +116 -0
  575. package/dist/senses/voice/index.js +29 -0
  576. package/dist/senses/voice/meeting.js +113 -0
  577. package/dist/senses/voice/outbound.js +190 -0
  578. package/dist/senses/voice/phone.js +33 -0
  579. package/dist/senses/voice/playback.js +139 -0
  580. package/dist/senses/voice/realtime-eval.js +496 -0
  581. package/dist/senses/voice/realtime-trace.js +531 -0
  582. package/dist/senses/voice/transcript.js +70 -0
  583. package/dist/senses/voice/turn.js +191 -0
  584. package/dist/senses/voice/twilio-phone-runtime.js +807 -0
  585. package/dist/senses/voice/twilio-phone.js +5031 -0
  586. package/dist/senses/voice/types.js +2 -0
  587. package/dist/senses/voice/whisper.js +161 -0
  588. package/dist/senses/voice-entry.js +81 -0
  589. package/dist/senses/voice-realtime-eval-command.js +99 -0
  590. package/dist/senses/voice-realtime-eval-entry.js +21 -0
  591. package/dist/senses/voice-twilio-entry.js +87 -0
  592. package/dist/trips/core.js +138 -0
  593. package/dist/trips/store.js +265 -0
  594. package/dist/util/frontmatter.js +69 -0
  595. package/npm-shrinkwrap.json +8138 -0
  596. package/package.json +72 -13
  597. package/skills/agent-commerce.md +113 -0
  598. package/skills/browser-navigation.md +117 -0
  599. package/skills/commerce-setup-guide.md +116 -0
  600. package/skills/commerce-setup.md +84 -0
  601. package/skills/configure-dev-tools.md +99 -0
  602. package/skills/travel-planning.md +138 -0
  603. package/dist/heart/daemon/auth-flow.js +0 -430
  604. package/dist/heart/daemon/ouro-path-installer.js +0 -260
  605. package/dist/heart/daemon/ouro-version-manager.js +0 -171
  606. package/dist/heart/daemon/update-hooks.js +0 -138
  607. package/dist/heart/obligations.js +0 -191
  608. package/dist/heart/safe-workspace.js +0 -228
  609. package/dist/heart/session-recall.js +0 -116
  610. package/dist/mind/friends/channel.js +0 -84
  611. package/dist/mind/friends/group-context.js +0 -144
  612. package/dist/mind/friends/resolver.js +0 -84
  613. package/dist/mind/friends/store-file.js +0 -190
  614. package/dist/mind/friends/store.js +0 -4
  615. package/dist/mind/friends/tokens.js +0 -26
  616. package/dist/mind/friends/trust-explanation.js +0 -74
  617. package/dist/mind/friends/types.js +0 -29
  618. package/dist/repertoire/tasks/board.js +0 -134
  619. package/dist/repertoire/tasks/index.js +0 -224
  620. package/dist/repertoire/tasks/lifecycle.js +0 -80
  621. package/dist/repertoire/tasks/middleware.js +0 -65
  622. package/dist/repertoire/tasks/parser.js +0 -173
  623. package/dist/repertoire/tasks/scanner.js +0 -132
  624. package/dist/repertoire/tasks/transitions.js +0 -144
  625. package/dist/senses/bluebubbles-entry.js +0 -13
  626. package/dist/senses/bluebubbles.js +0 -1181
  627. package/dist/senses/debug-activity.js +0 -154
  628. package/dist/senses/inner-dialog-worker.js +0 -90
  629. package/dist/senses/inner-dialog.js +0 -525
  630. package/subagents/README.md +0 -7
  631. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/basilisk.md +0 -0
  632. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/jafar.md +0 -0
  633. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/jormungandr.md +0 -0
  634. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/kaa.md +0 -0
  635. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/medusa.md +0 -0
  636. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/monty.md +0 -0
  637. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/nagini.md +0 -0
  638. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/ouroboros.md +0 -0
  639. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/python.md +0 -0
  640. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/quetzalcoatl.md +0 -0
  641. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/sir-hiss.md +0 -0
  642. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/the-snake.md +0 -0
  643. /package/dist/{repertoire/tasks/types.js → heart/attachments/sources/adapter.js} +0 -0
  644. /package/dist/heart/{daemon → hatch}/hatch-animation.js +0 -0
  645. /package/dist/heart/{daemon → hatch}/specialist-orchestrator.js +0 -0
  646. /package/dist/heart/{daemon → versioning}/ouro-uti.js +0 -0
  647. /package/dist/heart/{daemon → versioning}/wrapper-publish-guard.js +0 -0
@@ -0,0 +1,105 @@
1
+ "use strict"
2
+
3
+ const { TextDecoder } = require("node:util")
4
+
5
+ const DECLARATION = "<?xml version=\"1.0\"?>"
6
+ const ROOT_OPEN = "<Container version=\"2\">"
7
+ const ROOT_CLOSE = "</Container>"
8
+ const NAME_START = /^[A-Za-z_]$/u
9
+ const NAME_CHARACTER = /^[A-Za-z0-9_.:-]$/u
10
+ const XML_WHITESPACE = /^[\t\n\r ]$/u
11
+ const UTF8_DECODER = new TextDecoder("utf-8", { fatal: true })
12
+
13
+ function decodeUtf8(value) {
14
+ if (typeof value === "string") return value
15
+ if (!(value instanceof Uint8Array)) throw new TypeError("DockerMan template bytes are invalid")
16
+ return UTF8_DECODER.decode(value)
17
+ }
18
+
19
+ function skipWhitespace(value, start) {
20
+ let index = start
21
+ while (XML_WHITESPACE.test(value[index] ?? "")) index += 1
22
+ return index
23
+ }
24
+
25
+ function readName(value, start) {
26
+ if (!NAME_START.test(value[start] ?? "")) return null
27
+ let index = start + 1
28
+ while (NAME_CHARACTER.test(value[index] ?? "")) index += 1
29
+ return { name: value.slice(start, index), index }
30
+ }
31
+
32
+ function readOpenTag(xml, start) {
33
+ if (xml[start] !== "<") return null
34
+ const parsedName = readName(xml, start + 1)
35
+ if (!parsedName) return null
36
+ const attributes = new Map()
37
+ let index = parsedName.index
38
+ while (index < xml.length) {
39
+ if (xml[index] === ">") return { name: parsedName.name, attributes: Object.fromEntries(attributes), form: "text", index: index + 1 }
40
+ if (xml.startsWith("/>", index)) return { name: parsedName.name, attributes: Object.fromEntries(attributes), form: "empty", index: index + 2 }
41
+ const whitespaceEnd = skipWhitespace(xml, index)
42
+ if (whitespaceEnd === index) return null
43
+ index = whitespaceEnd
44
+ if (xml[index] === ">" || xml.startsWith("/>", index)) continue
45
+ const attribute = readName(xml, index)
46
+ if (!attribute || attributes.has(attribute.name)) return null
47
+ index = skipWhitespace(xml, attribute.index)
48
+ if (xml[index] !== "=") return null
49
+ index = skipWhitespace(xml, index + 1)
50
+ if (xml[index] !== "\"") return null
51
+ const end = xml.indexOf("\"", index + 1)
52
+ if (end === -1) return null
53
+ const value = xml.slice(index + 1, end)
54
+ if (!/^[\x20-\x7E]*$/u.test(value) || value.includes("<") || value.includes("&")) return null
55
+ attributes.set(attribute.name, value)
56
+ index = end + 1
57
+ }
58
+ return null
59
+ }
60
+
61
+ function hasValidXmlCharacters(value) {
62
+ for (const character of value) {
63
+ const codePoint = character.codePointAt(0)
64
+ if (!(codePoint === 0x9 || codePoint === 0xA || codePoint === 0xD
65
+ || (codePoint >= 0x20 && codePoint <= 0xD7FF)
66
+ || (codePoint >= 0xE000 && codePoint <= 0xFFFD)
67
+ || (codePoint >= 0x10000 && codePoint <= 0x10FFFF))) return false
68
+ }
69
+ return true
70
+ }
71
+
72
+ // Canonical DockerMan dialect: exact declaration and Container root, flat leaf children, double-quoted ASCII attributes, and literal text without entities or auxiliary XML constructs.
73
+ function parseDockerManTemplateXml(input) {
74
+ let xml
75
+ try { xml = decodeUtf8(input) } catch { return null }
76
+ if (!hasValidXmlCharacters(xml) || !xml.startsWith(DECLARATION)) return null
77
+ let index = skipWhitespace(xml, DECLARATION.length)
78
+ if (!xml.startsWith(ROOT_OPEN, index)) return null
79
+ index += ROOT_OPEN.length
80
+ const children = []
81
+ while (index < xml.length) {
82
+ index = skipWhitespace(xml, index)
83
+ if (xml.startsWith(ROOT_CLOSE, index)) {
84
+ index = skipWhitespace(xml, index + ROOT_CLOSE.length)
85
+ return index === xml.length ? { root: { name: "Container", attributes: { version: "2" } }, children } : null
86
+ }
87
+ const child = readOpenTag(xml, index)
88
+ if (!child) return null
89
+ index = child.index
90
+ if (child.form === "empty") {
91
+ children.push({ name: child.name, attributes: child.attributes, form: child.form, text: "" })
92
+ continue
93
+ }
94
+ const close = `</${child.name}>`
95
+ const end = xml.indexOf("<", index)
96
+ if (end === -1 || !xml.startsWith(close, end)) return null
97
+ const text = xml.slice(index, end)
98
+ if (text.includes("&") || text.includes("]]>")) return null
99
+ children.push({ name: child.name, attributes: child.attributes, form: child.form, text })
100
+ index = end + close.length
101
+ }
102
+ return null
103
+ }
104
+
105
+ module.exports = { decodeUtf8, parseDockerManTemplateXml }
@@ -0,0 +1,60 @@
1
+ #!/usr/bin/env node
2
+ import * as path from "node:path"
3
+ import { pathToFileURL } from "node:url"
4
+
5
+ const USAGE = "Usage: migrate-sanctuary-bundle.mjs --package-root <path> --agent-root <path> --operation <migrate|rollback|finalize-rollback|commit|status|inspect> [--rollback-image-id <sha256:id> --target-image-id <sha256:id>]"
6
+ const BASE_KEYS = ["--package-root", "--agent-root", "--operation"]
7
+ const MIGRATE_KEYS = [...BASE_KEYS, "--rollback-image-id", "--target-image-id"]
8
+
9
+ function parseArguments(args) {
10
+ if (args.length % 2 !== 0) throw new Error(USAGE)
11
+ const values = new Map()
12
+ for (let index = 0; index < args.length; index += 2) {
13
+ const key = args[index]
14
+ const value = args[index + 1]
15
+ if (values.has(key) || typeof value !== "string" || value.length === 0) throw new Error(USAGE)
16
+ values.set(key, value)
17
+ }
18
+ const operation = values.get("--operation")
19
+ if (!["migrate", "rollback", "finalize-rollback", "commit", "status", "inspect"].includes(operation)) throw new Error(USAGE)
20
+ const expectedKeys = operation === "migrate" ? MIGRATE_KEYS : BASE_KEYS
21
+ if (values.size !== expectedKeys.length || expectedKeys.some((key) => !values.has(key)) || [...values.keys()].some((key) => !expectedKeys.includes(key))) throw new Error(USAGE)
22
+ return values
23
+ }
24
+
25
+ export function runSanctuaryBundleOperation(args, dependencies) {
26
+ const values = parseArguments(args)
27
+ const packageRoot = values.get("--package-root")
28
+ const agentRoot = values.get("--agent-root")
29
+ const operation = values.get("--operation")
30
+ const result = operation === "migrate"
31
+ ? dependencies.migrate({ packageRoot, agentRoot, retainRollback: true, rollbackImageId: values.get("--rollback-image-id"), targetImageId: values.get("--target-image-id") })
32
+ : operation === "rollback"
33
+ ? { rolledBack: dependencies.rollback(agentRoot, { retainRecord: true }) }
34
+ // `rollback` keeps crash-retry evidence; the host uses `finalize-rollback` only after old production is audited, ready, and restored to autostart.
35
+ : operation === "finalize-rollback"
36
+ ? { rolledBack: dependencies.rollback(agentRoot, { retainRecord: false }) }
37
+ : operation === "commit"
38
+ ? { committed: dependencies.commit(agentRoot) }
39
+ : operation === "status"
40
+ ? dependencies.status(agentRoot)
41
+ : dependencies.inspect({ packageRoot, agentRoot, runtimePackageVersion: dependencies.getPackageVersion() })
42
+ if (result === null || ("rolledBack" in result && !result.rolledBack) || ("committed" in result && !result.committed)) throw new Error(`${operation} found no pending Sanctuary bundle transaction`)
43
+ return result
44
+ }
45
+
46
+ export async function runSanctuaryBundleCli(args = process.argv.slice(2), output = process.stdout) {
47
+ const migration = await import("../../dist/heart/daemon/sanctuary-bundle-migration.js")
48
+ const manifest = await import("../../dist/mind/bundle-manifest.js")
49
+ const result = runSanctuaryBundleOperation(args, {
50
+ commit: migration.commitSanctuaryPackageManagedBundle,
51
+ getPackageVersion: manifest.getPackageVersion,
52
+ inspect: migration.inspectSanctuaryPackageManagedBundle,
53
+ migrate: migration.migrateSanctuaryPackageManagedBundle,
54
+ rollback: migration.rollbackSanctuaryPackageManagedBundle,
55
+ status: migration.inspectSanctuaryPackageManagedBundleRollback,
56
+ })
57
+ output.write(`${JSON.stringify(result)}\n`)
58
+ }
59
+
60
+ if (process.argv[1] && import.meta.url === pathToFileURL(path.resolve(process.argv[1])).href) await runSanctuaryBundleCli()
@@ -0,0 +1,92 @@
1
+ #!/bin/bash
2
+ set -euo pipefail
3
+
4
+ SPOOL_ROOT="/boot/config/custom/ouro-events/spool"
5
+ INSTALL_PATH="/boot/config/custom/ouro-events/bootstrap-spool.sh"
6
+ GO_FILE="/boot/config/go"
7
+ TMPFS_OPTIONS="size=4m,mode=0755,uid=0,gid=0,nodev,nosuid,noexec"
8
+ BOOT_HOOK="/bin/bash /boot/config/custom/ouro-events/bootstrap-spool.sh --mount"
9
+ ACTION="mount"
10
+
11
+ while (($# > 0)); do
12
+ case "$1" in
13
+ --mount|--install-only|--self-test) ACTION="${1#--}"; shift ;;
14
+ --go-file) GO_FILE="${2:?missing --go-file value}"; shift 2 ;;
15
+ --install-path) INSTALL_PATH="${2:?missing --install-path value}"; shift 2 ;;
16
+ *) echo "ouro event spool bootstrap: invalid argument: $1" >&2; exit 2 ;;
17
+ esac
18
+ done
19
+
20
+ mount_spool() {
21
+ local target="$1"
22
+ /bin/mkdir -p "$target"
23
+ /bin/chown 0:0 "$target"
24
+ /bin/chmod 0755 "$target"
25
+ if /bin/mountpoint -q "$target"; then
26
+ [[ "$(/bin/findmnt -n -o FSTYPE --target "$target")" == "tmpfs" ]] || { echo "ouro event spool bootstrap: canonical path is already a non-tmpfs mount" >&2; return 1; }
27
+ else
28
+ /bin/mount -t tmpfs -o "$TMPFS_OPTIONS" ouro-events-spool "$target"
29
+ fi
30
+ [[ "$(/bin/stat -c '%u:%g:%a' "$target")" == "0:0:755" ]] || { echo "ouro event spool bootstrap: tmpfs identity or mode is unsafe" >&2; return 1; }
31
+ local options
32
+ options="$(/bin/findmnt -n -o OPTIONS --target "$target")"
33
+ for required in nodev nosuid noexec; do
34
+ [[ ",$options," == *",$required,"* ]] || { echo "ouro event spool bootstrap: tmpfs is missing $required" >&2; return 1; }
35
+ done
36
+ local size_bytes
37
+ size_bytes="$(/bin/findmnt -bn -o SIZE --target "$target")"
38
+ [[ "$size_bytes" =~ ^[0-9]+$ && "$size_bytes" -le 4194304 ]] || { echo "ouro event spool bootstrap: tmpfs exceeds its 4 MiB bound" >&2; return 1; }
39
+ }
40
+
41
+ install_boot_hook() {
42
+ local install_dir resolved_source resolved_install
43
+ install_dir="$(dirname "$INSTALL_PATH")"
44
+ /bin/mkdir -p "$install_dir"
45
+ resolved_source="$(cd "$(dirname "$0")" && pwd -P)/$(basename "$0")"
46
+ resolved_install="$(cd "$install_dir" && pwd -P)/$(basename "$INSTALL_PATH")"
47
+ if [[ "$resolved_source" != "$resolved_install" ]]; then
48
+ install -m 0700 "$0" "$INSTALL_PATH"
49
+ else
50
+ /bin/chmod 0700 "$INSTALL_PATH"
51
+ fi
52
+ [[ -f "$GO_FILE" ]] || printf '#!/bin/bash\n' > "$GO_FILE"
53
+ if ! grep -Fqx "$BOOT_HOOK" "$GO_FILE"; then
54
+ local temporary="${GO_FILE}.ouro-events.$$"
55
+ awk -v hook="$BOOT_HOOK" 'NR == 1 && /^#!/ { print; print hook; next } NR == 1 { print hook } { print }' "$GO_FILE" > "$temporary"
56
+ /bin/chmod 0700 "$temporary"
57
+ /bin/mv "$temporary" "$GO_FILE"
58
+ fi
59
+ }
60
+
61
+ self_test() {
62
+ [[ "$(/usr/bin/id -u)" == "0" ]] || { echo "ouro event spool bootstrap self-test requires root" >&2; return 1; }
63
+ local test_root target readonly_view
64
+ test_root="$(/usr/bin/mktemp -d /tmp/ouro-event-spool.XXXXXX)"
65
+ target="$test_root/spool"
66
+ readonly_view="$test_root/readonly"
67
+ /bin/chmod 0755 "$test_root"
68
+ /bin/mkdir "$target" "$readonly_view"
69
+ cleanup() {
70
+ /bin/umount "$readonly_view" 2>/dev/null || true
71
+ /bin/umount "$target" 2>/dev/null || true
72
+ /bin/rmdir "$readonly_view" "$target" "$test_root" 2>/dev/null || true
73
+ }
74
+ trap cleanup EXIT
75
+ mount_spool "$target"
76
+ /usr/bin/printf 'probe\n' > "$target/probe"
77
+ /bin/chown 0:0 "$target/probe"
78
+ /bin/chmod 0444 "$target/probe"
79
+ /bin/mount --bind "$target" "$readonly_view"
80
+ /bin/mount -o remount,bind,ro,nodev,nosuid,noexec "$readonly_view"
81
+ /usr/bin/setpriv --reuid=10001 --regid=10001 --clear-groups /usr/bin/test -r "$readonly_view/probe"
82
+ [[ ",$(/bin/findmnt -n -o OPTIONS --target "$readonly_view")," == *",ro,"* ]] || { echo "ouro event spool bootstrap: test bind is not read-only" >&2; return 1; }
83
+ echo "uid10001-ro-bind: pass"
84
+ cleanup
85
+ trap - EXIT
86
+ }
87
+
88
+ case "$ACTION" in
89
+ mount) mount_spool "$SPOOL_ROOT" ;;
90
+ install-only) install_boot_hook ;;
91
+ self-test) self_test ;;
92
+ esac
@@ -0,0 +1,310 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { createHash, randomBytes } from "node:crypto"
4
+ import * as fs from "node:fs"
5
+ import * as path from "node:path"
6
+ import { fileURLToPath } from "node:url"
7
+
8
+ const SPOOL_ROOT = "/boot/config/custom/ouro-events/spool"
9
+ const MAX_BYTES = 32 * 1024
10
+ const MAX_SPOOL_FILES = 4_096
11
+ const DEFAULT_EXPIRY_GRACE_MS = 24 * 60 * 60_000
12
+ const ENVELOPE_KEYS = ["action", "actionReceipt", "agent", "createdAt", "critical", "eventType", "evidence", "expiresAt", "incidentKey", "nonce", "observationRevision", "protectiveStateDigest", "protectiveStateObservedAt", "protectiveStateVerified", "schemaVersion", "source", "summary", "transitionId"]
13
+
14
+ function id(value, name) {
15
+ if (typeof value !== "string" || !/^[a-zA-Z0-9._:-]{1,160}$/u.test(value)) throw new Error(`${name} is invalid`)
16
+ return value
17
+ }
18
+
19
+ function bounded(value, name, max) {
20
+ if (typeof value !== "string" || !value || Buffer.byteLength(value) > max) throw new Error(`${name} must be bounded`)
21
+ return value
22
+ }
23
+
24
+ function stableInput(envelope) {
25
+ const { createdAt: _createdAt, expiresAt: _expiresAt, nonce: _nonce, protectiveStateObservedAt: _protectiveStateObservedAt, ...stable } = envelope
26
+ return stable
27
+ }
28
+
29
+ function canonicalIso(value) {
30
+ return typeof value === "string" && Number.isFinite(Date.parse(value)) && new Date(value).toISOString() === value
31
+ }
32
+
33
+ function validateEnvelope(envelope) {
34
+ const eventShapeIsAllowed = envelope && typeof envelope === "object" && !Array.isArray(envelope) && ((envelope.eventType === "usenet.protective_action" && envelope.action === "sabnzbd.pause")
35
+ || (envelope.eventType === "usenet.health_observation" && envelope.action === "usenet.observe"))
36
+ const observationTransitionIsAllowed = envelope?.eventType !== "usenet.health_observation" || /^(?:auth-failed|stalled|recovered|indeterminate):\d{8}T\d{6}Z$/u.test(String(envelope.transitionId))
37
+ if (!envelope || typeof envelope !== "object" || Array.isArray(envelope) || Object.keys(envelope).sort().join("\0") !== [...ENVELOPE_KEYS].sort().join("\0")
38
+ || envelope.schemaVersion !== 1 || envelope.agent !== "sanctuary" || envelope.source !== "sanctuary-usenet"
39
+ || !eventShapeIsAllowed || !observationTransitionIsAllowed
40
+ || envelope.critical !== true || !/^[a-zA-Z0-9._:-]{1,160}$/u.test(String(envelope.incidentKey)) || !/^[a-zA-Z0-9._:-]{1,160}$/u.test(String(envelope.transitionId))
41
+ || !/^[a-f0-9]{64}$/u.test(String(envelope.observationRevision)) || !/^[a-f0-9]{64}$/u.test(String(envelope.nonce))
42
+ || typeof envelope.protectiveStateVerified !== "boolean" || !/^[a-f0-9]{64}$/u.test(String(envelope.protectiveStateDigest))
43
+ || !canonicalIso(envelope.protectiveStateObservedAt)
44
+ || typeof envelope.actionReceipt !== "string" || !envelope.actionReceipt || Buffer.byteLength(envelope.actionReceipt) > 512
45
+ || typeof envelope.summary !== "string" || !envelope.summary || Buffer.byteLength(envelope.summary) > 4_096
46
+ || !Array.isArray(envelope.evidence) || envelope.evidence.length > 16 || envelope.evidence.some((entry) => typeof entry !== "string" || !entry || Buffer.byteLength(entry) > 2_048)
47
+ || !canonicalIso(envelope.createdAt) || !canonicalIso(envelope.expiresAt)
48
+ || Date.parse(envelope.expiresAt) <= Date.parse(envelope.createdAt)
49
+ || Date.parse(envelope.expiresAt) - Date.parse(envelope.createdAt) > 60 * 60_000) {
50
+ throw new Error("event envelope is invalid")
51
+ }
52
+ const serialized = `${JSON.stringify(envelope)}\n`
53
+ if (Buffer.byteLength(serialized) > MAX_BYTES) throw new Error("event envelope must be bounded")
54
+ return serialized
55
+ }
56
+
57
+ function buildEnvelope(input, now, nonce) {
58
+ const createdAt = new Date(now()).toISOString()
59
+ const expiresAt = new Date(Date.parse(createdAt) + 15 * 60_000).toISOString()
60
+ const evidence = input.evidence ?? []
61
+ if (!Array.isArray(evidence) || evidence.length > 16) throw new Error("evidence must be bounded")
62
+ const envelope = {
63
+ schemaVersion: input.schemaVersion,
64
+ agent: input.agent,
65
+ source: input.source,
66
+ eventType: input.eventType,
67
+ incidentKey: id(input.incidentKey, "incident key"),
68
+ transitionId: id(input.transitionId, "transition id"),
69
+ observationRevision: input.observationRevision,
70
+ action: input.action,
71
+ actionReceipt: bounded(input.actionReceipt, "action receipt", 512),
72
+ protectiveStateVerified: input.protectiveStateVerified,
73
+ protectiveStateDigest: input.protectiveStateDigest,
74
+ protectiveStateObservedAt: input.protectiveStateObservedAt,
75
+ critical: input.critical,
76
+ summary: bounded(input.summary, "summary", 4_096),
77
+ evidence: evidence.map((entry) => bounded(entry, "evidence", 2_048)),
78
+ createdAt,
79
+ expiresAt,
80
+ nonce: nonce(),
81
+ }
82
+ return { envelope, serialized: validateEnvelope(envelope) }
83
+ }
84
+
85
+ function linuxProcessStart(pid) {
86
+ try {
87
+ const raw = fs.readFileSync(`/proc/${pid}/stat`, "utf8")
88
+ return raw.slice(raw.lastIndexOf(")") + 2).trim().split(/\s+/u)[19] ?? null
89
+ } catch { return null }
90
+ }
91
+
92
+ const currentProcessStart = linuxProcessStart(process.pid) ?? `fallback-${Math.floor(Date.now() - process.uptime() * 1_000)}`
93
+
94
+ function ownerIsLive(owner) {
95
+ if (!owner || typeof owner.token !== "string" || !Number.isSafeInteger(owner.pid) || typeof owner.processStart !== "string") return false
96
+ try { process.kill(owner.pid, 0) } catch (error) { return error?.code !== "ESRCH" }
97
+ const observedStart = owner.pid === process.pid ? currentProcessStart : linuxProcessStart(owner.pid)
98
+ return observedStart === null || observedStart === owner.processStart
99
+ }
100
+
101
+ function openSpool(options) {
102
+ const effectiveUid = options.effectiveUid ?? process.getuid?.()
103
+ if (effectiveUid !== 0) throw new Error("ouro event producer must run as root")
104
+ const spoolRoot = options.spoolRoot ?? SPOOL_ROOT
105
+ const expectedOwnerUid = options.expectedSpoolOwnerUid ?? 0
106
+ const directory = fs.openSync(spoolRoot, fs.constants.O_RDONLY | fs.constants.O_DIRECTORY | fs.constants.O_NOFOLLOW)
107
+ const openedRoot = fs.fstatSync(directory)
108
+ const assertRootIdentity = () => {
109
+ const namedRoot = fs.lstatSync(spoolRoot)
110
+ if (!openedRoot.isDirectory() || openedRoot.uid !== expectedOwnerUid || (openedRoot.mode & 0o777) !== 0o755
111
+ || !namedRoot.isDirectory() || namedRoot.isSymbolicLink() || namedRoot.uid !== expectedOwnerUid || (namedRoot.mode & 0o777) !== 0o755
112
+ || namedRoot.dev !== openedRoot.dev || namedRoot.ino !== openedRoot.ino) {
113
+ throw new Error(expectedOwnerUid === 0 ? "event spool root must be root-owned and mode 0755" : "event spool root is unsafe")
114
+ }
115
+ }
116
+ try {
117
+ assertRootIdentity()
118
+ return { spoolRoot, expectedOwnerUid, directory, anchoredRoot: process.platform === "linux" ? `/proc/self/fd/${directory}` : spoolRoot, assertRootIdentity }
119
+ } catch (error) {
120
+ fs.closeSync(directory)
121
+ throw error
122
+ }
123
+ }
124
+
125
+ function writeOwner(ownerPath, owner) {
126
+ fs.writeFileSync(ownerPath, `${JSON.stringify(owner)}\n`, { mode: 0o600, flag: "wx" })
127
+ const file = fs.openSync(ownerPath, "r")
128
+ try { fs.fsyncSync(file) } finally { fs.closeSync(file) }
129
+ }
130
+
131
+ function withProducerLock(context, operation) {
132
+ const lockPath = path.join(context.anchoredRoot, ".producer.lock")
133
+ const ownerPath = path.join(lockPath, "owner.json")
134
+ const token = randomBytes(32).toString("hex")
135
+ const cleanup = (target, verifyToken) => {
136
+ if (verifyToken) {
137
+ let owner
138
+ try { owner = JSON.parse(fs.readFileSync(path.join(target, "owner.json"), "utf8")) } catch { return }
139
+ if (owner.token !== token) return
140
+ }
141
+ for (const name of ["event.tmp", "owner.json"]) {
142
+ try { fs.unlinkSync(path.join(target, name)) } catch (error) { if (error?.code !== "ENOENT") throw error }
143
+ }
144
+ fs.rmdirSync(target)
145
+ }
146
+ const deadline = Date.now() + 30_000
147
+ while (true) {
148
+ try {
149
+ fs.mkdirSync(lockPath, { mode: 0o700 })
150
+ writeOwner(ownerPath, { token, pid: process.pid, processStart: currentProcessStart, leaseUntil: new Date(Date.now() + 30_000).toISOString() })
151
+ break
152
+ } catch (error) {
153
+ if (error?.code !== "EEXIST") throw error
154
+ let owner = null
155
+ try { owner = JSON.parse(fs.readFileSync(ownerPath, "utf8")) } catch { /* incomplete owner */ }
156
+ if (ownerIsLive(owner)) {
157
+ if (Date.now() >= deadline) throw new Error("event transition publication is busy")
158
+ Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 10)
159
+ continue
160
+ }
161
+ let leaseActive
162
+ if (owner && canonicalIso(owner.leaseUntil)) {
163
+ leaseActive = Date.parse(owner.leaseUntil) > Date.now()
164
+ } else {
165
+ try {
166
+ leaseActive = Date.now() - fs.statSync(lockPath).mtimeMs <= 30_000
167
+ } catch (statError) {
168
+ if (statError?.code === "ENOENT") {
169
+ try {
170
+ fs.lstatSync(lockPath)
171
+ } catch (lstatError) {
172
+ if (lstatError?.code === "ENOENT") continue
173
+ throw lstatError
174
+ }
175
+ throw new Error("event transition publication lock is unsafe")
176
+ }
177
+ throw statError
178
+ }
179
+ }
180
+ if (leaseActive) {
181
+ if (Date.now() >= deadline) throw new Error("event transition publication is busy")
182
+ Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 10)
183
+ continue
184
+ }
185
+ const stalePath = `${lockPath}.stale-${token}`
186
+ try { fs.renameSync(lockPath, stalePath) } catch { continue }
187
+ cleanup(stalePath, false)
188
+ }
189
+ }
190
+ try { return operation(lockPath) } finally { cleanup(lockPath, true) }
191
+ }
192
+
193
+ function parseStoredEnvelope(raw, fileName) {
194
+ if (Buffer.byteLength(raw) > MAX_BYTES) throw new Error("event envelope must be bounded")
195
+ const envelope = JSON.parse(raw)
196
+ validateEnvelope(envelope)
197
+ const expectedName = `${createHash("sha256").update(`${envelope.source}\0${envelope.incidentKey}\0${envelope.transitionId}`).digest("hex")}.json`
198
+ if (fileName !== expectedName) throw new Error("event envelope filename is invalid")
199
+ return envelope
200
+ }
201
+
202
+ function readSafeEnvelope(context, fileName, directoryEntry) {
203
+ const filePath = path.join(context.anchoredRoot, fileName)
204
+ const before = fs.lstatSync(filePath)
205
+ if ((directoryEntry && !directoryEntry.isFile()) || before.isSymbolicLink() || !before.isFile() || before.uid !== context.expectedOwnerUid
206
+ || before.nlink !== 1 || (before.mode & 0o777) !== 0o444 || before.size < 2 || before.size > MAX_BYTES) throw new Error("existing event spool file is unsafe")
207
+ const file = fs.openSync(filePath, fs.constants.O_RDONLY | fs.constants.O_NOFOLLOW)
208
+ try {
209
+ const opened = fs.fstatSync(file)
210
+ if (!opened.isFile() || opened.dev !== before.dev || opened.ino !== before.ino || opened.size !== before.size || opened.uid !== context.expectedOwnerUid
211
+ || opened.nlink !== 1 || (opened.mode & 0o777) !== 0o444) throw new Error("existing event spool file changed")
212
+ return parseStoredEnvelope(fs.readFileSync(file, "utf8"), fileName)
213
+ } finally { fs.closeSync(file) }
214
+ }
215
+
216
+ function maintainUnderLock(context, options) {
217
+ const now = new Date(options.now?.() ?? new Date()).getTime()
218
+ const graceMs = options.graceMs ?? DEFAULT_EXPIRY_GRACE_MS
219
+ if (!Number.isSafeInteger(graceMs) || graceMs < 0) throw new Error("event spool expiry grace is invalid")
220
+ let pruned = 0
221
+ let preserved = 0
222
+ for (const entry of fs.readdirSync(context.anchoredRoot, { withFileTypes: true })) {
223
+ if (!entry.name.endsWith(".json")) continue
224
+ const filePath = path.join(context.anchoredRoot, entry.name)
225
+ try {
226
+ const envelope = readSafeEnvelope(context, entry.name, entry)
227
+ if (Date.parse(envelope.expiresAt) + graceMs >= now) { preserved += 1; continue }
228
+ fs.unlinkSync(filePath)
229
+ pruned += 1
230
+ } catch {
231
+ preserved += 1
232
+ }
233
+ }
234
+ if (pruned > 0) fs.fsyncSync(context.directory)
235
+ return { pruned, preserved }
236
+ }
237
+
238
+ export function maintainSpool(options = {}) {
239
+ const context = openSpool(options)
240
+ try { return withProducerLock(context, () => maintainUnderLock(context, options)) } finally { fs.closeSync(context.directory) }
241
+ }
242
+
243
+ export function emitEvent(input, options = {}) {
244
+ const context = openSpool(options)
245
+ try {
246
+ const { envelope, serialized } = buildEnvelope(input, options.now ?? (() => new Date()), options.nonce ?? (() => randomBytes(32).toString("hex")))
247
+ const digest = createHash("sha256").update(`${envelope.source}\0${envelope.incidentKey}\0${envelope.transitionId}`).digest("hex")
248
+ const fileName = `${digest}.json`
249
+ const filePath = path.join(context.spoolRoot, fileName)
250
+ return withProducerLock(context, (lockPath) => {
251
+ maintainUnderLock(context, options)
252
+ let existing = null
253
+ try {
254
+ existing = readSafeEnvelope(context, fileName)
255
+ } catch (error) { if (error?.code !== "ENOENT") throw error }
256
+ if (existing) {
257
+ if (JSON.stringify(stableInput(existing)) !== JSON.stringify(stableInput(envelope))) throw new Error("transition already exists with different content")
258
+ return { filePath, created: false }
259
+ }
260
+ const maxSpoolFiles = options.maxSpoolFiles ?? MAX_SPOOL_FILES
261
+ if (!Number.isSafeInteger(maxSpoolFiles) || maxSpoolFiles < 1) throw new Error("event spool capacity is invalid")
262
+ const currentFiles = fs.readdirSync(context.anchoredRoot, { withFileTypes: true }).filter((entry) => entry.isFile() && entry.name.endsWith(".json")).length
263
+ if (currentFiles >= maxSpoolFiles) throw new Error("event spool capacity is exhausted; unexpired and unsafe artifacts were preserved")
264
+ const temporary = path.join(lockPath, "event.tmp")
265
+ fs.writeFileSync(temporary, serialized, { encoding: "utf8", mode: 0o600, flag: "wx" })
266
+ const file = fs.openSync(temporary, "r")
267
+ try { fs.fsyncSync(file) } finally { fs.closeSync(file) }
268
+ fs.chmodSync(temporary, 0o444)
269
+ context.assertRootIdentity()
270
+ fs.renameSync(temporary, path.join(context.anchoredRoot, fileName))
271
+ fs.fsyncSync(context.directory)
272
+ context.assertRootIdentity()
273
+ return { filePath, created: true }
274
+ })
275
+ } finally { fs.closeSync(context.directory) }
276
+ }
277
+
278
+ function parseArgs(argv) {
279
+ const input = { schemaVersion: 1, critical: true, evidence: [] }
280
+ const names = new Map([
281
+ ["--agent", "agent"], ["--source", "source"], ["--event-type", "eventType"], ["--incident-key", "incidentKey"],
282
+ ["--transition-id", "transitionId"], ["--revision", "observationRevision"], ["--action", "action"],
283
+ ["--action-receipt", "actionReceipt"], ["--summary", "summary"],
284
+ ["--protective-state-digest", "protectiveStateDigest"], ["--protective-state-observed-at", "protectiveStateObservedAt"],
285
+ ])
286
+ for (let index = 0; index < argv.length; index += 2) {
287
+ const flag = argv[index]
288
+ const value = argv[index + 1]
289
+ if (flag === "--evidence" && value !== undefined) input.evidence.push(value)
290
+ else if (flag === "--protective-state-verified" && (value === "true" || value === "false")) input.protectiveStateVerified = value === "true"
291
+ else if (names.has(flag) && value !== undefined) input[names.get(flag)] = value
292
+ else throw new Error("event producer arguments are invalid")
293
+ }
294
+ return input
295
+ }
296
+
297
+ if (process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
298
+ try {
299
+ if (process.argv.length === 3 && process.argv[2] === "--maintain") {
300
+ const result = maintainSpool()
301
+ process.stdout.write(`maintained pruned=${result.pruned} preserved=${result.preserved}\n`)
302
+ } else {
303
+ const result = emitEvent(parseArgs(process.argv.slice(2)))
304
+ process.stdout.write(`${result.created ? "created" : "existing"} ${path.basename(result.filePath)}\n`)
305
+ }
306
+ } catch (error) {
307
+ process.stderr.write(`ouro event producer failed: ${error instanceof Error ? error.message : "unknown error"}\n`)
308
+ process.exitCode = 1
309
+ }
310
+ }
@@ -0,0 +1,54 @@
1
+ #!/bin/bash
2
+ set -euo pipefail
3
+
4
+ PRODUCER="/boot/config/custom/ouro-events/emit-event.mjs"
5
+
6
+ if [ "$#" -eq 2 ] && [ "$1" = "--self-test" ]; then
7
+ exec "$(command -v node)" "$2" --maintain
8
+ fi
9
+
10
+ if [ "$#" -ne 9 ]; then
11
+ echo "usage: emit-usenet-event.sh <action> <incident> <transition> <receipt> <summary> <evidence> <verified> <verification-digest> <observed-at>" >&2
12
+ exit 2
13
+ fi
14
+
15
+ ACTION="$1"
16
+ INCIDENT="$2"
17
+ TRANSITION="$3"
18
+ RECEIPT="$4"
19
+ SUMMARY="$5"
20
+ EVIDENCE="$6"
21
+ VERIFIED="$7"
22
+ VERIFICATION_DIGEST="$8"
23
+ VERIFIED_AT="$9"
24
+ EVENT_TYPE="usenet.protective_action"
25
+
26
+ case "$ACTION" in
27
+ sabnzbd.pause) ;;
28
+ usenet.observe) EVENT_TYPE="usenet.health_observation" ;;
29
+ *) echo "usenet event adapter: action is not allowlisted" >&2; exit 2 ;;
30
+ esac
31
+ case "$VERIFIED" in true|false) ;; *) echo "usenet event adapter: verification state is invalid" >&2; exit 2 ;; esac
32
+ [[ "$VERIFICATION_DIGEST" =~ ^[a-f0-9]{64}$ ]] || { echo "usenet event adapter: verification digest is invalid" >&2; exit 2; }
33
+
34
+ if [ "$ACTION" = "usenet.observe" ]; then
35
+ OBSERVED_STATE="${TRANSITION%%:*}"
36
+ REVISION="$(printf '%s\0%s\0%s' "$ACTION" "$INCIDENT" "$OBSERVED_STATE" | sha256sum | cut -d' ' -f1)"
37
+ else
38
+ REVISION="$(printf '%s\0%s\0%s\0%s' "$ACTION" "$INCIDENT" "$TRANSITION" "$RECEIPT" | sha256sum | cut -d' ' -f1)"
39
+ fi
40
+
41
+ exec /usr/local/bin/node "$PRODUCER" \
42
+ "--agent" "sanctuary" \
43
+ "--source" "sanctuary-usenet" \
44
+ "--event-type" "$EVENT_TYPE" \
45
+ "--incident-key" "$INCIDENT" \
46
+ "--transition-id" "$TRANSITION" \
47
+ "--revision" "$REVISION" \
48
+ "--action" "$ACTION" \
49
+ "--action-receipt" "$RECEIPT" \
50
+ "--summary" "$SUMMARY" \
51
+ "--evidence" "$EVIDENCE" \
52
+ "--protective-state-verified" "$VERIFIED" \
53
+ "--protective-state-digest" "$VERIFICATION_DIGEST" \
54
+ "--protective-state-observed-at" "$VERIFIED_AT"