@pleri/olam-cli 0.1.201 → 0.1.205
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/README.md +102 -169
- package/dist/agent-stream/agent-sdk-to-chunks.js +1 -1
- package/dist/agent-stream/driver-runner.js +73 -7
- package/dist/agent-stream/host-driver-launch.js +14 -1
- package/dist/agent-stream/prototype-gen-launch.js +113 -0
- package/dist/agent-stream/question-broker-bridge.js +335 -0
- package/dist/agent-stream/sdk-env-merge.demo.js +35 -0
- package/dist/agent-stream/sdk-env-merge.js +98 -0
- package/dist/image-digests.json +8 -8
- package/dist/index.js +9533 -6258
- package/dist/index.js.map +1 -1
- package/dist/mcp-server.js +24721 -12934
- package/hermes-bundle/version.json +1 -1
- package/hooks/__tests__/_loader.py +26 -0
- package/hooks/__tests__/prompts.py +63 -0
- package/hooks/__tests__/test_classify_bench.py +94 -0
- package/hooks/__tests__/test_classify_pins.py +78 -0
- package/hooks/model-router.py +17 -4
- package/host-cp/k8s/manifests/50-deployment.yaml +1 -1
- package/host-cp/k8s/manifests/auth-service/50-deployment.yaml +1 -1
- package/host-cp/k8s/manifests/kg-service/50-deployment.yaml +1 -1
- package/host-cp/k8s/manifests/mcp-auth-service/50-deployment.yaml +1 -1
- package/host-cp/k8s/manifests/memory-service/50-deployment.yaml +1 -1
- package/host-cp/src/op-side-longpoll.mjs +212 -0
- package/host-cp/src/plan-chat-proxy-headers.mjs +53 -0
- package/host-cp/src/plan-chat-service.mjs +100 -0
- package/host-cp/src/plan-orchestrator.mjs +100 -6
- package/host-cp/src/server.mjs +607 -45
- package/memory-hooks/agentmemory-classify-queue.mjs +363 -0
- package/memory-hooks/agentmemory-recall-trigger.mjs +233 -0
- package/memory-hooks/agentmemory-reflect-cite.mjs +332 -0
- package/memory-hooks/agentmemory-session-recall.js +332 -0
- package/memory-hooks/recall-log.mjs +185 -0
- package/package.json +9 -4
- package/dist/ask/checkout.d.ts +0 -19
- package/dist/ask/checkout.d.ts.map +0 -1
- package/dist/ask/checkout.js +0 -40
- package/dist/ask/checkout.js.map +0 -1
- package/dist/ask/knowledge-pack-builder.d.ts +0 -72
- package/dist/ask/knowledge-pack-builder.d.ts.map +0 -1
- package/dist/ask/knowledge-pack-builder.js +0 -96
- package/dist/ask/knowledge-pack-builder.js.map +0 -1
- package/dist/ask/knowledge-pack.generated.d.ts +0 -8
- package/dist/ask/knowledge-pack.generated.d.ts.map +0 -1
- package/dist/ask/knowledge-pack.generated.js +0 -2362
- package/dist/ask/knowledge-pack.generated.js.map +0 -1
- package/dist/ask/one-shot.d.ts +0 -21
- package/dist/ask/one-shot.d.ts.map +0 -1
- package/dist/ask/one-shot.js +0 -50
- package/dist/ask/one-shot.js.map +0 -1
- package/dist/ask/repl.d.ts +0 -30
- package/dist/ask/repl.d.ts.map +0 -1
- package/dist/ask/repl.js +0 -109
- package/dist/ask/repl.js.map +0 -1
- package/dist/ask/sdk-client.d.ts +0 -87
- package/dist/ask/sdk-client.d.ts.map +0 -1
- package/dist/ask/sdk-client.js +0 -118
- package/dist/ask/sdk-client.js.map +0 -1
- package/dist/ask/system-prompt.d.ts +0 -30
- package/dist/ask/system-prompt.d.ts.map +0 -1
- package/dist/ask/system-prompt.js +0 -31
- package/dist/ask/system-prompt.js.map +0 -1
- package/dist/cli-version.d.ts +0 -16
- package/dist/cli-version.d.ts.map +0 -1
- package/dist/cli-version.js +0 -39
- package/dist/cli-version.js.map +0 -1
- package/dist/commands/ask.d.ts +0 -27
- package/dist/commands/ask.d.ts.map +0 -1
- package/dist/commands/ask.js +0 -63
- package/dist/commands/ask.js.map +0 -1
- package/dist/commands/auth-list-json.d.ts +0 -87
- package/dist/commands/auth-list-json.d.ts.map +0 -1
- package/dist/commands/auth-list-json.js +0 -71
- package/dist/commands/auth-list-json.js.map +0 -1
- package/dist/commands/auth-migrate.d.ts +0 -212
- package/dist/commands/auth-migrate.d.ts.map +0 -1
- package/dist/commands/auth-migrate.js +0 -465
- package/dist/commands/auth-migrate.js.map +0 -1
- package/dist/commands/auth-status.d.ts +0 -51
- package/dist/commands/auth-status.d.ts.map +0 -1
- package/dist/commands/auth-status.js +0 -250
- package/dist/commands/auth-status.js.map +0 -1
- package/dist/commands/auth-upgrade.d.ts +0 -88
- package/dist/commands/auth-upgrade.d.ts.map +0 -1
- package/dist/commands/auth-upgrade.js +0 -431
- package/dist/commands/auth-upgrade.js.map +0 -1
- package/dist/commands/auth.d.ts +0 -31
- package/dist/commands/auth.d.ts.map +0 -1
- package/dist/commands/auth.js +0 -784
- package/dist/commands/auth.js.map +0 -1
- package/dist/commands/begin.d.ts +0 -27
- package/dist/commands/begin.d.ts.map +0 -1
- package/dist/commands/begin.js +0 -45
- package/dist/commands/begin.js.map +0 -1
- package/dist/commands/bootstrap.d.ts +0 -111
- package/dist/commands/bootstrap.d.ts.map +0 -1
- package/dist/commands/bootstrap.js +0 -485
- package/dist/commands/bootstrap.js.map +0 -1
- package/dist/commands/clean.d.ts +0 -41
- package/dist/commands/clean.d.ts.map +0 -1
- package/dist/commands/clean.js +0 -382
- package/dist/commands/clean.js.map +0 -1
- package/dist/commands/completion.d.ts +0 -30
- package/dist/commands/completion.d.ts.map +0 -1
- package/dist/commands/completion.js +0 -50
- package/dist/commands/completion.js.map +0 -1
- package/dist/commands/config.d.ts +0 -3
- package/dist/commands/config.d.ts.map +0 -1
- package/dist/commands/config.js +0 -146
- package/dist/commands/config.js.map +0 -1
- package/dist/commands/create.d.ts +0 -8
- package/dist/commands/create.d.ts.map +0 -1
- package/dist/commands/create.js +0 -775
- package/dist/commands/create.js.map +0 -1
- package/dist/commands/crystallize.d.ts +0 -18
- package/dist/commands/crystallize.d.ts.map +0 -1
- package/dist/commands/crystallize.js +0 -123
- package/dist/commands/crystallize.js.map +0 -1
- package/dist/commands/destroy.d.ts +0 -59
- package/dist/commands/destroy.d.ts.map +0 -1
- package/dist/commands/destroy.js +0 -148
- package/dist/commands/destroy.js.map +0 -1
- package/dist/commands/diagnose.d.ts +0 -36
- package/dist/commands/diagnose.d.ts.map +0 -1
- package/dist/commands/diagnose.js +0 -177
- package/dist/commands/diagnose.js.map +0 -1
- package/dist/commands/dispatch-resolve.d.ts +0 -54
- package/dist/commands/dispatch-resolve.d.ts.map +0 -1
- package/dist/commands/dispatch-resolve.js +0 -105
- package/dist/commands/dispatch-resolve.js.map +0 -1
- package/dist/commands/dispatch.d.ts +0 -18
- package/dist/commands/dispatch.d.ts.map +0 -1
- package/dist/commands/dispatch.js +0 -159
- package/dist/commands/dispatch.js.map +0 -1
- package/dist/commands/doctor.d.ts +0 -258
- package/dist/commands/doctor.d.ts.map +0 -1
- package/dist/commands/doctor.js +0 -1073
- package/dist/commands/doctor.js.map +0 -1
- package/dist/commands/enter.d.ts +0 -63
- package/dist/commands/enter.d.ts.map +0 -1
- package/dist/commands/enter.js +0 -230
- package/dist/commands/enter.js.map +0 -1
- package/dist/commands/flywheel/check-persona-skeleton.d.ts +0 -35
- package/dist/commands/flywheel/check-persona-skeleton.d.ts.map +0 -1
- package/dist/commands/flywheel/check-persona-skeleton.js +0 -151
- package/dist/commands/flywheel/check-persona-skeleton.js.map +0 -1
- package/dist/commands/flywheel/diversity-check.d.ts +0 -17
- package/dist/commands/flywheel/diversity-check.d.ts.map +0 -1
- package/dist/commands/flywheel/diversity-check.js +0 -64
- package/dist/commands/flywheel/diversity-check.js.map +0 -1
- package/dist/commands/flywheel/emit-breadcrumb.d.ts +0 -20
- package/dist/commands/flywheel/emit-breadcrumb.d.ts.map +0 -1
- package/dist/commands/flywheel/emit-breadcrumb.js +0 -137
- package/dist/commands/flywheel/emit-breadcrumb.js.map +0 -1
- package/dist/commands/flywheel/index.d.ts +0 -27
- package/dist/commands/flywheel/index.d.ts.map +0 -1
- package/dist/commands/flywheel/index.js +0 -54
- package/dist/commands/flywheel/index.js.map +0 -1
- package/dist/commands/flywheel/install-sessionstart-hook.d.ts +0 -64
- package/dist/commands/flywheel/install-sessionstart-hook.d.ts.map +0 -1
- package/dist/commands/flywheel/install-sessionstart-hook.js +0 -197
- package/dist/commands/flywheel/install-sessionstart-hook.js.map +0 -1
- package/dist/commands/flywheel/install-shims.d.ts +0 -41
- package/dist/commands/flywheel/install-shims.d.ts.map +0 -1
- package/dist/commands/flywheel/install-shims.js +0 -126
- package/dist/commands/flywheel/install-shims.js.map +0 -1
- package/dist/commands/flywheel/k10-measure.d.ts +0 -17
- package/dist/commands/flywheel/k10-measure.d.ts.map +0 -1
- package/dist/commands/flywheel/k10-measure.js +0 -63
- package/dist/commands/flywheel/k10-measure.js.map +0 -1
- package/dist/commands/flywheel/k5-score.d.ts +0 -14
- package/dist/commands/flywheel/k5-score.d.ts.map +0 -1
- package/dist/commands/flywheel/k5-score.js +0 -59
- package/dist/commands/flywheel/k5-score.js.map +0 -1
- package/dist/commands/flywheel/k5-validate.d.ts +0 -46
- package/dist/commands/flywheel/k5-validate.d.ts.map +0 -1
- package/dist/commands/flywheel/k5-validate.js +0 -246
- package/dist/commands/flywheel/k5-validate.js.map +0 -1
- package/dist/commands/flywheel/migrate-overlays.d.ts +0 -116
- package/dist/commands/flywheel/migrate-overlays.d.ts.map +0 -1
- package/dist/commands/flywheel/migrate-overlays.js +0 -792
- package/dist/commands/flywheel/migrate-overlays.js.map +0 -1
- package/dist/commands/flywheel/ping.d.ts +0 -21
- package/dist/commands/flywheel/ping.d.ts.map +0 -1
- package/dist/commands/flywheel/ping.js +0 -79
- package/dist/commands/flywheel/ping.js.map +0 -1
- package/dist/commands/flywheel/sanitize-persona-output.d.ts +0 -38
- package/dist/commands/flywheel/sanitize-persona-output.d.ts.map +0 -1
- package/dist/commands/flywheel/sanitize-persona-output.js +0 -102
- package/dist/commands/flywheel/sanitize-persona-output.js.map +0 -1
- package/dist/commands/flywheel/session-start.d.ts +0 -26
- package/dist/commands/flywheel/session-start.d.ts.map +0 -1
- package/dist/commands/flywheel/session-start.js +0 -119
- package/dist/commands/flywheel/session-start.js.map +0 -1
- package/dist/commands/hermes-kg-hook.d.ts +0 -36
- package/dist/commands/hermes-kg-hook.d.ts.map +0 -1
- package/dist/commands/hermes-kg-hook.js +0 -80
- package/dist/commands/hermes-kg-hook.js.map +0 -1
- package/dist/commands/hermes.d.ts +0 -46
- package/dist/commands/hermes.d.ts.map +0 -1
- package/dist/commands/hermes.js +0 -320
- package/dist/commands/hermes.js.map +0 -1
- package/dist/commands/host-cp.d.ts +0 -216
- package/dist/commands/host-cp.d.ts.map +0 -1
- package/dist/commands/host-cp.js +0 -913
- package/dist/commands/host-cp.js.map +0 -1
- package/dist/commands/implode.d.ts +0 -86
- package/dist/commands/implode.d.ts.map +0 -1
- package/dist/commands/implode.js +0 -468
- package/dist/commands/implode.js.map +0 -1
- package/dist/commands/init.d.ts +0 -86
- package/dist/commands/init.d.ts.map +0 -1
- package/dist/commands/init.js +0 -357
- package/dist/commands/init.js.map +0 -1
- package/dist/commands/install.d.ts +0 -22
- package/dist/commands/install.d.ts.map +0 -1
- package/dist/commands/install.js +0 -203
- package/dist/commands/install.js.map +0 -1
- package/dist/commands/keys-list-json.d.ts +0 -55
- package/dist/commands/keys-list-json.d.ts.map +0 -1
- package/dist/commands/keys-list-json.js +0 -54
- package/dist/commands/keys-list-json.js.map +0 -1
- package/dist/commands/keys.d.ts +0 -26
- package/dist/commands/keys.d.ts.map +0 -1
- package/dist/commands/keys.js +0 -157
- package/dist/commands/keys.js.map +0 -1
- package/dist/commands/kg-build.d.ts +0 -80
- package/dist/commands/kg-build.d.ts.map +0 -1
- package/dist/commands/kg-build.js +0 -282
- package/dist/commands/kg-build.js.map +0 -1
- package/dist/commands/kg-classify.d.ts +0 -30
- package/dist/commands/kg-classify.d.ts.map +0 -1
- package/dist/commands/kg-classify.js +0 -88
- package/dist/commands/kg-classify.js.map +0 -1
- package/dist/commands/kg-doctor.d.ts +0 -76
- package/dist/commands/kg-doctor.d.ts.map +0 -1
- package/dist/commands/kg-doctor.js +0 -262
- package/dist/commands/kg-doctor.js.map +0 -1
- package/dist/commands/kg-install-hook.d.ts +0 -20
- package/dist/commands/kg-install-hook.d.ts.map +0 -1
- package/dist/commands/kg-install-hook.js +0 -208
- package/dist/commands/kg-install-hook.js.map +0 -1
- package/dist/commands/kg-mirror.d.ts +0 -72
- package/dist/commands/kg-mirror.d.ts.map +0 -1
- package/dist/commands/kg-mirror.js +0 -397
- package/dist/commands/kg-mirror.js.map +0 -1
- package/dist/commands/kg-savings.d.ts +0 -20
- package/dist/commands/kg-savings.d.ts.map +0 -1
- package/dist/commands/kg-savings.js +0 -77
- package/dist/commands/kg-savings.js.map +0 -1
- package/dist/commands/kg-service-container.d.ts +0 -68
- package/dist/commands/kg-service-container.d.ts.map +0 -1
- package/dist/commands/kg-service-container.js +0 -191
- package/dist/commands/kg-service-container.js.map +0 -1
- package/dist/commands/kg-status.d.ts +0 -59
- package/dist/commands/kg-status.d.ts.map +0 -1
- package/dist/commands/kg-status.js +0 -344
- package/dist/commands/kg-status.js.map +0 -1
- package/dist/commands/kg-uninstall-hook.d.ts +0 -12
- package/dist/commands/kg-uninstall-hook.d.ts.map +0 -1
- package/dist/commands/kg-uninstall-hook.js +0 -121
- package/dist/commands/kg-uninstall-hook.js.map +0 -1
- package/dist/commands/kg-watch.d.ts +0 -49
- package/dist/commands/kg-watch.d.ts.map +0 -1
- package/dist/commands/kg-watch.js +0 -172
- package/dist/commands/kg-watch.js.map +0 -1
- package/dist/commands/lanes-list-json.d.ts +0 -69
- package/dist/commands/lanes-list-json.d.ts.map +0 -1
- package/dist/commands/lanes-list-json.js +0 -42
- package/dist/commands/lanes-list-json.js.map +0 -1
- package/dist/commands/lanes.d.ts +0 -18
- package/dist/commands/lanes.d.ts.map +0 -1
- package/dist/commands/lanes.js +0 -133
- package/dist/commands/lanes.js.map +0 -1
- package/dist/commands/list.d.ts +0 -33
- package/dist/commands/list.d.ts.map +0 -1
- package/dist/commands/list.js +0 -87
- package/dist/commands/list.js.map +0 -1
- package/dist/commands/logs.d.ts +0 -52
- package/dist/commands/logs.d.ts.map +0 -1
- package/dist/commands/logs.js +0 -180
- package/dist/commands/logs.js.map +0 -1
- package/dist/commands/mcp/add.d.ts +0 -9
- package/dist/commands/mcp/add.d.ts.map +0 -1
- package/dist/commands/mcp/add.js +0 -87
- package/dist/commands/mcp/add.js.map +0 -1
- package/dist/commands/mcp/client.d.ts +0 -60
- package/dist/commands/mcp/client.d.ts.map +0 -1
- package/dist/commands/mcp/client.js +0 -70
- package/dist/commands/mcp/client.js.map +0 -1
- package/dist/commands/mcp/complete.d.ts +0 -36
- package/dist/commands/mcp/complete.d.ts.map +0 -1
- package/dist/commands/mcp/complete.js +0 -66
- package/dist/commands/mcp/complete.js.map +0 -1
- package/dist/commands/mcp/import-discovery.d.ts +0 -25
- package/dist/commands/mcp/import-discovery.d.ts.map +0 -1
- package/dist/commands/mcp/import-discovery.js +0 -135
- package/dist/commands/mcp/import-discovery.js.map +0 -1
- package/dist/commands/mcp/import-validate.d.ts +0 -15
- package/dist/commands/mcp/import-validate.d.ts.map +0 -1
- package/dist/commands/mcp/import-validate.js +0 -55
- package/dist/commands/mcp/import-validate.js.map +0 -1
- package/dist/commands/mcp/import.d.ts +0 -12
- package/dist/commands/mcp/import.d.ts.map +0 -1
- package/dist/commands/mcp/import.js +0 -126
- package/dist/commands/mcp/import.js.map +0 -1
- package/dist/commands/mcp/index.d.ts +0 -14
- package/dist/commands/mcp/index.d.ts.map +0 -1
- package/dist/commands/mcp/index.js +0 -39
- package/dist/commands/mcp/index.js.map +0 -1
- package/dist/commands/mcp/install-shared.d.ts +0 -24
- package/dist/commands/mcp/install-shared.d.ts.map +0 -1
- package/dist/commands/mcp/install-shared.js +0 -42
- package/dist/commands/mcp/install-shared.js.map +0 -1
- package/dist/commands/mcp/install.d.ts +0 -20
- package/dist/commands/mcp/install.d.ts.map +0 -1
- package/dist/commands/mcp/install.js +0 -59
- package/dist/commands/mcp/install.js.map +0 -1
- package/dist/commands/mcp/list.d.ts +0 -6
- package/dist/commands/mcp/list.d.ts.map +0 -1
- package/dist/commands/mcp/list.js +0 -56
- package/dist/commands/mcp/list.js.map +0 -1
- package/dist/commands/mcp/login.d.ts +0 -6
- package/dist/commands/mcp/login.d.ts.map +0 -1
- package/dist/commands/mcp/login.js +0 -38
- package/dist/commands/mcp/login.js.map +0 -1
- package/dist/commands/mcp/remove.d.ts +0 -6
- package/dist/commands/mcp/remove.d.ts.map +0 -1
- package/dist/commands/mcp/remove.js +0 -21
- package/dist/commands/mcp/remove.js.map +0 -1
- package/dist/commands/mcp/revoke.d.ts +0 -11
- package/dist/commands/mcp/revoke.d.ts.map +0 -1
- package/dist/commands/mcp/revoke.js +0 -51
- package/dist/commands/mcp/revoke.js.map +0 -1
- package/dist/commands/mcp/serve.d.ts +0 -23
- package/dist/commands/mcp/serve.d.ts.map +0 -1
- package/dist/commands/mcp/serve.js +0 -55
- package/dist/commands/mcp/serve.js.map +0 -1
- package/dist/commands/mcp/status.d.ts +0 -6
- package/dist/commands/mcp/status.d.ts.map +0 -1
- package/dist/commands/mcp/status.js +0 -57
- package/dist/commands/mcp/status.js.map +0 -1
- package/dist/commands/mcp/uninstall.d.ts +0 -20
- package/dist/commands/mcp/uninstall.d.ts.map +0 -1
- package/dist/commands/mcp/uninstall.js +0 -60
- package/dist/commands/mcp/uninstall.js.map +0 -1
- package/dist/commands/memory/_paths.d.ts +0 -25
- package/dist/commands/memory/_paths.d.ts.map +0 -1
- package/dist/commands/memory/_paths.js +0 -57
- package/dist/commands/memory/_paths.js.map +0 -1
- package/dist/commands/memory/bridge.d.ts +0 -57
- package/dist/commands/memory/bridge.d.ts.map +0 -1
- package/dist/commands/memory/bridge.js +0 -152
- package/dist/commands/memory/bridge.js.map +0 -1
- package/dist/commands/memory/index.d.ts +0 -20
- package/dist/commands/memory/index.d.ts.map +0 -1
- package/dist/commands/memory/index.js +0 -47
- package/dist/commands/memory/index.js.map +0 -1
- package/dist/commands/memory/install-hooks.d.ts +0 -22
- package/dist/commands/memory/install-hooks.d.ts.map +0 -1
- package/dist/commands/memory/install-hooks.js +0 -156
- package/dist/commands/memory/install-hooks.js.map +0 -1
- package/dist/commands/memory/install.d.ts +0 -57
- package/dist/commands/memory/install.d.ts.map +0 -1
- package/dist/commands/memory/install.js +0 -114
- package/dist/commands/memory/install.js.map +0 -1
- package/dist/commands/memory/logs.d.ts +0 -19
- package/dist/commands/memory/logs.d.ts.map +0 -1
- package/dist/commands/memory/logs.js +0 -50
- package/dist/commands/memory/logs.js.map +0 -1
- package/dist/commands/memory/mode.d.ts +0 -47
- package/dist/commands/memory/mode.d.ts.map +0 -1
- package/dist/commands/memory/mode.js +0 -185
- package/dist/commands/memory/mode.js.map +0 -1
- package/dist/commands/memory/reclassify.d.ts +0 -56
- package/dist/commands/memory/reclassify.d.ts.map +0 -1
- package/dist/commands/memory/reclassify.js +0 -177
- package/dist/commands/memory/reclassify.js.map +0 -1
- package/dist/commands/memory/secret.d.ts +0 -16
- package/dist/commands/memory/secret.d.ts.map +0 -1
- package/dist/commands/memory/secret.js +0 -80
- package/dist/commands/memory/secret.js.map +0 -1
- package/dist/commands/memory/start.d.ts +0 -25
- package/dist/commands/memory/start.d.ts.map +0 -1
- package/dist/commands/memory/start.js +0 -83
- package/dist/commands/memory/start.js.map +0 -1
- package/dist/commands/memory/stats.d.ts +0 -69
- package/dist/commands/memory/stats.d.ts.map +0 -1
- package/dist/commands/memory/stats.js +0 -164
- package/dist/commands/memory/stats.js.map +0 -1
- package/dist/commands/memory/status.d.ts +0 -45
- package/dist/commands/memory/status.d.ts.map +0 -1
- package/dist/commands/memory/status.js +0 -134
- package/dist/commands/memory/status.js.map +0 -1
- package/dist/commands/memory/stop.d.ts +0 -13
- package/dist/commands/memory/stop.d.ts.map +0 -1
- package/dist/commands/memory/stop.js +0 -52
- package/dist/commands/memory/stop.js.map +0 -1
- package/dist/commands/memory/uninstall.d.ts +0 -19
- package/dist/commands/memory/uninstall.d.ts.map +0 -1
- package/dist/commands/memory/uninstall.js +0 -60
- package/dist/commands/memory/uninstall.js.map +0 -1
- package/dist/commands/memory-service-container.d.ts +0 -130
- package/dist/commands/memory-service-container.d.ts.map +0 -1
- package/dist/commands/memory-service-container.js +0 -251
- package/dist/commands/memory-service-container.js.map +0 -1
- package/dist/commands/observe.d.ts +0 -9
- package/dist/commands/observe.d.ts.map +0 -1
- package/dist/commands/observe.js +0 -42
- package/dist/commands/observe.js.map +0 -1
- package/dist/commands/plans-list-json.d.ts +0 -77
- package/dist/commands/plans-list-json.d.ts.map +0 -1
- package/dist/commands/plans-list-json.js +0 -61
- package/dist/commands/plans-list-json.js.map +0 -1
- package/dist/commands/plans.d.ts +0 -3
- package/dist/commands/plans.d.ts.map +0 -1
- package/dist/commands/plans.js +0 -221
- package/dist/commands/plans.js.map +0 -1
- package/dist/commands/policy-check.d.ts +0 -14
- package/dist/commands/policy-check.d.ts.map +0 -1
- package/dist/commands/policy-check.js +0 -76
- package/dist/commands/policy-check.js.map +0 -1
- package/dist/commands/pr.d.ts +0 -17
- package/dist/commands/pr.d.ts.map +0 -1
- package/dist/commands/pr.js +0 -148
- package/dist/commands/pr.js.map +0 -1
- package/dist/commands/ps.d.ts +0 -57
- package/dist/commands/ps.d.ts.map +0 -1
- package/dist/commands/ps.js +0 -202
- package/dist/commands/ps.js.map +0 -1
- package/dist/commands/refresh-helpers.d.ts +0 -25
- package/dist/commands/refresh-helpers.d.ts.map +0 -1
- package/dist/commands/refresh-helpers.js +0 -56
- package/dist/commands/refresh-helpers.js.map +0 -1
- package/dist/commands/refresh.d.ts +0 -23
- package/dist/commands/refresh.d.ts.map +0 -1
- package/dist/commands/refresh.js +0 -237
- package/dist/commands/refresh.js.map +0 -1
- package/dist/commands/rekey.d.ts +0 -84
- package/dist/commands/rekey.d.ts.map +0 -1
- package/dist/commands/rekey.js +0 -209
- package/dist/commands/rekey.js.map +0 -1
- package/dist/commands/repos-list-json.d.ts +0 -58
- package/dist/commands/repos-list-json.d.ts.map +0 -1
- package/dist/commands/repos-list-json.js +0 -45
- package/dist/commands/repos-list-json.js.map +0 -1
- package/dist/commands/repos.d.ts +0 -11
- package/dist/commands/repos.d.ts.map +0 -1
- package/dist/commands/repos.js +0 -102
- package/dist/commands/repos.js.map +0 -1
- package/dist/commands/restart.d.ts +0 -18
- package/dist/commands/restart.d.ts.map +0 -1
- package/dist/commands/restart.js +0 -113
- package/dist/commands/restart.js.map +0 -1
- package/dist/commands/resume.d.ts +0 -63
- package/dist/commands/resume.d.ts.map +0 -1
- package/dist/commands/resume.js +0 -174
- package/dist/commands/resume.js.map +0 -1
- package/dist/commands/runbooks.d.ts +0 -45
- package/dist/commands/runbooks.d.ts.map +0 -1
- package/dist/commands/runbooks.js +0 -313
- package/dist/commands/runbooks.js.map +0 -1
- package/dist/commands/seed.d.ts +0 -27
- package/dist/commands/seed.d.ts.map +0 -1
- package/dist/commands/seed.js +0 -303
- package/dist/commands/seed.js.map +0 -1
- package/dist/commands/services-tls.d.ts +0 -120
- package/dist/commands/services-tls.d.ts.map +0 -1
- package/dist/commands/services-tls.js +0 -489
- package/dist/commands/services-tls.js.map +0 -1
- package/dist/commands/services.d.ts +0 -218
- package/dist/commands/services.d.ts.map +0 -1
- package/dist/commands/services.js +0 -830
- package/dist/commands/services.js.map +0 -1
- package/dist/commands/setup-linux-gate.d.ts +0 -26
- package/dist/commands/setup-linux-gate.d.ts.map +0 -1
- package/dist/commands/setup-linux-gate.js +0 -40
- package/dist/commands/setup-linux-gate.js.map +0 -1
- package/dist/commands/setup-metrics.d.ts +0 -26
- package/dist/commands/setup-metrics.d.ts.map +0 -1
- package/dist/commands/setup-metrics.js +0 -56
- package/dist/commands/setup-metrics.js.map +0 -1
- package/dist/commands/setup-phase-5a-skill-source.d.ts +0 -84
- package/dist/commands/setup-phase-5a-skill-source.d.ts.map +0 -1
- package/dist/commands/setup-phase-5a-skill-source.js +0 -259
- package/dist/commands/setup-phase-5a-skill-source.js.map +0 -1
- package/dist/commands/setup-phase-5b-project-sweep.d.ts +0 -38
- package/dist/commands/setup-phase-5b-project-sweep.d.ts.map +0 -1
- package/dist/commands/setup-phase-5b-project-sweep.js +0 -175
- package/dist/commands/setup-phase-5b-project-sweep.js.map +0 -1
- package/dist/commands/setup-phase-8-kg-hook.d.ts +0 -48
- package/dist/commands/setup-phase-8-kg-hook.d.ts.map +0 -1
- package/dist/commands/setup-phase-8-kg-hook.js +0 -93
- package/dist/commands/setup-phase-8-kg-hook.js.map +0 -1
- package/dist/commands/setup-phase-9-memory-bridge.d.ts +0 -36
- package/dist/commands/setup-phase-9-memory-bridge.d.ts.map +0 -1
- package/dist/commands/setup-phase-9-memory-bridge.js +0 -59
- package/dist/commands/setup-phase-9-memory-bridge.js.map +0 -1
- package/dist/commands/setup.d.ts +0 -231
- package/dist/commands/setup.d.ts.map +0 -1
- package/dist/commands/setup.js +0 -1374
- package/dist/commands/setup.js.map +0 -1
- package/dist/commands/skills-100x.d.ts +0 -34
- package/dist/commands/skills-100x.d.ts.map +0 -1
- package/dist/commands/skills-100x.js +0 -405
- package/dist/commands/skills-100x.js.map +0 -1
- package/dist/commands/skills-doctor.d.ts +0 -14
- package/dist/commands/skills-doctor.d.ts.map +0 -1
- package/dist/commands/skills-doctor.js +0 -126
- package/dist/commands/skills-doctor.js.map +0 -1
- package/dist/commands/skills-hook.d.ts +0 -19
- package/dist/commands/skills-hook.d.ts.map +0 -1
- package/dist/commands/skills-hook.js +0 -99
- package/dist/commands/skills-hook.js.map +0 -1
- package/dist/commands/skills-install-model-router.d.ts +0 -20
- package/dist/commands/skills-install-model-router.d.ts.map +0 -1
- package/dist/commands/skills-install-model-router.js +0 -55
- package/dist/commands/skills-install-model-router.js.map +0 -1
- package/dist/commands/skills-migrate-back.d.ts +0 -21
- package/dist/commands/skills-migrate-back.d.ts.map +0 -1
- package/dist/commands/skills-migrate-back.js +0 -222
- package/dist/commands/skills-migrate-back.js.map +0 -1
- package/dist/commands/skills-migrate-hooks-back.d.ts +0 -19
- package/dist/commands/skills-migrate-hooks-back.d.ts.map +0 -1
- package/dist/commands/skills-migrate-hooks-back.js +0 -83
- package/dist/commands/skills-migrate-hooks-back.js.map +0 -1
- package/dist/commands/skills-migrate-hooks.d.ts +0 -40
- package/dist/commands/skills-migrate-hooks.d.ts.map +0 -1
- package/dist/commands/skills-migrate-hooks.js +0 -178
- package/dist/commands/skills-migrate-hooks.js.map +0 -1
- package/dist/commands/skills-migrate.d.ts +0 -33
- package/dist/commands/skills-migrate.d.ts.map +0 -1
- package/dist/commands/skills-migrate.js +0 -216
- package/dist/commands/skills-migrate.js.map +0 -1
- package/dist/commands/skills-onboard.d.ts +0 -26
- package/dist/commands/skills-onboard.d.ts.map +0 -1
- package/dist/commands/skills-onboard.js +0 -230
- package/dist/commands/skills-onboard.js.map +0 -1
- package/dist/commands/skills-shadow-backups.d.ts +0 -15
- package/dist/commands/skills-shadow-backups.d.ts.map +0 -1
- package/dist/commands/skills-shadow-backups.js +0 -132
- package/dist/commands/skills-shadow-backups.js.map +0 -1
- package/dist/commands/skills-source.d.ts +0 -61
- package/dist/commands/skills-source.d.ts.map +0 -1
- package/dist/commands/skills-source.js +0 -895
- package/dist/commands/skills-source.js.map +0 -1
- package/dist/commands/skills.d.ts +0 -59
- package/dist/commands/skills.d.ts.map +0 -1
- package/dist/commands/skills.js +0 -461
- package/dist/commands/skills.js.map +0 -1
- package/dist/commands/status.d.ts +0 -65
- package/dist/commands/status.d.ts.map +0 -1
- package/dist/commands/status.js +0 -249
- package/dist/commands/status.js.map +0 -1
- package/dist/commands/stop.d.ts +0 -10
- package/dist/commands/stop.d.ts.map +0 -1
- package/dist/commands/stop.js +0 -17
- package/dist/commands/stop.js.map +0 -1
- package/dist/commands/substrate-audit-log.d.ts +0 -51
- package/dist/commands/substrate-audit-log.d.ts.map +0 -1
- package/dist/commands/substrate-audit-log.js +0 -161
- package/dist/commands/substrate-audit-log.js.map +0 -1
- package/dist/commands/substrate.d.ts +0 -87
- package/dist/commands/substrate.d.ts.map +0 -1
- package/dist/commands/substrate.js +0 -194
- package/dist/commands/substrate.js.map +0 -1
- package/dist/commands/update.d.ts +0 -89
- package/dist/commands/update.d.ts.map +0 -1
- package/dist/commands/update.js +0 -331
- package/dist/commands/update.js.map +0 -1
- package/dist/commands/upgrade-history.d.ts +0 -15
- package/dist/commands/upgrade-history.d.ts.map +0 -1
- package/dist/commands/upgrade-history.js +0 -34
- package/dist/commands/upgrade-history.js.map +0 -1
- package/dist/commands/upgrade-lock.d.ts +0 -93
- package/dist/commands/upgrade-lock.d.ts.map +0 -1
- package/dist/commands/upgrade-lock.js +0 -225
- package/dist/commands/upgrade-lock.js.map +0 -1
- package/dist/commands/upgrade-log.d.ts +0 -86
- package/dist/commands/upgrade-log.d.ts.map +0 -1
- package/dist/commands/upgrade-log.js +0 -146
- package/dist/commands/upgrade-log.js.map +0 -1
- package/dist/commands/upgrade.d.ts +0 -445
- package/dist/commands/upgrade.d.ts.map +0 -1
- package/dist/commands/upgrade.js +0 -1718
- package/dist/commands/upgrade.js.map +0 -1
- package/dist/commands/workspace-list-json.d.ts +0 -73
- package/dist/commands/workspace-list-json.d.ts.map +0 -1
- package/dist/commands/workspace-list-json.js +0 -59
- package/dist/commands/workspace-list-json.js.map +0 -1
- package/dist/commands/workspace.d.ts +0 -23
- package/dist/commands/workspace.d.ts.map +0 -1
- package/dist/commands/workspace.js +0 -203
- package/dist/commands/workspace.js.map +0 -1
- package/dist/commands/world-snapshot.d.ts +0 -32
- package/dist/commands/world-snapshot.d.ts.map +0 -1
- package/dist/commands/world-snapshot.js +0 -531
- package/dist/commands/world-snapshot.js.map +0 -1
- package/dist/commands/world-upgrade.d.ts +0 -33
- package/dist/commands/world-upgrade.d.ts.map +0 -1
- package/dist/commands/world-upgrade.js +0 -82
- package/dist/commands/world-upgrade.js.map +0 -1
- package/dist/commands/world.d.ts +0 -12
- package/dist/commands/world.d.ts.map +0 -1
- package/dist/commands/world.js +0 -18
- package/dist/commands/world.js.map +0 -1
- package/dist/commands/worldspec/compile.d.ts +0 -20
- package/dist/commands/worldspec/compile.d.ts.map +0 -1
- package/dist/commands/worldspec/compile.js +0 -130
- package/dist/commands/worldspec/compile.js.map +0 -1
- package/dist/commands/worldspec/index.d.ts +0 -12
- package/dist/commands/worldspec/index.d.ts.map +0 -1
- package/dist/commands/worldspec/index.js +0 -23
- package/dist/commands/worldspec/index.js.map +0 -1
- package/dist/commands/worldspec/init.d.ts +0 -15
- package/dist/commands/worldspec/init.d.ts.map +0 -1
- package/dist/commands/worldspec/init.js +0 -166
- package/dist/commands/worldspec/init.js.map +0 -1
- package/dist/commands/worldspec/schema.d.ts +0 -11
- package/dist/commands/worldspec/schema.d.ts.map +0 -1
- package/dist/commands/worldspec/schema.js +0 -55
- package/dist/commands/worldspec/schema.js.map +0 -1
- package/dist/commands/worldspec/validate.d.ts +0 -15
- package/dist/commands/worldspec/validate.d.ts.map +0 -1
- package/dist/commands/worldspec/validate.js +0 -66
- package/dist/commands/worldspec/validate.js.map +0 -1
- package/dist/commands/yolo.d.ts +0 -95
- package/dist/commands/yolo.d.ts.map +0 -1
- package/dist/commands/yolo.js +0 -377
- package/dist/commands/yolo.js.map +0 -1
- package/dist/context.d.ts +0 -30
- package/dist/context.d.ts.map +0 -1
- package/dist/context.js +0 -56
- package/dist/context.js.map +0 -1
- package/dist/docker-host.d.ts +0 -18
- package/dist/docker-host.d.ts.map +0 -1
- package/dist/docker-host.js +0 -17
- package/dist/docker-host.js.map +0 -1
- package/dist/exit-codes.d.ts +0 -67
- package/dist/exit-codes.d.ts.map +0 -1
- package/dist/exit-codes.js +0 -67
- package/dist/exit-codes.js.map +0 -1
- package/dist/from-manifest.d.ts +0 -53
- package/dist/from-manifest.d.ts.map +0 -1
- package/dist/from-manifest.js +0 -95
- package/dist/from-manifest.js.map +0 -1
- package/dist/image-presence.d.ts +0 -40
- package/dist/image-presence.d.ts.map +0 -1
- package/dist/image-presence.js +0 -39
- package/dist/image-presence.js.map +0 -1
- package/dist/index.d.ts +0 -9
- package/dist/index.d.ts.map +0 -1
- package/dist/install-root.d.ts +0 -74
- package/dist/install-root.d.ts.map +0 -1
- package/dist/install-root.js +0 -98
- package/dist/install-root.js.map +0 -1
- package/dist/lib/anthropic-base-url-file.d.ts +0 -37
- package/dist/lib/anthropic-base-url-file.d.ts.map +0 -1
- package/dist/lib/anthropic-base-url-file.js +0 -46
- package/dist/lib/anthropic-base-url-file.js.map +0 -1
- package/dist/lib/auth-backend.d.ts +0 -168
- package/dist/lib/auth-backend.d.ts.map +0 -1
- package/dist/lib/auth-backend.js +0 -172
- package/dist/lib/auth-backend.js.map +0 -1
- package/dist/lib/auth-list-cache.d.ts +0 -67
- package/dist/lib/auth-list-cache.d.ts.map +0 -1
- package/dist/lib/auth-list-cache.js +0 -84
- package/dist/lib/auth-list-cache.js.map +0 -1
- package/dist/lib/auth-list.d.ts +0 -107
- package/dist/lib/auth-list.d.ts.map +0 -1
- package/dist/lib/auth-list.js +0 -123
- package/dist/lib/auth-list.js.map +0 -1
- package/dist/lib/auth-login.d.ts +0 -92
- package/dist/lib/auth-login.d.ts.map +0 -1
- package/dist/lib/auth-login.js +0 -124
- package/dist/lib/auth-login.js.map +0 -1
- package/dist/lib/auth-mutator-backend.d.ts +0 -54
- package/dist/lib/auth-mutator-backend.d.ts.map +0 -1
- package/dist/lib/auth-mutator-backend.js +0 -62
- package/dist/lib/auth-mutator-backend.js.map +0 -1
- package/dist/lib/auth-refresh-kubernetes.d.ts +0 -65
- package/dist/lib/auth-refresh-kubernetes.d.ts.map +0 -1
- package/dist/lib/auth-refresh-kubernetes.js +0 -125
- package/dist/lib/auth-refresh-kubernetes.js.map +0 -1
- package/dist/lib/auth-remote.d.ts +0 -172
- package/dist/lib/auth-remote.d.ts.map +0 -1
- package/dist/lib/auth-remote.js +0 -394
- package/dist/lib/auth-remote.js.map +0 -1
- package/dist/lib/bootstrap-kubernetes.d.ts +0 -164
- package/dist/lib/bootstrap-kubernetes.d.ts.map +0 -1
- package/dist/lib/bootstrap-kubernetes.js +0 -1002
- package/dist/lib/bootstrap-kubernetes.js.map +0 -1
- package/dist/lib/build-if-stale.d.ts +0 -33
- package/dist/lib/build-if-stale.d.ts.map +0 -1
- package/dist/lib/build-if-stale.js +0 -156
- package/dist/lib/build-if-stale.js.map +0 -1
- package/dist/lib/bundle-freshness.d.ts +0 -57
- package/dist/lib/bundle-freshness.d.ts.map +0 -1
- package/dist/lib/bundle-freshness.js +0 -223
- package/dist/lib/bundle-freshness.js.map +0 -1
- package/dist/lib/bundle-source.d.ts +0 -52
- package/dist/lib/bundle-source.d.ts.map +0 -1
- package/dist/lib/bundle-source.js +0 -83
- package/dist/lib/bundle-source.js.map +0 -1
- package/dist/lib/cf-access-token.d.ts +0 -32
- package/dist/lib/cf-access-token.d.ts.map +0 -1
- package/dist/lib/cf-access-token.js +0 -51
- package/dist/lib/cf-access-token.js.map +0 -1
- package/dist/lib/completion-generator.d.ts +0 -107
- package/dist/lib/completion-generator.d.ts.map +0 -1
- package/dist/lib/completion-generator.js +0 -226
- package/dist/lib/completion-generator.js.map +0 -1
- package/dist/lib/config.d.ts +0 -114
- package/dist/lib/config.d.ts.map +0 -1
- package/dist/lib/config.js +0 -246
- package/dist/lib/config.js.map +0 -1
- package/dist/lib/flywheel-probes.d.ts +0 -58
- package/dist/lib/flywheel-probes.d.ts.map +0 -1
- package/dist/lib/flywheel-probes.js +0 -163
- package/dist/lib/flywheel-probes.js.map +0 -1
- package/dist/lib/health-probes.d.ts +0 -267
- package/dist/lib/health-probes.d.ts.map +0 -1
- package/dist/lib/health-probes.js +0 -933
- package/dist/lib/health-probes.js.map +0 -1
- package/dist/lib/help-groups.d.ts +0 -36
- package/dist/lib/help-groups.d.ts.map +0 -1
- package/dist/lib/help-groups.js +0 -124
- package/dist/lib/help-groups.js.map +0 -1
- package/dist/lib/host-side-proxy.d.ts +0 -67
- package/dist/lib/host-side-proxy.d.ts.map +0 -1
- package/dist/lib/host-side-proxy.js +0 -177
- package/dist/lib/host-side-proxy.js.map +0 -1
- package/dist/lib/instrumentation.d.ts +0 -85
- package/dist/lib/instrumentation.d.ts.map +0 -1
- package/dist/lib/instrumentation.js +0 -104
- package/dist/lib/instrumentation.js.map +0 -1
- package/dist/lib/k8s-bootstrap.d.ts +0 -126
- package/dist/lib/k8s-bootstrap.d.ts.map +0 -1
- package/dist/lib/k8s-bootstrap.js +0 -218
- package/dist/lib/k8s-bootstrap.js.map +0 -1
- package/dist/lib/k8s-context-discovery.d.ts +0 -80
- package/dist/lib/k8s-context-discovery.d.ts.map +0 -1
- package/dist/lib/k8s-context-discovery.js +0 -102
- package/dist/lib/k8s-context-discovery.js.map +0 -1
- package/dist/lib/k8s-secret-render.d.ts +0 -141
- package/dist/lib/k8s-secret-render.d.ts.map +0 -1
- package/dist/lib/k8s-secret-render.js +0 -318
- package/dist/lib/k8s-secret-render.js.map +0 -1
- package/dist/lib/kg-caps.d.ts +0 -19
- package/dist/lib/kg-caps.d.ts.map +0 -1
- package/dist/lib/kg-caps.js +0 -19
- package/dist/lib/kg-caps.js.map +0 -1
- package/dist/lib/kubectl-context.d.ts +0 -87
- package/dist/lib/kubectl-context.d.ts.map +0 -1
- package/dist/lib/kubectl-context.js +0 -105
- package/dist/lib/kubectl-context.js.map +0 -1
- package/dist/lib/kubectl-wrap.d.ts +0 -65
- package/dist/lib/kubectl-wrap.d.ts.map +0 -1
- package/dist/lib/kubectl-wrap.js +0 -135
- package/dist/lib/kubectl-wrap.js.map +0 -1
- package/dist/lib/manifest-refresh.d.ts +0 -136
- package/dist/lib/manifest-refresh.d.ts.map +0 -1
- package/dist/lib/manifest-refresh.js +0 -298
- package/dist/lib/manifest-refresh.js.map +0 -1
- package/dist/lib/memory-host-process-migration.d.ts +0 -56
- package/dist/lib/memory-host-process-migration.d.ts.map +0 -1
- package/dist/lib/memory-host-process-migration.js +0 -156
- package/dist/lib/memory-host-process-migration.js.map +0 -1
- package/dist/lib/memory-secret.d.ts +0 -83
- package/dist/lib/memory-secret.d.ts.map +0 -1
- package/dist/lib/memory-secret.js +0 -147
- package/dist/lib/memory-secret.js.map +0 -1
- package/dist/lib/peripheral-registry.d.ts +0 -53
- package/dist/lib/peripheral-registry.d.ts.map +0 -1
- package/dist/lib/peripheral-registry.js +0 -73
- package/dist/lib/peripheral-registry.js.map +0 -1
- package/dist/lib/plans-client.d.ts +0 -69
- package/dist/lib/plans-client.d.ts.map +0 -1
- package/dist/lib/plans-client.js +0 -140
- package/dist/lib/plans-client.js.map +0 -1
- package/dist/lib/port-forward.d.ts +0 -168
- package/dist/lib/port-forward.d.ts.map +0 -1
- package/dist/lib/port-forward.js +0 -393
- package/dist/lib/port-forward.js.map +0 -1
- package/dist/lib/shell-rc.d.ts +0 -90
- package/dist/lib/shell-rc.d.ts.map +0 -1
- package/dist/lib/shell-rc.js +0 -91
- package/dist/lib/shell-rc.js.map +0 -1
- package/dist/lib/shim-generator.d.ts +0 -51
- package/dist/lib/shim-generator.d.ts.map +0 -1
- package/dist/lib/shim-generator.js +0 -88
- package/dist/lib/shim-generator.js.map +0 -1
- package/dist/lib/skills-apply-overlays.d.ts +0 -35
- package/dist/lib/skills-apply-overlays.d.ts.map +0 -1
- package/dist/lib/skills-apply-overlays.js +0 -243
- package/dist/lib/skills-apply-overlays.js.map +0 -1
- package/dist/lib/symlink-reconcile.d.ts +0 -32
- package/dist/lib/symlink-reconcile.d.ts.map +0 -1
- package/dist/lib/symlink-reconcile.js +0 -80
- package/dist/lib/symlink-reconcile.js.map +0 -1
- package/dist/lib/upgrade-check.d.ts +0 -60
- package/dist/lib/upgrade-check.d.ts.map +0 -1
- package/dist/lib/upgrade-check.js +0 -169
- package/dist/lib/upgrade-check.js.map +0 -1
- package/dist/lib/upgrade-kubernetes.d.ts +0 -193
- package/dist/lib/upgrade-kubernetes.d.ts.map +0 -1
- package/dist/lib/upgrade-kubernetes.js +0 -1014
- package/dist/lib/upgrade-kubernetes.js.map +0 -1
- package/dist/lib/world-mcp-register.d.ts +0 -98
- package/dist/lib/world-mcp-register.d.ts.map +0 -1
- package/dist/lib/world-mcp-register.js +0 -117
- package/dist/lib/world-mcp-register.js.map +0 -1
- package/dist/output.d.ts +0 -10
- package/dist/output.d.ts.map +0 -1
- package/dist/output.js +0 -31
- package/dist/output.js.map +0 -1
- package/dist/pleri-config.d.ts +0 -22
- package/dist/pleri-config.d.ts.map +0 -1
- package/dist/pleri-config.js +0 -42
- package/dist/pleri-config.js.map +0 -1
- package/dist/protocol-version.d.ts +0 -79
- package/dist/protocol-version.d.ts.map +0 -1
- package/dist/protocol-version.js +0 -133
- package/dist/protocol-version.js.map +0 -1
- package/dist/registry-allowlist.d.ts +0 -47
- package/dist/registry-allowlist.d.ts.map +0 -1
- package/dist/registry-allowlist.js +0 -67
- package/dist/registry-allowlist.js.map +0 -1
- package/dist/spawn/home-override.d.ts +0 -82
- package/dist/spawn/home-override.d.ts.map +0 -1
- package/dist/spawn/home-override.js +0 -107
- package/dist/spawn/home-override.js.map +0 -1
- package/host-cp/src/linear-sync.mjs +0 -43
package/dist/commands/upgrade.js
DELETED
|
@@ -1,1718 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* `olam upgrade` — self-upgrade the local Olam dev stack.
|
|
3
|
-
*
|
|
4
|
-
* Runs the full manual sequence in one command:
|
|
5
|
-
* git fetch + git pull --ff-only
|
|
6
|
-
* npm install
|
|
7
|
-
* npm run build
|
|
8
|
-
* vite build (SPA)
|
|
9
|
-
* bash build-host-cp.sh (image)
|
|
10
|
-
* docker compose up -d --force-recreate
|
|
11
|
-
* wait for /health
|
|
12
|
-
*/
|
|
13
|
-
import * as fs from 'node:fs';
|
|
14
|
-
import * as path from 'node:path';
|
|
15
|
-
import { spawnSync } from 'node:child_process';
|
|
16
|
-
import ora from 'ora';
|
|
17
|
-
import pc from 'picocolors';
|
|
18
|
-
import { printError, printSuccess, printInfo, printWarning, printHeader } from '../output.js';
|
|
19
|
-
import { buildComposeEnv, captureGhToken, findComposeFile, readAuthSecret, runCompose } from './host-cp.js';
|
|
20
|
-
import { acquireLock, releaseLock, formatRefusalMessage, LOCK_FILE_PATH } from './upgrade-lock.js';
|
|
21
|
-
import { appendUpgradeLog } from './upgrade-log.js';
|
|
22
|
-
import { handleHistory, parseHistoryOpts } from './upgrade-history.js';
|
|
23
|
-
import { AuthContainerController } from '@olam/core/src/auth/index.js';
|
|
24
|
-
import { loadImageDigests, pullImageWithRetry, realDocker, } from './bootstrap.js';
|
|
25
|
-
import { EXIT_BOOTSTRAP_PULL_FAILED, EXIT_GENERIC_ERROR, EXIT_PROTOCOL_MISMATCH, } from '../exit-codes.js';
|
|
26
|
-
import { checkProtocolOverlap, parseProtocolVersionsLabel } from '../protocol-version.js';
|
|
27
|
-
import { isDevMode, MissingBuildScriptError } from '../install-root.js';
|
|
28
|
-
import { readConfig } from '../lib/config.js';
|
|
29
|
-
import { runUpgradeKubernetes } from '../lib/upgrade-kubernetes.js';
|
|
30
|
-
const AUTH_HEALTH_URL = 'http://127.0.0.1:9999/health';
|
|
31
|
-
/**
|
|
32
|
-
* Check whether node_modules is in sync with package-lock.json.
|
|
33
|
-
*
|
|
34
|
-
* npm writes node_modules/.package-lock.json after every successful install.
|
|
35
|
-
* If its mtime is >= package-lock.json's mtime, the last install ran after
|
|
36
|
-
* the last lockfile change — node_modules is current.
|
|
37
|
-
*
|
|
38
|
-
* Returns true → skip install (node_modules reflects the current lockfile).
|
|
39
|
-
* Returns false → run install (lockfile changed or marker absent).
|
|
40
|
-
*/
|
|
41
|
-
export function isNodeModulesInSync(cwd) {
|
|
42
|
-
const lockPath = path.join(cwd, 'package-lock.json');
|
|
43
|
-
const markerPath = path.join(cwd, 'node_modules', '.package-lock.json');
|
|
44
|
-
if (!fs.existsSync(lockPath) || !fs.existsSync(markerPath))
|
|
45
|
-
return false;
|
|
46
|
-
try {
|
|
47
|
-
const lockStat = fs.statSync(lockPath);
|
|
48
|
-
const markerStat = fs.statSync(markerPath);
|
|
49
|
-
return markerStat.mtimeMs >= lockStat.mtimeMs;
|
|
50
|
-
}
|
|
51
|
-
catch {
|
|
52
|
-
return false;
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
/**
|
|
56
|
-
* Decide whether the npm install step should be skipped.
|
|
57
|
-
*
|
|
58
|
-
* Priority:
|
|
59
|
-
* 1. --skip-install flag → always skip
|
|
60
|
-
* 2. node_modules in sync with lockfile → skip (log reason)
|
|
61
|
-
* 3. otherwise → run install
|
|
62
|
-
*/
|
|
63
|
-
export function shouldSkipInstall(opts, cwd) {
|
|
64
|
-
if (opts.skipInstall) {
|
|
65
|
-
return { skip: true, reason: '--skip-install flag set' };
|
|
66
|
-
}
|
|
67
|
-
if (isNodeModulesInSync(cwd)) {
|
|
68
|
-
return { skip: true, reason: 'node_modules in sync with package-lock.json' };
|
|
69
|
-
}
|
|
70
|
-
return { skip: false };
|
|
71
|
-
}
|
|
72
|
-
/** Check cwd looks like the olam repo root. */
|
|
73
|
-
export function validateRepoRoot(cwd) {
|
|
74
|
-
const marker = path.join(cwd, 'packages/host-cp/compose.yaml');
|
|
75
|
-
if (!fs.existsSync(marker)) {
|
|
76
|
-
return {
|
|
77
|
-
ok: false,
|
|
78
|
-
error: `Not an olam repo root (expected ${marker}).\n` +
|
|
79
|
-
'Run `olam upgrade` from the root of your olam checkout.',
|
|
80
|
-
};
|
|
81
|
-
}
|
|
82
|
-
return { ok: true };
|
|
83
|
-
}
|
|
84
|
-
/** Normalise raw Commander option object into typed opts. */
|
|
85
|
-
export function parseUpgradeOpts(raw) {
|
|
86
|
-
const rawN = raw.n;
|
|
87
|
-
const historyN = typeof rawN === 'number'
|
|
88
|
-
? rawN
|
|
89
|
-
: typeof rawN === 'string'
|
|
90
|
-
? Number.parseInt(rawN, 10)
|
|
91
|
-
: 10;
|
|
92
|
-
return {
|
|
93
|
-
yes: raw.yes === true,
|
|
94
|
-
skipImage: raw.skipImage === true,
|
|
95
|
-
skipInstall: raw.skipInstall === true,
|
|
96
|
-
branch: raw.branch ?? null,
|
|
97
|
-
rollback: raw.rollback === true,
|
|
98
|
-
force: raw.force === true,
|
|
99
|
-
noCache: raw.noCache === true,
|
|
100
|
-
history: raw.history === true,
|
|
101
|
-
historyN: Number.isFinite(historyN) && historyN > 0 ? historyN : 10,
|
|
102
|
-
historyJson: raw.json === true,
|
|
103
|
-
fromSource: raw.fromSource === true,
|
|
104
|
-
forceRefreshManifests: raw.forceRefreshManifests === true,
|
|
105
|
-
acceptSecurityRegression: raw.acceptSecurityRegression === true,
|
|
106
|
-
};
|
|
107
|
-
}
|
|
108
|
-
/**
|
|
109
|
-
* Extract the JS bundle hash from index.html.
|
|
110
|
-
* Looks for the canonical Vite output pattern: `/assets/index-<hash>.js`.
|
|
111
|
-
*/
|
|
112
|
-
export function extractBundleHash(indexHtml) {
|
|
113
|
-
const match = indexHtml.match(/\/assets\/index-([A-Za-z0-9_-]+)\.js/);
|
|
114
|
-
return match ? (match[1] ?? null) : null;
|
|
115
|
-
}
|
|
116
|
-
function runStep(label, cmd, args, opts = {}) {
|
|
117
|
-
const start = Date.now();
|
|
118
|
-
process.stdout.write(` ${pc.dim(label.padEnd(34))}`);
|
|
119
|
-
const result = spawnSync(cmd, [...args], {
|
|
120
|
-
encoding: 'utf-8',
|
|
121
|
-
stdio: ['ignore', 'pipe', 'pipe'],
|
|
122
|
-
cwd: opts.cwd ?? process.cwd(),
|
|
123
|
-
env: { ...process.env, ...(opts.env ?? {}) },
|
|
124
|
-
});
|
|
125
|
-
const durationMs = Date.now() - start;
|
|
126
|
-
const ok = result.status === 0 && result.error === undefined;
|
|
127
|
-
const dur = `${(durationMs / 1000).toFixed(1)}s`;
|
|
128
|
-
process.stdout.write(`${ok ? pc.green('✓') : pc.red('✗')} ${dur}\n`);
|
|
129
|
-
return {
|
|
130
|
-
ok,
|
|
131
|
-
stdout: result.stdout ?? '',
|
|
132
|
-
stderr: result.stderr ?? '',
|
|
133
|
-
durationMs,
|
|
134
|
-
};
|
|
135
|
-
}
|
|
136
|
-
function isGitDirty(cwd) {
|
|
137
|
-
const result = spawnSync('git', ['status', '--porcelain'], {
|
|
138
|
-
encoding: 'utf-8',
|
|
139
|
-
stdio: ['ignore', 'pipe', 'pipe'],
|
|
140
|
-
cwd,
|
|
141
|
-
});
|
|
142
|
-
return (result.stdout ?? '').trim().length > 0;
|
|
143
|
-
}
|
|
144
|
-
function hasGitUpstream(cwd) {
|
|
145
|
-
const result = spawnSync('git', ['rev-parse', '--abbrev-ref', '@{u}'], {
|
|
146
|
-
encoding: 'utf-8',
|
|
147
|
-
stdio: ['ignore', 'pipe', 'pipe'],
|
|
148
|
-
cwd,
|
|
149
|
-
});
|
|
150
|
-
return result.status === 0;
|
|
151
|
-
}
|
|
152
|
-
/**
|
|
153
|
-
* Capture HEAD SHA via `git rev-parse HEAD`. Returns null on failure.
|
|
154
|
-
*
|
|
155
|
-
* Phase 2a — A2: must be invoked AFTER `git pull --ff-only` so the captured
|
|
156
|
-
* SHA reflects the state we're upgrading TO (not the pre-pull state). The
|
|
157
|
-
* pull's whole purpose is to advance HEAD; capturing before would refuse the
|
|
158
|
-
* CLI's own pull as drift at A6's swap-boundary check.
|
|
159
|
-
*
|
|
160
|
-
* The returned SHA is sticky for the rest of the run (no per-step re-reads);
|
|
161
|
-
* A6 / B4 re-read once at the swap boundary to detect operator-driven mid-flight
|
|
162
|
-
* `git checkout` / `git reset` that happen DURING the build window.
|
|
163
|
-
*/
|
|
164
|
-
export function captureHeadSha(cwd) {
|
|
165
|
-
const result = spawnSync('git', ['rev-parse', 'HEAD'], {
|
|
166
|
-
encoding: 'utf-8',
|
|
167
|
-
stdio: ['ignore', 'pipe', 'pipe'],
|
|
168
|
-
cwd,
|
|
169
|
-
});
|
|
170
|
-
if (result.status !== 0)
|
|
171
|
-
return null;
|
|
172
|
-
const sha = (result.stdout ?? '').trim();
|
|
173
|
-
// git rev-parse HEAD returns 40-char lowercase hex; defensive validation.
|
|
174
|
-
if (!/^[0-9a-f]{40}$/.test(sha))
|
|
175
|
-
return null;
|
|
176
|
-
return sha;
|
|
177
|
-
}
|
|
178
|
-
/** Abbreviate a 40-char SHA to 8 chars for human-readable output. */
|
|
179
|
-
export function abbreviateSha(sha) {
|
|
180
|
-
return sha.slice(0, 8);
|
|
181
|
-
}
|
|
182
|
-
/**
|
|
183
|
-
* Check whether a docker image tag exists locally (Phase 2b — B1).
|
|
184
|
-
*
|
|
185
|
-
* Uses `docker image inspect` which exits 0 only when ALL specified
|
|
186
|
-
* images exist locally. Single-image variant for the rollback pre-flight.
|
|
187
|
-
*/
|
|
188
|
-
export function imageExists(tag) {
|
|
189
|
-
try {
|
|
190
|
-
const result = spawnSync('docker', ['image', 'inspect', '--format', '{{.Id}}', tag], {
|
|
191
|
-
encoding: 'utf-8',
|
|
192
|
-
stdio: ['ignore', 'pipe', 'ignore'],
|
|
193
|
-
});
|
|
194
|
-
return result.status === 0;
|
|
195
|
-
}
|
|
196
|
-
catch {
|
|
197
|
-
return false;
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
/**
|
|
201
|
-
* Pre-flight check for `olam upgrade --rollback` (Phase 2b — B1).
|
|
202
|
-
*
|
|
203
|
-
* Verifies that all three `:olam-rollback` tags exist. Returns an error
|
|
204
|
-
* message naming the missing image(s) when any are absent — typically
|
|
205
|
-
* the first-upgrade case where no prior canonical existed for one or
|
|
206
|
-
* more components, leaving the rollback set incoherent (see audit A6-001).
|
|
207
|
-
*
|
|
208
|
-
* Returns null when all three are present (rollback is safe to proceed).
|
|
209
|
-
*/
|
|
210
|
-
export function checkRollbackSetExists(plan) {
|
|
211
|
-
const missing = plan.filter((p) => !imageExists(p.rollback)).map((p) => p.rollback);
|
|
212
|
-
if (missing.length === 0)
|
|
213
|
-
return null;
|
|
214
|
-
return missing.join(', ');
|
|
215
|
-
}
|
|
216
|
-
/**
|
|
217
|
-
* Run docker create + docker inspect for a single image.
|
|
218
|
-
*
|
|
219
|
-
* Returns ok=true when:
|
|
220
|
-
* - `docker create <image>` exits 0 (image manifest valid, layers downloadable).
|
|
221
|
-
* - `docker inspect <image> --format '{{index .Config.Labels "olam.build.sha"}}'`
|
|
222
|
-
* returns the expected `targetSha`.
|
|
223
|
-
*
|
|
224
|
-
* The container created by `docker create` is removed via `docker rm` even on
|
|
225
|
-
* failure paths (best-effort cleanup; orphans are harmless and pruned by the
|
|
226
|
-
* daemon's GC eventually).
|
|
227
|
-
*/
|
|
228
|
-
// Cap each docker shellout. The smoke path expects the image to already
|
|
229
|
-
// be present locally (post-pull), so docker create / inspect / rm are
|
|
230
|
-
// sub-second on the happy path. The cap protects two real-world hangs:
|
|
231
|
-
// (1) a wedged dockerd not responding, and (2) a missing image where
|
|
232
|
-
// docker tries to pull from Docker Hub and the registry's 404 is slow
|
|
233
|
-
// enough to blow vitest's default test budget. 30s is generous for the
|
|
234
|
-
// happy path and bounded enough for production callers.
|
|
235
|
-
const SMOKE_DOCKER_TIMEOUT_MS = 30_000;
|
|
236
|
-
export function smokeImage(image, targetSha) {
|
|
237
|
-
// 1. docker create — allocates the container; doesn't start the entrypoint.
|
|
238
|
-
const createResult = spawnSync('docker', ['create', '--name', `olam-smoke-${Date.now()}`, image], {
|
|
239
|
-
encoding: 'utf-8',
|
|
240
|
-
stdio: ['ignore', 'pipe', 'pipe'],
|
|
241
|
-
timeout: SMOKE_DOCKER_TIMEOUT_MS,
|
|
242
|
-
});
|
|
243
|
-
if (createResult.status !== 0) {
|
|
244
|
-
return {
|
|
245
|
-
image,
|
|
246
|
-
ok: false,
|
|
247
|
-
bakedSha: null,
|
|
248
|
-
error: `docker create failed: ${(createResult.stderr ?? '').trim()}`,
|
|
249
|
-
};
|
|
250
|
-
}
|
|
251
|
-
const containerId = (createResult.stdout ?? '').trim();
|
|
252
|
-
// 2. docker inspect — read the OLAM_BUILD_SHA label.
|
|
253
|
-
const inspectResult = spawnSync('docker', ['inspect', '--format', '{{index .Config.Labels "olam.build.sha"}}', image], {
|
|
254
|
-
encoding: 'utf-8',
|
|
255
|
-
stdio: ['ignore', 'pipe', 'pipe'],
|
|
256
|
-
timeout: SMOKE_DOCKER_TIMEOUT_MS,
|
|
257
|
-
});
|
|
258
|
-
// 3. Cleanup — best-effort; ignore exit code.
|
|
259
|
-
if (containerId.length > 0) {
|
|
260
|
-
spawnSync('docker', ['rm', '-f', containerId], {
|
|
261
|
-
encoding: 'utf-8',
|
|
262
|
-
stdio: ['ignore', 'ignore', 'ignore'],
|
|
263
|
-
timeout: SMOKE_DOCKER_TIMEOUT_MS,
|
|
264
|
-
});
|
|
265
|
-
}
|
|
266
|
-
if (inspectResult.status !== 0) {
|
|
267
|
-
return {
|
|
268
|
-
image,
|
|
269
|
-
ok: false,
|
|
270
|
-
bakedSha: null,
|
|
271
|
-
error: `docker inspect failed: ${(inspectResult.stderr ?? '').trim()}`,
|
|
272
|
-
};
|
|
273
|
-
}
|
|
274
|
-
const bakedSha = (inspectResult.stdout ?? '').trim();
|
|
275
|
-
// Empty output means the label is absent — that's a build-corrupt signal.
|
|
276
|
-
if (bakedSha.length === 0) {
|
|
277
|
-
return {
|
|
278
|
-
image,
|
|
279
|
-
ok: false,
|
|
280
|
-
bakedSha: null,
|
|
281
|
-
error: 'olam.build.sha label is missing or empty',
|
|
282
|
-
};
|
|
283
|
-
}
|
|
284
|
-
// Allow either full 40-char SHA or "unknown" (build-host-cp.sh writes
|
|
285
|
-
// "unknown" when git rev-parse fails). Match against targetSha for
|
|
286
|
-
// success.
|
|
287
|
-
if (bakedSha !== targetSha) {
|
|
288
|
-
return {
|
|
289
|
-
image,
|
|
290
|
-
ok: false,
|
|
291
|
-
bakedSha,
|
|
292
|
-
error: `baked SHA ${abbreviateSha(bakedSha)} ≠ target SHA ${abbreviateSha(targetSha)}`,
|
|
293
|
-
};
|
|
294
|
-
}
|
|
295
|
-
return { image, ok: true, bakedSha };
|
|
296
|
-
}
|
|
297
|
-
export const PRODUCTION_SWAP_PLAN = [
|
|
298
|
-
{ transient: 'olam-auth:olam-next', canonical: 'olam-auth:local', rollback: 'olam-auth:olam-rollback' },
|
|
299
|
-
{ transient: 'olam-devbox:olam-next', canonical: 'olam-devbox:latest', rollback: 'olam-devbox:olam-rollback' },
|
|
300
|
-
{ transient: 'olam-host-cp:olam-next', canonical: 'olam-host-cp:latest', rollback: 'olam-host-cp:olam-rollback' },
|
|
301
|
-
{ transient: 'olam-mcp-auth:olam-next', canonical: 'olam-mcp-auth:local', rollback: 'olam-mcp-auth:olam-rollback' },
|
|
302
|
-
{ transient: 'olam-kg-service:olam-next', canonical: 'olam-kg-service:local', rollback: 'olam-kg-service:olam-rollback' },
|
|
303
|
-
];
|
|
304
|
-
/**
|
|
305
|
-
* Run `docker tag <source> <dest>`. Returns ok=false with stderr trimmed
|
|
306
|
-
* on failure (e.g. source image absent). No retry — caller decides.
|
|
307
|
-
*
|
|
308
|
-
* Per audit A6-003: spawnSync may throw synchronously under fork pressure
|
|
309
|
-
* (libuv clone(2) failures). The try/catch ensures performAtomicSwap can
|
|
310
|
-
* always proceed to its summary phase — a thrown exception escaping
|
|
311
|
-
* dockerTag would leak the upgrade lock and produce no SwapResult, which
|
|
312
|
-
* confuses both the operator AND Phase 2b's --rollback recovery path.
|
|
313
|
-
*/
|
|
314
|
-
export function dockerTag(source, dest) {
|
|
315
|
-
try {
|
|
316
|
-
const result = spawnSync('docker', ['tag', source, dest], {
|
|
317
|
-
encoding: 'utf-8',
|
|
318
|
-
stdio: ['ignore', 'ignore', 'pipe'],
|
|
319
|
-
});
|
|
320
|
-
if (result.status === 0 && result.error === undefined)
|
|
321
|
-
return { ok: true };
|
|
322
|
-
return {
|
|
323
|
-
ok: false,
|
|
324
|
-
error: (result.stderr ?? '').trim() || result.error?.message || 'docker tag failed',
|
|
325
|
-
};
|
|
326
|
-
}
|
|
327
|
-
catch (err) {
|
|
328
|
-
return {
|
|
329
|
-
ok: false,
|
|
330
|
-
error: err instanceof Error ? `spawnSync threw: ${err.message}` : 'spawnSync threw',
|
|
331
|
-
};
|
|
332
|
-
}
|
|
333
|
-
}
|
|
334
|
-
/**
|
|
335
|
-
* Atomic-ish 3-image set swap.
|
|
336
|
-
*
|
|
337
|
-
* Six sequential `docker tag` ops in two phases:
|
|
338
|
-
*
|
|
339
|
-
* Phase 1 (rollback-save):
|
|
340
|
-
* 1. canonical → :olam-rollback (image 1)
|
|
341
|
-
* 2. canonical → :olam-rollback (image 2)
|
|
342
|
-
* 3. canonical → :olam-rollback (image 3)
|
|
343
|
-
*
|
|
344
|
-
* Phase 2 (canonical-advance):
|
|
345
|
-
* 4. :olam-next → canonical (image 1)
|
|
346
|
-
* 5. :olam-next → canonical (image 2)
|
|
347
|
-
* 6. :olam-next → canonical (image 3)
|
|
348
|
-
*
|
|
349
|
-
* Invariants:
|
|
350
|
-
*
|
|
351
|
-
* - **First-upgrade tolerance**: any of steps 1-3 may fail with "no such
|
|
352
|
-
* image" if the operator has never had a canonical-tagged image. Those
|
|
353
|
-
* failures are NON-FATAL (recorded in rollbackError but not aborted) —
|
|
354
|
-
* `:olam-rollback` simply doesn't exist for that image; Phase 2b's
|
|
355
|
-
* `--rollback` pre-flight detects this and refuses.
|
|
356
|
-
*
|
|
357
|
-
* - **Canonical-advance fatality**: any failure in steps 4-6 is fatal.
|
|
358
|
-
* The swap is partially advanced; canonical tags are now mixed (some
|
|
359
|
-
* at SHA-Y, some at SHA-X). Operator runs `olam upgrade --rollback`
|
|
360
|
-
* (Phase 2b) which uses the FULL `:olam-rollback` set written in Phase 1
|
|
361
|
-
* to restore coherent prior state.
|
|
362
|
-
*
|
|
363
|
-
* - **SIGKILL recovery**: if killed during Phase 1, partial `:olam-rollback`
|
|
364
|
-
* exists but canonical is intact — operator's next `olam upgrade` succeeds
|
|
365
|
-
* normally (the partial `:olam-rollback` is overwritten by the next
|
|
366
|
-
* successful run). If killed during Phase 2, canonical is mixed —
|
|
367
|
-
* operator must `olam upgrade --rollback` to recover.
|
|
368
|
-
*
|
|
369
|
-
* The "atomic-ish" qualifier: `docker tag` is per-image atomic (POSIX rename
|
|
370
|
-
* of a symbolic name), but the SET of 3 canonical tags is updated sequentially
|
|
371
|
-
* across ~1s wall-clock. Sub-second window is acceptable for solo-dev/dogfood
|
|
372
|
-
* per the plan's local-dev/dogfood priority axis.
|
|
373
|
-
*/
|
|
374
|
-
export function performAtomicSwap(plan) {
|
|
375
|
-
const steps = plan.map((p) => ({
|
|
376
|
-
image: p.canonical,
|
|
377
|
-
rollbackSaved: false,
|
|
378
|
-
canonicalAdvanced: false,
|
|
379
|
-
}));
|
|
380
|
-
// Phase 1: preserve previous-good as :olam-rollback (steps 1-3).
|
|
381
|
-
// Non-fatal failures: missing canonical (first-upgrade) is acceptable.
|
|
382
|
-
for (let i = 0; i < plan.length; i++) {
|
|
383
|
-
const p = plan[i];
|
|
384
|
-
const r = dockerTag(p.canonical, p.rollback);
|
|
385
|
-
steps[i] = {
|
|
386
|
-
...steps[i],
|
|
387
|
-
rollbackSaved: r.ok,
|
|
388
|
-
...(r.error !== undefined && { rollbackError: r.error }),
|
|
389
|
-
};
|
|
390
|
-
}
|
|
391
|
-
// Phase 2: advance canonical to :olam-next (steps 4-6).
|
|
392
|
-
// FATAL failure: canonical is mixed. Recovery via `olam upgrade --rollback`.
|
|
393
|
-
let advanceFailed = false;
|
|
394
|
-
let firstFailureIdx = -1;
|
|
395
|
-
for (let i = 0; i < plan.length; i++) {
|
|
396
|
-
const p = plan[i];
|
|
397
|
-
if (advanceFailed) {
|
|
398
|
-
// Skip remaining advances after first failure — leaves canonical mixed
|
|
399
|
-
// but caller's recovery path is `olam upgrade --rollback`, not
|
|
400
|
-
// continue-and-hope.
|
|
401
|
-
steps[i] = { ...steps[i], canonicalAdvanced: false };
|
|
402
|
-
continue;
|
|
403
|
-
}
|
|
404
|
-
const r = dockerTag(p.transient, p.canonical);
|
|
405
|
-
steps[i] = {
|
|
406
|
-
...steps[i],
|
|
407
|
-
canonicalAdvanced: r.ok,
|
|
408
|
-
...(r.error !== undefined && { canonicalError: r.error }),
|
|
409
|
-
};
|
|
410
|
-
if (!r.ok) {
|
|
411
|
-
advanceFailed = true;
|
|
412
|
-
firstFailureIdx = i;
|
|
413
|
-
}
|
|
414
|
-
}
|
|
415
|
-
const allAdvanced = steps.every((s) => s.canonicalAdvanced);
|
|
416
|
-
const noneAdvanced = steps.every((s) => !s.canonicalAdvanced);
|
|
417
|
-
const partialAdvance = !allAdvanced && !noneAdvanced;
|
|
418
|
-
const rollbackCoherent = steps.every((s) => s.rollbackSaved);
|
|
419
|
-
let summary;
|
|
420
|
-
if (allAdvanced) {
|
|
421
|
-
const rollbacks = steps.filter((s) => s.rollbackSaved).length;
|
|
422
|
-
summary = `Swapped ${plan.length} canonical tags; ${rollbacks} :olam-rollback preserved`;
|
|
423
|
-
}
|
|
424
|
-
else if (partialAdvance) {
|
|
425
|
-
const advanced = steps.filter((s) => s.canonicalAdvanced).length;
|
|
426
|
-
const failedStep = steps[firstFailureIdx];
|
|
427
|
-
// Audit A6-001: only recommend --rollback when the rollback set is COHERENT.
|
|
428
|
-
// Otherwise the operator would either partially restore or hit Phase 2b's
|
|
429
|
-
// pre-flight refusal — misleading either way.
|
|
430
|
-
const recoveryHint = rollbackCoherent
|
|
431
|
-
? `Run \`olam upgrade --rollback\` to restore coherent prior state.`
|
|
432
|
-
: `Rollback set INCOHERENT (${steps.filter((s) => s.rollbackSaved).length} of ${plan.length} :olam-rollback tags written). Manual recovery required: inspect images and re-tag canonical from a known-good source.`;
|
|
433
|
-
summary = `PARTIAL: ${advanced} of ${plan.length} canonical tags advanced before failure on ${failedStep?.image}: ${failedStep?.canonicalError}. ${recoveryHint}`;
|
|
434
|
-
}
|
|
435
|
-
else {
|
|
436
|
-
const failedStep = steps[firstFailureIdx];
|
|
437
|
-
summary = `Failed on first canonical-advance (${failedStep?.image}): ${failedStep?.canonicalError}. Canonical tags untouched.`;
|
|
438
|
-
}
|
|
439
|
-
return {
|
|
440
|
-
ok: allAdvanced,
|
|
441
|
-
steps,
|
|
442
|
-
partialAdvance,
|
|
443
|
-
rollbackCoherent,
|
|
444
|
-
summary,
|
|
445
|
-
};
|
|
446
|
-
}
|
|
447
|
-
/**
|
|
448
|
-
* Inverse of performAtomicSwap — restore canonical from :olam-rollback
|
|
449
|
-
* (Phase 2b — B1). Three sequential `docker tag` ops:
|
|
450
|
-
*
|
|
451
|
-
* docker tag olam-auth:olam-rollback olam-auth:local
|
|
452
|
-
* docker tag olam-devbox:olam-rollback olam-devbox:latest
|
|
453
|
-
* docker tag olam-host-cp:olam-rollback olam-host-cp:latest
|
|
454
|
-
*
|
|
455
|
-
* No two-phase ceremony — the source `:olam-rollback` set is already a
|
|
456
|
-
* coherent prior-good captured by a previous successful `olam upgrade`,
|
|
457
|
-
* so we don't need to preserve current canonical (it's known-broken,
|
|
458
|
-
* which is why we're rolling back).
|
|
459
|
-
*
|
|
460
|
-
* Caller MUST pre-flight via `checkRollbackSetExists()` before invoking.
|
|
461
|
-
* Behavior on missing source is per-image fatal (returns ok=false +
|
|
462
|
-
* error naming the missing image).
|
|
463
|
-
*/
|
|
464
|
-
export function performRollbackSwap(plan) {
|
|
465
|
-
const results = [];
|
|
466
|
-
for (const p of plan) {
|
|
467
|
-
const r = dockerTag(p.rollback, p.canonical);
|
|
468
|
-
results.push({
|
|
469
|
-
image: p.canonical,
|
|
470
|
-
ok: r.ok,
|
|
471
|
-
...(r.error !== undefined && { error: r.error }),
|
|
472
|
-
});
|
|
473
|
-
}
|
|
474
|
-
const allOk = results.every((r) => r.ok);
|
|
475
|
-
const summary = allOk
|
|
476
|
-
? `Rolled back ${plan.length} canonical tags from :olam-rollback`
|
|
477
|
-
: `PARTIAL rollback: ${results.filter((r) => r.ok).length} of ${plan.length} succeeded; failed: ${results.filter((r) => !r.ok).map((r) => r.image).join(', ')}`;
|
|
478
|
-
return { ok: allOk, results, summary };
|
|
479
|
-
}
|
|
480
|
-
async function confirm(message) {
|
|
481
|
-
if (!process.stdin.isTTY)
|
|
482
|
-
return true;
|
|
483
|
-
const { createInterface } = await import('node:readline');
|
|
484
|
-
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
485
|
-
return new Promise((resolve) => {
|
|
486
|
-
rl.question(`${message} [y/N] `, (answer) => {
|
|
487
|
-
rl.close();
|
|
488
|
-
resolve(answer.toLowerCase() === 'y' || answer.toLowerCase() === 'yes');
|
|
489
|
-
});
|
|
490
|
-
});
|
|
491
|
-
}
|
|
492
|
-
async function waitForHealth(timeoutMs = 10_000) {
|
|
493
|
-
const deadline = Date.now() + timeoutMs;
|
|
494
|
-
while (Date.now() < deadline) {
|
|
495
|
-
try {
|
|
496
|
-
const res = await fetch('http://127.0.0.1:19000/health', {
|
|
497
|
-
signal: AbortSignal.timeout(2000),
|
|
498
|
-
});
|
|
499
|
-
if (res.ok)
|
|
500
|
-
return true;
|
|
501
|
-
}
|
|
502
|
-
catch {
|
|
503
|
-
// not up yet
|
|
504
|
-
}
|
|
505
|
-
await new Promise((r) => setTimeout(r, 500));
|
|
506
|
-
}
|
|
507
|
-
return false;
|
|
508
|
-
}
|
|
509
|
-
/**
|
|
510
|
-
* Poll /api/version/status until all three component `.running` SHAs match
|
|
511
|
-
* `targetSha`, or until `timeoutMs` elapses. Returns the final snapshot.
|
|
512
|
-
*
|
|
513
|
-
* Phase 2a — A8: this is the success criterion for the entire upgrade.
|
|
514
|
-
* After A6's atomic swap + A7's recreate, the new images should report
|
|
515
|
-
* the new SHA via OLAM_BUILD_SHA baked at build time. Round-trip through
|
|
516
|
-
* Phase 1's detection path closes the loop.
|
|
517
|
-
*
|
|
518
|
-
* Returns:
|
|
519
|
-
* - { matched: true, snapshot } when all three SHAs equal targetSha within timeout.
|
|
520
|
-
* - { matched: false, snapshot } when timeout expires; caller decides
|
|
521
|
-
* whether to warn (recreate succeeded but propagation slow) or error.
|
|
522
|
-
* - { matched: false, snapshot: null } when /api/version/status is
|
|
523
|
-
* unreachable for the entire timeout (host-cp didn't come back up).
|
|
524
|
-
*/
|
|
525
|
-
export async function waitForVersionMatch(targetSha, timeoutMs = 90_000, pollIntervalMs = 1_000) {
|
|
526
|
-
const deadline = Date.now() + timeoutMs;
|
|
527
|
-
let lastSnapshot = null;
|
|
528
|
-
while (Date.now() < deadline) {
|
|
529
|
-
try {
|
|
530
|
-
const res = await fetch('http://127.0.0.1:19000/api/version/status', {
|
|
531
|
-
signal: AbortSignal.timeout(2_000),
|
|
532
|
-
});
|
|
533
|
-
if (res.ok) {
|
|
534
|
-
const snapshot = (await res.json());
|
|
535
|
-
lastSnapshot = snapshot;
|
|
536
|
-
if (snapshot.hostCp?.running === targetSha &&
|
|
537
|
-
snapshot.authService?.running === targetSha &&
|
|
538
|
-
snapshot.devbox?.running === targetSha) {
|
|
539
|
-
return { matched: true, snapshot };
|
|
540
|
-
}
|
|
541
|
-
}
|
|
542
|
-
}
|
|
543
|
-
catch {
|
|
544
|
-
// host-cp not yet ready or transient network blip
|
|
545
|
-
}
|
|
546
|
-
await new Promise((r) => setTimeout(r, pollIntervalMs));
|
|
547
|
-
}
|
|
548
|
-
return { matched: false, snapshot: lastSnapshot };
|
|
549
|
-
}
|
|
550
|
-
/**
|
|
551
|
-
* Format a version-snapshot mismatch into a readable per-component diff
|
|
552
|
-
* (Phase 2a — A8). Used in the timeout-warn path so the operator sees
|
|
553
|
-
* which component is lagging.
|
|
554
|
-
*/
|
|
555
|
-
export function formatVersionMismatch(targetSha, snapshot) {
|
|
556
|
-
if (!snapshot)
|
|
557
|
-
return 'No /api/version/status response received within timeout.';
|
|
558
|
-
const lines = [];
|
|
559
|
-
for (const [name, comp] of [
|
|
560
|
-
['host-cp', snapshot.hostCp],
|
|
561
|
-
['auth-service', snapshot.authService],
|
|
562
|
-
['devbox', snapshot.devbox],
|
|
563
|
-
]) {
|
|
564
|
-
const match = comp?.running === targetSha;
|
|
565
|
-
lines.push(` ${match ? '✓' : '✗'} ${name}: running=${abbreviateSha(comp?.running ?? 'unknown')} target=${abbreviateSha(targetSha)}`);
|
|
566
|
-
}
|
|
567
|
-
return lines.join('\n');
|
|
568
|
-
}
|
|
569
|
-
/**
|
|
570
|
-
* Block until auth-service /health responds or timeout expires (Phase 2a — A7).
|
|
571
|
-
*
|
|
572
|
-
* Mirrors auth-upgrade.ts's waitForAuthHealth — kept inline to avoid a
|
|
573
|
-
* circular dep. When auth-upgrade.ts is refactored later (Phase G+),
|
|
574
|
-
* extract a shared helper.
|
|
575
|
-
*/
|
|
576
|
-
/**
|
|
577
|
-
* Default 60s. The auth-service container's first /health response after
|
|
578
|
-
* a recreate can lag 10–30s on a cold sqlite migration + Express handler
|
|
579
|
-
* boot, observed during PR #311's upgrade-trigger dogfood. 15s (the
|
|
580
|
-
* previous default) timed out the upgrader pipeline at the recreate-
|
|
581
|
-
* verification step on cold-cache machines. 60s gives the cold-boot path
|
|
582
|
-
* comfortable headroom; the warn message still tells the operator what
|
|
583
|
-
* to inspect if it actually exceeds 60s.
|
|
584
|
-
*/
|
|
585
|
-
const AUTH_HEALTH_TIMEOUT_MS = 60_000;
|
|
586
|
-
async function waitForAuthHealthLocal(timeoutMs = AUTH_HEALTH_TIMEOUT_MS) {
|
|
587
|
-
const deadline = Date.now() + timeoutMs;
|
|
588
|
-
while (Date.now() < deadline) {
|
|
589
|
-
try {
|
|
590
|
-
const res = await fetch(AUTH_HEALTH_URL, { signal: AbortSignal.timeout(2000) });
|
|
591
|
-
if (res.ok)
|
|
592
|
-
return true;
|
|
593
|
-
}
|
|
594
|
-
catch {
|
|
595
|
-
// not up yet
|
|
596
|
-
}
|
|
597
|
-
await new Promise((r) => setTimeout(r, 500));
|
|
598
|
-
}
|
|
599
|
-
return false;
|
|
600
|
-
}
|
|
601
|
-
/**
|
|
602
|
-
* Recreate the auth-service container against the freshly-tagged
|
|
603
|
-
* `olam-auth:local` image (Phase 2a — A7).
|
|
604
|
-
*
|
|
605
|
-
* Mirrors auth-upgrade.ts:237-275: docker stop → docker rm →
|
|
606
|
-
* AuthContainerController.start(). Auth-service is NOT in compose.yaml
|
|
607
|
-
* (it runs via the controller's docker run with secret injection), so
|
|
608
|
-
* we cannot reuse `docker compose --force-recreate auth-service` —
|
|
609
|
-
* compose would fail with "no such service: auth-service" (verified
|
|
610
|
-
* during pass-2 review F2 audit).
|
|
611
|
-
*
|
|
612
|
-
* Errors:
|
|
613
|
-
* - docker stop / docker rm errors are swallowed (container may not
|
|
614
|
-
* be running or may not exist; both are recoverable states).
|
|
615
|
-
* - AuthContainerController.start() throws on real failures (image
|
|
616
|
-
* missing, port conflict, secret missing); caller catches and
|
|
617
|
-
* reports.
|
|
618
|
-
*
|
|
619
|
-
* Returns true on successful recreate + /health response within 15s.
|
|
620
|
-
*/
|
|
621
|
-
async function recreateAuthService() {
|
|
622
|
-
const start = Date.now();
|
|
623
|
-
try {
|
|
624
|
-
// Step 1: stop + remove. Errors swallowed — container may be absent/stopped.
|
|
625
|
-
spawnSync('docker', ['stop', 'olam-auth'], {
|
|
626
|
-
encoding: 'utf-8',
|
|
627
|
-
stdio: ['ignore', 'ignore', 'ignore'],
|
|
628
|
-
});
|
|
629
|
-
spawnSync('docker', ['rm', 'olam-auth'], {
|
|
630
|
-
encoding: 'utf-8',
|
|
631
|
-
stdio: ['ignore', 'ignore', 'ignore'],
|
|
632
|
-
});
|
|
633
|
-
// Step 2: start the new container via the controller (handles secret
|
|
634
|
-
// injection; reads OLAM_AUTH_SECRET from env or ~/.olam/auth-secret).
|
|
635
|
-
const controller = new AuthContainerController();
|
|
636
|
-
controller.start();
|
|
637
|
-
// Step 3: wait for /health.
|
|
638
|
-
const healthy = await waitForAuthHealthLocal();
|
|
639
|
-
const durationMs = Date.now() - start;
|
|
640
|
-
if (!healthy) {
|
|
641
|
-
return {
|
|
642
|
-
ok: false,
|
|
643
|
-
durationMs,
|
|
644
|
-
error: `auth-service /health did not respond within ${AUTH_HEALTH_TIMEOUT_MS / 1000}s after recreate`,
|
|
645
|
-
};
|
|
646
|
-
}
|
|
647
|
-
return { ok: true, durationMs };
|
|
648
|
-
}
|
|
649
|
-
catch (err) {
|
|
650
|
-
return {
|
|
651
|
-
ok: false,
|
|
652
|
-
durationMs: Date.now() - start,
|
|
653
|
-
error: err instanceof Error ? err.message : String(err),
|
|
654
|
-
};
|
|
655
|
-
}
|
|
656
|
-
}
|
|
657
|
-
function readBundleHash(cwd) {
|
|
658
|
-
const indexPath = path.join(cwd, 'packages/plan-chat-spa/dist/client/index.html');
|
|
659
|
-
if (!fs.existsSync(indexPath))
|
|
660
|
-
return null;
|
|
661
|
-
return extractBundleHash(fs.readFileSync(indexPath, 'utf-8'));
|
|
662
|
-
}
|
|
663
|
-
export async function runUpgradePullByDigest(deps = {}) {
|
|
664
|
-
const docker = deps.docker ?? realDocker;
|
|
665
|
-
const digests = deps.digests ?? loadImageDigests();
|
|
666
|
-
const registry = deps.registry ?? digests.$registry ?? 'ghcr.io/pleri';
|
|
667
|
-
printHeader('olam upgrade (pull-by-digest)');
|
|
668
|
-
// Confirmation prompt — skipped when deps.yes is true (--yes flag) or
|
|
669
|
-
// when stdin is not a TTY (CI / non-interactive).
|
|
670
|
-
if (!deps.yes) {
|
|
671
|
-
const proceed = await confirm('Pull latest images and recreate stack?');
|
|
672
|
-
if (!proceed) {
|
|
673
|
-
printInfo('Aborted', 'no changes made');
|
|
674
|
-
return { exitCode: 0, summary: 'aborted by operator' };
|
|
675
|
-
}
|
|
676
|
-
}
|
|
677
|
-
// Step 1 — daemon smoke. Re-uses bootstrap's docker.info().
|
|
678
|
-
const infoSpinner = ora('Checking docker daemon').start();
|
|
679
|
-
const info = await docker.info();
|
|
680
|
-
if (info.exitCode !== 0) {
|
|
681
|
-
infoSpinner.fail('docker daemon not reachable');
|
|
682
|
-
process.stderr.write(`${pc.red('error')} docker info exited with ${info.exitCode}.\n` +
|
|
683
|
-
' Ensure Docker Desktop / Colima / Rancher is running, then retry.\n');
|
|
684
|
-
return { exitCode: EXIT_GENERIC_ERROR, summary: 'docker daemon not reachable' };
|
|
685
|
-
}
|
|
686
|
-
infoSpinner.succeed('docker daemon reachable');
|
|
687
|
-
// Step 2 — parallel pull images by digest. mcp-auth is conditional on
|
|
688
|
-
// the digest being present in the manifest (older tarballs don't have
|
|
689
|
-
// it; pre-mcp-auth releases shipped 3 images, post-mcp-auth ship 4).
|
|
690
|
-
const hasMcpAuthDigest = typeof digests['mcp-auth'] === 'string' && digests['mcp-auth'].length > 0;
|
|
691
|
-
const hasKgServiceDigest = typeof digests['kg-service'] === 'string' && digests['kg-service'].length > 0;
|
|
692
|
-
// devbox-slim Phase A: pull :base alongside :browser-aliased :latest so
|
|
693
|
-
// the new DEFAULT_DEVBOX_IMAGE = olam-devbox:base resolves locally on a
|
|
694
|
-
// fresh `olam upgrade -y` host. Older releases without devbox-base in
|
|
695
|
-
// image-digests.json still upgrade cleanly — devbox-base is gated on
|
|
696
|
-
// its presence the same way mcp-auth is.
|
|
697
|
-
const hasDevboxBaseDigest = typeof digests['devbox-base'] === 'string' && digests['devbox-base'].length > 0;
|
|
698
|
-
const baseRefs = [
|
|
699
|
-
{ name: 'host-cp', ref: `${registry}/olam-host-cp@${digests['host-cp']}` },
|
|
700
|
-
{ name: 'auth', ref: `${registry}/olam-auth@${digests.auth}` },
|
|
701
|
-
{ name: 'devbox', ref: `${registry}/olam-devbox@${digests.devbox}` },
|
|
702
|
-
];
|
|
703
|
-
if (hasDevboxBaseDigest) {
|
|
704
|
-
baseRefs.push({
|
|
705
|
-
name: 'devbox-base',
|
|
706
|
-
ref: `${registry}/olam-devbox@${digests['devbox-base']}`,
|
|
707
|
-
});
|
|
708
|
-
}
|
|
709
|
-
if (hasMcpAuthDigest) {
|
|
710
|
-
baseRefs.push({
|
|
711
|
-
name: 'mcp-auth',
|
|
712
|
-
ref: `${registry}/olam-mcp-auth@${digests['mcp-auth']}`,
|
|
713
|
-
});
|
|
714
|
-
}
|
|
715
|
-
// kg-service joined the publish DAG in v0.1.139 (release.yml fix #565).
|
|
716
|
-
// Gated on digest presence so older tarballs (≤ v0.1.138) without the
|
|
717
|
-
// entry still upgrade cleanly — same pattern as mcp-auth + devbox-base.
|
|
718
|
-
if (hasKgServiceDigest) {
|
|
719
|
-
baseRefs.push({
|
|
720
|
-
name: 'kg-service',
|
|
721
|
-
ref: `${registry}/olam-kg-service@${digests['kg-service']}`,
|
|
722
|
-
});
|
|
723
|
-
}
|
|
724
|
-
const imageRefs = baseRefs;
|
|
725
|
-
const pullSpinner = ora(`Pulling images (${imageRefs.length} parallel)`).start();
|
|
726
|
-
const pullStart = Date.now();
|
|
727
|
-
const pullResults = await Promise.all(imageRefs.map(async ({ name, ref }) => ({
|
|
728
|
-
name,
|
|
729
|
-
ref,
|
|
730
|
-
result: await pullImageWithRetry(ref, docker),
|
|
731
|
-
})));
|
|
732
|
-
const pullElapsed = ((Date.now() - pullStart) / 1000).toFixed(1);
|
|
733
|
-
const failed = pullResults.filter((r) => r.result.exitCode !== 0);
|
|
734
|
-
if (failed.length > 0) {
|
|
735
|
-
pullSpinner.fail(`Pull failed for ${failed.map((r) => r.name).join(', ')}`);
|
|
736
|
-
for (const f of failed) {
|
|
737
|
-
process.stderr.write(` ${pc.red(f.name)} (${f.ref}):\n` +
|
|
738
|
-
` exit=${f.result.exitCode}\n` +
|
|
739
|
-
` stderr: ${f.result.stderr.split('\n')[0] ?? '(empty)'}\n`);
|
|
740
|
-
}
|
|
741
|
-
process.stderr.write('\n Remedy: re-run after resolving network / GHCR availability.\n' +
|
|
742
|
-
' For monorepo dev, `OLAM_DEV=1 olam upgrade --from-source` builds locally.\n');
|
|
743
|
-
return {
|
|
744
|
-
exitCode: EXIT_BOOTSTRAP_PULL_FAILED,
|
|
745
|
-
summary: `pull failed: ${failed.map((r) => r.name).join(', ')}`,
|
|
746
|
-
};
|
|
747
|
-
}
|
|
748
|
-
pullSpinner.succeed(`Pulled ${imageRefs.length} images in ${pullElapsed}s`);
|
|
749
|
-
// Step 3 — protocol-version handshake on every image.
|
|
750
|
-
const handshakeSpinner = ora('Verifying olam.protocol.versions handshake').start();
|
|
751
|
-
for (const { name, ref } of imageRefs) {
|
|
752
|
-
const inspect = await docker.inspectLabel(ref, 'olam.protocol.versions');
|
|
753
|
-
if (inspect.exitCode !== 0) {
|
|
754
|
-
handshakeSpinner.fail(`Could not inspect ${name}`);
|
|
755
|
-
process.stderr.write(`${pc.red('error')} docker inspect ${ref} failed: ${inspect.stderr}\n`);
|
|
756
|
-
return { exitCode: EXIT_GENERIC_ERROR, summary: `inspect failed: ${name}` };
|
|
757
|
-
}
|
|
758
|
-
const labelValue = (inspect.stdout || '').trim();
|
|
759
|
-
const versions = parseProtocolVersionsLabel(labelValue === '<no value>' ? '' : labelValue);
|
|
760
|
-
const decision = checkProtocolOverlap(versions);
|
|
761
|
-
if (!decision.compatible) {
|
|
762
|
-
handshakeSpinner.fail(`Protocol mismatch on ${name}`);
|
|
763
|
-
process.stderr.write(`${pc.red('error')} ${decision.remedy}\n`);
|
|
764
|
-
return {
|
|
765
|
-
exitCode: EXIT_PROTOCOL_MISMATCH,
|
|
766
|
-
summary: `protocol mismatch: ${name}`,
|
|
767
|
-
};
|
|
768
|
-
}
|
|
769
|
-
}
|
|
770
|
-
handshakeSpinner.succeed(`Protocol handshake passed (all ${imageRefs.length} images)`);
|
|
771
|
-
// Step 4 — re-tag pulled-by-digest images to the canonical local tags
|
|
772
|
-
// that compose.yaml + AuthContainerController expect. Pulling by
|
|
773
|
-
// digest only writes a content-addressed reference; compose / the
|
|
774
|
-
// controller key on `olam-host-cp:latest` and `olam-auth:local`
|
|
775
|
-
// respectively, so we tag explicitly.
|
|
776
|
-
// Compose.yaml uses `image: ghcr.io/pleri/olam-host-cp:latest` (registry-
|
|
777
|
-
// prefixed), NOT bare `olam-host-cp:latest`. Docker treats the two refs as
|
|
778
|
-
// distinct local tags. Pre-fix, the upgrade retagged ONLY the bare-name
|
|
779
|
-
// tag, so the registry-prefixed tag (used by compose) kept pointing at
|
|
780
|
-
// whatever the previous pull put there — leaving compose's recreate to
|
|
781
|
-
// pick the OLD image despite the new one being in cache. End state:
|
|
782
|
-
// `olam upgrade` reports "Re-tagged 3 images to canonical refs" + "host-cp
|
|
783
|
-
// recreated" + "Upgrade complete (running …NEW…)" but the actually-
|
|
784
|
-
// running container stayed on the old digest. Repro: compare
|
|
785
|
-
// `docker inspect <ghcr-prefixed>:latest` vs `docker inspect <bare>:latest`
|
|
786
|
-
// mid-upgrade — they diverge.
|
|
787
|
-
// Fix: retag BOTH names. The bare-name tag is kept for legacy callers
|
|
788
|
-
// (e.g. `docker run olam-host-cp:latest` in scripts); the registry-
|
|
789
|
-
// prefixed tag is what compose + AuthContainerController actually use.
|
|
790
|
-
// Look up by NAME rather than positional index — devbox-slim Phase A
|
|
791
|
-
// inserted devbox-base into the array, so positional indices for
|
|
792
|
-
// mcp-auth shifted. Name-based lookup is order-independent.
|
|
793
|
-
const refByName = (n) => {
|
|
794
|
-
const found = imageRefs.find((r) => r.name === n);
|
|
795
|
-
if (!found)
|
|
796
|
-
throw new Error(`upgrade: missing imageRefs entry for "${n}"`);
|
|
797
|
-
return found.ref;
|
|
798
|
-
};
|
|
799
|
-
const tagPlanBase = [
|
|
800
|
-
{ from: refByName('host-cp'), to: 'olam-host-cp:latest', name: 'host-cp (bare)' },
|
|
801
|
-
{ from: refByName('host-cp'), to: `${registry}/olam-host-cp:latest`, name: 'host-cp (registry)' },
|
|
802
|
-
{ from: refByName('auth'), to: 'olam-auth:local', name: 'auth (bare)' },
|
|
803
|
-
{ from: refByName('auth'), to: `${registry}/olam-auth:latest`, name: 'auth (registry)' },
|
|
804
|
-
{ from: refByName('devbox'), to: 'olam-devbox:latest', name: 'devbox (bare)' },
|
|
805
|
-
{ from: refByName('devbox'), to: `${registry}/olam-devbox:latest`, name: 'devbox (registry)' },
|
|
806
|
-
];
|
|
807
|
-
// devbox-slim Phase A: tag the :base variant so DEFAULT_DEVBOX_IMAGE
|
|
808
|
-
// = olam-devbox:base resolves locally after upgrade. Tag both bare
|
|
809
|
-
// and registry-prefixed for parity with :latest above.
|
|
810
|
-
if (hasDevboxBaseDigest) {
|
|
811
|
-
tagPlanBase.push({ from: refByName('devbox-base'), to: 'olam-devbox:base', name: 'devbox-base (bare)' }, { from: refByName('devbox-base'), to: `${registry}/olam-devbox:base`, name: 'devbox-base (registry)' });
|
|
812
|
-
}
|
|
813
|
-
if (hasMcpAuthDigest) {
|
|
814
|
-
tagPlanBase.push({ from: refByName('mcp-auth'), to: 'olam-mcp-auth:local', name: 'mcp-auth (bare)' }, { from: refByName('mcp-auth'), to: `${registry}/olam-mcp-auth:latest`, name: 'mcp-auth (registry)' });
|
|
815
|
-
}
|
|
816
|
-
// kg-service: canonical bare tag `:local` matches
|
|
817
|
-
// KG_SERVICE_LOCAL_TAG in kg-service-container.ts. Operators running
|
|
818
|
-
// `olam services up` after upgrade pick this up.
|
|
819
|
-
if (hasKgServiceDigest) {
|
|
820
|
-
tagPlanBase.push({ from: refByName('kg-service'), to: 'olam-kg-service:local', name: 'kg-service (bare)' }, { from: refByName('kg-service'), to: `${registry}/olam-kg-service:latest`, name: 'kg-service (registry)' });
|
|
821
|
-
}
|
|
822
|
-
const tagPlan = tagPlanBase;
|
|
823
|
-
const tagSpinner = ora('Tagging digests → canonical local refs').start();
|
|
824
|
-
const tagger = deps.tagImpl ?? dockerTag;
|
|
825
|
-
for (const t of tagPlan) {
|
|
826
|
-
const r = tagger(t.from, t.to);
|
|
827
|
-
if (!r.ok) {
|
|
828
|
-
tagSpinner.fail(`docker tag failed for ${t.name}`);
|
|
829
|
-
process.stderr.write(`${pc.red('error')} ${r.error ?? 'docker tag failed'}\n`);
|
|
830
|
-
return { exitCode: EXIT_GENERIC_ERROR, summary: `tag failed: ${t.name}` };
|
|
831
|
-
}
|
|
832
|
-
}
|
|
833
|
-
tagSpinner.succeed(`Re-tagged ${tagPlan.length} image refs to canonical names`);
|
|
834
|
-
// Step 5 — recreate the docker-socket-proxy + host-cp via docker
|
|
835
|
-
// compose. Auth-service is NOT in compose.yaml (runs via
|
|
836
|
-
// AuthContainerController.start), so we recreate it separately in
|
|
837
|
-
// step 6.
|
|
838
|
-
const composeFile = deps.composeFile ?? findComposeFile();
|
|
839
|
-
const authSecret = deps.authSecret ?? readAuthSecret();
|
|
840
|
-
const composeRunner = deps.runComposeImpl ?? runCompose;
|
|
841
|
-
// Step 5a — recreate docker-socket-proxy FIRST. Prior to this
|
|
842
|
-
// change we skipped the sidecar entirely (`--no-deps host-cp` only),
|
|
843
|
-
// on the assumption "proxy doesn't change between releases." That
|
|
844
|
-
// broke when PR #460 shipped a new compose.yaml that enabled
|
|
845
|
-
// IMAGES=1 on the proxy: `olam upgrade -y` pulled new images and
|
|
846
|
-
// recreated host-cp, but the running proxy stayed on the old
|
|
847
|
-
// (IMAGES=0) config — so the new /api/version/status comparator's
|
|
848
|
-
// GET /images/<ref>/json calls stayed denied until the operator
|
|
849
|
-
// manually `docker compose up -d --force-recreate
|
|
850
|
-
// docker-socket-proxy`. Any future compose.yaml tweak to the
|
|
851
|
-
// sidecar would silently no-op the same way.
|
|
852
|
-
//
|
|
853
|
-
// The original PR #311 concern (recreating the proxy collides with
|
|
854
|
-
// host-cp's upgrade-trigger spawning the upgrader inside the same
|
|
855
|
-
// compose project) doesn't apply here: we recreate the proxy
|
|
856
|
-
// BEFORE host-cp, and `--no-deps` keeps compose from cascading.
|
|
857
|
-
// Brief proxy disconnect (~5s) is invisible to the operator CLI
|
|
858
|
-
// and tolerated by host-cp's docker client (retries on next poll).
|
|
859
|
-
const proxySpinner = ora('docker compose recreate docker-socket-proxy').start();
|
|
860
|
-
const proxyResult = composeRunner(['up', '-d', '--force-recreate', '--no-deps', 'docker-socket-proxy'], composeFile, buildComposeEnv(authSecret, captureGhToken()));
|
|
861
|
-
if (!proxyResult.ok) {
|
|
862
|
-
proxySpinner.fail('docker-socket-proxy recreate failed');
|
|
863
|
-
process.stderr.write(`${pc.red('error')} docker compose up --force-recreate docker-socket-proxy failed:\n` +
|
|
864
|
-
` ${proxyResult.stderr.split('\n').slice(0, 3).join('\n ')}\n`);
|
|
865
|
-
return { exitCode: EXIT_GENERIC_ERROR, summary: 'socket-proxy recreate failed' };
|
|
866
|
-
}
|
|
867
|
-
proxySpinner.succeed('docker-socket-proxy recreated');
|
|
868
|
-
// Step 5b-pre — clean up pre-rename name-collision orphan. PR #596/#597
|
|
869
|
-
// renamed the compose service host-cp → olam-host-cp; the container_name
|
|
870
|
-
// was already olam-host-cp, so a pre-existing container from before the
|
|
871
|
-
// rename carries labels `com.docker.compose.service=host-cp` and
|
|
872
|
-
// `com.docker.compose.project=host-cp` AND occupies the `olam-host-cp`
|
|
873
|
-
// container name the new service expects. `--force-recreate` cannot
|
|
874
|
-
// reclaim a container whose service label belongs to a different
|
|
875
|
-
// (now non-existent) compose service, so the recreate aborts with
|
|
876
|
-
// "Conflict. The container name "/olam-host-cp" is already in use".
|
|
877
|
-
// Force-remove the orphan first; no-op when no orphan exists or the
|
|
878
|
-
// container already belongs to the new service.
|
|
879
|
-
const inspectContainer = deps.inspectContainerImpl ?? defaultInspectContainerLabels;
|
|
880
|
-
const removeContainer = deps.removeContainerImpl ?? defaultRemoveContainer;
|
|
881
|
-
const orphanCheck = inspectContainer('olam-host-cp');
|
|
882
|
-
if (!orphanCheck.ok) {
|
|
883
|
-
process.stderr.write(`${pc.red('error')} docker inspect olam-host-cp failed:\n` +
|
|
884
|
-
` ${orphanCheck.stderr.split('\n').slice(0, 3).join('\n ')}\n`);
|
|
885
|
-
return { exitCode: EXIT_GENERIC_ERROR, summary: 'orphan inspect failed' };
|
|
886
|
-
}
|
|
887
|
-
if (orphanCheck.exists && orphanCheck.service !== 'olam-host-cp') {
|
|
888
|
-
process.stderr.write(`${pc.yellow('info')} Removing pre-rename orphan container olam-host-cp ` +
|
|
889
|
-
`(old project=${orphanCheck.project || '<none>'}, old service=${orphanCheck.service || '<none>'})\n`);
|
|
890
|
-
const rm = removeContainer('olam-host-cp');
|
|
891
|
-
if (!rm.ok) {
|
|
892
|
-
process.stderr.write(`${pc.red('error')} docker rm -f olam-host-cp failed:\n` +
|
|
893
|
-
` ${rm.stderr.split('\n').slice(0, 3).join('\n ')}\n`);
|
|
894
|
-
return { exitCode: EXIT_GENERIC_ERROR, summary: 'orphan cleanup failed' };
|
|
895
|
-
}
|
|
896
|
-
}
|
|
897
|
-
// Step 5b — recreate host-cp. `--no-deps` keeps compose from
|
|
898
|
-
// touching the proxy AGAIN (we just did it in step 5a) and from
|
|
899
|
-
// touching auth-service (which isn't a compose service).
|
|
900
|
-
const composeSpinner = ora('docker compose recreate olam-host-cp').start();
|
|
901
|
-
const composeResult = composeRunner(['up', '-d', '--force-recreate', '--no-deps', 'olam-host-cp'], composeFile, buildComposeEnv(authSecret, captureGhToken()));
|
|
902
|
-
if (!composeResult.ok) {
|
|
903
|
-
composeSpinner.fail('compose recreate failed');
|
|
904
|
-
process.stderr.write(`${pc.red('error')} docker compose up --force-recreate olam-host-cp failed:\n` +
|
|
905
|
-
` ${composeResult.stderr.split('\n').slice(0, 3).join('\n ')}\n`);
|
|
906
|
-
return { exitCode: EXIT_GENERIC_ERROR, summary: 'compose recreate failed' };
|
|
907
|
-
}
|
|
908
|
-
composeSpinner.succeed('olam-host-cp recreated');
|
|
909
|
-
// Step 6 — recreate auth-service.
|
|
910
|
-
const authSpinner = ora('recreate auth-service').start();
|
|
911
|
-
const authRecreate = deps.recreateAuth ?? defaultRecreateAuthForUpgrade;
|
|
912
|
-
const authResult = await authRecreate();
|
|
913
|
-
if (!authResult.ok) {
|
|
914
|
-
authSpinner.fail('auth-service recreate failed');
|
|
915
|
-
process.stderr.write(`${pc.red('error')} ${authResult.error ?? 'unknown failure'}\n`);
|
|
916
|
-
return { exitCode: EXIT_GENERIC_ERROR, summary: 'auth recreate failed' };
|
|
917
|
-
}
|
|
918
|
-
authSpinner.succeed('auth-service running on new image');
|
|
919
|
-
// Step 7 — recreate mcp-auth-service when its digest is in the manifest.
|
|
920
|
-
// Older tarballs (pre-services-bringup) skip this step entirely.
|
|
921
|
-
if (hasMcpAuthDigest) {
|
|
922
|
-
const mcpSpinner = ora('recreate mcp-auth-service').start();
|
|
923
|
-
const mcpRecreate = deps.recreateMcpAuth ?? defaultRecreateMcpAuthForUpgrade;
|
|
924
|
-
const mcpResult = await mcpRecreate();
|
|
925
|
-
if (!mcpResult.ok) {
|
|
926
|
-
mcpSpinner.fail('mcp-auth-service recreate failed');
|
|
927
|
-
process.stderr.write(`${pc.red('error')} ${mcpResult.error ?? 'unknown failure'}\n`);
|
|
928
|
-
return { exitCode: EXIT_GENERIC_ERROR, summary: 'mcp-auth recreate failed' };
|
|
929
|
-
}
|
|
930
|
-
mcpSpinner.succeed('mcp-auth-service running on new image');
|
|
931
|
-
}
|
|
932
|
-
printSuccess('Upgrade complete (pull-by-digest)');
|
|
933
|
-
printInfo('host-cp', `running (${digests['host-cp'].slice(0, 19)}…)`);
|
|
934
|
-
printInfo('auth', `running (${digests.auth.slice(0, 19)}…)`);
|
|
935
|
-
printInfo('devbox', `pulled (${digests.devbox.slice(0, 19)}…)`);
|
|
936
|
-
if (hasMcpAuthDigest) {
|
|
937
|
-
printInfo('mcp-auth', `running (${digests['mcp-auth'].slice(0, 19)}…)`);
|
|
938
|
-
}
|
|
939
|
-
return { exitCode: 0, summary: 'stack upgraded' };
|
|
940
|
-
}
|
|
941
|
-
/**
|
|
942
|
-
* Default mcp-auth-service recreate driver for the pull-by-digest upgrade
|
|
943
|
-
* path. Stops the existing container, removes it, then starts a fresh one
|
|
944
|
-
* via the new image. Mirrors `defaultRecreateAuthForUpgrade` shape.
|
|
945
|
-
*
|
|
946
|
-
* Implementation lives in @olam/core's container controllers — but for the
|
|
947
|
-
* upgrade path we just docker-rm + docker-run because the controller
|
|
948
|
-
* encapsulates start-with-image semantics that match what we want here.
|
|
949
|
-
*/
|
|
950
|
-
async function defaultRecreateMcpAuthForUpgrade() {
|
|
951
|
-
// Reuse the existing recreateAuthService logic as a template — the
|
|
952
|
-
// mcp-auth service follows the same lifecycle (atomic-rename store,
|
|
953
|
-
// health-checked start). When the McpAuthContainerController lands as
|
|
954
|
-
// a sibling, this function should switch to controller.start().
|
|
955
|
-
try {
|
|
956
|
-
const { spawnSync: ss } = await import('node:child_process');
|
|
957
|
-
ss('docker', ['stop', 'olam-mcp-auth'], { stdio: 'ignore' });
|
|
958
|
-
ss('docker', ['rm', '-f', 'olam-mcp-auth'], { stdio: 'ignore' });
|
|
959
|
-
const startResult = ss('docker', [
|
|
960
|
-
'run', '-d', '--name', 'olam-mcp-auth',
|
|
961
|
-
'-p', '9998:9998',
|
|
962
|
-
'-v', 'olam-mcp-auth-data:/mcp-auth-data',
|
|
963
|
-
'--restart', 'unless-stopped',
|
|
964
|
-
'olam-mcp-auth:local',
|
|
965
|
-
], { stdio: 'pipe' });
|
|
966
|
-
if (startResult.status !== 0) {
|
|
967
|
-
return { ok: false, error: `docker run failed: ${startResult.stderr?.toString() ?? 'unknown'}` };
|
|
968
|
-
}
|
|
969
|
-
return { ok: true };
|
|
970
|
-
}
|
|
971
|
-
catch (err) {
|
|
972
|
-
return { ok: false, error: err instanceof Error ? err.message : String(err) };
|
|
973
|
-
}
|
|
974
|
-
}
|
|
975
|
-
/**
|
|
976
|
-
* Default auth-recreate driver for the pull-by-digest upgrade path.
|
|
977
|
-
* Mirrors recreateAuthService() defined later in this file but trimmed
|
|
978
|
-
* to a UpgradePullDeps.recreateAuth-shaped result.
|
|
979
|
-
*/
|
|
980
|
-
async function defaultRecreateAuthForUpgrade() {
|
|
981
|
-
try {
|
|
982
|
-
spawnSync('docker', ['stop', 'olam-auth'], {
|
|
983
|
-
encoding: 'utf-8',
|
|
984
|
-
stdio: ['ignore', 'ignore', 'ignore'],
|
|
985
|
-
});
|
|
986
|
-
spawnSync('docker', ['rm', 'olam-auth'], {
|
|
987
|
-
encoding: 'utf-8',
|
|
988
|
-
stdio: ['ignore', 'ignore', 'ignore'],
|
|
989
|
-
});
|
|
990
|
-
const controller = new AuthContainerController();
|
|
991
|
-
controller.start();
|
|
992
|
-
const healthy = await waitForAuthHealthLocal();
|
|
993
|
-
if (!healthy) {
|
|
994
|
-
return {
|
|
995
|
-
ok: false,
|
|
996
|
-
error: `auth-service /health did not respond within ${AUTH_HEALTH_TIMEOUT_MS / 1000}s`,
|
|
997
|
-
};
|
|
998
|
-
}
|
|
999
|
-
return { ok: true };
|
|
1000
|
-
}
|
|
1001
|
-
catch (err) {
|
|
1002
|
-
return {
|
|
1003
|
-
ok: false,
|
|
1004
|
-
error: err instanceof Error ? err.message : String(err),
|
|
1005
|
-
};
|
|
1006
|
-
}
|
|
1007
|
-
}
|
|
1008
|
-
/**
|
|
1009
|
-
* Default `docker inspect` driver for the Step 5b-pre orphan guard.
|
|
1010
|
-
* Reads the container's `com.docker.compose.project` and
|
|
1011
|
-
* `com.docker.compose.service` labels in a single inspect call. Distinguishes
|
|
1012
|
-
* "container does not exist" (treated as ok+exists=false) from real
|
|
1013
|
-
* inspect failures (daemon down, permission errors — propagated as ok=false).
|
|
1014
|
-
*/
|
|
1015
|
-
function defaultInspectContainerLabels(containerName) {
|
|
1016
|
-
const result = spawnSync('docker', [
|
|
1017
|
-
'inspect',
|
|
1018
|
-
'--format',
|
|
1019
|
-
'{{index .Config.Labels "com.docker.compose.project"}}|{{index .Config.Labels "com.docker.compose.service"}}',
|
|
1020
|
-
containerName,
|
|
1021
|
-
], { encoding: 'utf-8', stdio: ['ignore', 'pipe', 'pipe'] });
|
|
1022
|
-
const stderr = result.stderr ?? '';
|
|
1023
|
-
if (result.status === 0) {
|
|
1024
|
-
const [project = '', service = ''] = (result.stdout ?? '').trim().split('|');
|
|
1025
|
-
return { ok: true, exists: true, project, service, stderr: '' };
|
|
1026
|
-
}
|
|
1027
|
-
// Docker returns exit 1 with "No such object: <name>" when the container
|
|
1028
|
-
// is absent — that's the happy "no collision" case, not an error.
|
|
1029
|
-
if (/No such (object|container)/i.test(stderr)) {
|
|
1030
|
-
return { ok: true, exists: false, project: '', service: '', stderr: '' };
|
|
1031
|
-
}
|
|
1032
|
-
// spawn failed before docker could run (e.g. ENOENT — docker not in
|
|
1033
|
-
// PATH). Step 1 (daemon smoke) would already have failed in that case,
|
|
1034
|
-
// so by the time we get here we know docker is reachable. Treating
|
|
1035
|
-
// status=null as "container absent" lets unit tests exercise the
|
|
1036
|
-
// recreate flow without a docker daemon and without an inspect mock.
|
|
1037
|
-
if (result.status === null) {
|
|
1038
|
-
return { ok: true, exists: false, project: '', service: '', stderr: '' };
|
|
1039
|
-
}
|
|
1040
|
-
return { ok: false, exists: false, project: '', service: '', stderr };
|
|
1041
|
-
}
|
|
1042
|
-
/**
|
|
1043
|
-
* Default `docker rm -f` driver for the Step 5b-pre orphan guard.
|
|
1044
|
-
*/
|
|
1045
|
-
function defaultRemoveContainer(containerName) {
|
|
1046
|
-
const result = spawnSync('docker', ['rm', '-f', containerName], {
|
|
1047
|
-
encoding: 'utf-8',
|
|
1048
|
-
stdio: ['ignore', 'pipe', 'pipe'],
|
|
1049
|
-
});
|
|
1050
|
-
return { ok: result.status === 0, stderr: result.stderr ?? '' };
|
|
1051
|
-
}
|
|
1052
|
-
// ── Main handler ──────────────────────────────────────────────────
|
|
1053
|
-
async function handleUpgrade(opts) {
|
|
1054
|
-
const cwd = process.cwd();
|
|
1055
|
-
// 1. Verify repo root.
|
|
1056
|
-
const rootCheck = validateRepoRoot(cwd);
|
|
1057
|
-
if (!rootCheck.ok) {
|
|
1058
|
-
printError(rootCheck.error);
|
|
1059
|
-
process.exitCode = 1;
|
|
1060
|
-
return;
|
|
1061
|
-
}
|
|
1062
|
-
// Phase 2c — C2: --history reads the audit log; dispatched BEFORE the
|
|
1063
|
-
// upgrade header so the log table prints clean, without the "olam upgrade"
|
|
1064
|
-
// / Steps prelude.
|
|
1065
|
-
if (opts.history) {
|
|
1066
|
-
handleHistory(parseHistoryOpts({ n: opts.historyN, json: opts.historyJson }));
|
|
1067
|
-
return;
|
|
1068
|
-
}
|
|
1069
|
-
// 2. Print plan.
|
|
1070
|
-
printHeader('olam upgrade');
|
|
1071
|
-
const steps = [
|
|
1072
|
-
'git fetch origin --prune',
|
|
1073
|
-
'git pull --ff-only',
|
|
1074
|
-
'npm install',
|
|
1075
|
-
'npm run build (TS workspaces)',
|
|
1076
|
-
'vite build (SPA)',
|
|
1077
|
-
...(opts.skipImage ? [] : [
|
|
1078
|
-
'bash build-auth.sh (auth-service image)',
|
|
1079
|
-
'bash build-devbox.sh (devbox image)',
|
|
1080
|
-
'bash build-host-cp.sh (host-cp image)',
|
|
1081
|
-
'smoke (docker create + inspect)',
|
|
1082
|
-
'atomic 6-tag swap (canonical -> :olam-rollback; :olam-next -> canonical)',
|
|
1083
|
-
'docker compose --force-recreate olam-host-cp + AuthContainerController.start auth',
|
|
1084
|
-
'poll /api/version/status until SHAs match',
|
|
1085
|
-
]),
|
|
1086
|
-
];
|
|
1087
|
-
printInfo('Steps', steps.join(', '));
|
|
1088
|
-
if (opts.skipImage)
|
|
1089
|
-
printInfo('Mode', '--skip-image: no docker rebuild');
|
|
1090
|
-
if (opts.skipInstall)
|
|
1091
|
-
printInfo('Mode', '--skip-install: npm install will be skipped');
|
|
1092
|
-
if (opts.branch)
|
|
1093
|
-
printInfo('Branch', opts.branch);
|
|
1094
|
-
process.stdout.write('\n');
|
|
1095
|
-
// 3. Confirm unless --yes.
|
|
1096
|
-
if (!opts.yes) {
|
|
1097
|
-
const proceed = await confirm('Proceed?');
|
|
1098
|
-
if (!proceed) {
|
|
1099
|
-
printInfo('Aborted', 'no changes made');
|
|
1100
|
-
return;
|
|
1101
|
-
}
|
|
1102
|
-
}
|
|
1103
|
-
// Phase 2b — B1: rollback path. Branches at the top so --rollback skips
|
|
1104
|
-
// the entire git-pull/build/swap sequence and only retags + recreates.
|
|
1105
|
-
if (opts.rollback) {
|
|
1106
|
-
return await handleRollback();
|
|
1107
|
-
}
|
|
1108
|
-
// 3b. Acquire CLI lock (Phase 2a — A1). Refuses if a live upgrade is in flight;
|
|
1109
|
-
// auto-recovers stale locks (parse-error / empty / dead-pid / >5 min).
|
|
1110
|
-
const lock = acquireLock();
|
|
1111
|
-
if (!lock.acquired) {
|
|
1112
|
-
printError(formatRefusalMessage(lock, LOCK_FILE_PATH));
|
|
1113
|
-
process.exitCode = 1;
|
|
1114
|
-
return;
|
|
1115
|
-
}
|
|
1116
|
-
// SIGINT / SIGTERM handler — release the lock before terminating so the
|
|
1117
|
-
// operator's next invocation doesn't have to wait for stale-recovery
|
|
1118
|
-
// (audit A1-005). `process.once` so a second Ctrl-C terminates immediately.
|
|
1119
|
-
let signalReleased = false;
|
|
1120
|
-
const releaseOnSignal = (signal) => {
|
|
1121
|
-
if (signalReleased)
|
|
1122
|
-
return;
|
|
1123
|
-
signalReleased = true;
|
|
1124
|
-
try {
|
|
1125
|
-
releaseLock();
|
|
1126
|
-
}
|
|
1127
|
-
catch {
|
|
1128
|
-
// best-effort
|
|
1129
|
-
}
|
|
1130
|
-
// Standard shell exit code for signal-induced termination: 128 + signal-number.
|
|
1131
|
-
process.exit(signal === 'SIGINT' ? 130 : 143);
|
|
1132
|
-
};
|
|
1133
|
-
process.once('SIGINT', releaseOnSignal);
|
|
1134
|
-
process.once('SIGTERM', releaseOnSignal);
|
|
1135
|
-
// Phase 2c — C1: collect a JSONL log row. Mutated as the upgrade progresses;
|
|
1136
|
-
// appended once on the way out (success OR failure) so --history surfaces
|
|
1137
|
-
// every attempt, not just successful ones.
|
|
1138
|
-
const logRow = {
|
|
1139
|
-
started_at: Date.now(),
|
|
1140
|
-
durations_ms: {},
|
|
1141
|
-
sha_target: '',
|
|
1142
|
-
failed_step: null,
|
|
1143
|
-
status: 'failed', // default; flipped to 'success' on clean exit
|
|
1144
|
-
};
|
|
1145
|
-
try {
|
|
1146
|
-
await runUpgradeStepsWithLockHeld(opts, cwd, logRow);
|
|
1147
|
-
if (process.exitCode !== 1)
|
|
1148
|
-
logRow.status = 'success';
|
|
1149
|
-
}
|
|
1150
|
-
finally {
|
|
1151
|
-
const ended_at = Date.now();
|
|
1152
|
-
const row = {
|
|
1153
|
-
ts: new Date(ended_at).toISOString(),
|
|
1154
|
-
started_at: logRow.started_at,
|
|
1155
|
-
ended_at,
|
|
1156
|
-
sha_target: logRow.sha_target,
|
|
1157
|
-
status: logRow.status,
|
|
1158
|
-
failed_step: logRow.failed_step,
|
|
1159
|
-
durations_ms: logRow.durations_ms,
|
|
1160
|
-
};
|
|
1161
|
-
appendUpgradeLog(row);
|
|
1162
|
-
releaseLock();
|
|
1163
|
-
process.removeListener('SIGINT', releaseOnSignal);
|
|
1164
|
-
process.removeListener('SIGTERM', releaseOnSignal);
|
|
1165
|
-
}
|
|
1166
|
-
}
|
|
1167
|
-
/**
|
|
1168
|
-
* Phase 2b — B1: handle `olam upgrade --rollback`.
|
|
1169
|
-
*
|
|
1170
|
-
* Pre-flights all three :olam-rollback tags exist; refuses with exit 1 if
|
|
1171
|
-
* any missing. Else atomically retags :olam-rollback → canonical for all
|
|
1172
|
-
* three images, then recreates host-cp (compose) + auth-service (controller).
|
|
1173
|
-
* No git pull, no build, no smoke — the rollback target is a known-good
|
|
1174
|
-
* image set captured by a previous successful upgrade.
|
|
1175
|
-
*
|
|
1176
|
-
* Acquires the same upgrade lock as the regular path so concurrent
|
|
1177
|
-
* --rollback + --normal-upgrade refuse at the file-mutex layer.
|
|
1178
|
-
*/
|
|
1179
|
-
async function handleRollback() {
|
|
1180
|
-
printHeader('olam upgrade --rollback');
|
|
1181
|
-
// 1. Pre-flight — verify rollback set exists.
|
|
1182
|
-
const missing = checkRollbackSetExists(PRODUCTION_SWAP_PLAN);
|
|
1183
|
-
if (missing !== null) {
|
|
1184
|
-
printError(`No rollback-set available — missing :olam-rollback tag(s): ${missing}\n\n` +
|
|
1185
|
-
'A rollback-set is created by the FIRST successful `olam upgrade`. If this\n' +
|
|
1186
|
-
'is your first install, run `olam upgrade` to populate the rollback set.\n' +
|
|
1187
|
-
'If a previous upgrade was incomplete, the rollback set may be partial;\n' +
|
|
1188
|
-
'manually inspect images with `docker images olam-*:olam-rollback`.');
|
|
1189
|
-
process.exitCode = 1;
|
|
1190
|
-
return;
|
|
1191
|
-
}
|
|
1192
|
-
// 2. Acquire lock (same primitive as the upgrade path).
|
|
1193
|
-
const lock = acquireLock();
|
|
1194
|
-
if (!lock.acquired) {
|
|
1195
|
-
printError(formatRefusalMessage(lock, LOCK_FILE_PATH));
|
|
1196
|
-
process.exitCode = 1;
|
|
1197
|
-
return;
|
|
1198
|
-
}
|
|
1199
|
-
let signalReleased = false;
|
|
1200
|
-
const releaseOnSignal = (signal) => {
|
|
1201
|
-
if (signalReleased)
|
|
1202
|
-
return;
|
|
1203
|
-
signalReleased = true;
|
|
1204
|
-
try {
|
|
1205
|
-
releaseLock();
|
|
1206
|
-
}
|
|
1207
|
-
catch {
|
|
1208
|
-
/* best-effort */
|
|
1209
|
-
}
|
|
1210
|
-
process.exit(signal === 'SIGINT' ? 130 : 143);
|
|
1211
|
-
};
|
|
1212
|
-
process.once('SIGINT', releaseOnSignal);
|
|
1213
|
-
process.once('SIGTERM', releaseOnSignal);
|
|
1214
|
-
try {
|
|
1215
|
-
// 3. Inverse swap: 3 docker tag ops.
|
|
1216
|
-
process.stdout.write(` ${pc.dim('rollback retag (3 ops)'.padEnd(34))}`);
|
|
1217
|
-
const swapStart = Date.now();
|
|
1218
|
-
const swapResult = performRollbackSwap(PRODUCTION_SWAP_PLAN);
|
|
1219
|
-
const swapDur = `${((Date.now() - swapStart) / 1000).toFixed(1)}s`;
|
|
1220
|
-
process.stdout.write(`${swapResult.ok ? pc.green('✓') : pc.red('✗')} ${swapDur}\n`);
|
|
1221
|
-
if (!swapResult.ok) {
|
|
1222
|
-
printError(`Rollback retag failed: ${swapResult.summary}`);
|
|
1223
|
-
process.exitCode = 1;
|
|
1224
|
-
return;
|
|
1225
|
-
}
|
|
1226
|
-
printInfo('Rollback', swapResult.summary);
|
|
1227
|
-
// 4. Recreate containers (host-cp via compose, auth via controller).
|
|
1228
|
-
const composeFile = findComposeFile();
|
|
1229
|
-
const authSecret = readAuthSecret();
|
|
1230
|
-
process.stdout.write(` ${pc.dim('docker compose recreate olam-host-cp'.padEnd(34))}`);
|
|
1231
|
-
const composeStart = Date.now();
|
|
1232
|
-
// `--no-deps` matches the forward-upgrade path (line ~1025) so the
|
|
1233
|
-
// rollback recreate doesn't also bounce docker-socket-proxy.
|
|
1234
|
-
const composeResult = runCompose(['up', '-d', '--force-recreate', '--no-deps', 'olam-host-cp'], composeFile, buildComposeEnv(authSecret, captureGhToken()));
|
|
1235
|
-
const composeDur = `${((Date.now() - composeStart) / 1000).toFixed(1)}s`;
|
|
1236
|
-
process.stdout.write(`${composeResult.ok ? pc.green('✓') : pc.red('✗')} ${composeDur}\n`);
|
|
1237
|
-
if (!composeResult.ok) {
|
|
1238
|
-
printError(`Rollback compose recreate failed:\n${composeResult.stderr}\n` +
|
|
1239
|
-
'Canonical tags are at :olam-rollback (good); container restart pending. ' +
|
|
1240
|
-
'Manually: `docker compose -f packages/host-cp/compose.yaml up -d --force-recreate olam-host-cp`.');
|
|
1241
|
-
process.exitCode = 1;
|
|
1242
|
-
return;
|
|
1243
|
-
}
|
|
1244
|
-
process.stdout.write(` ${pc.dim('recreate auth-service'.padEnd(34))}`);
|
|
1245
|
-
const authResult = await recreateAuthService();
|
|
1246
|
-
const authDur = `${(authResult.durationMs / 1000).toFixed(1)}s`;
|
|
1247
|
-
process.stdout.write(`${authResult.ok ? pc.green('✓') : pc.red('✗')} ${authDur}\n`);
|
|
1248
|
-
if (!authResult.ok) {
|
|
1249
|
-
printError(`Auth-service recreate failed: ${authResult.error ?? 'unknown'}`);
|
|
1250
|
-
process.exitCode = 1;
|
|
1251
|
-
return;
|
|
1252
|
-
}
|
|
1253
|
-
process.stdout.write('\n');
|
|
1254
|
-
printSuccess('Rollback complete — canonical tags restored from :olam-rollback');
|
|
1255
|
-
}
|
|
1256
|
-
finally {
|
|
1257
|
-
releaseLock();
|
|
1258
|
-
process.removeListener('SIGINT', releaseOnSignal);
|
|
1259
|
-
process.removeListener('SIGTERM', releaseOnSignal);
|
|
1260
|
-
}
|
|
1261
|
-
}
|
|
1262
|
-
/**
|
|
1263
|
-
* Internal — runs all state-changing upgrade steps inside the lock.
|
|
1264
|
-
* Extracted so handleUpgrade can wrap in try/finally without indenting the body.
|
|
1265
|
-
*/
|
|
1266
|
-
async function runUpgradeStepsWithLockHeld(opts, cwd, logRow) {
|
|
1267
|
-
// 4a. Branch switch (--branch).
|
|
1268
|
-
if (opts.branch !== null) {
|
|
1269
|
-
if (isGitDirty(cwd)) {
|
|
1270
|
-
printError(`Working tree is dirty. Stash changes first:\n git stash\nThen retry with --branch ${opts.branch}`);
|
|
1271
|
-
process.exitCode = 1;
|
|
1272
|
-
return;
|
|
1273
|
-
}
|
|
1274
|
-
const switchResult = runStep(`git checkout ${opts.branch}`, 'git', ['checkout', opts.branch], { cwd });
|
|
1275
|
-
if (!switchResult.ok) {
|
|
1276
|
-
printError(`Failed to switch to branch ${opts.branch}:\n${switchResult.stderr}`);
|
|
1277
|
-
process.exitCode = 1;
|
|
1278
|
-
return;
|
|
1279
|
-
}
|
|
1280
|
-
}
|
|
1281
|
-
// 4b. Dirty check before pull (skip if we just switched).
|
|
1282
|
-
if (opts.branch === null && isGitDirty(cwd)) {
|
|
1283
|
-
printError('Working tree is dirty. Stash or commit changes first:\n git stash\nThen re-run `olam upgrade`.');
|
|
1284
|
-
process.exitCode = 1;
|
|
1285
|
-
return;
|
|
1286
|
-
}
|
|
1287
|
-
// 4c. Upstream check.
|
|
1288
|
-
if (!hasGitUpstream(cwd)) {
|
|
1289
|
-
printError('Current branch has no upstream remote. Set one first:\n git branch --set-upstream-to=origin/<branch>\nThen re-run `olam upgrade`.');
|
|
1290
|
-
process.exitCode = 1;
|
|
1291
|
-
return;
|
|
1292
|
-
}
|
|
1293
|
-
const timings = [];
|
|
1294
|
-
// Step a: git fetch.
|
|
1295
|
-
const fetchResult = runStep('git fetch origin --prune', 'git', ['fetch', 'origin', '--prune'], { cwd });
|
|
1296
|
-
timings.push({ label: 'git fetch', durationMs: fetchResult.durationMs });
|
|
1297
|
-
if (!fetchResult.ok) {
|
|
1298
|
-
printError(`git fetch failed:\n${fetchResult.stderr}`);
|
|
1299
|
-
process.exitCode = 1;
|
|
1300
|
-
return;
|
|
1301
|
-
}
|
|
1302
|
-
// Step b: git pull --ff-only.
|
|
1303
|
-
const pullResult = runStep('git pull --ff-only', 'git', ['pull', '--ff-only'], { cwd });
|
|
1304
|
-
timings.push({ label: 'git pull', durationMs: pullResult.durationMs });
|
|
1305
|
-
if (!pullResult.ok) {
|
|
1306
|
-
printError(`git pull --ff-only failed:\n${pullResult.stderr}\n` +
|
|
1307
|
-
'If there are conflicts, resolve them manually then re-run `olam upgrade`.');
|
|
1308
|
-
process.exitCode = 1;
|
|
1309
|
-
return;
|
|
1310
|
-
}
|
|
1311
|
-
// Phase 2a — A2: capture HEAD SHA AFTER pull (sticky for the run).
|
|
1312
|
-
// The pull is what we're upgrading to; capturing before would self-refuse
|
|
1313
|
-
// at A6's swap-boundary drift check. _targetSha is consumed by A6 (atomic
|
|
1314
|
-
// swap) and B4 (drift refusal). Phase 2a A2 just stashes it; A6/B4 land it
|
|
1315
|
-
// load-bearing.
|
|
1316
|
-
const _targetSha = captureHeadSha(cwd);
|
|
1317
|
-
logRow.sha_target = _targetSha ?? '';
|
|
1318
|
-
if (_targetSha === null) {
|
|
1319
|
-
logRow.failed_step = 'capture HEAD SHA';
|
|
1320
|
-
printError('Failed to capture HEAD SHA via `git rev-parse HEAD`. Aborting upgrade.\n' +
|
|
1321
|
-
'Re-run from a clean git checkout; ensure `git rev-parse HEAD` returns a 40-char SHA.');
|
|
1322
|
-
process.exitCode = 1;
|
|
1323
|
-
return;
|
|
1324
|
-
}
|
|
1325
|
-
printInfo('Target SHA', abbreviateSha(_targetSha));
|
|
1326
|
-
// Step c: npm install (skip when in sync or --skip-install).
|
|
1327
|
-
const installDecision = shouldSkipInstall(opts, cwd);
|
|
1328
|
-
if (installDecision.skip) {
|
|
1329
|
-
printInfo('npm install', `skipped — ${installDecision.reason}`);
|
|
1330
|
-
timings.push({ label: 'npm install', durationMs: 0 });
|
|
1331
|
-
}
|
|
1332
|
-
else {
|
|
1333
|
-
const installResult = runStep('npm install', 'npm', ['install', '--silent', '--no-audit', '--no-fund'], { cwd });
|
|
1334
|
-
timings.push({ label: 'npm install', durationMs: installResult.durationMs });
|
|
1335
|
-
if (!installResult.ok) {
|
|
1336
|
-
printError(`npm install failed:\n${installResult.stderr}\n\n` +
|
|
1337
|
-
'Recovery options:\n' +
|
|
1338
|
-
' • If a native module (e.g. better-sqlite3) failed to compile on your Node version:\n' +
|
|
1339
|
-
' olam upgrade --skip-install\n' +
|
|
1340
|
-
' This skips npm install and uses the existing node_modules.\n' +
|
|
1341
|
-
' • To fix the native module, upgrade it in the relevant package.json,\n' +
|
|
1342
|
-
' run `npm install` manually, then retry `olam upgrade --skip-install`.');
|
|
1343
|
-
process.exitCode = 1;
|
|
1344
|
-
return;
|
|
1345
|
-
}
|
|
1346
|
-
}
|
|
1347
|
-
// Step d: npm run build (TS workspaces).
|
|
1348
|
-
const buildResult = runStep('npm run build', 'npm', ['run', 'build'], { cwd });
|
|
1349
|
-
timings.push({ label: 'npm run build', durationMs: buildResult.durationMs });
|
|
1350
|
-
if (!buildResult.ok) {
|
|
1351
|
-
printError(`npm run build failed:\n${buildResult.stderr}`);
|
|
1352
|
-
process.exitCode = 1;
|
|
1353
|
-
return;
|
|
1354
|
-
}
|
|
1355
|
-
// Step e: vite build (SPA).
|
|
1356
|
-
const authSecret = readAuthSecret();
|
|
1357
|
-
const spaDir = path.join(cwd, 'packages/plan-chat-spa');
|
|
1358
|
-
const spaResult = runStep('vite build (SPA)', 'npx', ['vite', 'build'], { cwd: spaDir, env: buildComposeEnv(authSecret, captureGhToken()) });
|
|
1359
|
-
timings.push({ label: 'vite build (SPA)', durationMs: spaResult.durationMs });
|
|
1360
|
-
if (!spaResult.ok) {
|
|
1361
|
-
printError(`vite build failed:\n${spaResult.stderr}`);
|
|
1362
|
-
process.exitCode = 1;
|
|
1363
|
-
return;
|
|
1364
|
-
}
|
|
1365
|
-
if (opts.skipImage) {
|
|
1366
|
-
// Summary — no docker steps.
|
|
1367
|
-
process.stdout.write('\n');
|
|
1368
|
-
printSuccess('Source rebuilt (--skip-image: docker not touched)');
|
|
1369
|
-
const hash = readBundleHash(cwd);
|
|
1370
|
-
if (hash)
|
|
1371
|
-
printInfo('Bundle hash', hash);
|
|
1372
|
-
printTimings(timings);
|
|
1373
|
-
return;
|
|
1374
|
-
}
|
|
1375
|
-
// Note: A4-A8 step durations are captured in `timings`; the per-step
|
|
1376
|
-
// durations_ms snapshot on the log row reflects the full timings array
|
|
1377
|
-
// at the end of the run (logRow.durations_ms is updated below at each
|
|
1378
|
-
// significant boundary so a mid-run failure is recorded with what we know).
|
|
1379
|
-
// Phase 2a — A4: sequential build invocation (auth → devbox → host-cp)
|
|
1380
|
-
// with OLAM_TAG=olam-next so each script tags its image transiently per
|
|
1381
|
-
// A3's retag block. Order is load-bearing: auth first minimises P3's
|
|
1382
|
-
// in-flight 401 window when the recreate (A7) restarts auth before
|
|
1383
|
-
// host-cp. Devbox uses inherit-stdio (live tee) per audit F13 since
|
|
1384
|
-
// its cold-cache build dominates the 12-22 min budget and silent
|
|
1385
|
-
// capture is indistinguishable from a hang.
|
|
1386
|
-
// Phase 2b — B3: --no-cache passes through to all three build scripts.
|
|
1387
|
-
// The build scripts honor DOCKER_BUILD_NO_CACHE via the build-arg env mechanism
|
|
1388
|
-
// documented in their shell. (B3 implementation: forward the env; build
|
|
1389
|
-
// scripts treat unset as default cache enabled.)
|
|
1390
|
-
const olamTagEnv = { OLAM_TAG: 'olam-next' };
|
|
1391
|
-
if (opts.noCache) {
|
|
1392
|
-
olamTagEnv.DOCKER_BUILD_NO_CACHE = '1';
|
|
1393
|
-
}
|
|
1394
|
-
const buildScripts = [
|
|
1395
|
-
{ label: 'bash build-auth.sh', relPath: 'packages/adapters/src/docker/build-auth.sh', tee: false },
|
|
1396
|
-
{ label: 'bash build-devbox.sh', relPath: 'packages/adapters/src/docker/build-devbox.sh', tee: true },
|
|
1397
|
-
{ label: 'bash build-host-cp.sh', relPath: 'packages/adapters/src/docker/build-host-cp.sh', tee: false },
|
|
1398
|
-
];
|
|
1399
|
-
// Phase B6: resolve each build script via installRoot + dev-mode check.
|
|
1400
|
-
// Outside dev mode, surface the named remedy and exit cleanly. The
|
|
1401
|
-
// pull-by-digest default path (no --from-source flag) lands in C6.
|
|
1402
|
-
const { resolveBuildScript, MissingBuildScriptError } = await import('../install-root.js');
|
|
1403
|
-
for (const step of buildScripts) {
|
|
1404
|
-
let scriptPath;
|
|
1405
|
-
try {
|
|
1406
|
-
scriptPath = resolveBuildScript({ scriptRelPath: step.relPath });
|
|
1407
|
-
}
|
|
1408
|
-
catch (err) {
|
|
1409
|
-
if (err instanceof MissingBuildScriptError) {
|
|
1410
|
-
printError(err.message);
|
|
1411
|
-
process.exitCode = 1;
|
|
1412
|
-
return;
|
|
1413
|
-
}
|
|
1414
|
-
throw err;
|
|
1415
|
-
}
|
|
1416
|
-
if (step.tee) {
|
|
1417
|
-
// Live-tee variant: stdio: 'inherit' so docker build's apt/bundle/npm
|
|
1418
|
-
// progress reaches the operator's terminal in real-time. No stdout
|
|
1419
|
-
// capture means we can't include stderr in the failure message —
|
|
1420
|
-
// operator already saw the failure inline.
|
|
1421
|
-
process.stdout.write(` ${pc.dim(step.label.padEnd(34))}\n`);
|
|
1422
|
-
const start = Date.now();
|
|
1423
|
-
const result = spawnSync('bash', [scriptPath], {
|
|
1424
|
-
stdio: 'inherit',
|
|
1425
|
-
cwd,
|
|
1426
|
-
env: { ...process.env, ...olamTagEnv },
|
|
1427
|
-
});
|
|
1428
|
-
const durationMs = Date.now() - start;
|
|
1429
|
-
const ok = result.status === 0 && result.error === undefined;
|
|
1430
|
-
const dur = `${(durationMs / 1000).toFixed(1)}s`;
|
|
1431
|
-
process.stdout.write(` ${pc.dim(step.label.padEnd(34))}${ok ? pc.green('✓') : pc.red('✗')} ${dur}\n`);
|
|
1432
|
-
timings.push({ label: step.label, durationMs });
|
|
1433
|
-
if (!ok) {
|
|
1434
|
-
printError(`${step.label} failed (see output above for details).`);
|
|
1435
|
-
process.exitCode = 1;
|
|
1436
|
-
return;
|
|
1437
|
-
}
|
|
1438
|
-
}
|
|
1439
|
-
else {
|
|
1440
|
-
const result = runStep(step.label, 'bash', [scriptPath], {
|
|
1441
|
-
cwd,
|
|
1442
|
-
env: olamTagEnv,
|
|
1443
|
-
});
|
|
1444
|
-
timings.push({ label: step.label, durationMs: result.durationMs });
|
|
1445
|
-
logRow.durations_ms[step.label] = result.durationMs;
|
|
1446
|
-
if (!result.ok) {
|
|
1447
|
-
logRow.failed_step = step.label;
|
|
1448
|
-
printError(`${step.label} failed:\n${result.stderr.split('\n').slice(-3).join('\n')}`);
|
|
1449
|
-
process.exitCode = 1;
|
|
1450
|
-
return;
|
|
1451
|
-
}
|
|
1452
|
-
}
|
|
1453
|
-
}
|
|
1454
|
-
// Snapshot durations to logRow so a later-step failure preserves what we know.
|
|
1455
|
-
for (const t of timings)
|
|
1456
|
-
logRow.durations_ms[t.label] = t.durationMs;
|
|
1457
|
-
// Phase 2a — A5: smoke each :olam-next image via docker create + inspect.
|
|
1458
|
-
// Catches build-corrupt cases (manifest invalid, OLAM_BUILD_SHA label
|
|
1459
|
-
// missing, baked SHA != target SHA) before A6's atomic swap touches
|
|
1460
|
-
// canonical tags. Sub-second per image; no port bind.
|
|
1461
|
-
const smokeStart = Date.now();
|
|
1462
|
-
process.stdout.write(` ${pc.dim('smoke (docker create + inspect)'.padEnd(34))}`);
|
|
1463
|
-
const smokeImages = [
|
|
1464
|
-
'olam-auth:olam-next',
|
|
1465
|
-
'olam-devbox:olam-next',
|
|
1466
|
-
'olam-host-cp:olam-next',
|
|
1467
|
-
];
|
|
1468
|
-
const smokeResults = smokeImages.map((img) => smokeImage(img, _targetSha));
|
|
1469
|
-
const smokeFailures = smokeResults.filter((r) => !r.ok);
|
|
1470
|
-
const smokeDurationMs = Date.now() - smokeStart;
|
|
1471
|
-
const smokeDur = `${(smokeDurationMs / 1000).toFixed(1)}s`;
|
|
1472
|
-
process.stdout.write(`${smokeFailures.length === 0 ? pc.green('✓') : pc.red('✗')} ${smokeDur}\n`);
|
|
1473
|
-
timings.push({ label: 'smoke', durationMs: smokeDurationMs });
|
|
1474
|
-
if (smokeFailures.length > 0) {
|
|
1475
|
-
printError(`Smoke failed for ${smokeFailures.length} of ${smokeResults.length} images:\n` +
|
|
1476
|
-
smokeFailures.map((r) => ` - ${r.image}: ${r.error}`).join('\n') +
|
|
1477
|
-
'\nCanonical tags (`:latest`/`:local`) untouched. Investigate the failed image(s),' +
|
|
1478
|
-
' then re-run `olam upgrade` (--no-cache if cache-poisoning suspected).');
|
|
1479
|
-
process.exitCode = 1;
|
|
1480
|
-
return;
|
|
1481
|
-
}
|
|
1482
|
-
// Phase 2b — B4: SHA drift check at swap boundary.
|
|
1483
|
-
// Re-read HEAD via `git rev-parse HEAD` and compare to A2's captured
|
|
1484
|
-
// _targetSha. If different, refuse the swap unless --force.
|
|
1485
|
-
const swapBoundarySha = captureHeadSha(cwd);
|
|
1486
|
-
if (swapBoundarySha !== null && swapBoundarySha !== _targetSha && !opts.force) {
|
|
1487
|
-
printError(`HEAD drifted during build window:\n` +
|
|
1488
|
-
` captured (after pull): ${abbreviateSha(_targetSha)}\n` +
|
|
1489
|
-
` current at swap: ${abbreviateSha(swapBoundarySha)}\n\n` +
|
|
1490
|
-
'Operator-driven `git checkout` or `git reset` triggered drift.\n' +
|
|
1491
|
-
'Recovery options:\n' +
|
|
1492
|
-
' • Re-run `olam upgrade` (will rebuild against current HEAD).\n' +
|
|
1493
|
-
' • Pass `--force` to swap anyway (canonical advances to the\n' +
|
|
1494
|
-
' captured-at-pull SHA, NOT current HEAD).');
|
|
1495
|
-
process.exitCode = 1;
|
|
1496
|
-
return;
|
|
1497
|
-
}
|
|
1498
|
-
// Phase 2a — A6: atomic 6-tag swap.
|
|
1499
|
-
// Phase 1 of swap: preserve previous-good as :olam-rollback (3 ops).
|
|
1500
|
-
// Phase 2 of swap: advance canonical to :olam-next (3 ops).
|
|
1501
|
-
// Sub-second wall-clock; SIGKILL during Phase 2 is recoverable via
|
|
1502
|
-
// `olam upgrade --rollback` (Phase 2b) since :olam-rollback is fully
|
|
1503
|
-
// populated before any canonical tag is touched.
|
|
1504
|
-
process.stdout.write(` ${pc.dim('atomic 6-tag swap'.padEnd(34))}`);
|
|
1505
|
-
const swapStart = Date.now();
|
|
1506
|
-
const swapResult = performAtomicSwap(PRODUCTION_SWAP_PLAN);
|
|
1507
|
-
const swapDurationMs = Date.now() - swapStart;
|
|
1508
|
-
const swapDur = `${(swapDurationMs / 1000).toFixed(1)}s`;
|
|
1509
|
-
process.stdout.write(`${swapResult.ok ? pc.green('✓') : pc.red('✗')} ${swapDur}\n`);
|
|
1510
|
-
timings.push({ label: 'atomic swap', durationMs: swapDurationMs });
|
|
1511
|
-
if (!swapResult.ok) {
|
|
1512
|
-
printError(`Atomic swap failed: ${swapResult.summary}`);
|
|
1513
|
-
process.exitCode = 1;
|
|
1514
|
-
return;
|
|
1515
|
-
}
|
|
1516
|
-
printInfo('Swap', swapResult.summary);
|
|
1517
|
-
// Step g: docker compose up -d --force-recreate.
|
|
1518
|
-
const composeFile = findComposeFile();
|
|
1519
|
-
process.stdout.write(` ${pc.dim('docker compose recreate'.padEnd(34))}`);
|
|
1520
|
-
const composeStart = Date.now();
|
|
1521
|
-
const composeResult = runCompose(['up', '-d', '--force-recreate'], composeFile, buildComposeEnv(authSecret, captureGhToken()));
|
|
1522
|
-
const composeDurationMs = Date.now() - composeStart;
|
|
1523
|
-
const composeOk = composeResult.ok;
|
|
1524
|
-
const composeDur = `${(composeDurationMs / 1000).toFixed(1)}s`;
|
|
1525
|
-
process.stdout.write(`${composeOk ? pc.green('✓') : pc.red('✗')} ${composeDur}\n`);
|
|
1526
|
-
timings.push({ label: 'container recreate', durationMs: composeDurationMs });
|
|
1527
|
-
if (!composeOk) {
|
|
1528
|
-
// Audit A6-002: at this point canonical tags are at NEW SHA but the stack
|
|
1529
|
-
// failed to start. Operator needs to know --rollback is one command away.
|
|
1530
|
-
printError(`docker compose up --force-recreate failed:\n${composeResult.stderr}\n\n` +
|
|
1531
|
-
'Canonical tags advanced to new SHA but the stack failed to start.\n' +
|
|
1532
|
-
'Recovery options:\n' +
|
|
1533
|
-
' • Run `olam upgrade --rollback` to restore the prior :olam-rollback set, then investigate.\n' +
|
|
1534
|
-
' • Manually `docker logs olam-host-cp` to diagnose; if recoverable, retry recreate without rollback.');
|
|
1535
|
-
process.exitCode = 1;
|
|
1536
|
-
return;
|
|
1537
|
-
}
|
|
1538
|
-
// Phase 2a — A7: recreate auth-service via AuthContainerController.
|
|
1539
|
-
// Auth is NOT in compose.yaml; reusing the auth-upgrade.ts recreate pattern
|
|
1540
|
-
// (docker stop → docker rm → controller.start() → wait /health). The 25s
|
|
1541
|
-
// in-flight 401 window for active world API calls during this recreate is
|
|
1542
|
-
// documented in the operator's confirmation prompt (P3 mitigation).
|
|
1543
|
-
process.stdout.write(` ${pc.dim('recreate auth-service'.padEnd(34))}`);
|
|
1544
|
-
const authResult = await recreateAuthService();
|
|
1545
|
-
const authDur = `${(authResult.durationMs / 1000).toFixed(1)}s`;
|
|
1546
|
-
process.stdout.write(`${authResult.ok ? pc.green('✓') : pc.red('✗')} ${authDur}\n`);
|
|
1547
|
-
timings.push({ label: 'auth recreate', durationMs: authResult.durationMs });
|
|
1548
|
-
if (!authResult.ok) {
|
|
1549
|
-
printError(`Auth-service recreate failed: ${authResult.error ?? 'unknown'}\n\n` +
|
|
1550
|
-
'Canonical tags advanced to new SHA; host-cp recreated but auth-service is broken.\n' +
|
|
1551
|
-
'Recovery options:\n' +
|
|
1552
|
-
' • Run `olam upgrade --rollback` to restore the prior :olam-rollback set + working stack.\n' +
|
|
1553
|
-
' • Manually: `docker logs olam-auth` to diagnose; `olam auth up` to restart.');
|
|
1554
|
-
process.exitCode = 1;
|
|
1555
|
-
return;
|
|
1556
|
-
}
|
|
1557
|
-
// Step h: wait for /health (host-cp readiness probe).
|
|
1558
|
-
process.stdout.write(` ${pc.dim('waiting for /health'.padEnd(34))}`);
|
|
1559
|
-
const healthStart = Date.now();
|
|
1560
|
-
const healthy = await waitForHealth(10_000);
|
|
1561
|
-
const healthDurationMs = Date.now() - healthStart;
|
|
1562
|
-
const healthDur = `${(healthDurationMs / 1000).toFixed(1)}s`;
|
|
1563
|
-
process.stdout.write(`${healthy ? pc.green('✓') : pc.yellow('?')} ${healthDur}\n`);
|
|
1564
|
-
timings.push({ label: '/health', durationMs: healthDurationMs });
|
|
1565
|
-
if (!healthy) {
|
|
1566
|
-
printWarning('Host CP started but /health did not respond within 10s.\n' +
|
|
1567
|
-
' • Check: docker logs olam-host-cp\n' +
|
|
1568
|
-
' • If the new SHA is broken: `olam upgrade --rollback` restores the prior set in <30s.');
|
|
1569
|
-
}
|
|
1570
|
-
// Phase 2a — A8: poll /api/version/status until all three SHAs match
|
|
1571
|
-
// captured target. This is the success criterion for the entire upgrade —
|
|
1572
|
-
// round-trips through Phase 1's detection path so the SPA banner clears
|
|
1573
|
-
// automatically once the polling loop succeeds.
|
|
1574
|
-
//
|
|
1575
|
-
// Phase 2.5 polish: 60s wasn't enough on first dogfood — host-cp's /health
|
|
1576
|
-
// came back instantly but /api/version/status needed >60s to start serving
|
|
1577
|
-
// (cold-boot of express handler chain + first registry-stat reads). 90s
|
|
1578
|
-
// gives reliable headroom without making the warn-path feel slow when
|
|
1579
|
-
// host-cp is genuinely stuck.
|
|
1580
|
-
process.stdout.write(` ${pc.dim('verify /version/status round-trip'.padEnd(34))}`);
|
|
1581
|
-
const versionStart = Date.now();
|
|
1582
|
-
const versionMatch = await waitForVersionMatch(_targetSha, 90_000);
|
|
1583
|
-
const versionDurationMs = Date.now() - versionStart;
|
|
1584
|
-
const versionDur = `${(versionDurationMs / 1000).toFixed(1)}s`;
|
|
1585
|
-
process.stdout.write(`${versionMatch.matched ? pc.green('✓') : pc.yellow('?')} ${versionDur}\n`);
|
|
1586
|
-
timings.push({ label: '/version/status round-trip', durationMs: versionDurationMs });
|
|
1587
|
-
if (!versionMatch.matched) {
|
|
1588
|
-
// Non-fatal — recreate succeeded; SHA propagation may be slow on cold
|
|
1589
|
-
// host-cp boot. Operator gets diagnostic output + can decide whether to
|
|
1590
|
-
// re-run, wait, or roll back.
|
|
1591
|
-
printWarning(`Version round-trip incomplete after ${(versionDurationMs / 1000).toFixed(0)}s:\n` +
|
|
1592
|
-
formatVersionMismatch(_targetSha, versionMatch.snapshot) + '\n' +
|
|
1593
|
-
' • Banner may still show UPDATE AVAILABLE until host-cp\'s next ' +
|
|
1594
|
-
'poll cycle (~60s).\n' +
|
|
1595
|
-
' • If the mismatch persists, `olam upgrade --rollback` restores ' +
|
|
1596
|
-
'the prior :olam-rollback set.');
|
|
1597
|
-
}
|
|
1598
|
-
// 5. Summary.
|
|
1599
|
-
process.stdout.write('\n');
|
|
1600
|
-
printSuccess('Upgrade complete');
|
|
1601
|
-
const hash = readBundleHash(cwd);
|
|
1602
|
-
if (hash)
|
|
1603
|
-
printInfo('Bundle hash', hash);
|
|
1604
|
-
printTimings(timings);
|
|
1605
|
-
}
|
|
1606
|
-
function printTimings(timings) {
|
|
1607
|
-
const total = timings.reduce((s, t) => s + t.durationMs, 0);
|
|
1608
|
-
process.stdout.write('\n');
|
|
1609
|
-
for (const t of timings) {
|
|
1610
|
-
printInfo(t.label, `${(t.durationMs / 1000).toFixed(1)}s`);
|
|
1611
|
-
}
|
|
1612
|
-
printInfo('total', `${(total / 1000).toFixed(1)}s`);
|
|
1613
|
-
}
|
|
1614
|
-
// ── Register ──────────────────────────────────────────────────────
|
|
1615
|
-
export function registerUpgrade(program) {
|
|
1616
|
-
program
|
|
1617
|
-
.command('upgrade')
|
|
1618
|
-
.description('Upgrade the local Olam stack to the CLI\'s pinned image digests')
|
|
1619
|
-
.option('-y, --yes', 'Skip the confirmation prompt')
|
|
1620
|
-
.option('--skip-image', 'Skip docker image rebuild + container recreate (requires --from-source; ignored on default digest path)')
|
|
1621
|
-
.option('--skip-install', 'Skip npm install entirely (use existing node_modules as-is). ' +
|
|
1622
|
-
'Requires --from-source; ignored on default digest path.')
|
|
1623
|
-
.option('--branch <name>', 'Switch to this branch before pulling (requires --from-source; ignored on default digest path)')
|
|
1624
|
-
.option('--rollback', 'Restore canonical tags from the :olam-rollback set (created by the prior successful upgrade).\n' +
|
|
1625
|
-
' No git pull, no build, no smoke — just retag + recreate.')
|
|
1626
|
-
.option('--force', 'Bypass HEAD-drift refusal at the swap boundary (requires --from-source; ignored on default digest path).\n' +
|
|
1627
|
-
' Swap advances canonical to the captured-at-pull SHA even if current HEAD differs.')
|
|
1628
|
-
.option('--no-cache', 'Pass --no-cache to all three build scripts (DOCKER_BUILD_NO_CACHE=1).\n' +
|
|
1629
|
-
' Useful when retrying after a cache-poisoning failure.')
|
|
1630
|
-
.option('--history', 'Print the upgrade history (~/.olam/upgrade.log) and exit.\n' +
|
|
1631
|
-
' No upgrade is performed.')
|
|
1632
|
-
.option('-n <count>', 'Number of history rows to print (default 10)', '10')
|
|
1633
|
-
.option('--json', 'Emit history as JSONL instead of a table')
|
|
1634
|
-
.option('--from-source', 'Build host-cp + auth + devbox from monorepo source (legacy path).\n' +
|
|
1635
|
-
' Requires OLAM_DEV=1 + a `packages/` sibling at the install root.\n' +
|
|
1636
|
-
' Default (no flag): pull pre-built images from ghcr.io by digest.')
|
|
1637
|
-
.option('--force-refresh-manifests', '[kubernetes substrate only] Re-apply manifests from ~/.olam/k8s/manifests/ before\n' +
|
|
1638
|
-
' the rollout. Security-sensitive field diffs require\n' +
|
|
1639
|
-
' --accept-security-regression to proceed.')
|
|
1640
|
-
.option('--accept-security-regression', '[kubernetes substrate only] Accept security-sensitive field changes in manifests\n' +
|
|
1641
|
-
' when using --force-refresh-manifests. Writes an audit\n' +
|
|
1642
|
-
' entry to ~/.olam/state/manifest-refresh-audit.jsonl.')
|
|
1643
|
-
.option('--rotate-secrets', '[kubernetes substrate only — B5] Regenerate every rendered Secret value.\n' +
|
|
1644
|
-
' WARNING: rotation breaks worlds that have cached prior\n' +
|
|
1645
|
-
' tokens. Default behaviour reuses values from\n' +
|
|
1646
|
-
' ~/.olam/k8s-secrets-state.json so reapply is non-destructive.')
|
|
1647
|
-
.option('--skip-k8s-bootstrap', '[kubernetes substrate only — B4] Skip the namespace + RBAC + secret bootstrap step.\n' +
|
|
1648
|
-
' Use when these are managed out-of-band (e.g. GitOps).')
|
|
1649
|
-
.action(async (opts) => {
|
|
1650
|
-
const parsed = parseUpgradeOpts(opts);
|
|
1651
|
-
// Phase 1b — C2: substrate-aware routing.
|
|
1652
|
-
// Read host.substrate from ~/.olam/config.json BEFORE any other branching.
|
|
1653
|
-
// Compose path: unchanged (byte-identical to prior behaviour).
|
|
1654
|
-
// Kubernetes path: 8-step flow in upgrade-kubernetes.ts.
|
|
1655
|
-
const cfg = readConfig();
|
|
1656
|
-
if (cfg.host.substrate === 'kubernetes') {
|
|
1657
|
-
try {
|
|
1658
|
-
const result = await runUpgradeKubernetes({
|
|
1659
|
-
forceRefreshManifests: parsed.forceRefreshManifests,
|
|
1660
|
-
acceptSecurityRegression: parsed.acceptSecurityRegression,
|
|
1661
|
-
rotateSecrets: opts.rotateSecrets === true,
|
|
1662
|
-
// B4: bootstrap defaults ON for the CLI entry point so npm-only
|
|
1663
|
-
// operators get namespace + RBAC + secrets auto-applied. Opt-out
|
|
1664
|
-
// via --skip-k8s-bootstrap (GitOps-managed clusters).
|
|
1665
|
-
runK8sBootstrap: opts.skipK8sBootstrap !== true,
|
|
1666
|
-
});
|
|
1667
|
-
process.exitCode = result.exitCode;
|
|
1668
|
-
}
|
|
1669
|
-
catch (err) {
|
|
1670
|
-
printError(err instanceof Error ? err.message : String(err));
|
|
1671
|
-
process.exitCode = EXIT_GENERIC_ERROR;
|
|
1672
|
-
}
|
|
1673
|
-
return;
|
|
1674
|
-
}
|
|
1675
|
-
// compose path — unchanged below this line ────────────────────
|
|
1676
|
-
// Phase C — C6: --from-source gates the legacy build path. Default
|
|
1677
|
-
// pull-by-digest path lands installed-CLI operators on a fresh
|
|
1678
|
-
// image set without a monorepo clone.
|
|
1679
|
-
if (parsed.fromSource) {
|
|
1680
|
-
if (!isDevMode()) {
|
|
1681
|
-
printError(new MissingBuildScriptError('packages/adapters/src/docker/build-*.sh').message);
|
|
1682
|
-
process.exitCode = 1;
|
|
1683
|
-
return;
|
|
1684
|
-
}
|
|
1685
|
-
await handleUpgrade(parsed);
|
|
1686
|
-
return;
|
|
1687
|
-
}
|
|
1688
|
-
// --history is read-only; honour it on the default path too so
|
|
1689
|
-
// operators don't have to remember --from-source just to read the log.
|
|
1690
|
-
if (parsed.history) {
|
|
1691
|
-
handleHistory(parseHistoryOpts({ n: parsed.historyN, json: parsed.historyJson }));
|
|
1692
|
-
return;
|
|
1693
|
-
}
|
|
1694
|
-
// Warn about flags that only work with --from-source.
|
|
1695
|
-
// These flags are silently ignored on the pull-by-digest path; warn
|
|
1696
|
-
// the operator so they don't think the flag took effect.
|
|
1697
|
-
const digestPathIgnoredFlags = [
|
|
1698
|
-
[parsed.skipImage, '--skip-image'],
|
|
1699
|
-
[parsed.skipInstall, '--skip-install'],
|
|
1700
|
-
[parsed.branch !== null, '--branch'],
|
|
1701
|
-
[parsed.force, '--force'],
|
|
1702
|
-
];
|
|
1703
|
-
for (const [active, flag] of digestPathIgnoredFlags) {
|
|
1704
|
-
if (active) {
|
|
1705
|
-
process.stderr.write(`${pc.yellow('warning')} ${flag} only takes effect with --from-source; ignoring.\n`);
|
|
1706
|
-
}
|
|
1707
|
-
}
|
|
1708
|
-
try {
|
|
1709
|
-
const result = await runUpgradePullByDigest({ yes: parsed.yes });
|
|
1710
|
-
process.exitCode = result.exitCode;
|
|
1711
|
-
}
|
|
1712
|
-
catch (err) {
|
|
1713
|
-
printError(err instanceof Error ? err.message : String(err));
|
|
1714
|
-
process.exitCode = EXIT_GENERIC_ERROR;
|
|
1715
|
-
}
|
|
1716
|
-
});
|
|
1717
|
-
}
|
|
1718
|
-
//# sourceMappingURL=upgrade.js.map
|