@ouro.bot/cli 0.1.0-alpha.83 → 0.1.0-alpha.831
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +154 -23
- package/RepairGuide.ouro/agent.json +5 -0
- package/RepairGuide.ouro/psyche/IDENTITY.md +19 -0
- package/RepairGuide.ouro/psyche/SOUL.md +55 -0
- package/RepairGuide.ouro/skills/diagnose-broken-remote.md +63 -0
- package/RepairGuide.ouro/skills/diagnose-stacked-typed-issues.md +35 -0
- package/RepairGuide.ouro/skills/diagnose-sync-blocked.md +54 -0
- package/RepairGuide.ouro/skills/diagnose-vault-expired.md +60 -0
- package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/agent.json +4 -2
- package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/SOUL.md +2 -2
- package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/the-serpent.md +1 -1
- package/assets/bluebubbles-host +264 -0
- package/assets/sanctuary-host-launcher.sh +16 -0
- package/changelog.json +5308 -16
- package/deploy/unraid/Dockerfile +54 -0
- package/deploy/unraid/README.txt +2983 -0
- package/deploy/unraid/audit-container-spec.sh +4 -0
- package/deploy/unraid/container-runtime.json +1 -0
- package/deploy/unraid/docker-man-template-transaction.mjs +711 -0
- package/deploy/unraid/docker-man-template-xml.cjs +105 -0
- package/deploy/unraid/migrate-sanctuary-bundle.mjs +60 -0
- package/deploy/unraid/ouro-events/bootstrap-spool.sh +92 -0
- package/deploy/unraid/ouro-events/emit-event.mjs +310 -0
- package/deploy/unraid/ouro-events/emit-usenet-event.sh +54 -0
- package/deploy/unraid/ouro-events/install-usenet-guard.sh +435 -0
- package/deploy/unraid/ouro-events/usenet-health.sh +176 -0
- package/deploy/unraid/sanctuary-acceptance-adapter.sh +43 -0
- package/deploy/unraid/sanctuary-acceptance-contract.json +198 -0
- package/deploy/unraid/sanctuary-acceptance-harness.sh +23 -0
- package/deploy/unraid/sanctuary-authority-installation.json +36 -0
- package/deploy/unraid/sanctuary-authority-service.sh +30 -0
- package/deploy/unraid/sanctuary-deployment-target.mjs +710 -0
- package/deploy/unraid/sanctuary-safe-rename.py +24 -0
- package/deploy/unraid/sanctuary-unit16-host-broker.mjs +2084 -0
- package/deploy/unraid/sanctuary-unit16-run.sh +560 -0
- package/deploy/unraid/sanctuary-unit18-target-audit.sh +27 -0
- package/deploy/unraid/sanctuary.ouro/agent.json +66 -0
- package/deploy/unraid/sanctuary.ouro/arc/README.md +3 -0
- package/deploy/unraid/sanctuary.ouro/bundle-meta.json +5 -0
- package/deploy/unraid/sanctuary.ouro/habits/sanctuary-health.md +8 -0
- package/deploy/unraid/sanctuary.ouro/mcp/media-mcp.mjs +795 -0
- package/deploy/unraid/sanctuary.ouro/provider-readiness.json +11 -0
- package/deploy/unraid/sanctuary.ouro/psyche/ASPIRATIONS.md +5 -0
- package/deploy/unraid/sanctuary.ouro/psyche/IDENTITY.md +5 -0
- package/deploy/unraid/sanctuary.ouro/psyche/LORE.md +15 -0
- package/deploy/unraid/sanctuary.ouro/psyche/SOUL.md +25 -0
- package/deploy/unraid/sanctuary.ouro/psyche/TACIT.md +15 -0
- package/deploy/unraid/sanctuary.ouro/state/policy/steward.json +7 -0
- package/deploy/unraid/sanctuary.ouro/tool-profiles.json +135 -0
- package/deploy/unraid/sanctuary.xml +28 -0
- package/dist/a2a/card.js +57 -0
- package/dist/a2a/client.js +180 -0
- package/dist/a2a/config.js +50 -0
- package/dist/a2a/delegation-stores.js +56 -0
- package/dist/a2a/did-resolution.js +93 -0
- package/dist/a2a/identity.js +170 -0
- package/dist/a2a/inbound-share.js +107 -0
- package/dist/a2a/mission-result-wire.js +196 -0
- package/dist/a2a/onboarding.js +115 -0
- package/dist/a2a/pin-store.js +132 -0
- package/dist/a2a/seen-ledger.js +120 -0
- package/dist/a2a/server.js +604 -0
- package/dist/a2a/task-store.js +69 -0
- package/dist/a2a/transport.js +45 -0
- package/dist/a2a/types.js +3 -0
- package/dist/arc/attention-types.js +8 -0
- package/dist/arc/cares.js +360 -0
- package/dist/arc/episodes.js +118 -0
- package/dist/arc/evolution.js +490 -0
- package/dist/arc/flight-recorder.js +1034 -0
- package/dist/arc/intentions.js +134 -0
- package/dist/arc/json-store.js +117 -0
- package/dist/arc/obligations.js +386 -0
- package/dist/arc/packets.js +336 -0
- package/dist/arc/presence.js +185 -0
- package/dist/arc/task-lifecycle.js +57 -0
- package/dist/commerce/store.js +755 -0
- package/dist/commerce/types.js +3 -0
- package/dist/heart/active-work.js +798 -40
- package/dist/heart/agent-entry.js +252 -3
- package/dist/heart/approval-files.js +113 -0
- package/dist/heart/approval-store.js +960 -0
- package/dist/heart/attachments/image-normalize.js +194 -0
- package/dist/heart/attachments/materialize.js +97 -0
- package/dist/heart/attachments/originals.js +88 -0
- package/dist/heart/attachments/render.js +29 -0
- package/dist/heart/attachments/sources/bluebubbles.js +156 -0
- package/dist/heart/attachments/sources/cli-local-file.js +78 -0
- package/dist/heart/attachments/sources/index.js +18 -0
- package/dist/heart/attachments/sources/telegram.js +71 -0
- package/dist/heart/attachments/store.js +132 -0
- package/dist/heart/attachments/types.js +93 -0
- package/dist/heart/auth/auth-flow.js +495 -0
- package/dist/heart/autonomy-budget.js +478 -0
- package/dist/heart/awaiting/await-alert.js +148 -0
- package/dist/heart/awaiting/await-expiry.js +143 -0
- package/dist/heart/awaiting/await-loader.js +91 -0
- package/dist/heart/awaiting/await-parser.js +149 -0
- package/dist/heart/awaiting/await-runtime-state.js +111 -0
- package/dist/heart/awaiting/await-scheduler.js +382 -0
- package/dist/heart/background-operations.js +281 -0
- package/dist/heart/bridges/manager.js +137 -17
- package/dist/heart/bridges/store.js +14 -2
- package/dist/heart/bundle-state.js +168 -0
- package/dist/heart/commitments.js +44 -18
- package/dist/heart/config-registry.js +331 -0
- package/dist/heart/config.js +174 -131
- package/dist/heart/context-loss-gauntlet.js +387 -0
- package/dist/heart/context-loss-sentinel.js +1157 -0
- package/dist/heart/core.js +2169 -581
- package/dist/heart/cross-chat-delivery.js +9 -22
- package/dist/heart/daemon/agent-config-check.js +451 -0
- package/dist/heart/daemon/agent-discovery.js +185 -3
- package/dist/heart/daemon/agent-service.js +542 -0
- package/dist/heart/daemon/agentic-repair.js +547 -0
- package/dist/heart/daemon/await-private-wake.js +59 -0
- package/dist/heart/daemon/bluebubbles-cli-integration.js +71 -0
- package/dist/heart/daemon/bluebubbles-health-diagnostics.js +122 -0
- package/dist/heart/daemon/bluebubbles-host-protocol.js +433 -0
- package/dist/heart/daemon/bluebubbles-host.js +273 -0
- package/dist/heart/daemon/boot-sync-probe.js +197 -0
- package/dist/heart/daemon/cadence.js +180 -0
- package/dist/heart/daemon/cli-defaults.js +854 -0
- package/dist/heart/daemon/cli-desk.js +322 -0
- package/dist/heart/daemon/cli-exec.js +8822 -0
- package/dist/heart/daemon/cli-help.js +716 -0
- package/dist/heart/daemon/cli-parse.js +2771 -0
- package/dist/heart/daemon/cli-render-doctor.js +57 -0
- package/dist/heart/daemon/cli-render.js +777 -0
- package/dist/heart/daemon/cli-types.js +8 -0
- package/dist/heart/daemon/connect-bay.js +323 -0
- package/dist/heart/daemon/container-credential-bootstrap.js +273 -0
- package/dist/heart/daemon/container-healthcheck.js +112 -0
- package/dist/heart/daemon/container-runtime.js +97 -0
- package/dist/heart/daemon/container-spec-auditor-main.js +171 -0
- package/dist/heart/daemon/container-spec-auditor.js +229 -0
- package/dist/heart/daemon/daemon-bootstrap-startup.js +115 -0
- package/dist/heart/daemon/daemon-cli.js +29 -2349
- package/dist/heart/daemon/daemon-entry.js +930 -11
- package/dist/heart/daemon/daemon-health.js +186 -0
- package/dist/heart/daemon/daemon-rollup.js +57 -0
- package/dist/heart/daemon/daemon-runtime-sync.js +88 -13
- package/dist/heart/daemon/daemon-tombstone.js +236 -0
- package/dist/heart/daemon/daemon.js +2084 -105
- package/dist/heart/daemon/dns-workflow.js +394 -0
- package/dist/heart/daemon/doctor-types.js +8 -0
- package/dist/heart/daemon/doctor.js +1755 -0
- package/dist/heart/daemon/freshness.js +345 -0
- package/dist/heart/daemon/habit-private-wake.js +61 -0
- package/dist/heart/daemon/health-monitor.js +142 -11
- package/dist/heart/daemon/hooks/agent-config-v2.js +33 -0
- package/dist/heart/daemon/hooks/bundle-meta.js +135 -1
- package/dist/heart/daemon/http-health-probe.js +80 -0
- package/dist/heart/daemon/human-command-screens.js +234 -0
- package/dist/heart/daemon/human-readiness.js +114 -0
- package/dist/heart/daemon/inner-status.js +78 -0
- package/dist/heart/daemon/interactive-repair.js +394 -0
- package/dist/heart/daemon/launchd.js +37 -8
- package/dist/heart/daemon/log-tailer.js +79 -10
- package/dist/heart/daemon/logs-prune.js +132 -0
- package/dist/heart/daemon/mcp-canary.js +524 -0
- package/dist/heart/daemon/message-router.js +65 -5
- package/dist/heart/daemon/migrate-to-desk.js +848 -0
- package/dist/heart/daemon/os-cron-deps.js +135 -0
- package/dist/heart/daemon/os-cron.js +22 -14
- package/dist/heart/daemon/ouro-bot-entry.js +4 -2
- package/dist/heart/daemon/ouro-entry.js +3 -1
- package/dist/heart/daemon/plugin-cli.js +432 -0
- package/dist/heart/daemon/process-manager.js +733 -61
- package/dist/heart/daemon/provider-discovery.js +137 -0
- package/dist/heart/daemon/provider-ping-progress.js +83 -0
- package/dist/heart/daemon/prunable-bundle.js +144 -0
- package/dist/heart/daemon/pulse.js +511 -0
- package/dist/heart/daemon/readiness-repair.js +365 -0
- package/dist/heart/daemon/run-hooks.js +2 -0
- package/dist/heart/daemon/runtime-logging.js +47 -14
- package/dist/heart/daemon/runtime-metadata.js +2 -30
- package/dist/heart/daemon/runtime-mode.js +13 -2
- package/dist/heart/daemon/safe-mode.js +161 -0
- package/dist/heart/daemon/sanctuary-acceptance-adapter.js +2654 -0
- package/dist/heart/daemon/sanctuary-acceptance-harness.js +1830 -0
- package/dist/heart/daemon/sanctuary-acceptance-marker.js +267 -0
- package/dist/heart/daemon/sanctuary-acceptance-scenarios.js +955 -0
- package/dist/heart/daemon/sanctuary-authority-codec.js +86 -0
- package/dist/heart/daemon/sanctuary-authority-epoch.js +256 -0
- package/dist/heart/daemon/sanctuary-authority-installation.js +140 -0
- package/dist/heart/daemon/sanctuary-authority-ledger.js +241 -0
- package/dist/heart/daemon/sanctuary-authority-root-lifecycle.js +819 -0
- package/dist/heart/daemon/sanctuary-authority-vault-migration.js +79 -0
- package/dist/heart/daemon/sanctuary-bundle-migration.js +729 -0
- package/dist/heart/daemon/sanctuary-host-authority.js +1008 -0
- package/dist/heart/daemon/sanctuary-host-detached-supervisor.js +494 -0
- package/dist/heart/daemon/sanctuary-host-executor.js +670 -0
- package/dist/heart/daemon/sanctuary-host-linux-kernel.js +334 -0
- package/dist/heart/daemon/sanctuary-host-supervisor-entry.js +207 -0
- package/dist/heart/daemon/sanctuary-host-supervisor.js +95 -0
- package/dist/heart/daemon/sanctuary-package-management.js +103 -0
- package/dist/heart/daemon/sanctuary-scheduler-liveness.js +227 -0
- package/dist/heart/daemon/sanctuary-scheduler-origin.js +132 -0
- package/dist/heart/daemon/sanctuary-telegram-authority-entry.js +445 -0
- package/dist/heart/daemon/sanctuary-telegram-authority-gateway.js +585 -0
- package/dist/heart/daemon/sanctuary-telegram-authority-service.js +630 -0
- package/dist/heart/daemon/sense-manager.js +699 -48
- package/dist/heart/daemon/session-id-resolver.js +131 -0
- package/dist/heart/daemon/skill-management-installer.js +1 -1
- package/dist/heart/daemon/socket-client.js +180 -12
- package/dist/heart/daemon/stale-bundle-prune.js +113 -0
- package/dist/heart/daemon/startup-tui.js +339 -0
- package/dist/heart/daemon/supercronic-supervisor.js +282 -0
- package/dist/heart/daemon/task-scheduler.js +117 -39
- package/dist/heart/daemon/terminal-ui.js +499 -0
- package/dist/heart/daemon/thoughts.js +138 -54
- package/dist/heart/daemon/up-progress.js +366 -0
- package/dist/heart/daemon/vault-items.js +56 -0
- package/dist/heart/delegation.js +1 -4
- package/dist/heart/external-events/router.js +1430 -0
- package/dist/heart/frontend-approval-runtime.js +506 -0
- package/dist/heart/frontend-journal.js +215 -0
- package/dist/heart/frontend-session-service.js +237 -0
- package/dist/heart/frontend-socket-client.js +164 -0
- package/dist/heart/frontend-socket.js +400 -0
- package/dist/heart/habits/habit-cancel.js +810 -0
- package/dist/heart/habits/habit-lifecycle.js +1327 -0
- package/dist/heart/habits/habit-migration.js +194 -0
- package/dist/heart/habits/habit-parser.js +278 -0
- package/dist/heart/habits/habit-runtime-state.js +93 -0
- package/dist/heart/habits/habit-scheduler.js +653 -0
- package/dist/heart/habits/habit-session-summary.js +318 -0
- package/dist/heart/habits/habit-session.js +636 -0
- package/dist/heart/{daemon → hatch}/hatch-flow.js +55 -62
- package/dist/heart/{daemon → hatch}/hatch-specialist.js +6 -8
- package/dist/heart/{daemon → hatch}/specialist-prompt.js +12 -9
- package/dist/heart/{daemon → hatch}/specialist-tools.js +53 -18
- package/dist/heart/identity.js +196 -59
- package/dist/heart/kept-notes.js +289 -0
- package/dist/heart/kicks.js +1 -1
- package/dist/heart/machine-identity.js +161 -0
- package/dist/heart/mail-import-discovery.js +356 -0
- package/dist/heart/mailbox/mailbox-http-hooks.js +98 -0
- package/dist/heart/mailbox/mailbox-http-response.js +7 -0
- package/dist/heart/mailbox/mailbox-http-routes.js +398 -0
- package/dist/heart/mailbox/mailbox-http-static.js +103 -0
- package/dist/heart/mailbox/mailbox-http-transport.js +129 -0
- package/dist/heart/mailbox/mailbox-http.js +99 -0
- package/dist/heart/mailbox/mailbox-read.js +39 -0
- package/dist/heart/mailbox/mailbox-types.js +27 -0
- package/dist/heart/mailbox/mailbox-view.js +197 -0
- package/dist/heart/mailbox/readers/agent-machine.js +428 -0
- package/dist/heart/mailbox/readers/continuity-readers.js +329 -0
- package/dist/heart/mailbox/readers/mail.js +375 -0
- package/dist/heart/mailbox/readers/runtime-readers.js +823 -0
- package/dist/heart/mailbox/readers/sessions.js +231 -0
- package/dist/heart/mailbox/readers/shared.js +111 -0
- package/dist/heart/mcp/mcp-server.js +696 -0
- package/dist/heart/migrate-config.js +100 -0
- package/dist/heart/model-capabilities.js +11 -0
- package/dist/heart/orientation-frame.js +345 -0
- package/dist/heart/platform.js +81 -0
- package/dist/heart/primitives.js +1 -1
- package/dist/heart/private-runtime/decision-renderer.js +158 -0
- package/dist/heart/private-runtime/index.js +18 -0
- package/dist/heart/private-runtime/ledger.js +221 -0
- package/dist/heart/private-runtime/policy.js +252 -0
- package/dist/heart/private-runtime/types.js +2 -0
- package/dist/heart/progress-story.js +1 -1
- package/dist/heart/provider-attempt.js +137 -0
- package/dist/heart/provider-binding-resolver.js +273 -0
- package/dist/heart/provider-credentials.js +449 -0
- package/dist/heart/provider-failover.js +311 -0
- package/dist/heart/provider-models.js +96 -0
- package/dist/heart/provider-ping.js +265 -0
- package/dist/heart/provider-readiness-cache.js +119 -0
- package/dist/heart/provider-visibility.js +188 -0
- package/dist/heart/providers/anthropic-token.js +131 -0
- package/dist/heart/providers/anthropic.js +173 -60
- package/dist/heart/providers/azure.js +23 -11
- package/dist/heart/providers/error-classification.js +127 -0
- package/dist/heart/providers/github-copilot.js +34 -38
- package/dist/heart/providers/minimax-vlm.js +189 -0
- package/dist/heart/providers/minimax.js +26 -8
- package/dist/heart/providers/openai-codex-token.js +349 -0
- package/dist/heart/providers/openai-codex.js +55 -40
- package/dist/heart/providers/openai-compatible.js +177 -0
- package/dist/heart/run-ledger.js +247 -0
- package/dist/heart/runtime-capability-check.js +170 -0
- package/dist/heart/runtime-credentials.js +607 -0
- package/dist/heart/runtime-cwd.js +87 -0
- package/dist/heart/sense-truth.js +22 -4
- package/dist/heart/session-activity.js +92 -24
- package/dist/heart/session-events.js +1504 -0
- package/dist/heart/session-playback-cli-main.js +5 -0
- package/dist/heart/session-playback-cli.js +36 -0
- package/dist/heart/session-playback.js +231 -0
- package/dist/heart/session-redaction-repair-cli-main.js +31 -0
- package/dist/heart/session-redaction-repair.js +552 -0
- package/dist/heart/session-stats-cli-main.js +5 -0
- package/dist/heart/session-stats.js +183 -0
- package/dist/heart/session-transcript.js +133 -0
- package/dist/heart/start-of-turn-packet.js +372 -0
- package/dist/heart/steward-policy.js +618 -0
- package/dist/heart/streaming.js +604 -207
- package/dist/heart/structured-output.js +196 -0
- package/dist/heart/sync-classification.js +176 -0
- package/dist/heart/sync.js +449 -0
- package/dist/heart/target-resolution.js +11 -7
- package/dist/heart/tempo.js +93 -0
- package/dist/heart/temporal-view.js +41 -0
- package/dist/heart/timeouts.js +101 -0
- package/dist/heart/tool-activity-callbacks.js +59 -0
- package/dist/heart/tool-approval.js +382 -0
- package/dist/heart/tool-description.js +155 -0
- package/dist/heart/tool-friction.js +55 -0
- package/dist/heart/tool-loop.js +200 -0
- package/dist/heart/turn-context.js +482 -0
- package/dist/heart/turn-execution-lease.js +30 -0
- package/dist/heart/{daemon → versioning}/ouro-bot-global-installer.js +6 -5
- package/dist/heart/{daemon → versioning}/ouro-bot-wrapper.js +1 -1
- package/dist/heart/versioning/ouro-path-installer.js +432 -0
- package/dist/heart/versioning/ouro-recovery-launcher.js +76 -0
- package/dist/heart/versioning/ouro-version-manager.js +409 -0
- package/dist/heart/{daemon → versioning}/staged-restart.js +40 -8
- package/dist/heart/{daemon → versioning}/update-checker.js +6 -1
- package/dist/heart/versioning/update-hooks.js +154 -0
- package/dist/heart/versioning/version-intent.js +114 -0
- package/dist/heart/work-card.js +380 -0
- package/dist/mailbox-ui/assets/index-Cyv4mw3Y.js +1 -0
- package/dist/mailbox-ui/assets/index-Du_9G9WO.css +1 -0
- package/dist/mailbox-ui/assets/vendor-CcN1XpQ9.js +61 -0
- package/dist/mailbox-ui/index.html +16 -0
- package/dist/mailroom/attention.js +167 -0
- package/dist/mailroom/autonomy.js +209 -0
- package/dist/mailroom/blob-store.js +889 -0
- package/dist/mailroom/body-cache.js +61 -0
- package/dist/mailroom/cache-sync-cli.js +58 -0
- package/dist/mailroom/core.js +803 -0
- package/dist/mailroom/entry.js +160 -0
- package/dist/mailroom/file-store.js +568 -0
- package/dist/mailroom/hosted-cache-sync.js +384 -0
- package/dist/mailroom/mbox-import.js +393 -0
- package/dist/mailroom/migration.js +164 -0
- package/dist/mailroom/outbound.js +380 -0
- package/dist/mailroom/policy.js +263 -0
- package/dist/mailroom/reader.js +292 -0
- package/dist/mailroom/search-cache.js +513 -0
- package/dist/mailroom/search-relevance.js +319 -0
- package/dist/mailroom/smtp-ingress.js +176 -0
- package/dist/mailroom/source-state.js +176 -0
- package/dist/mailroom/thread.js +109 -0
- package/dist/mailroom/travel-extract.js +89 -0
- package/dist/mind/bundle-manifest.js +24 -2
- package/dist/mind/context.js +309 -111
- package/dist/mind/desk-section.js +362 -0
- package/dist/mind/diary-integrity.js +60 -0
- package/dist/mind/{memory.js → diary.js} +68 -77
- package/dist/mind/embedding-provider.js +60 -0
- package/dist/mind/file-state.js +179 -0
- package/dist/mind/{associative-recall.js → note-search.js} +47 -58
- package/dist/mind/obligation-steering.js +199 -3
- package/dist/mind/pending.js +36 -10
- package/dist/mind/prompt-budget.js +479 -0
- package/dist/mind/prompt-refresh.js +3 -2
- package/dist/mind/prompt.js +1050 -189
- package/dist/mind/provenance-trust.js +26 -0
- package/dist/mind/record-paths.js +312 -0
- package/dist/mind/scrutiny.js +173 -0
- package/dist/mind/session-transaction.js +564 -0
- package/dist/nerves/cli-logging.js +7 -1
- package/dist/nerves/coverage/audit-rules.js +15 -6
- package/dist/nerves/coverage/audit.js +28 -2
- package/dist/nerves/coverage/cli.js +1 -1
- package/dist/nerves/coverage/contract.js +5 -5
- package/dist/nerves/coverage/file-completeness.js +139 -5
- package/dist/nerves/coverage/run-artifacts.js +55 -35
- package/dist/nerves/coverage/source-scanner.js +4 -4
- package/dist/nerves/event-buffer.js +111 -0
- package/dist/nerves/index.js +293 -9
- package/dist/nerves/observation.js +20 -0
- package/dist/nerves/redact.js +79 -0
- package/dist/nerves/review/cli-main.js +5 -0
- package/dist/nerves/review/cli.js +156 -0
- package/dist/nerves/review/core.js +159 -0
- package/dist/nerves/runtime.js +17 -1
- package/dist/repertoire/ado-client.js +15 -56
- package/dist/repertoire/ado-semantic.js +16 -10
- package/dist/repertoire/api-client.js +97 -0
- package/dist/repertoire/bitwarden-store.js +1045 -0
- package/dist/repertoire/bundle-templates.js +71 -0
- package/dist/repertoire/bw-installer.js +180 -0
- package/dist/repertoire/coding/codex-jsonl.js +64 -0
- package/dist/repertoire/coding/context-pack.js +331 -0
- package/dist/repertoire/coding/feedback.js +174 -32
- package/dist/repertoire/coding/index.js +15 -3
- package/dist/repertoire/coding/manager.js +159 -10
- package/dist/repertoire/coding/spawner.js +58 -11
- package/dist/repertoire/coding/tools.js +221 -15
- package/dist/repertoire/coding/workbench-client.js +272 -0
- package/dist/repertoire/coding/workbench-manager.js +346 -0
- package/dist/repertoire/commerce-errors.js +109 -0
- package/dist/repertoire/commerce-self-test.js +156 -0
- package/dist/repertoire/credential-access.js +178 -0
- package/dist/repertoire/desk/classifier.js +362 -0
- package/dist/repertoire/duffel-client.js +185 -0
- package/dist/repertoire/github-client.js +14 -55
- package/dist/repertoire/graph-client.js +11 -52
- package/dist/repertoire/guardrails.js +811 -18
- package/dist/repertoire/mcp-client.js +44 -3
- package/dist/repertoire/mcp-manager.js +427 -139
- package/dist/repertoire/mcp-tools.js +124 -0
- package/dist/repertoire/plugin-mcp.js +175 -0
- package/dist/repertoire/plugins.js +253 -0
- package/dist/repertoire/relationship-authorization.js +322 -0
- package/dist/repertoire/shell-sessions.js +134 -0
- package/dist/repertoire/skills.js +48 -4
- package/dist/repertoire/stripe-client.js +131 -0
- package/dist/repertoire/tool-arguments.js +170 -0
- package/dist/repertoire/tool-results.js +29 -0
- package/dist/repertoire/tools-a2a.js +699 -0
- package/dist/repertoire/tools-attachments.js +322 -0
- package/dist/repertoire/tools-awaiting.js +575 -0
- package/dist/repertoire/tools-base.js +77 -1138
- package/dist/repertoire/tools-bluebubbles.js +2 -0
- package/dist/repertoire/tools-bridge.js +144 -0
- package/dist/repertoire/tools-bundle.js +993 -0
- package/dist/repertoire/tools-commerce.js +253 -0
- package/dist/repertoire/tools-config.js +186 -0
- package/dist/repertoire/tools-continuity.js +575 -0
- package/dist/repertoire/tools-credential.js +383 -0
- package/dist/repertoire/tools-evolution.js +527 -0
- package/dist/repertoire/tools-files.js +344 -0
- package/dist/repertoire/tools-flight.js +290 -0
- package/dist/repertoire/tools-flow.js +119 -0
- package/dist/repertoire/tools-github.js +3 -8
- package/dist/repertoire/tools-habits.js +103 -0
- package/dist/repertoire/tools-mail.js +1828 -0
- package/dist/repertoire/tools-notes.js +485 -0
- package/dist/repertoire/tools-obligations.js +182 -0
- package/dist/repertoire/tools-orientation.js +31 -0
- package/dist/repertoire/tools-record.js +482 -0
- package/dist/repertoire/tools-rsvp.js +139 -0
- package/dist/repertoire/tools-runtime.js +150 -0
- package/dist/repertoire/tools-sanctuary-host.js +202 -0
- package/dist/repertoire/tools-session.js +1133 -0
- package/dist/repertoire/tools-shell.js +149 -0
- package/dist/repertoire/tools-steward-policy.js +119 -0
- package/dist/repertoire/tools-stripe.js +224 -0
- package/dist/repertoire/tools-surface.js +369 -0
- package/dist/repertoire/tools-teams.js +12 -39
- package/dist/repertoire/tools-telegram-contacts.js +37 -0
- package/dist/repertoire/tools-travel.js +125 -0
- package/dist/repertoire/tools-trip.js +982 -0
- package/dist/repertoire/tools-unraid.js +531 -0
- package/dist/repertoire/tools-user-profile.js +146 -0
- package/dist/repertoire/tools-vault.js +40 -0
- package/dist/repertoire/tools-voice.js +145 -0
- package/dist/repertoire/tools.js +494 -148
- package/dist/repertoire/travel-api-client.js +360 -0
- package/dist/repertoire/unraid-client.js +155 -0
- package/dist/repertoire/unraid-restart.js +339 -0
- package/dist/repertoire/user-profile.js +131 -0
- package/dist/repertoire/vault-setup.js +246 -0
- package/dist/repertoire/vault-unlock.js +628 -0
- package/dist/rsvp/aisleplanner-client.js +354 -0
- package/dist/rsvp/cli.js +926 -0
- package/dist/rsvp/config.js +327 -0
- package/dist/rsvp/cutover.js +433 -0
- package/dist/rsvp/diagnostics.js +232 -0
- package/dist/rsvp/diff-renderer.js +176 -0
- package/dist/rsvp/habit-policy.js +110 -0
- package/dist/rsvp/habit-stage.js +123 -0
- package/dist/rsvp/incident-bundle.js +97 -0
- package/dist/rsvp/migration.js +204 -0
- package/dist/rsvp/native-habit-runner.js +630 -0
- package/dist/rsvp/outbound-state.js +628 -0
- package/dist/rsvp/query.js +118 -0
- package/dist/rsvp/replay.js +233 -0
- package/dist/rsvp/snapshot.js +232 -0
- package/dist/rsvp/spend-ledger.js +175 -0
- package/dist/scripts/claude-code-hook.js +41 -0
- package/dist/scripts/claude-code-stop-hook.js +47 -0
- package/dist/senses/a2a-entry.js +115 -0
- package/dist/senses/acp-server.js +386 -0
- package/dist/senses/attention-queue.js +186 -0
- package/dist/senses/await-turn-message.js +58 -0
- package/dist/senses/bluebubbles/active-turns.js +216 -0
- package/dist/senses/bluebubbles/attachment-cache.js +53 -0
- package/dist/senses/bluebubbles/attachment-download.js +137 -0
- package/dist/senses/{bluebubbles-client.js → bluebubbles/client.js} +355 -40
- package/dist/senses/bluebubbles/context-packet.js +207 -0
- package/dist/senses/bluebubbles/context-smoke.js +144 -0
- package/dist/senses/bluebubbles/entry.js +90 -0
- package/dist/senses/{bluebubbles-inbound-log.js → bluebubbles/inbound-log.js} +21 -3
- package/dist/senses/bluebubbles/index.js +3841 -0
- package/dist/senses/bluebubbles/latest-turn.js +311 -0
- package/dist/senses/{bluebubbles-media.js → bluebubbles/media.js} +121 -71
- package/dist/senses/{bluebubbles-model.js → bluebubbles/model.js} +159 -28
- package/dist/senses/{bluebubbles-mutation-log.js → bluebubbles/mutation-log.js} +24 -13
- package/dist/senses/bluebubbles/outbound-state.js +308 -0
- package/dist/senses/bluebubbles/processed-log.js +133 -0
- package/dist/senses/bluebubbles/reaction-policy.js +47 -0
- package/dist/senses/bluebubbles/replay.js +284 -0
- package/dist/senses/{bluebubbles-runtime-state.js → bluebubbles/runtime-state.js} +30 -2
- package/dist/senses/bluebubbles/semantic-receipts.js +1986 -0
- package/dist/senses/{bluebubbles-session-cleanup.js → bluebubbles/session-cleanup.js} +1 -1
- package/dist/senses/bluebubbles/webhook-registration.js +228 -0
- package/dist/senses/bluebubbles-meta-guard.js +39 -0
- package/dist/senses/cli/bracketed-paste.js +82 -0
- package/dist/senses/cli/image-paste.js +287 -0
- package/dist/senses/cli/image-ref-navigation.js +75 -0
- package/dist/senses/cli/ink-app.js +156 -0
- package/dist/senses/cli/inline-diff.js +64 -0
- package/dist/senses/cli/input-keys.js +174 -0
- package/dist/senses/cli/kill-ring.js +86 -0
- package/dist/senses/cli/message-list.js +51 -0
- package/dist/senses/cli/ouro-tui.js +607 -0
- package/dist/senses/cli/spinner-imperative.js +135 -0
- package/dist/senses/cli/spinner.js +101 -0
- package/dist/senses/cli/status-line.js +60 -0
- package/dist/senses/cli/streaming-markdown.js +526 -0
- package/dist/senses/cli/tool-display.js +85 -0
- package/dist/senses/cli/tool-render.js +85 -0
- package/dist/senses/cli/tui-store.js +241 -0
- package/dist/senses/cli/virtual-list.js +35 -0
- package/dist/senses/cli-entry.js +60 -8
- package/dist/senses/cli.js +604 -320
- package/dist/senses/commands.js +66 -3
- package/dist/senses/context-packet-ledger.js +371 -0
- package/dist/senses/context-packets.js +154 -0
- package/dist/senses/habit-lifecycle-guard.js +24 -0
- package/dist/senses/habit-turn-message.js +160 -0
- package/dist/senses/ingress-evidence.js +2 -0
- package/dist/senses/mail-entry.js +66 -0
- package/dist/senses/mail.js +524 -0
- package/dist/senses/pipeline.js +1115 -204
- package/dist/senses/private-runtime-worker.js +859 -0
- package/dist/senses/private-runtime.js +1517 -0
- package/dist/senses/proactive-content-guard.js +51 -0
- package/dist/senses/root-host-approval-port.js +345 -0
- package/dist/senses/root-host-approval-runtime.js +393 -0
- package/dist/senses/sanctuary-authority-resident.js +102 -0
- package/dist/senses/sanctuary-download-credit-presentation.js +31 -0
- package/dist/senses/sanctuary-full-visibility-contract.js +302 -0
- package/dist/senses/sanctuary-grounding.js +118 -0
- package/dist/senses/sanctuary-health-acceptance-probe-entry.js +12 -0
- package/dist/senses/sanctuary-health-acceptance-probe.js +774 -0
- package/dist/senses/sanctuary-health-runner.js +100 -0
- package/dist/senses/sanctuary-health.js +408 -0
- package/dist/senses/sanctuary-install-state-contract.js +126 -0
- package/dist/senses/sanctuary-interactive-control.js +410 -0
- package/dist/senses/sanctuary-media-catalog-contract.js +297 -0
- package/dist/senses/sanctuary-media-optimization.js +514 -0
- package/dist/senses/sanctuary-runtime.js +340 -0
- package/dist/senses/sanctuary-sab.js +85 -0
- package/dist/senses/sanctuary-storage-optimization-contract.js +32 -0
- package/dist/senses/shared-turn.js +772 -0
- package/dist/senses/surface-tool.js +109 -0
- package/dist/senses/teams-entry.js +60 -8
- package/dist/senses/teams.js +519 -218
- package/dist/senses/telegram-admission.js +718 -0
- package/dist/senses/telegram-approval-runtime.js +793 -0
- package/dist/senses/telegram-attachments.js +186 -0
- package/dist/senses/telegram-audit-ledger.js +220 -0
- package/dist/senses/telegram-authority-transport.js +231 -0
- package/dist/senses/telegram-client.js +1749 -0
- package/dist/senses/telegram-effect-adapter.js +896 -0
- package/dist/senses/telegram-entry.js +83 -0
- package/dist/senses/telegram.js +2324 -0
- package/dist/senses/trust-gate.js +104 -9
- package/dist/senses/voice/audio-playback.js +237 -0
- package/dist/senses/voice/audio-routing.js +119 -0
- package/dist/senses/voice/elevenlabs.js +202 -0
- package/dist/senses/voice/floor-control.js +431 -0
- package/dist/senses/voice/floor-controller.js +115 -0
- package/dist/senses/voice/golden-path.js +116 -0
- package/dist/senses/voice/index.js +29 -0
- package/dist/senses/voice/meeting.js +113 -0
- package/dist/senses/voice/outbound.js +190 -0
- package/dist/senses/voice/phone.js +33 -0
- package/dist/senses/voice/playback.js +139 -0
- package/dist/senses/voice/realtime-eval.js +496 -0
- package/dist/senses/voice/realtime-trace.js +531 -0
- package/dist/senses/voice/transcript.js +70 -0
- package/dist/senses/voice/turn.js +191 -0
- package/dist/senses/voice/twilio-phone-runtime.js +807 -0
- package/dist/senses/voice/twilio-phone.js +5031 -0
- package/dist/senses/voice/types.js +2 -0
- package/dist/senses/voice/whisper.js +161 -0
- package/dist/senses/voice-entry.js +81 -0
- package/dist/senses/voice-realtime-eval-command.js +99 -0
- package/dist/senses/voice-realtime-eval-entry.js +21 -0
- package/dist/senses/voice-twilio-entry.js +87 -0
- package/dist/trips/core.js +138 -0
- package/dist/trips/store.js +265 -0
- package/dist/util/frontmatter.js +69 -0
- package/npm-shrinkwrap.json +8138 -0
- package/package.json +72 -13
- package/skills/agent-commerce.md +113 -0
- package/skills/browser-navigation.md +117 -0
- package/skills/commerce-setup-guide.md +116 -0
- package/skills/commerce-setup.md +84 -0
- package/skills/configure-dev-tools.md +99 -0
- package/skills/travel-planning.md +138 -0
- package/dist/heart/daemon/auth-flow.js +0 -430
- package/dist/heart/daemon/ouro-path-installer.js +0 -260
- package/dist/heart/daemon/ouro-version-manager.js +0 -171
- package/dist/heart/daemon/update-hooks.js +0 -138
- package/dist/heart/obligations.js +0 -191
- package/dist/heart/safe-workspace.js +0 -228
- package/dist/heart/session-recall.js +0 -116
- package/dist/mind/friends/channel.js +0 -84
- package/dist/mind/friends/group-context.js +0 -144
- package/dist/mind/friends/resolver.js +0 -84
- package/dist/mind/friends/store-file.js +0 -190
- package/dist/mind/friends/store.js +0 -4
- package/dist/mind/friends/tokens.js +0 -26
- package/dist/mind/friends/trust-explanation.js +0 -74
- package/dist/mind/friends/types.js +0 -29
- package/dist/repertoire/tasks/board.js +0 -134
- package/dist/repertoire/tasks/index.js +0 -224
- package/dist/repertoire/tasks/lifecycle.js +0 -80
- package/dist/repertoire/tasks/middleware.js +0 -65
- package/dist/repertoire/tasks/parser.js +0 -173
- package/dist/repertoire/tasks/scanner.js +0 -132
- package/dist/repertoire/tasks/transitions.js +0 -144
- package/dist/senses/bluebubbles-entry.js +0 -13
- package/dist/senses/bluebubbles.js +0 -1181
- package/dist/senses/debug-activity.js +0 -154
- package/dist/senses/inner-dialog-worker.js +0 -90
- package/dist/senses/inner-dialog.js +0 -525
- package/subagents/README.md +0 -7
- /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/basilisk.md +0 -0
- /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/jafar.md +0 -0
- /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/jormungandr.md +0 -0
- /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/kaa.md +0 -0
- /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/medusa.md +0 -0
- /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/monty.md +0 -0
- /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/nagini.md +0 -0
- /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/ouroboros.md +0 -0
- /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/python.md +0 -0
- /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/quetzalcoatl.md +0 -0
- /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/sir-hiss.md +0 -0
- /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/the-snake.md +0 -0
- /package/dist/{repertoire/tasks/types.js → heart/attachments/sources/adapter.js} +0 -0
- /package/dist/heart/{daemon → hatch}/hatch-animation.js +0 -0
- /package/dist/heart/{daemon → hatch}/specialist-orchestrator.js +0 -0
- /package/dist/heart/{daemon → versioning}/ouro-uti.js +0 -0
- /package/dist/heart/{daemon → versioning}/wrapper-publish-guard.js +0 -0
|
@@ -0,0 +1,711 @@
|
|
|
1
|
+
#!/usr/local/bin/node
|
|
2
|
+
|
|
3
|
+
import { execFileSync } from "node:child_process"
|
|
4
|
+
import { createHash } from "node:crypto"
|
|
5
|
+
import { closeSync, constants, fchmodSync, fchownSync, fsyncSync, lstatSync, mkdirSync, openSync, readFileSync, readdirSync, realpathSync, renameSync, unlinkSync, writeFileSync } from "node:fs"
|
|
6
|
+
import { basename, dirname, resolve } from "node:path"
|
|
7
|
+
import { fileURLToPath } from "node:url"
|
|
8
|
+
|
|
9
|
+
import xmlValidator from "./docker-man-template-xml.cjs"
|
|
10
|
+
|
|
11
|
+
const TARGET_PATH = "/boot/config/plugins/dockerMan/templates-user/my-ouro-butler.xml"
|
|
12
|
+
const JOURNAL_PATH = "/boot/config/custom/ouro-butler/docker-man-template-transaction.json"
|
|
13
|
+
const TEMPLATE_URL = "https://raw.githubusercontent.com/ourostack/ouroboros/main/deploy/unraid/sanctuary.xml"
|
|
14
|
+
const ICON = "https://raw.githubusercontent.com/ourostack/ouroboros/main/assets/ouroboros.png"
|
|
15
|
+
const COMMUNITY_APPS_ENTRY_PATH = "/usr/local/emhttp/plugins/community.applications/include/exec.php"
|
|
16
|
+
const COMMUNITY_APPS_HELPER_PATH = "/usr/local/emhttp/plugins/community.applications/include/previous_apps_helpers.php"
|
|
17
|
+
const IMAGE_ID = /^sha256:[0-9a-f]{64}$/u
|
|
18
|
+
const CONTAINER_ID = /^[0-9a-f]{64}$/u
|
|
19
|
+
const VERSION_TAG = /^ghcr\.io\/ourostack\/ouroboros-butler:[0-9]+\.[0-9]+\.[0-9]+(?:-[0-9A-Za-z.-]+)?$/u
|
|
20
|
+
const JELLYFIN_STATES = new Set(["created", "running", "paused", "restarting", "removing", "exited", "dead"])
|
|
21
|
+
const JELLYFIN_FORMAT = '{"name":{{json .Name}},"containerId":{{json .Id}},"imageId":{{json .Image}},"state":{{json .State.Status}},"restartCount":{{json .RestartCount}}}'
|
|
22
|
+
const AUTHORITY_INSTALL_STEPS = ["freeze-resident", "stage-authority", "verify-token-rotation", "transfer-cursor", "remove-resident-token", "start-gateway", "configure-resident", "start-resident", "verify-install"]
|
|
23
|
+
const AUTHORITY_ROLLBACK_STEPS = ["freeze-resident", "retire-registrations", "reconcile-executions", "stop-gateway", "end-epoch", "restore-token-cursor", "restore-resident", "verify-rollback"]
|
|
24
|
+
|
|
25
|
+
function object(value, label) {
|
|
26
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error(`${label} must be an object`)
|
|
27
|
+
return value
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function exactKeys(value, expected, label) {
|
|
31
|
+
const keys = Object.keys(value).sort()
|
|
32
|
+
if (JSON.stringify(keys) !== JSON.stringify([...expected].sort())) throw new Error(`${label} has unexpected fields`)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function validateJellyfinState(raw, label = "Jellyfin checkpoint") {
|
|
36
|
+
const value = object(raw, label)
|
|
37
|
+
exactKeys(value, ["containerId", "imageId", "state", "restartCount"], label)
|
|
38
|
+
if (!CONTAINER_ID.test(value.containerId) || !IMAGE_ID.test(value.imageId) || !JELLYFIN_STATES.has(value.state) || !Number.isSafeInteger(value.restartCount) || value.restartCount < 0) throw new Error(`${label} is invalid`)
|
|
39
|
+
return { containerId: value.containerId, imageId: value.imageId, state: value.state, restartCount: value.restartCount }
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function inspectCurrentJellyfinState() {
|
|
43
|
+
let inspected
|
|
44
|
+
try {
|
|
45
|
+
inspected = JSON.parse(execFileSync("/usr/bin/docker", ["container", "inspect", "--format", JELLYFIN_FORMAT, "jellyfin"], { encoding: "utf8", maxBuffer: 65_536, stdio: ["ignore", "pipe", "ignore"] }).trim())
|
|
46
|
+
} catch {
|
|
47
|
+
throw new Error("Jellyfin container inspection failed")
|
|
48
|
+
}
|
|
49
|
+
const value = object(inspected, "Jellyfin container inspection")
|
|
50
|
+
exactKeys(value, ["name", "containerId", "imageId", "state", "restartCount"], "Jellyfin container inspection")
|
|
51
|
+
if (value.name !== "/jellyfin") throw new Error("Jellyfin container identity is invalid")
|
|
52
|
+
return validateJellyfinState({ containerId: value.containerId, imageId: value.imageId, state: value.state, restartCount: value.restartCount })
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function assertJellyfinUnchanged(expected) {
|
|
56
|
+
const current = inspectCurrentJellyfinState()
|
|
57
|
+
if (JSON.stringify(current) !== JSON.stringify(expected)) throw new Error("Jellyfin changed during the Butler transaction")
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function digest(bytes) {
|
|
61
|
+
return `sha256:${createHash("sha256").update(bytes).digest("hex")}`
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function recordDigest(record) {
|
|
65
|
+
const { digest: _ignored, ...unsigned } = record
|
|
66
|
+
return digest(Buffer.from(JSON.stringify(unsigned)))
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function directChildren(document, name) {
|
|
70
|
+
return document.children.filter((child) => child.name === name)
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function singleTextChild(document, name) {
|
|
74
|
+
const children = directChildren(document, name)
|
|
75
|
+
if (children.length !== 1 || children[0].form !== "text" || Object.keys(children[0].attributes).length !== 0) throw new Error(`template ${name} must appear exactly once as plain text`)
|
|
76
|
+
return children[0].text
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function templateIdentity(bytes, expectedRepository) {
|
|
80
|
+
const document = xmlValidator.parseDockerManTemplateXml(bytes)
|
|
81
|
+
if (!document) throw new Error("canonical DockerMan XML structure is invalid")
|
|
82
|
+
const identity = {
|
|
83
|
+
name: singleTextChild(document, "Name"),
|
|
84
|
+
repository: singleTextChild(document, "Repository"),
|
|
85
|
+
templateUrl: singleTextChild(document, "TemplateURL"),
|
|
86
|
+
icon: singleTextChild(document, "Icon"),
|
|
87
|
+
}
|
|
88
|
+
if (identity.name !== "ouro-butler" || !/^[a-zA-Z0-9][a-zA-Z0-9_.-]+$/u.test(identity.name)) throw new Error("template technical name is invalid")
|
|
89
|
+
if (identity.repository !== expectedRepository || !VERSION_TAG.test(identity.repository)) throw new Error("template version reference is invalid")
|
|
90
|
+
if (identity.templateUrl !== TEMPLATE_URL || identity.icon !== ICON) throw new Error("template identity is invalid")
|
|
91
|
+
const webUi = directChildren(document, "WebUI")
|
|
92
|
+
if (webUi.length !== 1 || webUi[0].form !== "empty" || Object.keys(webUi[0].attributes).length !== 0) throw new Error("template WebUI must be exactly empty")
|
|
93
|
+
return identity
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function validateDirectory(path, expectedUid, expectedGid) {
|
|
97
|
+
const metadata = lstatSync(path)
|
|
98
|
+
if (!metadata.isDirectory() || metadata.isSymbolicLink() || metadata.uid !== expectedUid || metadata.gid !== expectedGid || (metadata.mode & 0o777) !== 0o700 || realpathSync(path) !== resolve(path)) throw new Error("transaction parent is unsafe")
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function validateRegularFile(path, expectedUid, expectedGid, label) {
|
|
102
|
+
const metadata = lstatSync(path)
|
|
103
|
+
if (!metadata.isFile() || metadata.isSymbolicLink() || metadata.uid !== expectedUid || metadata.gid !== expectedGid || (metadata.mode & 0o777) !== 0o600) throw new Error(`${label} metadata is invalid`)
|
|
104
|
+
return metadata
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function metadataIfPresent(path) {
|
|
108
|
+
try {
|
|
109
|
+
return lstatSync(path)
|
|
110
|
+
} catch (error) {
|
|
111
|
+
if (error?.code === "ENOENT") return null
|
|
112
|
+
throw error
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export function isSafeRecoveryTemporaryMetadata(metadata, expectedUid, expectedGid) {
|
|
117
|
+
return Boolean(metadata
|
|
118
|
+
&& typeof metadata.isFile === "function"
|
|
119
|
+
&& typeof metadata.isSymbolicLink === "function"
|
|
120
|
+
&& metadata.isFile()
|
|
121
|
+
&& !metadata.isSymbolicLink()
|
|
122
|
+
&& metadata.uid === expectedUid
|
|
123
|
+
&& metadata.gid === expectedGid
|
|
124
|
+
&& (metadata.mode & 0o777) === 0o600)
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function paths(options = {}) {
|
|
128
|
+
const targetPath = options.targetPath ?? TARGET_PATH
|
|
129
|
+
const journalPath = options.journalPath ?? JOURNAL_PATH
|
|
130
|
+
const expectedUid = options.expectedUid ?? 0
|
|
131
|
+
const expectedGid = options.expectedGid ?? 0
|
|
132
|
+
return {
|
|
133
|
+
targetPath,
|
|
134
|
+
journalPath,
|
|
135
|
+
templateParent: dirname(targetPath),
|
|
136
|
+
journalParent: dirname(journalPath),
|
|
137
|
+
targetTemporary: `${dirname(targetPath)}/.${basename(targetPath)}.ouro-transaction.tmp`,
|
|
138
|
+
journalTemporary: `${dirname(journalPath)}/.${basename(journalPath)}.ouro-transaction.tmp`,
|
|
139
|
+
expectedUid,
|
|
140
|
+
expectedGid,
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function validateTemplateParent(state, allowTemporaryResidue = false) {
|
|
145
|
+
validateDirectory(state.templateParent, state.expectedUid, state.expectedGid)
|
|
146
|
+
const matches = readdirSync(state.templateParent).filter((entry) => entry.toLowerCase() === basename(state.targetPath).toLowerCase())
|
|
147
|
+
if (matches.length > 1 || (matches.length === 1 && matches[0] !== basename(state.targetPath))) throw new Error("case-folded DockerMan template name is ambiguous")
|
|
148
|
+
if (!allowTemporaryResidue && metadataIfPresent(state.targetTemporary)) throw new Error("template transaction temporary state is ambiguous")
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function validateJournalParent(state, allowTemporaryResidue = false) {
|
|
152
|
+
validateDirectory(state.journalParent, state.expectedUid, state.expectedGid)
|
|
153
|
+
if (!allowTemporaryResidue && metadataIfPresent(state.journalTemporary)) throw new Error("template transaction temporary state is ambiguous")
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function validateParents(state, allowTemporaryResidue = false) {
|
|
157
|
+
validateTemplateParent(state, allowTemporaryResidue)
|
|
158
|
+
validateJournalParent(state, allowTemporaryResidue)
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function ensureJournalParent(state) {
|
|
162
|
+
if (!metadataIfPresent(state.journalParent)) {
|
|
163
|
+
const parent = dirname(state.journalParent)
|
|
164
|
+
validateDirectory(parent, state.expectedUid, state.expectedGid)
|
|
165
|
+
mkdirSync(state.journalParent, { mode: 0o700 })
|
|
166
|
+
syncDirectory(parent)
|
|
167
|
+
}
|
|
168
|
+
validateJournalParent(state)
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function syncDirectory(path) {
|
|
172
|
+
const fd = openSync(path, constants.O_RDONLY)
|
|
173
|
+
try { fsyncSync(fd) } finally { closeSync(fd) }
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function atomicWrite(path, temporaryPath, bytes, metadata) {
|
|
177
|
+
const fd = openSync(temporaryPath, constants.O_CREAT | constants.O_EXCL | constants.O_WRONLY | constants.O_NOFOLLOW, metadata.mode)
|
|
178
|
+
try {
|
|
179
|
+
writeFileSync(fd, bytes)
|
|
180
|
+
fchmodSync(fd, metadata.mode)
|
|
181
|
+
fchownSync(fd, metadata.uid, metadata.gid)
|
|
182
|
+
fsyncSync(fd)
|
|
183
|
+
} finally {
|
|
184
|
+
closeSync(fd)
|
|
185
|
+
}
|
|
186
|
+
renameSync(temporaryPath, path)
|
|
187
|
+
syncDirectory(dirname(path))
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function deleteDurably(path) {
|
|
191
|
+
unlinkSync(path)
|
|
192
|
+
syncDirectory(dirname(path))
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function decodeCanonicalBase64(value) {
|
|
196
|
+
if (typeof value !== "string" || Buffer.from(value, "base64").toString("base64") !== value) throw new Error("template transaction journal prior bytes are invalid")
|
|
197
|
+
return Buffer.from(value, "base64")
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function validateJournalRecord(raw, state) {
|
|
201
|
+
const record = object(raw, "template transaction journal")
|
|
202
|
+
exactKeys(record, ["schemaVersion", "state", "target", "priorTemplate", "priorTemplateDigest", "targetTemplateDigest", "canonicalVersionTag", "reviewedManifestDigest", "rollbackImageId", "targetImageId", "jellyfin", "digest", ...(Object.hasOwn(record, "authority") ? ["authority"] : [])], "template transaction journal")
|
|
203
|
+
if (Object.hasOwn(record, "authority")) validateAuthorityHandoff(record.authority, record)
|
|
204
|
+
if (record.schemaVersion !== 1 || !["rollback", "committing"].includes(record.state)) throw new Error("template transaction journal state is invalid")
|
|
205
|
+
const target = object(record.target, "template transaction journal target")
|
|
206
|
+
exactKeys(target, ["path", "name", "templateUrl", "icon"], "template transaction journal target")
|
|
207
|
+
if (target.path !== state.targetPath || target.name !== "ouro-butler" || target.templateUrl !== TEMPLATE_URL || target.icon !== ICON) throw new Error("template transaction journal target is invalid")
|
|
208
|
+
const prior = object(record.priorTemplate, "template transaction journal prior template")
|
|
209
|
+
exactKeys(prior, ["present", "bytesBase64", "metadata"], "template transaction journal prior template")
|
|
210
|
+
if (typeof prior.present !== "boolean") throw new Error("template transaction journal prior presence is invalid")
|
|
211
|
+
let priorBytes = null
|
|
212
|
+
if (prior.present) {
|
|
213
|
+
priorBytes = decodeCanonicalBase64(prior.bytesBase64)
|
|
214
|
+
const metadata = object(prior.metadata, "template transaction journal prior metadata")
|
|
215
|
+
exactKeys(metadata, ["uid", "gid", "mode"], "template transaction journal prior metadata")
|
|
216
|
+
if (metadata.uid !== state.expectedUid || metadata.gid !== state.expectedGid || metadata.mode !== 0o600 || record.priorTemplateDigest !== digest(priorBytes)) throw new Error("template transaction journal prior metadata is invalid")
|
|
217
|
+
} else if (prior.bytesBase64 !== null || prior.metadata !== null || record.priorTemplateDigest !== null) {
|
|
218
|
+
throw new Error("template transaction journal absent prior state is invalid")
|
|
219
|
+
}
|
|
220
|
+
if (!VERSION_TAG.test(record.canonicalVersionTag) || !IMAGE_ID.test(record.reviewedManifestDigest) || !IMAGE_ID.test(record.rollbackImageId) || !IMAGE_ID.test(record.targetImageId) || record.rollbackImageId === record.targetImageId || !IMAGE_ID.test(record.targetTemplateDigest)) throw new Error("template transaction journal release identity is invalid")
|
|
221
|
+
const jellyfin = validateJellyfinState(record.jellyfin, "template transaction journal Jellyfin checkpoint")
|
|
222
|
+
if (!IMAGE_ID.test(record.digest) || record.digest !== recordDigest(record)) throw new Error("template transaction journal digest is invalid")
|
|
223
|
+
return { ...record, jellyfin, priorBytes }
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
function readJournal(state) {
|
|
227
|
+
if (!metadataIfPresent(state.journalPath)) return null
|
|
228
|
+
validateRegularFile(state.journalPath, state.expectedUid, state.expectedGid, "template transaction journal")
|
|
229
|
+
let parsed
|
|
230
|
+
try { parsed = JSON.parse(readFileSync(state.journalPath, "utf8")) } catch { throw new Error("template transaction journal JSON is invalid") }
|
|
231
|
+
return validateJournalRecord(parsed, state)
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
function writeJournal(record, state) {
|
|
235
|
+
const complete = { ...record, digest: recordDigest(record) }
|
|
236
|
+
atomicWrite(state.journalPath, state.journalTemporary, Buffer.from(`${JSON.stringify(complete)}\n`), { uid: state.expectedUid, gid: state.expectedGid, mode: 0o600 })
|
|
237
|
+
return complete
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
function currentTargetDigest(state) {
|
|
241
|
+
if (!metadataIfPresent(state.targetPath)) return null
|
|
242
|
+
validateRegularFile(state.targetPath, state.expectedUid, state.expectedGid, "DockerMan template")
|
|
243
|
+
return digest(readFileSync(state.targetPath))
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
function inspectRecoveryTemporary(state) {
|
|
247
|
+
validateParents(state, true)
|
|
248
|
+
const residues = [state.targetTemporary, state.journalTemporary]
|
|
249
|
+
.map((path) => ({ path, metadata: metadataIfPresent(path) }))
|
|
250
|
+
.filter((entry) => entry.metadata)
|
|
251
|
+
if (residues.length === 0) return null
|
|
252
|
+
if (residues.length !== 1) throw new Error("template transaction recovery temporary state is ambiguous")
|
|
253
|
+
const residue = residues[0]
|
|
254
|
+
if (!isSafeRecoveryTemporaryMetadata(residue.metadata, state.expectedUid, state.expectedGid)) throw new Error("template transaction recovery temporary file is unsafe")
|
|
255
|
+
const journalMetadata = metadataIfPresent(state.journalPath)
|
|
256
|
+
if (journalMetadata) readJournal(state)
|
|
257
|
+
if (residue.path === state.targetTemporary) {
|
|
258
|
+
if (!journalMetadata) throw new Error("template transaction recovery target temporary file is orphaned")
|
|
259
|
+
const journal = readJournal(state)
|
|
260
|
+
const installedDigest = currentTargetDigest(state)
|
|
261
|
+
const allowedDigests = [journal.targetTemplateDigest, journal.priorTemplateDigest]
|
|
262
|
+
if (!(allowedDigests.includes(installedDigest) || (!journal.priorTemplate.present && installedDigest === null))) throw new Error("template transaction recovery target state is ambiguous")
|
|
263
|
+
}
|
|
264
|
+
return residue
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
function cleanupRecoveryTemporary(state) {
|
|
268
|
+
const residue = inspectRecoveryTemporary(state)
|
|
269
|
+
if (!residue) return
|
|
270
|
+
deleteDurably(residue.path)
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
function publicJournalRecord(result) {
|
|
274
|
+
if (!result) return null
|
|
275
|
+
const { priorBytes: _ignored, ...record } = result
|
|
276
|
+
return record
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
export function inspectDockerManTemplateTransactionRecoveryIdentity(options = {}) {
|
|
280
|
+
const state = paths(options)
|
|
281
|
+
inspectRecoveryTemporary(state)
|
|
282
|
+
const current = readJournal(state)
|
|
283
|
+
const jellyfin = inspectCurrentJellyfinState()
|
|
284
|
+
if (current && JSON.stringify(jellyfin) !== JSON.stringify(current.jellyfin)) throw new Error("Jellyfin changed during the Butler transaction")
|
|
285
|
+
return publicJournalRecord(current)
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
export function inspectDockerManTemplateTransactionForRecovery(options = {}) {
|
|
289
|
+
const state = paths(options)
|
|
290
|
+
const expected = inspectDockerManTemplateTransactionRecoveryIdentity(options)
|
|
291
|
+
cleanupRecoveryTemporary(state)
|
|
292
|
+
const current = inspectDockerManTemplateTransaction(options)
|
|
293
|
+
if (JSON.stringify(current) !== JSON.stringify(expected)) throw new Error("template transaction changed during recovery")
|
|
294
|
+
return current
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
export function inspectDockerManTemplateTransaction(options = {}) {
|
|
298
|
+
const state = paths(options)
|
|
299
|
+
validateParents(state)
|
|
300
|
+
return publicJournalRecord(readJournal(state))
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
function validateTemplateSource(input, state) {
|
|
304
|
+
validateTemplateParent(state)
|
|
305
|
+
if (metadataIfPresent(state.targetPath)) validateRegularFile(state.targetPath, state.expectedUid, state.expectedGid, "DockerMan template")
|
|
306
|
+
validateDirectory(dirname(input.sourceTemplatePath), state.expectedUid, state.expectedGid)
|
|
307
|
+
validateRegularFile(input.sourceTemplatePath, state.expectedUid, state.expectedGid, "source template")
|
|
308
|
+
if (!VERSION_TAG.test(input.canonicalVersionTag) || !IMAGE_ID.test(input.reviewedManifestDigest) || !IMAGE_ID.test(input.rollbackImageId) || !IMAGE_ID.test(input.targetImageId) || input.rollbackImageId === input.targetImageId) throw new Error("reviewed release identity is invalid")
|
|
309
|
+
const sourceBytes = readFileSync(input.sourceTemplatePath)
|
|
310
|
+
const identity = templateIdentity(sourceBytes, input.canonicalVersionTag)
|
|
311
|
+
return { sourceBytes, identity }
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
export function prepareDockerManTemplateTransaction(input, options = {}) {
|
|
315
|
+
const state = paths(options)
|
|
316
|
+
const { sourceBytes, identity } = validateTemplateSource(input, state)
|
|
317
|
+
const jellyfin = inspectCurrentJellyfinState()
|
|
318
|
+
ensureJournalParent(state)
|
|
319
|
+
const existing = readJournal(state)
|
|
320
|
+
if (existing) {
|
|
321
|
+
if (existing.canonicalVersionTag !== input.canonicalVersionTag || existing.reviewedManifestDigest !== input.reviewedManifestDigest || existing.rollbackImageId !== input.rollbackImageId || existing.targetImageId !== input.targetImageId || existing.targetTemplateDigest !== digest(sourceBytes) || currentTargetDigest(state) !== existing.targetTemplateDigest || JSON.stringify(existing.jellyfin) !== JSON.stringify(jellyfin)) throw new Error("pending template transaction does not match the reviewed release")
|
|
322
|
+
const { priorBytes: _ignored, ...record } = existing
|
|
323
|
+
return record
|
|
324
|
+
}
|
|
325
|
+
let priorTemplate
|
|
326
|
+
let priorTemplateDigest = null
|
|
327
|
+
if (metadataIfPresent(state.targetPath)) {
|
|
328
|
+
const metadata = validateRegularFile(state.targetPath, state.expectedUid, state.expectedGid, "DockerMan template")
|
|
329
|
+
const bytes = readFileSync(state.targetPath)
|
|
330
|
+
priorTemplateDigest = digest(bytes)
|
|
331
|
+
priorTemplate = { present: true, bytesBase64: bytes.toString("base64"), metadata: { uid: metadata.uid, gid: metadata.gid, mode: metadata.mode & 0o777 } }
|
|
332
|
+
} else {
|
|
333
|
+
priorTemplate = { present: false, bytesBase64: null, metadata: null }
|
|
334
|
+
}
|
|
335
|
+
const unsigned = {
|
|
336
|
+
schemaVersion: 1,
|
|
337
|
+
state: "rollback",
|
|
338
|
+
target: { path: state.targetPath, name: identity.name, templateUrl: identity.templateUrl, icon: identity.icon },
|
|
339
|
+
priorTemplate,
|
|
340
|
+
priorTemplateDigest,
|
|
341
|
+
targetTemplateDigest: digest(sourceBytes),
|
|
342
|
+
canonicalVersionTag: input.canonicalVersionTag,
|
|
343
|
+
reviewedManifestDigest: input.reviewedManifestDigest,
|
|
344
|
+
rollbackImageId: input.rollbackImageId,
|
|
345
|
+
targetImageId: input.targetImageId,
|
|
346
|
+
jellyfin,
|
|
347
|
+
}
|
|
348
|
+
const record = writeJournal(unsigned, state)
|
|
349
|
+
options.checkpoint?.("after-template-journal")
|
|
350
|
+
atomicWrite(state.targetPath, state.targetTemporary, sourceBytes, { uid: state.expectedUid, gid: state.expectedGid, mode: 0o600 })
|
|
351
|
+
options.checkpoint?.("after-template-replacement")
|
|
352
|
+
return record
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
export function markDockerManTemplateTransactionCommitting(options = {}) {
|
|
356
|
+
const state = paths(options)
|
|
357
|
+
validateParents(state)
|
|
358
|
+
const current = readJournal(state)
|
|
359
|
+
if (!current) throw new Error("template transaction journal is absent")
|
|
360
|
+
requireActiveAuthority(current, state)
|
|
361
|
+
if (currentTargetDigest(state) !== current.targetTemplateDigest) throw new Error("installed DockerMan template does not match the transaction")
|
|
362
|
+
assertJellyfinUnchanged(current.jellyfin)
|
|
363
|
+
if (current.state === "committing") {
|
|
364
|
+
const { priorBytes: _ignored, ...record } = current
|
|
365
|
+
return record
|
|
366
|
+
}
|
|
367
|
+
const { priorBytes: _ignored, digest: _digest, ...record } = current
|
|
368
|
+
const committing = writeJournal({ ...record, state: "committing" }, state)
|
|
369
|
+
options.checkpoint?.("after-template-committing")
|
|
370
|
+
return committing
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
export function rollbackDockerManTemplateTransaction(options = {}) {
|
|
374
|
+
const state = paths(options)
|
|
375
|
+
validateParents(state)
|
|
376
|
+
const current = readJournal(state)
|
|
377
|
+
if (!current) return false
|
|
378
|
+
if (current.authority && current.authority.state !== "retired") throw new Error("authority epoch must be retired before template rollback")
|
|
379
|
+
const installedDigest = currentTargetDigest(state)
|
|
380
|
+
assertJellyfinUnchanged(current.jellyfin)
|
|
381
|
+
if (current.priorTemplate.present) {
|
|
382
|
+
if (installedDigest !== current.targetTemplateDigest && installedDigest !== current.priorTemplateDigest) throw new Error("installed DockerMan template cannot be safely restored")
|
|
383
|
+
if (installedDigest !== current.priorTemplateDigest) atomicWrite(state.targetPath, state.targetTemporary, current.priorBytes, current.priorTemplate.metadata)
|
|
384
|
+
} else if (installedDigest !== null) {
|
|
385
|
+
if (installedDigest !== current.targetTemplateDigest) throw new Error("installed DockerMan template cannot be safely removed")
|
|
386
|
+
deleteDurably(state.targetPath)
|
|
387
|
+
}
|
|
388
|
+
options.checkpoint?.("after-template-restore")
|
|
389
|
+
deleteDurably(state.journalPath)
|
|
390
|
+
return true
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
function validateFinalProof(proof, record, state) {
|
|
394
|
+
const root = object(proof, "final install proof")
|
|
395
|
+
const container = object(root.container, "final container proof")
|
|
396
|
+
const labels = object(container.labels, "final container labels")
|
|
397
|
+
if (container.name !== "/ouro-butler" || container.imageId !== record.targetImageId || container.imageReference !== record.canonicalVersionTag || container.running !== true || container.healthy !== true || container.autostart !== true) throw new Error("final container proof is invalid")
|
|
398
|
+
if (labels["net.unraid.docker.managed"] !== "dockerman" || labels["net.unraid.docker.icon"] !== record.target.icon || Object.prototype.hasOwnProperty.call(labels, "net.unraid.docker.webui")) throw new Error("final container install labels are invalid")
|
|
399
|
+
const bundle = object(root.bundle, "final bundle proof")
|
|
400
|
+
const bundleData = object(bundle.data, "final bundle proof data")
|
|
401
|
+
if (bundle.ok !== true || bundleData.parity !== "exact" || bundleData.journalState !== "absent" || bundleData.ready !== true) throw new Error("final bundle proof is invalid")
|
|
402
|
+
const dockerMan = object(root.dockerMan, "final DockerMan proof")
|
|
403
|
+
if (dockerMan.templatePath !== state.targetPath || dockerMan.name !== "ouro-butler" || dockerMan.repository !== record.canonicalVersionTag || dockerMan.templateUrl !== record.target.templateUrl || dockerMan.icon !== record.target.icon) throw new Error("final DockerMan proof is invalid")
|
|
404
|
+
const communityApps = object(root.communityApps, "final Community Apps proof")
|
|
405
|
+
const inlineImplementation = communityApps.stateModel === "previous-apps-inline-v1" && communityApps.entryPath === COMMUNITY_APPS_ENTRY_PATH && communityApps.entryFunction === "previous_apps" && communityApps.implementationPath === COMMUNITY_APPS_ENTRY_PATH && communityApps.implementationSymbol === "previous_apps"
|
|
406
|
+
const helperImplementation = communityApps.stateModel === "previous-apps-helper-v1" && communityApps.entryPath === COMMUNITY_APPS_ENTRY_PATH && communityApps.entryFunction === "previous_apps" && communityApps.implementationPath === COMMUNITY_APPS_HELPER_PATH && communityApps.implementationSymbol === "PreviousAppsHelpers::collectDockerApplications"
|
|
407
|
+
const recognizedImplementation = inlineImplementation || helperImplementation
|
|
408
|
+
if (communityApps.installed !== true || communityApps.name !== "ouro-butler" || communityApps.repository !== record.canonicalVersionTag || communityApps.templateUrl !== record.target.templateUrl || !recognizedImplementation) throw new Error("final Community Apps proof is invalid")
|
|
409
|
+
const jellyfin = validateJellyfinState(root.jellyfin, "final Jellyfin proof")
|
|
410
|
+
if (JSON.stringify(jellyfin) !== JSON.stringify(record.jellyfin)) throw new Error("final Jellyfin proof is invalid")
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
export function commitDockerManTemplateTransaction(proof, options = {}) {
|
|
414
|
+
const state = paths(options)
|
|
415
|
+
validateParents(state)
|
|
416
|
+
const current = readJournal(state)
|
|
417
|
+
if (!current) return false
|
|
418
|
+
requireActiveAuthority(current, state)
|
|
419
|
+
if (current.state !== "committing" || currentTargetDigest(state) !== current.targetTemplateDigest) throw new Error("template transaction is not ready to commit")
|
|
420
|
+
validateFinalProof(proof, current, state)
|
|
421
|
+
assertJellyfinUnchanged(current.jellyfin)
|
|
422
|
+
deleteDurably(state.journalPath)
|
|
423
|
+
return true
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
function requireActiveAuthority(current, state) {
|
|
427
|
+
const document = xmlValidator.parseDockerManTemplateXml(readFileSync(state.targetPath))
|
|
428
|
+
const gateway = document?.children.some((child) => child.name === "Config" && child.attributes.Target === "/run/ouro-authority")
|
|
429
|
+
if ((gateway || current.authority) && current.authority?.state !== "active") throw new Error("authority handoff is not verified")
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
function validateAuthorityPlan(raw) {
|
|
433
|
+
const plan = object(raw, "authority handoff plan")
|
|
434
|
+
exactKeys(plan, ["schemaVersion", "epochId", "packageDigest", "publicKeyDigest", "botId", "ownerUserId", "ownerChatId", "predecessorContract", "targetContract"], "authority handoff plan")
|
|
435
|
+
if (plan.schemaVersion !== 1 || !/^[A-Za-z0-9_-]{1,128}$/u.test(plan.epochId) || !IMAGE_ID.test(plan.packageDigest) || (plan.publicKeyDigest !== null && !IMAGE_ID.test(plan.publicKeyDigest))
|
|
436
|
+
|| !["botId", "ownerUserId", "ownerChatId"].every((key) => typeof plan[key] === "string" && /^[1-9][0-9]*$/u.test(plan[key]))
|
|
437
|
+
|| plan.ownerUserId !== plan.ownerChatId || plan.predecessorContract !== "canonical-pre-gateway" || plan.targetContract !== "canonical-gateway") throw new Error("authority handoff plan is invalid")
|
|
438
|
+
return plan
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
function authorityEffectIdentity(record, effect) {
|
|
442
|
+
return digest(Buffer.from(JSON.stringify([record.targetImageId, record.rollbackImageId, record.reviewedManifestDigest, record.authority.plan, effect.direction, effect.step, effect.beforeDigest, effect.afterDigest])))
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
function validateAuthorityHandoff(raw, record) {
|
|
446
|
+
const handoff = object(raw, "authority handoff")
|
|
447
|
+
exactKeys(handoff, ["plan", "state", "completed", "rollbackCompleted", "pending", "cancelled"], "authority handoff")
|
|
448
|
+
validateAuthorityPlan(handoff.plan)
|
|
449
|
+
if (!["installing", "active", "retiring", "retired"].includes(handoff.state) || !Array.isArray(handoff.completed) || !Array.isArray(handoff.rollbackCompleted) || !Array.isArray(handoff.cancelled)) throw new Error("authority handoff state is invalid")
|
|
450
|
+
const validateEffect = (effect, direction, step) => {
|
|
451
|
+
object(effect, "authority effect")
|
|
452
|
+
exactKeys(effect, ["direction", "step", "effectId", "beforeDigest", "afterDigest"], "authority effect")
|
|
453
|
+
if (effect.direction !== direction || effect.step !== step || !IMAGE_ID.test(effect.beforeDigest) || !IMAGE_ID.test(effect.afterDigest) || effect.beforeDigest === effect.afterDigest
|
|
454
|
+
|| effect.effectId !== authorityEffectIdentity(record, effect)) throw new Error("authority effect identity is invalid")
|
|
455
|
+
}
|
|
456
|
+
for (const [direction, entries, steps] of [["install", handoff.completed, AUTHORITY_INSTALL_STEPS], ["rollback", handoff.rollbackCompleted, AUTHORITY_ROLLBACK_STEPS]]) {
|
|
457
|
+
if (entries.length > steps.length) throw new Error("authority effect order is invalid")
|
|
458
|
+
entries.forEach((effect, index) => validateEffect(effect, direction, steps[index]))
|
|
459
|
+
}
|
|
460
|
+
const rollback = handoff.state === "retiring" || handoff.state === "retired"
|
|
461
|
+
if (handoff.cancelled.length > 1 || (handoff.cancelled.length !== 0 && !rollback)) throw new Error("authority cancelled effect is invalid")
|
|
462
|
+
for (const effect of handoff.cancelled) validateEffect(effect, "install", AUTHORITY_INSTALL_STEPS[handoff.completed.length])
|
|
463
|
+
if ((!rollback && handoff.rollbackCompleted.length !== 0) || (handoff.state === "active" && handoff.completed.length !== AUTHORITY_INSTALL_STEPS.length)
|
|
464
|
+
|| (handoff.state === "retired" && handoff.rollbackCompleted.length !== AUTHORITY_ROLLBACK_STEPS.length)) throw new Error("authority terminal handoff is invalid")
|
|
465
|
+
if (handoff.pending !== null) {
|
|
466
|
+
if (handoff.state === "active" || handoff.state === "retired") throw new Error("authority terminal handoff has a pending effect")
|
|
467
|
+
const steps = rollback ? AUTHORITY_ROLLBACK_STEPS : AUTHORITY_INSTALL_STEPS
|
|
468
|
+
const completed = rollback ? handoff.rollbackCompleted : handoff.completed
|
|
469
|
+
validateEffect(handoff.pending, rollback ? "rollback" : "install", steps[completed.length])
|
|
470
|
+
}
|
|
471
|
+
return handoff
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
function writeAuthorityHandoff(record, authority, state) {
|
|
475
|
+
const { priorBytes: _bytes, digest: _digest, ...unsigned } = record
|
|
476
|
+
const next = { ...unsigned, authority }
|
|
477
|
+
validateAuthorityHandoff(authority, next)
|
|
478
|
+
return writeJournal(next, state)
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
export function beginDockerManAuthorityHandoff(plan, options = {}) {
|
|
482
|
+
validateAuthorityPlan(plan)
|
|
483
|
+
const state = paths(options)
|
|
484
|
+
validateParents(state)
|
|
485
|
+
const current = readJournal(state)
|
|
486
|
+
if (!current || (!current.authority && current.state !== "rollback")) throw new Error("authority handoff requires the prepared deployment transaction")
|
|
487
|
+
assertJellyfinUnchanged(current.jellyfin)
|
|
488
|
+
if (current.authority) {
|
|
489
|
+
if (JSON.stringify(current.authority.plan) !== JSON.stringify(plan)) throw new Error("authority handoff plan changed")
|
|
490
|
+
return current.authority
|
|
491
|
+
}
|
|
492
|
+
return writeAuthorityHandoff(current, { plan, state: "installing", completed: [], rollbackCompleted: [], cancelled: [], pending: null }, state).authority
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
async function withAuthorityLease(options, work) {
|
|
496
|
+
const state = paths(options)
|
|
497
|
+
validateParents(state)
|
|
498
|
+
const withLease = options.withLease ?? (await import("../../dist/mind/session-transaction.js")).withSessionTurnLease
|
|
499
|
+
return withLease(state.journalPath, () => work(state), { timeoutMs: 0, confinementRoot: state.journalParent })
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
export async function runDockerManAuthorityEffect(name, effect, options = {}) {
|
|
503
|
+
return withAuthorityLease(options, (state) => runAuthorityEffectLocked(name, effect, state))
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
export async function beginDockerManAuthorityRollback(readback, options = {}) {
|
|
507
|
+
return withAuthorityLease(options, (state) => beginAuthorityRollbackLocked(readback, state))
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
async function beginAuthorityRollbackLocked(readback, state) {
|
|
511
|
+
const current = readJournal(state)
|
|
512
|
+
if (!current?.authority) throw new Error("authority handoff is absent")
|
|
513
|
+
const handoff = current.authority
|
|
514
|
+
if (handoff.state === "retired" || handoff.state === "retiring") return handoff
|
|
515
|
+
const pending = handoff.pending
|
|
516
|
+
const observed = pending ? await readback() : null
|
|
517
|
+
if (pending && observed !== pending.beforeDigest && observed !== pending.afterDigest) throw new Error("authority pending effect is ambiguous")
|
|
518
|
+
if (readJournal(state).digest !== current.digest) throw new Error("authority transaction changed during recovery")
|
|
519
|
+
assertJellyfinUnchanged(current.jellyfin)
|
|
520
|
+
return writeAuthorityHandoff(current, {
|
|
521
|
+
...handoff, state: "retiring", pending: null,
|
|
522
|
+
completed: pending && observed === pending.afterDigest ? [...handoff.completed, pending] : handoff.completed,
|
|
523
|
+
cancelled: pending && observed === pending.beforeDigest ? [...handoff.cancelled, pending] : handoff.cancelled,
|
|
524
|
+
}, state).authority
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
async function runAuthorityEffectLocked(name, effect, state) {
|
|
528
|
+
let current = readJournal(state)
|
|
529
|
+
if (!current?.authority) throw new Error("authority handoff is absent")
|
|
530
|
+
assertJellyfinUnchanged(current.jellyfin)
|
|
531
|
+
const direction = name.startsWith("rollback:") ? "rollback" : "install"
|
|
532
|
+
const step = direction === "rollback" ? name.slice("rollback:".length) : name
|
|
533
|
+
const steps = direction === "rollback" ? AUTHORITY_ROLLBACK_STEPS : AUTHORITY_INSTALL_STEPS
|
|
534
|
+
if (!steps.includes(step)) throw new Error("authority handoff step is invalid")
|
|
535
|
+
if (!IMAGE_ID.test(effect.beforeDigest) || !IMAGE_ID.test(effect.afterDigest) || effect.beforeDigest === effect.afterDigest) throw new Error("authority effect identity is invalid")
|
|
536
|
+
let handoff = current.authority
|
|
537
|
+
if (direction === "install" && ["retiring", "retired"].includes(handoff.state)) throw new Error("authority handoff is retiring")
|
|
538
|
+
const expected = { direction, step, beforeDigest: effect.beforeDigest, afterDigest: effect.afterDigest }
|
|
539
|
+
const intent = { ...expected, effectId: authorityEffectIdentity(current, expected) }
|
|
540
|
+
const completedKey = direction === "install" ? "completed" : "rollbackCompleted"
|
|
541
|
+
const completed = handoff[completedKey]
|
|
542
|
+
const prior = completed.find((entry) => entry.step === step)
|
|
543
|
+
if (prior) {
|
|
544
|
+
if (prior.effectId !== intent.effectId || await effect.readback() !== prior.afterDigest) throw new Error("authority completed effect changed")
|
|
545
|
+
return prior
|
|
546
|
+
}
|
|
547
|
+
if (steps[completed.length] !== step) throw new Error("authority handoff effect order is invalid")
|
|
548
|
+
if (handoff.pending && handoff.pending.effectId !== intent.effectId) throw new Error("authority pending effect changed; recovery is required before rollback")
|
|
549
|
+
if (!handoff.pending) {
|
|
550
|
+
handoff = { ...handoff, state: direction === "rollback" ? "retiring" : "installing", pending: intent }
|
|
551
|
+
current = writeAuthorityHandoff(current, handoff, state)
|
|
552
|
+
}
|
|
553
|
+
const observed = await effect.readback()
|
|
554
|
+
if (observed !== intent.beforeDigest && observed !== intent.afterDigest) throw new Error("authority handoff effect is ambiguous")
|
|
555
|
+
if (observed === intent.beforeDigest) await effect.apply(intent.effectId)
|
|
556
|
+
if (await effect.readback() !== intent.afterDigest) throw new Error("authority handoff effect readback failed")
|
|
557
|
+
const latest = readJournal(state)
|
|
558
|
+
if (latest.digest !== current.digest) throw new Error("authority transaction changed during effect")
|
|
559
|
+
assertJellyfinUnchanged(latest.jellyfin)
|
|
560
|
+
const entries = [...completed, intent]
|
|
561
|
+
handoff = { ...handoff, [completedKey]: entries, pending: null, state: entries.length === steps.length ? direction === "install" ? "active" : "retired" : direction === "install" ? "installing" : "retiring" }
|
|
562
|
+
writeAuthorityHandoff(latest, handoff, state)
|
|
563
|
+
return intent
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
export function verifyDockerManTemplateTransactionJellyfin(options = {}) {
|
|
567
|
+
const state = paths(options)
|
|
568
|
+
validateParents(state)
|
|
569
|
+
const current = readJournal(state)
|
|
570
|
+
if (!current) throw new Error("template transaction journal is absent")
|
|
571
|
+
assertJellyfinUnchanged(current.jellyfin)
|
|
572
|
+
return true
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
export function decideDockerManTemplateRecovery(record, evidence) {
|
|
576
|
+
const state = record?.state
|
|
577
|
+
const bundle = evidence?.bundleJournalState
|
|
578
|
+
const production = evidence?.production
|
|
579
|
+
if (!["rollback", "committing"].includes(state) || !["absent", "rollback", "committing"].includes(bundle) || !["rollback-exact", "target-exact-committing", "target-exact-ready", "adoption-source-exact", "adoption-target-exact-ready", "adoption-evidence-exact-stopped"].includes(production)) throw new Error("template transaction recovery evidence is invalid")
|
|
580
|
+
if (bundle === "rollback" && production === "rollback-exact") return "rollback-both"
|
|
581
|
+
if (state === "committing" && bundle === "committing" && production === "target-exact-committing") {
|
|
582
|
+
const inspection = evidence?.inspection
|
|
583
|
+
if (inspection?.ok !== true || inspection?.data?.parity !== "exact" || inspection?.data?.journalState !== "committing" || inspection?.data?.ready !== false) throw new Error("template transaction recovery inspection is invalid")
|
|
584
|
+
return "finish-bundle-commit"
|
|
585
|
+
}
|
|
586
|
+
if (state === "committing" && bundle === "absent" && production === "target-exact-ready") {
|
|
587
|
+
const inspection = evidence?.inspection
|
|
588
|
+
if (inspection?.ok !== true || inspection?.data?.parity !== "exact" || inspection?.data?.journalState !== "absent" || inspection?.data?.ready !== true) throw new Error("template transaction recovery inspection is invalid")
|
|
589
|
+
return "finish-template-commit"
|
|
590
|
+
}
|
|
591
|
+
if (bundle === "absent" && production === "rollback-exact") return "restore-prior-template"
|
|
592
|
+
if (state === "rollback" && bundle === "absent" && production === "adoption-source-exact") return "restore-prior-template"
|
|
593
|
+
if (state === "rollback" && bundle === "absent" && production === "adoption-target-exact-ready") {
|
|
594
|
+
const inspection = evidence?.inspection
|
|
595
|
+
if (inspection?.ok !== true || inspection?.data?.parity !== "exact" || inspection?.data?.journalState !== "absent" || inspection?.data?.ready !== true) throw new Error("template transaction recovery inspection is invalid")
|
|
596
|
+
return "roll-forward-adoption"
|
|
597
|
+
}
|
|
598
|
+
if (state === "rollback" && bundle === "absent" && production === "adoption-evidence-exact-stopped") return "quarantine-adoption"
|
|
599
|
+
throw new Error("template and bundle transaction topology is incompatible")
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
function parseArguments(argv) {
|
|
603
|
+
const [operation, ...rest] = argv
|
|
604
|
+
const values = new Map()
|
|
605
|
+
for (let index = 0; index < rest.length; index += 2) {
|
|
606
|
+
const flag = rest[index]
|
|
607
|
+
const value = rest[index + 1]
|
|
608
|
+
if (!flag?.startsWith("--") || !value || values.has(flag)) throw new Error("template transaction arguments are invalid")
|
|
609
|
+
values.set(flag, value)
|
|
610
|
+
}
|
|
611
|
+
return { operation, values }
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
function readRootJson(path, label) {
|
|
615
|
+
validateRegularFile(path, 0, 0, label)
|
|
616
|
+
try { return JSON.parse(readFileSync(path, "utf8")) } catch { throw new Error(`${label} JSON is invalid`) }
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
export function runDockerManTemplateTransactionCli(argv, options = {}, write = (text) => process.stdout.write(text)) {
|
|
620
|
+
const { operation, values } = parseArguments(argv)
|
|
621
|
+
if (operation?.startsWith("authority-") && values.size === 0) {
|
|
622
|
+
return runRootLifecycle(operation, options).then((result) => write(`${JSON.stringify(result)}\n`))
|
|
623
|
+
}
|
|
624
|
+
let result
|
|
625
|
+
if (operation === "prepare" && values.size === 5) {
|
|
626
|
+
result = prepareDockerManTemplateTransaction({
|
|
627
|
+
sourceTemplatePath: values.get("--source-template"),
|
|
628
|
+
canonicalVersionTag: values.get("--version-tag"),
|
|
629
|
+
reviewedManifestDigest: values.get("--manifest-digest"),
|
|
630
|
+
rollbackImageId: values.get("--rollback-image-id"),
|
|
631
|
+
targetImageId: values.get("--target-image-id"),
|
|
632
|
+
}, options)
|
|
633
|
+
} else if (operation === "mark-committing" && values.size === 0) {
|
|
634
|
+
ensureJournalParent(paths(options))
|
|
635
|
+
result = markDockerManTemplateTransactionCommitting(options)
|
|
636
|
+
} else if (operation === "rollback" && values.size === 0) {
|
|
637
|
+
ensureJournalParent(paths(options))
|
|
638
|
+
result = { rolledBack: rollbackDockerManTemplateTransaction(options) }
|
|
639
|
+
} else if (operation === "commit" && values.size === 1 && values.get("--proof")) {
|
|
640
|
+
ensureJournalParent(paths(options))
|
|
641
|
+
result = { committed: commitDockerManTemplateTransaction(readRootJson(values.get("--proof"), "final install proof"), options) }
|
|
642
|
+
} else if (operation === "status" && values.size === 0) {
|
|
643
|
+
ensureJournalParent(paths(options))
|
|
644
|
+
result = inspectDockerManTemplateTransaction(options)
|
|
645
|
+
} else if (operation === "recover-status" && values.size === 0) {
|
|
646
|
+
const state = paths(options)
|
|
647
|
+
if (!metadataIfPresent(state.journalParent)) {
|
|
648
|
+
inspectCurrentJellyfinState()
|
|
649
|
+
ensureJournalParent(state)
|
|
650
|
+
}
|
|
651
|
+
result = inspectDockerManTemplateTransactionForRecovery(options)
|
|
652
|
+
} else if (operation === "recovery-identity" && values.size === 0) {
|
|
653
|
+
const state = paths(options)
|
|
654
|
+
if (!metadataIfPresent(state.journalParent)) {
|
|
655
|
+
inspectCurrentJellyfinState()
|
|
656
|
+
result = null
|
|
657
|
+
} else {
|
|
658
|
+
result = inspectDockerManTemplateTransactionRecoveryIdentity(options)
|
|
659
|
+
}
|
|
660
|
+
} else if (operation === "recovery-action" && values.size === 1 && values.get("--evidence")) {
|
|
661
|
+
ensureJournalParent(paths(options))
|
|
662
|
+
const record = inspectDockerManTemplateTransaction(options)
|
|
663
|
+
if (!record) throw new Error("template transaction journal is absent")
|
|
664
|
+
result = { action: decideDockerManTemplateRecovery(record, readRootJson(values.get("--evidence"), "template recovery evidence")) }
|
|
665
|
+
} else if (operation === "verify-jellyfin" && values.size === 0) {
|
|
666
|
+
result = { unchanged: verifyDockerManTemplateTransactionJellyfin(options) }
|
|
667
|
+
} else if (operation === "jellyfin-status" && values.size === 0) {
|
|
668
|
+
result = inspectCurrentJellyfinState()
|
|
669
|
+
} else {
|
|
670
|
+
throw new Error("Usage: docker-man-template-transaction.mjs <prepare|mark-committing|rollback|commit|status|recover-status|recovery-identity|recovery-action|verify-jellyfin|jellyfin-status> [fixed operation arguments]")
|
|
671
|
+
}
|
|
672
|
+
write(`${JSON.stringify(result)}\n`)
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
async function runRootLifecycle(operation, options) {
|
|
676
|
+
const selected = {
|
|
677
|
+
"authority-install": AUTHORITY_INSTALL_STEPS.slice(0, 6),
|
|
678
|
+
"authority-activate": AUTHORITY_INSTALL_STEPS.slice(6),
|
|
679
|
+
"authority-retire": AUTHORITY_ROLLBACK_STEPS.slice(0, 6).map((step) => `rollback:${step}`),
|
|
680
|
+
"authority-restore": AUTHORITY_ROLLBACK_STEPS.slice(6).map((step) => `rollback:${step}`),
|
|
681
|
+
}[operation]
|
|
682
|
+
if (!selected) throw new Error("authority lifecycle operation is invalid")
|
|
683
|
+
return withAuthorityLease(options, async (state) => {
|
|
684
|
+
let record = readJournal(state)
|
|
685
|
+
if (!record) throw new Error("authority lifecycle requires the prepared deployment transaction")
|
|
686
|
+
const RootLifecycle = options.RootLifecycle ?? (await import("../../dist/heart/daemon/sanctuary-authority-root-lifecycle.js")).SanctuaryAuthorityRootLifecycle
|
|
687
|
+
const lifecycle = new RootLifecycle(record, options.rootLifecycleOptions)
|
|
688
|
+
beginDockerManAuthorityHandoff(lifecycle.plan(), options)
|
|
689
|
+
record = readJournal(state)
|
|
690
|
+
if (["authority-install", "authority-activate"].includes(operation) && ["retiring", "retired"].includes(record.authority.state)) throw new Error("authority epoch is retiring or retired")
|
|
691
|
+
if (operation === "authority-activate" && record.authority.completed.length < 6) throw new Error("gateway installation must finish before resident activation")
|
|
692
|
+
if (operation === "authority-restore" && record.authority.rollbackCompleted.length < 6) throw new Error("authority retirement must finish before resident restoration")
|
|
693
|
+
if (operation === "authority-retire") {
|
|
694
|
+
const pending = record.authority.pending
|
|
695
|
+
await beginAuthorityRollbackLocked(pending ? lifecycle.effect(pending.step).readback : null, state)
|
|
696
|
+
}
|
|
697
|
+
for (const name of selected) {
|
|
698
|
+
const rollback = name.startsWith("rollback:")
|
|
699
|
+
const step = rollback ? name.slice("rollback:".length) : name
|
|
700
|
+
const current = readJournal(state).authority
|
|
701
|
+
// Completed historical effects may have intentionally changed at later
|
|
702
|
+
// steps (a frozen resident becomes running). Recheck the next real boundary,
|
|
703
|
+
// never replay earlier physical work merely to reproduce a historical hash.
|
|
704
|
+
if (current[rollback ? "rollbackCompleted" : "completed"].some((entry) => entry.step === step)) continue
|
|
705
|
+
await runAuthorityEffectLocked(name, lifecycle.effect(name), state)
|
|
706
|
+
}
|
|
707
|
+
return readJournal(state).authority
|
|
708
|
+
})
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
if (process.argv[1] === fileURLToPath(import.meta.url)) await runDockerManTemplateTransactionCli(process.argv.slice(2))
|