@quickchatai/quickreview 0.0.0-stage → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +23 -0
- package/README.md +385 -2
- package/THIRD-PARTY-NOTICES.md +134 -0
- package/agents/pass-abstraction.md +64 -0
- package/agents/pass-ask-human.md +134 -0
- package/agents/pass-backcompat.md +67 -0
- package/agents/pass-baseline.md +61 -0
- package/agents/pass-behaviour-delta.md +64 -0
- package/agents/pass-bounds.md +36 -0
- package/agents/pass-claims.md +31 -0
- package/agents/pass-concurrency.md +168 -0
- package/agents/pass-consistency.md +57 -0
- package/agents/pass-contracts.md +30 -0
- package/agents/pass-data-safety.md +54 -0
- package/agents/pass-deferred.md +35 -0
- package/agents/pass-delivery.md +37 -0
- package/agents/pass-duplication.md +60 -0
- package/agents/pass-intent.md +63 -0
- package/agents/pass-lint-conformance.md +46 -0
- package/agents/pass-maintainability.md +67 -0
- package/agents/pass-placement.md +68 -0
- package/agents/pass-prod-risk.md +24 -0
- package/agents/pass-rollout.md +32 -0
- package/agents/pass-roundtrip.md +46 -0
- package/agents/pass-security.md +31 -0
- package/agents/pass-simplicity.md +68 -0
- package/agents/pass-statelessness.md +27 -0
- package/agents/pass-style.md +37 -0
- package/agents/pass-sweep.md +57 -0
- package/agents/pass-tests.md +58 -0
- package/agents/pass-ui.md +23 -0
- package/agents/pass-validate.md +50 -0
- package/agents/pass-widened.md +46 -0
- package/agents/scan/pass-scan.md +86 -0
- package/bin/qcr.js +11 -0
- package/config.example.toml +1067 -0
- package/deploy/ansible/README.md +356 -0
- package/deploy/ansible/group_vars/all.yml +212 -0
- package/deploy/ansible/inventory.example.ini +25 -0
- package/deploy/ansible/playbook.yml +360 -0
- package/deploy/collect-secrets.sh +289 -0
- package/deploy/collect-slack-secrets.mjs +1190 -0
- package/deploy/github-app-manifest.json +31 -0
- package/deploy/manifest.schema.json +75 -0
- package/deploy/qcr-update.sh +803 -0
- package/deploy/reviewer/AGENTS.override.md +12 -0
- package/dist/extensions/cli/bash-guard-ambient.js +42 -0
- package/dist/extensions/cli/bash-guard-ambient.js.map +1 -0
- package/dist/extensions/cli/bash-guard.js +5 -0
- package/dist/extensions/cli/bash-guard.js.map +1 -0
- package/dist/extensions/cli/companion-guard.js +5 -0
- package/dist/extensions/cli/companion-guard.js.map +1 -0
- package/dist/extensions/cli/debug-round.js +8 -0
- package/dist/extensions/cli/debug-round.js.map +1 -0
- package/dist/extensions/cli/inject-bridge.js +5 -0
- package/dist/extensions/cli/inject-bridge.js.map +1 -0
- package/dist/extensions/cli/ledger-pin.js +5 -0
- package/dist/extensions/cli/ledger-pin.js.map +1 -0
- package/dist/extensions/cli/lens.js +13 -0
- package/dist/extensions/cli/lens.js.map +1 -0
- package/dist/extensions/cli/muted-theme.js +5 -0
- package/dist/extensions/cli/muted-theme.js.map +1 -0
- package/dist/extensions/cli/onboarding.js +5 -0
- package/dist/extensions/cli/onboarding.js.map +1 -0
- package/dist/extensions/cli/setup.js +5 -0
- package/dist/extensions/cli/setup.js.map +1 -0
- package/dist/extensions/cli/tui-view.js +5 -0
- package/dist/extensions/cli/tui-view.js.map +1 -0
- package/dist/extensions/companion-guard.js +361 -0
- package/dist/extensions/companion-guard.js.map +1 -0
- package/dist/extensions/debug-round.js +204 -0
- package/dist/extensions/debug-round.js.map +1 -0
- package/dist/extensions/inject-bridge.js +603 -0
- package/dist/extensions/inject-bridge.js.map +1 -0
- package/dist/extensions/muted-theme.js +12 -0
- package/dist/extensions/muted-theme.js.map +1 -0
- package/dist/extensions/onboarding.js +175 -0
- package/dist/extensions/onboarding.js.map +1 -0
- package/dist/extensions/setup-replay.js +269 -0
- package/dist/extensions/setup-replay.js.map +1 -0
- package/dist/extensions/setup-scan.js +998 -0
- package/dist/extensions/setup-scan.js.map +1 -0
- package/dist/extensions/setup-triage.js +175 -0
- package/dist/extensions/setup-triage.js.map +1 -0
- package/dist/extensions/setup.js +1160 -0
- package/dist/extensions/setup.js.map +1 -0
- package/dist/extensions/tui-view.js +389 -0
- package/dist/extensions/tui-view.js.map +1 -0
- package/dist/extensions/vendor/openviking-pi/client.js +376 -0
- package/dist/extensions/vendor/openviking-pi/client.js.map +1 -0
- package/dist/extensions/vendor/openviking-pi/config.js +189 -0
- package/dist/extensions/vendor/openviking-pi/config.js.map +1 -0
- package/dist/extensions/vendor/openviking-pi/index.js +421 -0
- package/dist/extensions/vendor/openviking-pi/index.js.map +1 -0
- package/dist/extensions/vendor/openviking-pi/lib/capture-adapter.mjs +86 -0
- package/dist/extensions/vendor/openviking-pi/lib/capture-adapter.mjs.map +1 -0
- package/dist/extensions/vendor/openviking-pi/lib/takeover-core.mjs +408 -0
- package/dist/extensions/vendor/openviking-pi/lib/takeover-core.mjs.map +1 -0
- package/dist/extensions/vendor/openviking-pi/lib/uri-guard-adapter.mjs +41 -0
- package/dist/extensions/vendor/openviking-pi/lib/uri-guard-adapter.mjs.map +1 -0
- package/dist/extensions/vendor/openviking-pi/qcr-hint.js +290 -0
- package/dist/extensions/vendor/openviking-pi/qcr-hint.js.map +1 -0
- package/dist/extensions/vendor/openviking-pi/qcr-honest.js +148 -0
- package/dist/extensions/vendor/openviking-pi/qcr-honest.js.map +1 -0
- package/dist/extensions/vendor/openviking-pi/qcr-hooks.js +68 -0
- package/dist/extensions/vendor/openviking-pi/qcr-hooks.js.map +1 -0
- package/dist/extensions/vendor/openviking-pi/qcr-leaf.js +77 -0
- package/dist/extensions/vendor/openviking-pi/qcr-leaf.js.map +1 -0
- package/dist/extensions/vendor/openviking-pi/qcr-lesson.js +216 -0
- package/dist/extensions/vendor/openviking-pi/qcr-lesson.js.map +1 -0
- package/dist/extensions/vendor/openviking-pi/qcr-namespaces.js +154 -0
- package/dist/extensions/vendor/openviking-pi/qcr-namespaces.js.map +1 -0
- package/dist/extensions/vendor/openviking-pi/qcr-recall.js +254 -0
- package/dist/extensions/vendor/openviking-pi/qcr-recall.js.map +1 -0
- package/dist/extensions/vendor/openviking-pi/qcr-shim.js +94 -0
- package/dist/extensions/vendor/openviking-pi/qcr-shim.js.map +1 -0
- package/dist/extensions/vendor/openviking-pi/qcr-write.js +333 -0
- package/dist/extensions/vendor/openviking-pi/qcr-write.js.map +1 -0
- package/dist/extensions/vendor/openviking-pi/recall.js +108 -0
- package/dist/extensions/vendor/openviking-pi/recall.js.map +1 -0
- package/dist/extensions/vendor/openviking-pi/shared/capture-utils.mjs +529 -0
- package/dist/extensions/vendor/openviking-pi/shared/capture-utils.mjs.map +1 -0
- package/dist/extensions/vendor/openviking-pi/shared/credentials.mjs +205 -0
- package/dist/extensions/vendor/openviking-pi/shared/credentials.mjs.map +1 -0
- package/dist/extensions/vendor/openviking-pi/shared/pending-queue.mjs +442 -0
- package/dist/extensions/vendor/openviking-pi/shared/pending-queue.mjs.map +1 -0
- package/dist/extensions/vendor/openviking-pi/shared/profile-inject.mjs +286 -0
- package/dist/extensions/vendor/openviking-pi/shared/profile-inject.mjs.map +1 -0
- package/dist/extensions/vendor/openviking-pi/shared/recall-compress-core.mjs +189 -0
- package/dist/extensions/vendor/openviking-pi/shared/recall-compress-core.mjs.map +1 -0
- package/dist/extensions/vendor/openviking-pi/shared/recall-core.mjs +580 -0
- package/dist/extensions/vendor/openviking-pi/shared/recall-core.mjs.map +1 -0
- package/dist/extensions/vendor/openviking-pi/shared/retryable.mjs +11 -0
- package/dist/extensions/vendor/openviking-pi/shared/retryable.mjs.map +1 -0
- package/dist/extensions/vendor/openviking-pi/shared/session-model.mjs +63 -0
- package/dist/extensions/vendor/openviking-pi/shared/session-model.mjs.map +1 -0
- package/dist/extensions/vendor/openviking-pi/shared/uri-guard.mjs +58 -0
- package/dist/extensions/vendor/openviking-pi/shared/uri-guard.mjs.map +1 -0
- package/dist/extensions/vendor/openviking-pi/shared/workspace-peer.mjs +16 -0
- package/dist/extensions/vendor/openviking-pi/shared/workspace-peer.mjs.map +1 -0
- package/dist/extensions/vendor/openviking-pi/sync.js +127 -0
- package/dist/extensions/vendor/openviking-pi/sync.js.map +1 -0
- package/dist/extensions/vendor/openviking-pi/takeover.js +25 -0
- package/dist/extensions/vendor/openviking-pi/takeover.js.map +1 -0
- package/dist/extensions/vendor/openviking-pi/tools.js +417 -0
- package/dist/extensions/vendor/openviking-pi/tools.js.map +1 -0
- package/dist/src/anomalies.js +483 -0
- package/dist/src/anomalies.js.map +1 -0
- package/dist/src/answerworker.js +180 -0
- package/dist/src/answerworker.js.map +1 -0
- package/dist/src/appstamp.js +68 -0
- package/dist/src/appstamp.js.map +1 -0
- package/dist/src/archive/admission.js +3 -0
- package/dist/src/archive/admission.js.map +1 -0
- package/dist/src/archive/archivedb.js +474 -0
- package/dist/src/archive/archivedb.js.map +1 -0
- package/dist/src/archive/archiver.js +1770 -0
- package/dist/src/archive/archiver.js.map +1 -0
- package/dist/src/archive/archivewrite.js +198 -0
- package/dist/src/archive/archivewrite.js.map +1 -0
- package/dist/src/archive/bundle.js +266 -0
- package/dist/src/archive/bundle.js.map +1 -0
- package/dist/src/archive/dbmove.js +638 -0
- package/dist/src/archive/dbmove.js.map +1 -0
- package/dist/src/archive/duplicatecounts.js +80 -0
- package/dist/src/archive/duplicatecounts.js.map +1 -0
- package/dist/src/archive/eligibility.js +113 -0
- package/dist/src/archive/eligibility.js.map +1 -0
- package/dist/src/archive/extract.js +288 -0
- package/dist/src/archive/extract.js.map +1 -0
- package/dist/src/archive/fsutil.js +330 -0
- package/dist/src/archive/fsutil.js.map +1 -0
- package/dist/src/archive/gzfile.js +277 -0
- package/dist/src/archive/gzfile.js.map +1 -0
- package/dist/src/archive/kinds.js +22 -0
- package/dist/src/archive/kinds.js.map +1 -0
- package/dist/src/archive/livecheckpoint.js +178 -0
- package/dist/src/archive/livecheckpoint.js.map +1 -0
- package/dist/src/archive/liveretention.js +145 -0
- package/dist/src/archive/liveretention.js.map +1 -0
- package/dist/src/archive/manifest.js +284 -0
- package/dist/src/archive/manifest.js.map +1 -0
- package/dist/src/archive/overseer.js +283 -0
- package/dist/src/archive/overseer.js.map +1 -0
- package/dist/src/archive/piglobal.js +129 -0
- package/dist/src/archive/piglobal.js.map +1 -0
- package/dist/src/archive/plan.js +398 -0
- package/dist/src/archive/plan.js.map +1 -0
- package/dist/src/archive/prarchive.js +831 -0
- package/dist/src/archive/prarchive.js.map +1 -0
- package/dist/src/archive/prfiles.js +303 -0
- package/dist/src/archive/prfiles.js.map +1 -0
- package/dist/src/archive/report.js +169 -0
- package/dist/src/archive/report.js.map +1 -0
- package/dist/src/archive/restore.js +276 -0
- package/dist/src/archive/restore.js.map +1 -0
- package/dist/src/archive/rows.js +106 -0
- package/dist/src/archive/rows.js.map +1 -0
- package/dist/src/archive/seal.js +480 -0
- package/dist/src/archive/seal.js.map +1 -0
- package/dist/src/archive/status.js +528 -0
- package/dist/src/archive/status.js.map +1 -0
- package/dist/src/archive/storepass.js +409 -0
- package/dist/src/archive/storepass.js.map +1 -0
- package/dist/src/backfill.js +217 -0
- package/dist/src/backfill.js.map +1 -0
- package/dist/src/bench/cost.js +87 -0
- package/dist/src/bench/exec.js +76 -0
- package/dist/src/bench/values.js +14 -0
- package/dist/src/botcontrol.js +127 -0
- package/dist/src/botcontrol.js.map +1 -0
- package/dist/src/botworker.js +805 -0
- package/dist/src/botworker.js.map +1 -0
- package/dist/src/capture.js +234 -0
- package/dist/src/capture.js.map +1 -0
- package/dist/src/cases.js +1491 -0
- package/dist/src/cases.js.map +1 -0
- package/dist/src/channelfs.js +27 -0
- package/dist/src/channelfs.js.map +1 -0
- package/dist/src/checklist.js +312 -0
- package/dist/src/checklist.js.map +1 -0
- package/dist/src/ciretry.js +91 -0
- package/dist/src/ciretry.js.map +1 -0
- package/dist/src/cli.js +1916 -0
- package/dist/src/cli.js.map +1 -0
- package/dist/src/cliops.js +333 -0
- package/dist/src/cliops.js.map +1 -0
- package/dist/src/clonedir.js +15 -0
- package/dist/src/clonedir.js.map +1 -0
- package/dist/src/commandeffects.js +43 -0
- package/dist/src/commandeffects.js.map +1 -0
- package/dist/src/commands.js +1312 -0
- package/dist/src/commands.js.map +1 -0
- package/dist/src/companioninbox.js +203 -0
- package/dist/src/companioninbox.js.map +1 -0
- package/dist/src/compare.js +673 -0
- package/dist/src/compare.js.map +1 -0
- package/dist/src/config.js +1027 -0
- package/dist/src/config.js.map +1 -0
- package/dist/src/configcheck.js +111 -0
- package/dist/src/configcheck.js.map +1 -0
- package/dist/src/coreext.js +43 -0
- package/dist/src/coreext.js.map +1 -0
- package/dist/src/daemon.js +929 -0
- package/dist/src/daemon.js.map +1 -0
- package/dist/src/debuground.js +731 -0
- package/dist/src/debuground.js.map +1 -0
- package/dist/src/debugroundpi.js +219 -0
- package/dist/src/debugroundpi.js.map +1 -0
- package/dist/src/diagnostics.js +14 -0
- package/dist/src/diagnostics.js.map +1 -0
- package/dist/src/digest.js +139 -0
- package/dist/src/digest.js.map +1 -0
- package/dist/src/dispatch.js +1726 -0
- package/dist/src/dispatch.js.map +1 -0
- package/dist/src/engine/answerguard.js +18 -0
- package/dist/src/engine/answerguard.js.map +1 -0
- package/dist/src/engine/botsdk.js +262 -0
- package/dist/src/engine/botsdk.js.map +1 -0
- package/dist/src/engine/caserunner-sdk.js +332 -0
- package/dist/src/engine/caserunner-sdk.js.map +1 -0
- package/dist/src/engine/debugpi.js +194 -0
- package/dist/src/engine/debugpi.js.map +1 -0
- package/dist/src/eod.js +473 -0
- package/dist/src/eod.js.map +1 -0
- package/dist/src/eodpinwatch.js +391 -0
- package/dist/src/eodpinwatch.js.map +1 -0
- package/dist/src/escalation.js +1009 -0
- package/dist/src/escalation.js.map +1 -0
- package/dist/src/eventloop.js +779 -0
- package/dist/src/eventloop.js.map +1 -0
- package/dist/src/execboundary.js +430 -0
- package/dist/src/execboundary.js.map +1 -0
- package/dist/src/feedback.js +915 -0
- package/dist/src/feedback.js.map +1 -0
- package/dist/src/findingscheck.js +200 -0
- package/dist/src/findingscheck.js.map +1 -0
- package/dist/src/fsdenied.js +26 -0
- package/dist/src/fsdenied.js.map +1 -0
- package/dist/src/ghread.js +181 -0
- package/dist/src/ghread.js.map +1 -0
- package/dist/src/github.js +1063 -0
- package/dist/src/github.js.map +1 -0
- package/dist/src/homes.js +103 -0
- package/dist/src/homes.js.map +1 -0
- package/dist/src/hostroute.js +24 -0
- package/dist/src/hostroute.js.map +1 -0
- package/dist/src/improvements.js +625 -0
- package/dist/src/improvements.js.map +1 -0
- package/dist/src/install.js +946 -0
- package/dist/src/install.js.map +1 -0
- package/dist/src/installkind.js +40 -0
- package/dist/src/installkind.js.map +1 -0
- package/dist/src/ipc/capability.js +286 -0
- package/dist/src/ipc/capability.js.map +1 -0
- package/dist/src/ipc/client.js +226 -0
- package/dist/src/ipc/client.js.map +1 -0
- package/dist/src/ipc/control.js +70 -0
- package/dist/src/ipc/control.js.map +1 -0
- package/dist/src/ipc/ghreadshim.js +77 -0
- package/dist/src/ipc/ghreadshim.js.map +1 -0
- package/dist/src/ipc/launcher.js +65 -0
- package/dist/src/ipc/launcher.js.map +1 -0
- package/dist/src/ipc/permits.js +293 -0
- package/dist/src/ipc/permits.js.map +1 -0
- package/dist/src/ipc/postshim.js +137 -0
- package/dist/src/ipc/postshim.js.map +1 -0
- package/dist/src/ipc/protocol.js +239 -0
- package/dist/src/ipc/protocol.js.map +1 -0
- package/dist/src/ipc/server.js +312 -0
- package/dist/src/ipc/server.js.map +1 -0
- package/dist/src/ipc/workerspec.js +148 -0
- package/dist/src/ipc/workerspec.js.map +1 -0
- package/dist/src/learner/learner.js +1272 -0
- package/dist/src/learner/learner.js.map +1 -0
- package/dist/src/learner/miner.js +948 -0
- package/dist/src/learner/miner.js.map +1 -0
- package/dist/src/learner/packet.js +216 -0
- package/dist/src/learner/packet.js.map +1 -0
- package/dist/src/learner/receipt.js +135 -0
- package/dist/src/learner/receipt.js.map +1 -0
- package/dist/src/learner/status.js +38 -0
- package/dist/src/learner/status.js.map +1 -0
- package/dist/src/ledgercheck.js +120 -0
- package/dist/src/ledgercheck.js.map +1 -0
- package/dist/src/lessonledger.js +451 -0
- package/dist/src/lessonledger.js.map +1 -0
- package/dist/src/lessonselect.js +639 -0
- package/dist/src/lessonselect.js.map +1 -0
- package/dist/src/lessonshadow.js +540 -0
- package/dist/src/lessonshadow.js.map +1 -0
- package/dist/src/levers.js +302 -0
- package/dist/src/levers.js.map +1 -0
- package/dist/src/live/projection.js +450 -0
- package/dist/src/live/projection.js.map +1 -0
- package/dist/src/live/publish.js +83 -0
- package/dist/src/live/publish.js.map +1 -0
- package/dist/src/live/read.js +60 -0
- package/dist/src/live/read.js.map +1 -0
- package/dist/src/livecli.js +288 -0
- package/dist/src/livecli.js.map +1 -0
- package/dist/src/loginhint.js +42 -0
- package/dist/src/loginhint.js.map +1 -0
- package/dist/src/loginrefresh/atomic.js +66 -0
- package/dist/src/loginrefresh/atomic.js.map +1 -0
- package/dist/src/loginrefresh/lock.js +93 -0
- package/dist/src/loginrefresh/lock.js.map +1 -0
- package/dist/src/loginrefresh/main.js +58 -0
- package/dist/src/loginrefresh/main.js.map +1 -0
- package/dist/src/loginrefresh/pirefresh.js +49 -0
- package/dist/src/loginrefresh/pirefresh.js.map +1 -0
- package/dist/src/loginrefresh/refresher.js +286 -0
- package/dist/src/loginrefresh/refresher.js.map +1 -0
- package/dist/src/loginrefresh/supervisor.js +253 -0
- package/dist/src/loginrefresh/supervisor.js.map +1 -0
- package/dist/src/loginrefresh/types.js +4 -0
- package/dist/src/loginrefresh/types.js.map +1 -0
- package/dist/src/memory/context.js +251 -0
- package/dist/src/memory/context.js.map +1 -0
- package/dist/src/memory/datafence.js +5 -0
- package/dist/src/memory/datafence.js.map +1 -0
- package/dist/src/memory/delivery.js +131 -0
- package/dist/src/memory/delivery.js.map +1 -0
- package/dist/src/memory/eventtags.js +81 -0
- package/dist/src/memory/eventtags.js.map +1 -0
- package/dist/src/memory/health.js +713 -0
- package/dist/src/memory/health.js.map +1 -0
- package/dist/src/memory/import.js +504 -0
- package/dist/src/memory/import.js.map +1 -0
- package/dist/src/memory/layout.js +252 -0
- package/dist/src/memory/layout.js.map +1 -0
- package/dist/src/memory/legacy-file.js +100 -0
- package/dist/src/memory/legacy-file.js.map +1 -0
- package/dist/src/memory/lessons.js +370 -0
- package/dist/src/memory/lessons.js.map +1 -0
- package/dist/src/memory/ovsession.js +449 -0
- package/dist/src/memory/ovsession.js.map +1 -0
- package/dist/src/memory/relayout.js +259 -0
- package/dist/src/memory/relayout.js.map +1 -0
- package/dist/src/memory/roundactivity.js +180 -0
- package/dist/src/memory/roundactivity.js.map +1 -0
- package/dist/src/memory/selection.js +690 -0
- package/dist/src/memory/selection.js.map +1 -0
- package/dist/src/memory/shadowstatus.js +69 -0
- package/dist/src/memory/shadowstatus.js.map +1 -0
- package/dist/src/memory/store.js +83 -0
- package/dist/src/memory/store.js.map +1 -0
- package/dist/src/memory/textidentity.js +8 -0
- package/dist/src/memory/textidentity.js.map +1 -0
- package/dist/src/metrics.js +773 -0
- package/dist/src/metrics.js.map +1 -0
- package/dist/src/missingconfig.js +8 -0
- package/dist/src/missingconfig.js.map +1 -0
- package/dist/src/npmverbs.js +226 -0
- package/dist/src/npmverbs.js.map +1 -0
- package/dist/src/onboard/choices.js +191 -0
- package/dist/src/onboard/choices.js.map +1 -0
- package/dist/src/onboard/engine.js +344 -0
- package/dist/src/onboard/engine.js.map +1 -0
- package/dist/src/onboard/launch.js +83 -0
- package/dist/src/onboard/launch.js.map +1 -0
- package/dist/src/onboard/steps/appslug.js +211 -0
- package/dist/src/onboard/steps/appslug.js.map +1 -0
- package/dist/src/onboard/steps/bringup.js +408 -0
- package/dist/src/onboard/steps/bringup.js.map +1 -0
- package/dist/src/onboard/steps/compose.js +343 -0
- package/dist/src/onboard/steps/compose.js.map +1 -0
- package/dist/src/onboard/steps/configdelivery.js +50 -0
- package/dist/src/onboard/steps/configdelivery.js.map +1 -0
- package/dist/src/onboard/steps/configfill.js +1148 -0
- package/dist/src/onboard/steps/configfill.js.map +1 -0
- package/dist/src/onboard/steps/deploysh.js +80 -0
- package/dist/src/onboard/steps/deploysh.js.map +1 -0
- package/dist/src/onboard/steps/embeddings.js +472 -0
- package/dist/src/onboard/steps/embeddings.js.map +1 -0
- package/dist/src/onboard/steps/escalation.js +571 -0
- package/dist/src/onboard/steps/escalation.js.map +1 -0
- package/dist/src/onboard/steps/githubapp.js +1092 -0
- package/dist/src/onboard/steps/githubapp.js.map +1 -0
- package/dist/src/onboard/steps/golive.js +506 -0
- package/dist/src/onboard/steps/golive.js.map +1 -0
- package/dist/src/onboard/steps/index.js +67 -0
- package/dist/src/onboard/steps/index.js.map +1 -0
- package/dist/src/onboard/steps/ingress.js +762 -0
- package/dist/src/onboard/steps/ingress.js.map +1 -0
- package/dist/src/onboard/steps/inventory.js +420 -0
- package/dist/src/onboard/steps/inventory.js.map +1 -0
- package/dist/src/onboard/steps/llmauth.js +893 -0
- package/dist/src/onboard/steps/llmauth.js.map +1 -0
- package/dist/src/onboard/steps/memory.js +1060 -0
- package/dist/src/onboard/steps/memory.js.map +1 -0
- package/dist/src/onboard/steps/memorykeys.js +43 -0
- package/dist/src/onboard/steps/memorykeys.js.map +1 -0
- package/dist/src/onboard/steps/preflight.js +172 -0
- package/dist/src/onboard/steps/preflight.js.map +1 -0
- package/dist/src/onboard/steps/proof.js +502 -0
- package/dist/src/onboard/steps/proof.js.map +1 -0
- package/dist/src/onboard/steps/ssh.js +300 -0
- package/dist/src/onboard/steps/ssh.js.map +1 -0
- package/dist/src/onboard/steps/topology.js +291 -0
- package/dist/src/onboard/steps/topology.js.map +1 -0
- package/dist/src/onboard/steps/transport.js +511 -0
- package/dist/src/onboard/steps/transport.js.map +1 -0
- package/dist/src/onboard/steps/verifyup.js +362 -0
- package/dist/src/onboard/steps/verifyup.js.map +1 -0
- package/dist/src/onboard/steps/vmdeploy.js +368 -0
- package/dist/src/onboard/steps/vmdeploy.js.map +1 -0
- package/dist/src/onboard/toml.js +53 -0
- package/dist/src/onboard/toml.js.map +1 -0
- package/dist/src/opencli.js +508 -0
- package/dist/src/opencli.js.map +1 -0
- package/dist/src/openpr.js +457 -0
- package/dist/src/openpr.js.map +1 -0
- package/dist/src/openviking/client.js +821 -0
- package/dist/src/openviking/client.js.map +1 -0
- package/dist/src/openviking/context.js +218 -0
- package/dist/src/openviking/context.js.map +1 -0
- package/dist/src/openviking/embedder.js +457 -0
- package/dist/src/openviking/embedder.js.map +1 -0
- package/dist/src/openviking/gapsink.js +38 -0
- package/dist/src/openviking/gapsink.js.map +1 -0
- package/dist/src/openviking/health.js +69 -0
- package/dist/src/openviking/health.js.map +1 -0
- package/dist/src/openviking/index.js +24 -0
- package/dist/src/openviking/index.js.map +1 -0
- package/dist/src/openviking/lessonfind.js +54 -0
- package/dist/src/openviking/lessonfind.js.map +1 -0
- package/dist/src/openviking/probe.js +89 -0
- package/dist/src/openviking/probe.js.map +1 -0
- package/dist/src/openviking/proc.js +95 -0
- package/dist/src/openviking/proc.js.map +1 -0
- package/dist/src/openviking/provisionreport.js +144 -0
- package/dist/src/openviking/provisionreport.js.map +1 -0
- package/dist/src/openviking/reembed.js +317 -0
- package/dist/src/openviking/reembed.js.map +1 -0
- package/dist/src/openviking/relayout.js +153 -0
- package/dist/src/openviking/relayout.js.map +1 -0
- package/dist/src/openviking/sidecar-main.js +39 -0
- package/dist/src/openviking/sidecar-main.js.map +1 -0
- package/dist/src/openviking/sidecar.js +79 -0
- package/dist/src/openviking/sidecar.js.map +1 -0
- package/dist/src/openviking/store.js +1758 -0
- package/dist/src/openviking/store.js.map +1 -0
- package/dist/src/openviking/supervisor.js +422 -0
- package/dist/src/openviking/supervisor.js.map +1 -0
- package/dist/src/openviking/types.js +5 -0
- package/dist/src/openviking/types.js.map +1 -0
- package/dist/src/operator-recovery.js +172 -0
- package/dist/src/operator-recovery.js.map +1 -0
- package/dist/src/operatormessage.js +44 -0
- package/dist/src/operatormessage.js.map +1 -0
- package/dist/src/outcomes.js +1361 -0
- package/dist/src/outcomes.js.map +1 -0
- package/dist/src/piauthcopy.js +5 -0
- package/dist/src/piauthcopy.js.map +1 -0
- package/dist/src/picli.js +45 -0
- package/dist/src/picli.js.map +1 -0
- package/dist/src/picwd.js +25 -0
- package/dist/src/picwd.js.map +1 -0
- package/dist/src/pilogin.js +35 -0
- package/dist/src/pilogin.js.map +1 -0
- package/dist/src/pingpong.js +1028 -0
- package/dist/src/pingpong.js.map +1 -0
- package/dist/src/posting-journal.js +441 -0
- package/dist/src/posting-journal.js.map +1 -0
- package/dist/src/posting-outbox.js +662 -0
- package/dist/src/posting-outbox.js.map +1 -0
- package/dist/src/posting-receipt.js +203 -0
- package/dist/src/posting-receipt.js.map +1 -0
- package/dist/src/posting.js +348 -0
- package/dist/src/posting.js.map +1 -0
- package/dist/src/postops.js +637 -0
- package/dist/src/postops.js.map +1 -0
- package/dist/src/pr-activity.js +11 -0
- package/dist/src/pr-activity.js.map +1 -0
- package/dist/src/pr-author.js +5 -0
- package/dist/src/pr-author.js.map +1 -0
- package/dist/src/pr-conversation.js +355 -0
- package/dist/src/pr-conversation.js.map +1 -0
- package/dist/src/pr-session-lifecycle.js +542 -0
- package/dist/src/pr-session-lifecycle.js.map +1 -0
- package/dist/src/preflight.js +341 -0
- package/dist/src/preflight.js.map +1 -0
- package/dist/src/priorresolve.js +275 -0
- package/dist/src/priorresolve.js.map +1 -0
- package/dist/src/qcrbin.js +180 -0
- package/dist/src/qcrbin.js.map +1 -0
- package/dist/src/recheck.js +1398 -0
- package/dist/src/recheck.js.map +1 -0
- package/dist/src/reconcile.js +820 -0
- package/dist/src/reconcile.js.map +1 -0
- package/dist/src/recordedfindings.js +15 -0
- package/dist/src/recordedfindings.js.map +1 -0
- package/dist/src/regression.js +546 -0
- package/dist/src/regression.js.map +1 -0
- package/dist/src/replay.js +598 -0
- package/dist/src/replay.js.map +1 -0
- package/dist/src/requiredpasses.js +113 -0
- package/dist/src/requiredpasses.js.map +1 -0
- package/dist/src/resumecheck.js +168 -0
- package/dist/src/resumecheck.js.map +1 -0
- package/dist/src/reviewcli.js +671 -0
- package/dist/src/reviewcli.js.map +1 -0
- package/dist/src/reviewcontext.js +22 -0
- package/dist/src/reviewcontext.js.map +1 -0
- package/dist/src/reviewdetach.js +574 -0
- package/dist/src/reviewdetach.js.map +1 -0
- package/dist/src/reviewhistory.js +284 -0
- package/dist/src/reviewhistory.js.map +1 -0
- package/dist/src/reviewhome.js +26 -0
- package/dist/src/reviewhome.js.map +1 -0
- package/dist/src/reviewmemory.js +110 -0
- package/dist/src/reviewmemory.js.map +1 -0
- package/dist/src/reviewrendercli.js +104 -0
- package/dist/src/reviewrendercli.js.map +1 -0
- package/dist/src/round-work.js +20 -0
- package/dist/src/round-work.js.map +1 -0
- package/dist/src/runhost.js +72 -0
- package/dist/src/runhost.js.map +1 -0
- package/dist/src/scanauth.js +25 -0
- package/dist/src/scanauth.js.map +1 -0
- package/dist/src/scanbox.js +846 -0
- package/dist/src/scanbox.js.map +1 -0
- package/dist/src/scancli.js +1074 -0
- package/dist/src/scancli.js.map +1 -0
- package/dist/src/scandriverlog.js +30 -0
- package/dist/src/scandriverlog.js.map +1 -0
- package/dist/src/session-bridge.js +75 -0
- package/dist/src/session-bridge.js.map +1 -0
- package/dist/src/session-command-payload.js +29 -0
- package/dist/src/session-command-payload.js.map +1 -0
- package/dist/src/session-command-supervisor.js +939 -0
- package/dist/src/session-command-supervisor.js.map +1 -0
- package/dist/src/sessionjsonl.js +85 -0
- package/dist/src/sessionjsonl.js.map +1 -0
- package/dist/src/sessions.js +541 -0
- package/dist/src/sessions.js.map +1 -0
- package/dist/src/setup/adapters.js +330 -0
- package/dist/src/setup/adapters.js.map +1 -0
- package/dist/src/setup/baselines.js +64 -0
- package/dist/src/setup/baselines.js.map +1 -0
- package/dist/src/setup/caserunner.js +554 -0
- package/dist/src/setup/caserunner.js.map +1 -0
- package/dist/src/setup/configcommit.js +358 -0
- package/dist/src/setup/configcommit.js.map +1 -0
- package/dist/src/setup/engine.js +672 -0
- package/dist/src/setup/engine.js.map +1 -0
- package/dist/src/setup/facts.js +593 -0
- package/dist/src/setup/facts.js.map +1 -0
- package/dist/src/setup/findings.js +948 -0
- package/dist/src/setup/findings.js.map +1 -0
- package/dist/src/setup/history.js +338 -0
- package/dist/src/setup/history.js.map +1 -0
- package/dist/src/setup/interview.js +185 -0
- package/dist/src/setup/interview.js.map +1 -0
- package/dist/src/setup/ledger.js +207 -0
- package/dist/src/setup/ledger.js.map +1 -0
- package/dist/src/setup/pins.js +43 -0
- package/dist/src/setup/pins.js.map +1 -0
- package/dist/src/setup/portrait.js +432 -0
- package/dist/src/setup/portrait.js.map +1 -0
- package/dist/src/setup/presentation.js +9 -0
- package/dist/src/setup/presentation.js.map +1 -0
- package/dist/src/setup/profile.js +417 -0
- package/dist/src/setup/profile.js.map +1 -0
- package/dist/src/setup/replay.js +240 -0
- package/dist/src/setup/replay.js.map +1 -0
- package/dist/src/setup/replaybuild.js +311 -0
- package/dist/src/setup/replaybuild.js.map +1 -0
- package/dist/src/setup/replayrun.js +806 -0
- package/dist/src/setup/replayrun.js.map +1 -0
- package/dist/src/setup/replaysource.js +169 -0
- package/dist/src/setup/replaysource.js.map +1 -0
- package/dist/src/setup/retune.js +333 -0
- package/dist/src/setup/retune.js.map +1 -0
- package/dist/src/setup/retunelog.js +51 -0
- package/dist/src/setup/retunelog.js.map +1 -0
- package/dist/src/setup/scan.js +725 -0
- package/dist/src/setup/scan.js.map +1 -0
- package/dist/src/setup/scanbuild.js +133 -0
- package/dist/src/setup/scanbuild.js.map +1 -0
- package/dist/src/setup/scanfacts.js +96 -0
- package/dist/src/setup/scanfacts.js.map +1 -0
- package/dist/src/setup/scanlease.js +251 -0
- package/dist/src/setup/scanlease.js.map +1 -0
- package/dist/src/setup/scanreport.js +289 -0
- package/dist/src/setup/scanreport.js.map +1 -0
- package/dist/src/setup/scanrun.js +1733 -0
- package/dist/src/setup/scanrun.js.map +1 -0
- package/dist/src/setup/scantree.js +257 -0
- package/dist/src/setup/scantree.js.map +1 -0
- package/dist/src/setup/stage2.js +617 -0
- package/dist/src/setup/stage2.js.map +1 -0
- package/dist/src/setup/stage2request.js +259 -0
- package/dist/src/setup/stage2request.js.map +1 -0
- package/dist/src/setup/survey.js +307 -0
- package/dist/src/setup/survey.js.map +1 -0
- package/dist/src/setup/surveyrun.js +549 -0
- package/dist/src/setup/surveyrun.js.map +1 -0
- package/dist/src/setup/textsniff.js +89 -0
- package/dist/src/setup/textsniff.js.map +1 -0
- package/dist/src/setup/trackersink.js +395 -0
- package/dist/src/setup/trackersink.js.map +1 -0
- package/dist/src/setup/triage.js +636 -0
- package/dist/src/setup/triage.js.map +1 -0
- package/dist/src/setup/tuning.js +585 -0
- package/dist/src/setup/tuning.js.map +1 -0
- package/dist/src/setup/unitcut.js +437 -0
- package/dist/src/setup/unitcut.js.map +1 -0
- package/dist/src/singleflight.js +72 -0
- package/dist/src/singleflight.js.map +1 -0
- package/dist/src/slack/announce.js +725 -0
- package/dist/src/slack/announce.js.map +1 -0
- package/dist/src/slack/api.js +227 -0
- package/dist/src/slack/api.js.map +1 -0
- package/dist/src/slack/messages.js +158 -0
- package/dist/src/slack/messages.js.map +1 -0
- package/dist/src/slack/origin.js +550 -0
- package/dist/src/slack/origin.js.map +1 -0
- package/dist/src/slack/policy.js +225 -0
- package/dist/src/slack/policy.js.map +1 -0
- package/dist/src/slack/reply.js +153 -0
- package/dist/src/slack/reply.js.map +1 -0
- package/dist/src/slack/router.js +189 -0
- package/dist/src/slack/router.js.map +1 -0
- package/dist/src/slack/socket.js +323 -0
- package/dist/src/slack/socket.js.map +1 -0
- package/dist/src/slack/types.js +11 -0
- package/dist/src/slack/types.js.map +1 -0
- package/dist/src/sqlitehint.js +30 -0
- package/dist/src/sqlitehint.js.map +1 -0
- package/dist/src/ssh-observation.js +25 -0
- package/dist/src/ssh-observation.js.map +1 -0
- package/dist/src/state.js +5869 -0
- package/dist/src/state.js.map +1 -0
- package/dist/src/statemachine.js +220 -0
- package/dist/src/statemachine.js.map +1 -0
- package/dist/src/stubs.js +52 -0
- package/dist/src/stubs.js.map +1 -0
- package/dist/src/takeover.js +506 -0
- package/dist/src/takeover.js.map +1 -0
- package/dist/src/takeoverpi.js +249 -0
- package/dist/src/takeoverpi.js.map +1 -0
- package/dist/src/terminal-failure.js +26 -0
- package/dist/src/terminal-failure.js.map +1 -0
- package/dist/src/thread-resolution-refusal.js +4 -0
- package/dist/src/thread-resolution-refusal.js.map +1 -0
- package/dist/src/tmux.js +126 -0
- package/dist/src/tmux.js.map +1 -0
- package/dist/src/trackerid.js +43 -0
- package/dist/src/trackerid.js.map +1 -0
- package/dist/src/transcriptindex.js +512 -0
- package/dist/src/transcriptindex.js.map +1 -0
- package/dist/src/tui/actions.js +404 -0
- package/dist/src/tui/actions.js.map +1 -0
- package/dist/src/tui/alerts.js +523 -0
- package/dist/src/tui/alerts.js.map +1 -0
- package/dist/src/tui/app.js +1835 -0
- package/dist/src/tui/app.js.map +1 -0
- package/dist/src/tui/archiveops.js +164 -0
- package/dist/src/tui/archiveops.js.map +1 -0
- package/dist/src/tui/autofile.js +191 -0
- package/dist/src/tui/autofile.js.map +1 -0
- package/dist/src/tui/board.js +1143 -0
- package/dist/src/tui/board.js.map +1 -0
- package/dist/src/tui/candidates.js +699 -0
- package/dist/src/tui/candidates.js.map +1 -0
- package/dist/src/tui/cellwidth.js +151 -0
- package/dist/src/tui/cellwidth.js.map +1 -0
- package/dist/src/tui/changelog-store.js +404 -0
- package/dist/src/tui/changelog-store.js.map +1 -0
- package/dist/src/tui/chord.js +26 -0
- package/dist/src/tui/chord.js.map +1 -0
- package/dist/src/tui/clipboard.js +81 -0
- package/dist/src/tui/clipboard.js.map +1 -0
- package/dist/src/tui/cmdbar.js +253 -0
- package/dist/src/tui/cmdbar.js.map +1 -0
- package/dist/src/tui/companion.js +1275 -0
- package/dist/src/tui/companion.js.map +1 -0
- package/dist/src/tui/companionplan.js +200 -0
- package/dist/src/tui/companionplan.js.map +1 -0
- package/dist/src/tui/companiontab.js +61 -0
- package/dist/src/tui/companiontab.js.map +1 -0
- package/dist/src/tui/confighome.js +105 -0
- package/dist/src/tui/confighome.js.map +1 -0
- package/dist/src/tui/connect.js +44 -0
- package/dist/src/tui/connect.js.map +1 -0
- package/dist/src/tui/deploy-ceremony.js +75 -0
- package/dist/src/tui/deploy-ceremony.js.map +1 -0
- package/dist/src/tui/deploy-drain.js +93 -0
- package/dist/src/tui/deploy-drain.js.map +1 -0
- package/dist/src/tui/deploy-process.js +234 -0
- package/dist/src/tui/deploy-process.js.map +1 -0
- package/dist/src/tui/deploytab.js +1263 -0
- package/dist/src/tui/deploytab.js.map +1 -0
- package/dist/src/tui/design.js +111 -0
- package/dist/src/tui/design.js.map +1 -0
- package/dist/src/tui/digesttab.js +148 -0
- package/dist/src/tui/digesttab.js.map +1 -0
- package/dist/src/tui/emptystate.js +32 -0
- package/dist/src/tui/emptystate.js.map +1 -0
- package/dist/src/tui/evidence.js +790 -0
- package/dist/src/tui/evidence.js.map +1 -0
- package/dist/src/tui/feed.js +415 -0
- package/dist/src/tui/feed.js.map +1 -0
- package/dist/src/tui/feedbacktab.js +907 -0
- package/dist/src/tui/feedbacktab.js.map +1 -0
- package/dist/src/tui/filters.js +72 -0
- package/dist/src/tui/filters.js.map +1 -0
- package/dist/src/tui/ghread.js +272 -0
- package/dist/src/tui/ghread.js.map +1 -0
- package/dist/src/tui/historypull.js +61 -0
- package/dist/src/tui/historypull.js.map +1 -0
- package/dist/src/tui/inboxpull.js +197 -0
- package/dist/src/tui/inboxpull.js.map +1 -0
- package/dist/src/tui/index.js +1425 -0
- package/dist/src/tui/index.js.map +1 -0
- package/dist/src/tui/issuesink.js +206 -0
- package/dist/src/tui/issuesink.js.map +1 -0
- package/dist/src/tui/jsonfile.js +32 -0
- package/dist/src/tui/jsonfile.js.map +1 -0
- package/dist/src/tui/keys.js +97 -0
- package/dist/src/tui/keys.js.map +1 -0
- package/dist/src/tui/layout.js +70 -0
- package/dist/src/tui/layout.js.map +1 -0
- package/dist/src/tui/learning.js +469 -0
- package/dist/src/tui/learning.js.map +1 -0
- package/dist/src/tui/lessonswitch.js +103 -0
- package/dist/src/tui/lessonswitch.js.map +1 -0
- package/dist/src/tui/livecontrol.js +167 -0
- package/dist/src/tui/livecontrol.js.map +1 -0
- package/dist/src/tui/memforward.js +310 -0
- package/dist/src/tui/memforward.js.map +1 -0
- package/dist/src/tui/memincident.js +287 -0
- package/dist/src/tui/memincident.js.map +1 -0
- package/dist/src/tui/memory.js +485 -0
- package/dist/src/tui/memory.js.map +1 -0
- package/dist/src/tui/memtab.js +513 -0
- package/dist/src/tui/memtab.js.map +1 -0
- package/dist/src/tui/metricstab.js +979 -0
- package/dist/src/tui/metricstab.js.map +1 -0
- package/dist/src/tui/model.js +104 -0
- package/dist/src/tui/model.js.map +1 -0
- package/dist/src/tui/mouse.js +83 -0
- package/dist/src/tui/mouse.js.map +1 -0
- package/dist/src/tui/msg.js +196 -0
- package/dist/src/tui/msg.js.map +1 -0
- package/dist/src/tui/nativecompanion.js +321 -0
- package/dist/src/tui/nativecompanion.js.map +1 -0
- package/dist/src/tui/openurl.js +17 -0
- package/dist/src/tui/openurl.js.map +1 -0
- package/dist/src/tui/paint.js +501 -0
- package/dist/src/tui/paint.js.map +1 -0
- package/dist/src/tui/panes.js +74 -0
- package/dist/src/tui/panes.js.map +1 -0
- package/dist/src/tui/pipane.js +166 -0
- package/dist/src/tui/pipane.js.map +1 -0
- package/dist/src/tui/poll-hooks.js +11 -0
- package/dist/src/tui/poll-hooks.js.map +1 -0
- package/dist/src/tui/poll.js +113 -0
- package/dist/src/tui/poll.js.map +1 -0
- package/dist/src/tui/proposals.js +715 -0
- package/dist/src/tui/proposals.js.map +1 -0
- package/dist/src/tui/prpause.js +132 -0
- package/dist/src/tui/prpause.js.map +1 -0
- package/dist/src/tui/reload.js +351 -0
- package/dist/src/tui/reload.js.map +1 -0
- package/dist/src/tui/remote.js +312 -0
- package/dist/src/tui/remote.js.map +1 -0
- package/dist/src/tui/rollover.js +767 -0
- package/dist/src/tui/rollover.js.map +1 -0
- package/dist/src/tui/scantab.js +1053 -0
- package/dist/src/tui/scantab.js.map +1 -0
- package/dist/src/tui/setupentry.js +81 -0
- package/dist/src/tui/setupentry.js.map +1 -0
- package/dist/src/tui/sidebar.js +602 -0
- package/dist/src/tui/sidebar.js.map +1 -0
- package/dist/src/tui/sshtriage.js +393 -0
- package/dist/src/tui/sshtriage.js.map +1 -0
- package/dist/src/tui/startup.js +172 -0
- package/dist/src/tui/startup.js.map +1 -0
- package/dist/src/tui/tabs.js +41 -0
- package/dist/src/tui/tabs.js.map +1 -0
- package/dist/src/tui/termmirror.js +217 -0
- package/dist/src/tui/termmirror.js.map +1 -0
- package/dist/src/tui/theme.js +95 -0
- package/dist/src/tui/theme.js.map +1 -0
- package/dist/src/tui/ticketstab.js +224 -0
- package/dist/src/tui/ticketstab.js.map +1 -0
- package/dist/src/tui/tmuxsocket.js +101 -0
- package/dist/src/tui/tmuxsocket.js.map +1 -0
- package/dist/src/tui/trackertickets.js +77 -0
- package/dist/src/tui/trackertickets.js.map +1 -0
- package/dist/src/tui/unwedge.js +245 -0
- package/dist/src/tui/unwedge.js.map +1 -0
- package/dist/src/tui/viewer.js +479 -0
- package/dist/src/tui/viewer.js.map +1 -0
- package/dist/src/tui/viewerinput.js +125 -0
- package/dist/src/tui/viewerinput.js.map +1 -0
- package/dist/src/updatenotice.js +143 -0
- package/dist/src/updatenotice.js.map +1 -0
- package/dist/src/usage.js +1007 -0
- package/dist/src/usage.js.map +1 -0
- package/dist/src/verdicts.js +3848 -0
- package/dist/src/verdicts.js.map +1 -0
- package/dist/src/version.js +58 -0
- package/dist/src/version.js.map +1 -0
- package/dist/src/wakes.js +411 -0
- package/dist/src/wakes.js.map +1 -0
- package/dist/src/webhook.js +725 -0
- package/dist/src/webhook.js.map +1 -0
- package/dist/src/webhookheal.js +238 -0
- package/dist/src/webhookheal.js.map +1 -0
- package/dist/src/wire.js +1801 -0
- package/dist/src/wire.js.map +1 -0
- package/dist/src/worker-session-host.js +531 -0
- package/dist/src/worker-session-host.js.map +1 -0
- package/dist/src/workerbreaker.js +112 -0
- package/dist/src/workerbreaker.js.map +1 -0
- package/dist/src/workercontrol.js +49 -0
- package/dist/src/workercontrol.js.map +1 -0
- package/dist/src/workerlease.js +351 -0
- package/dist/src/workerlease.js.map +1 -0
- package/dist/src/workerliveness.js +84 -0
- package/dist/src/workerliveness.js.map +1 -0
- package/dist/src/workerlog.js +65 -0
- package/dist/src/workerlog.js.map +1 -0
- package/dist/src/workerruntime.js +10 -0
- package/dist/src/workerruntime.js.map +1 -0
- package/dist/src/workersupervisor.js +1206 -0
- package/dist/src/workersupervisor.js.map +1 -0
- package/dist/src/workerusage.js +23 -0
- package/dist/src/workerusage.js.map +1 -0
- package/dist/src/workerworld.js +332 -0
- package/dist/src/workerworld.js.map +1 -0
- package/dist/src/worktrees.js +984 -0
- package/dist/src/worktrees.js.map +1 -0
- package/docker/Dockerfile +284 -0
- package/docker/Dockerfile.dockerignore +35 -0
- package/docker/README.md +221 -0
- package/docker/companion/Dockerfile +139 -0
- package/docker/companion/agents/evidence.md +34 -0
- package/docker/companion/agents/explore.md +26 -0
- package/docker/companion/agents/pass-survey.md +49 -0
- package/docker/companion/agents/study.md +26 -0
- package/docker/companion/entrypoint.sh +343 -0
- package/docker/companion/memory-health.mjs +269 -0
- package/docker/entrypoint.sh +782 -0
- package/docker/memory/Dockerfile +191 -0
- package/docker/memory/Dockerfile.dockerignore +20 -0
- package/docker/memory/README.md +60 -0
- package/docker/memory/entrypoint.sh +163 -0
- package/docker/setid/qcr-setid.c +109 -0
- package/docker/tunnel/Dockerfile.cloudflared +51 -0
- package/docker/tunnel/Dockerfile.ngrok +40 -0
- package/docker/tunnel/sidecar.mjs +411 -0
- package/docker/vm/tmux.conf +26 -0
- package/docker-compose.yml +348 -0
- package/extensions/cli/bash-guard-ambient.ts +71 -0
- package/extensions/cli/bash-guard.ts +12 -0
- package/extensions/cli/companion-guard.ts +8 -0
- package/extensions/cli/debug-round.ts +9 -0
- package/extensions/cli/inject-bridge.ts +8 -0
- package/extensions/cli/ledger-pin.ts +13 -0
- package/extensions/cli/lens.ts +22 -0
- package/extensions/cli/muted-theme.ts +9 -0
- package/extensions/cli/onboarding.ts +8 -0
- package/extensions/cli/setup.ts +8 -0
- package/extensions/cli/tui-view.ts +8 -0
- package/extensions/companion-guard.ts +411 -0
- package/extensions/debug-round.ts +281 -0
- package/extensions/inject-bridge.ts +630 -0
- package/extensions/inline-bash.ts +102 -0
- package/extensions/muted-theme.json +68 -0
- package/extensions/muted-theme.ts +23 -0
- package/extensions/onboarding.ts +233 -0
- package/extensions/setup-replay.ts +336 -0
- package/extensions/setup-scan.ts +1236 -0
- package/extensions/setup-triage.ts +190 -0
- package/extensions/setup.ts +1428 -0
- package/extensions/tui-view.ts +489 -0
- package/extensions/vendor/openviking-pi/LICENSE +201 -0
- package/extensions/vendor/openviking-pi/NOTICE +68 -0
- package/extensions/vendor/openviking-pi/README.md +550 -0
- package/extensions/vendor/openviking-pi/client.ts +588 -0
- package/extensions/vendor/openviking-pi/config.json +28 -0
- package/extensions/vendor/openviking-pi/config.ts +271 -0
- package/extensions/vendor/openviking-pi/index.ts +523 -0
- package/extensions/vendor/openviking-pi/lib/capture-adapter.d.mts +10 -0
- package/extensions/vendor/openviking-pi/lib/capture-adapter.mjs +88 -0
- package/extensions/vendor/openviking-pi/lib/takeover-core.d.mts +80 -0
- package/extensions/vendor/openviking-pi/lib/takeover-core.mjs +406 -0
- package/extensions/vendor/openviking-pi/lib/uri-guard-adapter.d.mts +1 -0
- package/extensions/vendor/openviking-pi/lib/uri-guard-adapter.mjs +42 -0
- package/extensions/vendor/openviking-pi/patches/0000-vendoring-and-build-conformance.patch +726 -0
- package/extensions/vendor/openviking-pi/patches/0001-remove-session-before-compact-handler.patch +101 -0
- package/extensions/vendor/openviking-pi/patches/0002-no-ambient-work-in-subagent-leaves.patch +202 -0
- package/extensions/vendor/openviking-pi/patches/0003-never-auto-create-openviking-sessions.patch +120 -0
- package/extensions/vendor/openviking-pi/patches/0004-viking-write-durable-batch-write.patch +521 -0
- package/extensions/vendor/openviking-pi/patches/0005-second-memory-namespace.patch +786 -0
- package/extensions/vendor/openviking-pi/patches/0006-writes-that-stick-and-honest-errors.patch +996 -0
- package/extensions/vendor/openviking-pi/patches/0007-bounded-recall-no-commit.patch +794 -0
- package/extensions/vendor/openviking-pi/patches/0008-pr-derived-recall.patch +379 -0
- package/extensions/vendor/openviking-pi/patches/0009-pr-session-tools-in-round-areas.patch +375 -0
- package/extensions/vendor/openviking-pi/patches/0010-typed-lessons.patch +448 -0
- package/extensions/vendor/openviking-pi/patches/0011-review-session-memory-boundary.patch +382 -0
- package/extensions/vendor/openviking-pi/patches/0012-typecheck-against-the-real-pi.patch +129 -0
- package/extensions/vendor/openviking-pi/patches/0013-createopenviking-factory.patch +1168 -0
- package/extensions/vendor/openviking-pi/qcr-hint.ts +313 -0
- package/extensions/vendor/openviking-pi/qcr-honest.ts +183 -0
- package/extensions/vendor/openviking-pi/qcr-hooks.ts +87 -0
- package/extensions/vendor/openviking-pi/qcr-leaf.ts +78 -0
- package/extensions/vendor/openviking-pi/qcr-lesson.ts +231 -0
- package/extensions/vendor/openviking-pi/qcr-namespaces.ts +198 -0
- package/extensions/vendor/openviking-pi/qcr-recall.ts +288 -0
- package/extensions/vendor/openviking-pi/qcr-shim.ts +142 -0
- package/extensions/vendor/openviking-pi/qcr-write.ts +427 -0
- package/extensions/vendor/openviking-pi/recall.ts +149 -0
- package/extensions/vendor/openviking-pi/shared/capture-utils.d.mts +9 -0
- package/extensions/vendor/openviking-pi/shared/capture-utils.mjs +515 -0
- package/extensions/vendor/openviking-pi/shared/credentials.d.mts +14 -0
- package/extensions/vendor/openviking-pi/shared/credentials.mjs +228 -0
- package/extensions/vendor/openviking-pi/shared/pending-queue.d.mts +19 -0
- package/extensions/vendor/openviking-pi/shared/pending-queue.mjs +458 -0
- package/extensions/vendor/openviking-pi/shared/profile-inject.d.mts +17 -0
- package/extensions/vendor/openviking-pi/shared/profile-inject.mjs +294 -0
- package/extensions/vendor/openviking-pi/shared/recall-compress-core.mjs +195 -0
- package/extensions/vendor/openviking-pi/shared/recall-core.d.mts +13 -0
- package/extensions/vendor/openviking-pi/shared/recall-core.mjs +609 -0
- package/extensions/vendor/openviking-pi/shared/retryable.mjs +9 -0
- package/extensions/vendor/openviking-pi/shared/session-model.d.mts +3 -0
- package/extensions/vendor/openviking-pi/shared/session-model.mjs +57 -0
- package/extensions/vendor/openviking-pi/shared/uri-guard.mjs +56 -0
- package/extensions/vendor/openviking-pi/shared/workspace-peer.d.mts +5 -0
- package/extensions/vendor/openviking-pi/shared/workspace-peer.mjs +16 -0
- package/extensions/vendor/openviking-pi/sync.ts +163 -0
- package/extensions/vendor/openviking-pi/takeover.ts +36 -0
- package/extensions/vendor/openviking-pi/tools.ts +479 -0
- package/extensions/vendor/openviking-pi/upstream.json +48 -0
- package/package.json +138 -4
- package/profiles/default/README.md +21 -0
- package/profiles/default/addenda/.gitkeep +0 -0
- package/profiles/default/profile.toml +17 -0
- package/profiles/example-django/README.md +21 -0
- package/profiles/example-django/addenda/10-migration-discipline.md +13 -0
- package/profiles/example-django/addenda/20-new-column-nullability.md +12 -0
- package/profiles/example-django/addenda/30-test-doctrine.md +12 -0
- package/profiles/example-django/addenda/40-orm-usage.md +19 -0
- package/profiles/example-django/addenda/50-declared-shape.md +10 -0
- package/profiles/example-django/addenda/60-config-placement.md +14 -0
- package/profiles/example-django/heuristics.toml +24 -0
- package/profiles/example-django/profile.toml +9 -0
- package/profiles/fast/README.md +24 -0
- package/profiles/fast/profile.toml +59 -0
- package/profiles/minimal/README.md +17 -0
- package/profiles/minimal/addenda/.gitkeep +0 -0
- package/profiles/minimal/profile.toml +8 -0
- package/profiles/snappy/README.md +24 -0
- package/profiles/snappy/addenda/10-race-as-question.md +6 -0
- package/profiles/snappy/profile.toml +46 -0
- package/prompts/companion-day-open.md +46 -0
- package/prompts/companion-first-run.md +104 -0
- package/prompts/companion.md +821 -0
- package/prompts/conversation-system.md +117 -0
- package/prompts/eod-sweep.md +120 -0
- package/prompts/onboarding.md +74 -0
- package/prompts/pass-common.md +52 -0
- package/prompts/pass-quality-common.md +22 -0
- package/prompts/round-first.md +874 -0
- package/prompts/round-follow-up.md +991 -0
- package/prompts/round-re-review.md +889 -0
- package/prompts/round-recheck.md +196 -0
- package/prompts/round-staleness.md +856 -0
- package/schemas/review-result.v1.json +391 -0
- package/schemas/scan-result.v1.json +421 -0
- package/schemas/version.v1.json +57 -0
- package/scripts/approve-pr.sh +16 -0
- package/scripts/build-stamp.mjs +67 -0
- package/scripts/commit-verdict.sh +155 -0
- package/scripts/companion-e2e.sh +250 -0
- package/scripts/companion-memory-forward.mjs +1966 -0
- package/scripts/config-fingerprint.sh +28 -0
- package/scripts/deploy-manifest.mjs +141 -0
- package/scripts/deploy.sh +1435 -0
- package/scripts/directory-lock.mjs +193 -0
- package/scripts/heartbeat-fresh.mjs +54 -0
- package/scripts/install-locator.sh +328 -0
- package/scripts/install-wrapper.sh +76 -0
- package/scripts/memory-search-check.mjs +98 -0
- package/scripts/mint-token.sh +212 -0
- package/scripts/openviking-embedder.py +392 -0
- package/scripts/openviking-ov-conf.mjs +161 -0
- package/scripts/openviking-provision.mjs +1122 -0
- package/scripts/openviking-reembed.mjs +1154 -0
- package/scripts/post-review.sh +16 -0
- package/scripts/pr-checks.sh +256 -0
- package/scripts/pr-context.py +174 -0
- package/scripts/pr-context.sh +63 -0
- package/scripts/pr-self.sh +144 -0
- package/scripts/pr-threads.sh +145 -0
- package/scripts/qcr +15 -0
- package/scripts/qcr-host.sh +769 -0
- package/scripts/render-review.sh +24 -0
- package/scripts/reply-thread.sh +16 -0
- package/scripts/required-passes.py +112 -0
- package/scripts/smoke-companion.sh +211 -0
- package/scripts/tui-deploy-runner.mjs +61 -0
- package/scripts/verify-dist.mjs +201 -0
- package/scripts/vm-ghcr-login.sh +188 -0
- package/seeds/agents/pass-baseline.md +61 -0
- package/seeds/agents/pass-contracts.md +30 -0
- package/seeds/agents/pass-data-safety.md +54 -0
- package/seeds/agents/pass-delivery.md +37 -0
- package/seeds/agents/pass-prod-risk.md +24 -0
- package/seeds/agents/pass-security.md +31 -0
- package/seeds/agents/pass-statelessness.md +27 -0
- package/seeds/agents/pass-ui.md +23 -0
- package/seeds/agents/pass-validate.md +50 -0
- package/seeds/prompts/conversation-system.md +117 -0
- package/seeds/prompts/eod-sweep.md +120 -0
- package/seeds/prompts/pass-common.md +52 -0
- package/skills/companion/adjudicate/SKILL.md +247 -0
- package/skills/companion/evidence/SKILL.md +150 -0
- package/skills/companion/land/SKILL.md +200 -0
- package/skills/companion/replay/SKILL.md +138 -0
- package/skills/companion/setup/SKILL.md +167 -0
- package/skills/companion/survey/SKILL.md +160 -0
- package/skills/selfimprove/README.md +34 -0
- package/skills/selfimprove/SKILL.md +127 -0
- package/skills/selfimprove/agents/openai.yaml +2 -0
- package/skills/selfimprove/commands/selfimprove.md +6 -0
- package/skills/selfimprove/references/fix-loop.md +49 -0
- package/skills/selfimprove/references/ledger.md +20 -0
- package/skills/selfimprove/references/setup.md +40 -0
- package/src/anomalies.ts +566 -0
- package/src/answerworker.ts +251 -0
- package/src/appstamp.ts +65 -0
- package/src/archive/admission.ts +3 -0
- package/src/archive/archivedb.ts +548 -0
- package/src/archive/archiver.ts +1963 -0
- package/src/archive/archivewrite.ts +279 -0
- package/src/archive/bundle.ts +333 -0
- package/src/archive/dbmove.ts +911 -0
- package/src/archive/duplicatecounts.ts +98 -0
- package/src/archive/eligibility.ts +181 -0
- package/src/archive/extract.ts +342 -0
- package/src/archive/fsutil.ts +415 -0
- package/src/archive/gzfile.ts +370 -0
- package/src/archive/kinds.ts +21 -0
- package/src/archive/livecheckpoint.ts +217 -0
- package/src/archive/liveretention.ts +180 -0
- package/src/archive/manifest.ts +519 -0
- package/src/archive/overseer.ts +336 -0
- package/src/archive/piglobal.ts +174 -0
- package/src/archive/plan.ts +612 -0
- package/src/archive/prarchive.ts +984 -0
- package/src/archive/prfiles.ts +406 -0
- package/src/archive/report.ts +252 -0
- package/src/archive/restore.ts +330 -0
- package/src/archive/rows.ts +187 -0
- package/src/archive/seal.ts +630 -0
- package/src/archive/status.ts +719 -0
- package/src/archive/storepass.ts +581 -0
- package/src/backfill.ts +362 -0
- package/src/bench/cost.ts +113 -0
- package/src/bench/exec.ts +90 -0
- package/src/bench/values.ts +13 -0
- package/src/botcontrol.ts +176 -0
- package/src/botworker.ts +953 -0
- package/src/capture.ts +321 -0
- package/src/cases.ts +1789 -0
- package/src/channelfs.ts +26 -0
- package/src/checklist.ts +506 -0
- package/src/ciretry.ts +141 -0
- package/src/cli.ts +2271 -0
- package/src/cliops.ts +384 -0
- package/src/clonedir.ts +15 -0
- package/src/commandeffects.ts +57 -0
- package/src/commands.ts +1678 -0
- package/src/companioninbox.ts +239 -0
- package/src/compare.ts +957 -0
- package/src/config.ts +2050 -0
- package/src/configcheck.ts +133 -0
- package/src/coreext.ts +47 -0
- package/src/daemon.ts +1302 -0
- package/src/debuground.ts +927 -0
- package/src/debugroundpi.ts +262 -0
- package/src/diagnostics.ts +14 -0
- package/src/digest.ts +183 -0
- package/src/dispatch.ts +2250 -0
- package/src/engine/answerguard.ts +27 -0
- package/src/engine/botsdk.ts +336 -0
- package/src/engine/caserunner-sdk.ts +423 -0
- package/src/engine/debugpi.ts +235 -0
- package/src/eod.ts +669 -0
- package/src/eodpinwatch.ts +556 -0
- package/src/escalation.ts +1253 -0
- package/src/eventloop.ts +1045 -0
- package/src/execboundary.ts +633 -0
- package/src/feedback.ts +1162 -0
- package/src/findingscheck.ts +298 -0
- package/src/fsdenied.ts +24 -0
- package/src/ghread.ts +242 -0
- package/src/github.ts +1497 -0
- package/src/homes.ts +175 -0
- package/src/hostroute.ts +53 -0
- package/src/improvements.ts +787 -0
- package/src/install.ts +1103 -0
- package/src/installkind.ts +50 -0
- package/src/ipc/capability.ts +333 -0
- package/src/ipc/client.ts +269 -0
- package/src/ipc/control.ts +90 -0
- package/src/ipc/ghreadshim.ts +94 -0
- package/src/ipc/launcher.ts +65 -0
- package/src/ipc/permits.ts +362 -0
- package/src/ipc/postshim.ts +164 -0
- package/src/ipc/protocol.ts +342 -0
- package/src/ipc/server.ts +371 -0
- package/src/ipc/workerspec.ts +218 -0
- package/src/learner/learner.ts +1655 -0
- package/src/learner/miner.ts +1290 -0
- package/src/learner/packet.ts +340 -0
- package/src/learner/receipt.ts +168 -0
- package/src/learner/status.ts +107 -0
- package/src/ledgercheck.ts +157 -0
- package/src/lessonledger.ts +703 -0
- package/src/lessonselect.ts +888 -0
- package/src/lessonshadow.ts +810 -0
- package/src/levers.ts +393 -0
- package/src/live/projection.ts +491 -0
- package/src/live/publish.ts +92 -0
- package/src/live/read.ts +86 -0
- package/src/livecli.ts +336 -0
- package/src/loginhint.ts +56 -0
- package/src/loginrefresh/atomic.ts +76 -0
- package/src/loginrefresh/lock.ts +104 -0
- package/src/loginrefresh/main.ts +59 -0
- package/src/loginrefresh/pirefresh.ts +57 -0
- package/src/loginrefresh/refresher.ts +326 -0
- package/src/loginrefresh/supervisor.ts +273 -0
- package/src/loginrefresh/types.ts +35 -0
- package/src/memory/context.ts +385 -0
- package/src/memory/datafence.ts +9 -0
- package/src/memory/delivery.ts +171 -0
- package/src/memory/eventtags.ts +80 -0
- package/src/memory/health.ts +953 -0
- package/src/memory/import.ts +664 -0
- package/src/memory/layout.ts +261 -0
- package/src/memory/legacy-file.ts +104 -0
- package/src/memory/lessons.ts +447 -0
- package/src/memory/ovsession.ts +601 -0
- package/src/memory/relayout.ts +335 -0
- package/src/memory/roundactivity.ts +209 -0
- package/src/memory/selection.ts +989 -0
- package/src/memory/shadowstatus.ts +125 -0
- package/src/memory/store.ts +275 -0
- package/src/memory/textidentity.ts +8 -0
- package/src/metrics.ts +1056 -0
- package/src/missingconfig.ts +7 -0
- package/src/npmverbs.ts +264 -0
- package/src/onboard/choices.ts +222 -0
- package/src/onboard/engine.ts +518 -0
- package/src/onboard/launch.ts +115 -0
- package/src/onboard/steps/appslug.ts +265 -0
- package/src/onboard/steps/bringup.ts +482 -0
- package/src/onboard/steps/compose.ts +459 -0
- package/src/onboard/steps/configdelivery.ts +58 -0
- package/src/onboard/steps/configfill.ts +1352 -0
- package/src/onboard/steps/deploysh.ts +105 -0
- package/src/onboard/steps/embeddings.ts +550 -0
- package/src/onboard/steps/escalation.ts +640 -0
- package/src/onboard/steps/githubapp.ts +1414 -0
- package/src/onboard/steps/golive.ts +577 -0
- package/src/onboard/steps/index.ts +71 -0
- package/src/onboard/steps/ingress.ts +907 -0
- package/src/onboard/steps/inventory.ts +526 -0
- package/src/onboard/steps/llmauth.ts +1095 -0
- package/src/onboard/steps/memory.ts +1290 -0
- package/src/onboard/steps/memorykeys.ts +85 -0
- package/src/onboard/steps/preflight.ts +191 -0
- package/src/onboard/steps/proof.ts +624 -0
- package/src/onboard/steps/ssh.ts +377 -0
- package/src/onboard/steps/topology.ts +337 -0
- package/src/onboard/steps/transport.ts +665 -0
- package/src/onboard/steps/verifyup.ts +448 -0
- package/src/onboard/steps/vmdeploy.ts +442 -0
- package/src/onboard/toml.ts +50 -0
- package/src/opencli.ts +591 -0
- package/src/openpr.ts +568 -0
- package/src/openviking/client.ts +1131 -0
- package/src/openviking/context.ts +263 -0
- package/src/openviking/embedder.ts +516 -0
- package/src/openviking/gapsink.ts +63 -0
- package/src/openviking/health.ts +83 -0
- package/src/openviking/index.ts +119 -0
- package/src/openviking/lessonfind.ts +92 -0
- package/src/openviking/probe.ts +129 -0
- package/src/openviking/proc.ts +110 -0
- package/src/openviking/provisionreport.ts +160 -0
- package/src/openviking/reembed.ts +412 -0
- package/src/openviking/relayout.ts +177 -0
- package/src/openviking/sidecar-main.ts +38 -0
- package/src/openviking/sidecar.ts +106 -0
- package/src/openviking/store.ts +2025 -0
- package/src/openviking/supervisor.ts +474 -0
- package/src/openviking/types.ts +174 -0
- package/src/operator-recovery.ts +237 -0
- package/src/operatormessage.ts +52 -0
- package/src/outcomes.ts +1670 -0
- package/src/piauthcopy.ts +12 -0
- package/src/picli.ts +49 -0
- package/src/picwd.ts +25 -0
- package/src/pilogin.ts +37 -0
- package/src/pingpong.ts +1339 -0
- package/src/posting-journal.ts +549 -0
- package/src/posting-outbox.ts +802 -0
- package/src/posting-receipt.ts +324 -0
- package/src/posting.ts +603 -0
- package/src/postops.ts +832 -0
- package/src/pr-activity.ts +20 -0
- package/src/pr-author.ts +6 -0
- package/src/pr-conversation.ts +432 -0
- package/src/pr-session-lifecycle.ts +631 -0
- package/src/preflight.ts +391 -0
- package/src/priorresolve.ts +343 -0
- package/src/qcrbin.ts +186 -0
- package/src/recheck.ts +1775 -0
- package/src/reconcile.ts +1030 -0
- package/src/recordedfindings.ts +27 -0
- package/src/regression.ts +833 -0
- package/src/replay.ts +816 -0
- package/src/requiredpasses.ts +148 -0
- package/src/resumecheck.ts +246 -0
- package/src/reviewcli.ts +787 -0
- package/src/reviewcontext.ts +35 -0
- package/src/reviewdetach.ts +734 -0
- package/src/reviewhistory.ts +427 -0
- package/src/reviewhome.ts +36 -0
- package/src/reviewmemory.ts +133 -0
- package/src/reviewrendercli.ts +139 -0
- package/src/round-work.ts +42 -0
- package/src/runhost.ts +108 -0
- package/src/scanauth.ts +36 -0
- package/src/scanbox.ts +1081 -0
- package/src/scancli.ts +1296 -0
- package/src/scandriverlog.ts +32 -0
- package/src/session-bridge.ts +139 -0
- package/src/session-command-payload.ts +35 -0
- package/src/session-command-supervisor.ts +1142 -0
- package/src/sessionjsonl.ts +88 -0
- package/src/sessions.ts +659 -0
- package/src/setup/adapters.ts +335 -0
- package/src/setup/baselines.ts +110 -0
- package/src/setup/caserunner.ts +611 -0
- package/src/setup/configcommit.ts +443 -0
- package/src/setup/engine.ts +1001 -0
- package/src/setup/facts.ts +627 -0
- package/src/setup/findings.ts +1264 -0
- package/src/setup/history.ts +443 -0
- package/src/setup/interview.ts +243 -0
- package/src/setup/ledger.ts +324 -0
- package/src/setup/pins.ts +66 -0
- package/src/setup/portrait.ts +635 -0
- package/src/setup/presentation.ts +18 -0
- package/src/setup/profile.ts +508 -0
- package/src/setup/replay.ts +370 -0
- package/src/setup/replaybuild.ts +392 -0
- package/src/setup/replayrun.ts +1096 -0
- package/src/setup/replaysource.ts +198 -0
- package/src/setup/retune.ts +415 -0
- package/src/setup/retunelog.ts +95 -0
- package/src/setup/scan.ts +1033 -0
- package/src/setup/scanbuild.ts +168 -0
- package/src/setup/scanfacts.ts +107 -0
- package/src/setup/scanlease.ts +297 -0
- package/src/setup/scanreport.ts +373 -0
- package/src/setup/scanrun.ts +2268 -0
- package/src/setup/scantree.ts +259 -0
- package/src/setup/stage2.ts +926 -0
- package/src/setup/stage2request.ts +347 -0
- package/src/setup/survey.ts +458 -0
- package/src/setup/surveyrun.ts +758 -0
- package/src/setup/textsniff.ts +95 -0
- package/src/setup/trackersink.ts +433 -0
- package/src/setup/triage.ts +768 -0
- package/src/setup/tuning.ts +766 -0
- package/src/setup/unitcut.ts +523 -0
- package/src/singleflight.ts +86 -0
- package/src/slack/announce.ts +911 -0
- package/src/slack/api.ts +303 -0
- package/src/slack/messages.ts +288 -0
- package/src/slack/origin.ts +599 -0
- package/src/slack/policy.ts +354 -0
- package/src/slack/reply.ts +201 -0
- package/src/slack/router.ts +285 -0
- package/src/slack/socket.ts +384 -0
- package/src/slack/types.ts +108 -0
- package/src/sqlitehint.ts +32 -0
- package/src/ssh-observation.ts +30 -0
- package/src/state.ts +8243 -0
- package/src/statemachine.ts +369 -0
- package/src/stubs.ts +68 -0
- package/src/takeover.ts +634 -0
- package/src/takeoverpi.ts +293 -0
- package/src/terminal-failure.ts +41 -0
- package/src/thread-resolution-refusal.ts +2 -0
- package/src/tmux.ts +153 -0
- package/src/trackerid.ts +41 -0
- package/src/transcriptindex.ts +665 -0
- package/src/tui/actions.ts +484 -0
- package/src/tui/alerts.ts +666 -0
- package/src/tui/app.ts +1979 -0
- package/src/tui/archiveops.ts +192 -0
- package/src/tui/autofile.ts +233 -0
- package/src/tui/board.ts +1432 -0
- package/src/tui/candidates.ts +889 -0
- package/src/tui/cellwidth.ts +148 -0
- package/src/tui/changelog-store.ts +503 -0
- package/src/tui/chord.ts +25 -0
- package/src/tui/clipboard.ts +92 -0
- package/src/tui/cmdbar.ts +296 -0
- package/src/tui/companion.ts +1427 -0
- package/src/tui/companionplan.ts +247 -0
- package/src/tui/companiontab.ts +83 -0
- package/src/tui/confighome.ts +155 -0
- package/src/tui/connect.ts +68 -0
- package/src/tui/deploy-ceremony.ts +104 -0
- package/src/tui/deploy-drain.ts +129 -0
- package/src/tui/deploy-process.ts +265 -0
- package/src/tui/deploytab.ts +1523 -0
- package/src/tui/design.ts +125 -0
- package/src/tui/digesttab.ts +214 -0
- package/src/tui/emptystate.ts +39 -0
- package/src/tui/evidence.ts +999 -0
- package/src/tui/feed.ts +604 -0
- package/src/tui/feedbacktab.ts +1077 -0
- package/src/tui/filters.ts +90 -0
- package/src/tui/ghread.ts +321 -0
- package/src/tui/historypull.ts +81 -0
- package/src/tui/inboxpull.ts +236 -0
- package/src/tui/index.ts +1565 -0
- package/src/tui/issuesink.ts +238 -0
- package/src/tui/jsonfile.ts +42 -0
- package/src/tui/keys.ts +109 -0
- package/src/tui/layout.ts +108 -0
- package/src/tui/learning.ts +627 -0
- package/src/tui/lessonswitch.ts +118 -0
- package/src/tui/livecontrol.ts +228 -0
- package/src/tui/memforward.ts +443 -0
- package/src/tui/memincident.ts +363 -0
- package/src/tui/memory.ts +620 -0
- package/src/tui/memtab.ts +606 -0
- package/src/tui/metricstab.ts +1177 -0
- package/src/tui/model.ts +131 -0
- package/src/tui/mouse.ts +98 -0
- package/src/tui/msg.ts +268 -0
- package/src/tui/nativecompanion.ts +417 -0
- package/src/tui/openurl.ts +21 -0
- package/src/tui/paint.ts +511 -0
- package/src/tui/panes.ts +98 -0
- package/src/tui/pipane.ts +219 -0
- package/src/tui/poll-hooks.ts +12 -0
- package/src/tui/poll.ts +147 -0
- package/src/tui/proposals.ts +838 -0
- package/src/tui/prpause.ts +150 -0
- package/src/tui/reload.ts +436 -0
- package/src/tui/remote.ts +413 -0
- package/src/tui/rollover.ts +969 -0
- package/src/tui/scantab.ts +1290 -0
- package/src/tui/setupentry.ts +93 -0
- package/src/tui/sidebar.ts +749 -0
- package/src/tui/sshtriage.ts +510 -0
- package/src/tui/startup.ts +231 -0
- package/src/tui/tabs.ts +45 -0
- package/src/tui/termmirror.ts +224 -0
- package/src/tui/theme.ts +95 -0
- package/src/tui/ticketstab.ts +310 -0
- package/src/tui/tmuxsocket.ts +129 -0
- package/src/tui/trackertickets.ts +84 -0
- package/src/tui/unwedge.ts +286 -0
- package/src/tui/viewer.ts +575 -0
- package/src/tui/viewerinput.ts +173 -0
- package/src/updatenotice.ts +178 -0
- package/src/usage.ts +1154 -0
- package/src/verdicts.ts +4689 -0
- package/src/version.ts +80 -0
- package/src/wakes.ts +476 -0
- package/src/webhook.ts +942 -0
- package/src/webhookheal.ts +276 -0
- package/src/wire.ts +2153 -0
- package/src/worker-session-host.ts +602 -0
- package/src/workerbreaker.ts +156 -0
- package/src/workercontrol.ts +104 -0
- package/src/workerlease.ts +441 -0
- package/src/workerliveness.ts +103 -0
- package/src/workerlog.ts +79 -0
- package/src/workerruntime.ts +30 -0
- package/src/workersupervisor.ts +1538 -0
- package/src/workerusage.ts +42 -0
- package/src/workerworld.ts +427 -0
- package/src/worktrees.ts +1138 -0
- package/tests/fixtures/regression-cases.json +795 -0
- package/tests/fixtures/seed-manifest.json +19 -0
|
@@ -0,0 +1,1435 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# deploy.sh — deploy the review-sandbox VM. The runtime is the RELEASE IMAGE
|
|
3
|
+
# (docker/Dockerfile) in `daemon` mode, one Docker container named
|
|
4
|
+
# quickreview-sandbox plus the memory sidecar beside it, with cloudflared on the
|
|
5
|
+
# host. The images are built ONCE, by CI, from the release tag; the VM pulls
|
|
6
|
+
# those exact images by digest. Nothing is rsynced, built or installed on the
|
|
7
|
+
# VM by a deploy. Three modes:
|
|
8
|
+
#
|
|
9
|
+
# release <version> the deploy. A controller preflight (the manifest, the
|
|
10
|
+
# pins, the run spec), then
|
|
11
|
+
# tar config.toml profiles/ | ssh vm sudo qcr-update ...
|
|
12
|
+
# and the post-deploy checks. qcr-update
|
|
13
|
+
# (deploy/qcr-update.sh, installed by provision) is the
|
|
14
|
+
# only thing that runs on the VM: it takes a flock, refuses
|
|
15
|
+
# an undrained or busy board, pulls both images by digest,
|
|
16
|
+
# checks the new image's `qcr config check` on the staged
|
|
17
|
+
# config, stops the daemon, snapshots state, runs the new
|
|
18
|
+
# image's `daemon --preflight` on a scratch copy, seeds the
|
|
19
|
+
# sidecar store (legacy layout only), swaps the config,
|
|
20
|
+
# starts the sidecar and recreates the daemon -- and
|
|
21
|
+
# rescues the old version on any failure before the
|
|
22
|
+
# recreate. See the header of deploy/qcr-update.sh.
|
|
23
|
+
# --manifest FILE deploy-manifest-<version>.json, the
|
|
24
|
+
# CI artifact (deploy/manifest.schema.json;
|
|
25
|
+
# default ./deploy-manifest-<version>.json,
|
|
26
|
+
# else `gh release download` of the
|
|
27
|
+
# release's attached copy)
|
|
28
|
+
# --cutover-ack the VM still runs the legacy layout:
|
|
29
|
+
# this deploy is the cutover, and the
|
|
30
|
+
# first boot migrates forward. Without
|
|
31
|
+
# it the updater (and this script)
|
|
32
|
+
# refuse. `QCR_CUTOVER_ACK=<version>`
|
|
33
|
+
# in the environment is the same
|
|
34
|
+
# acknowledgement (the runbook and
|
|
35
|
+
# scripts/cutover.sh spell it so);
|
|
36
|
+
# one that names another version is
|
|
37
|
+
# refused.
|
|
38
|
+
# provision ansible installs packages, Docker, cloudflared, secrets,
|
|
39
|
+
# the directory layout, the updater
|
|
40
|
+
# (/usr/local/sbin/qcr-update) and its run spec
|
|
41
|
+
# (/etc/qcr/update.conf, rendered here from group_vars).
|
|
42
|
+
# It never pulls or starts an image. Re-run it when the
|
|
43
|
+
# run spec changes (a release whose image needs a newer
|
|
44
|
+
# run spec is refused with "re-run provision").
|
|
45
|
+
# verify post-deploy checks on the VM: the docker service (and
|
|
46
|
+
# cloudflared when the inventory enables it), the
|
|
47
|
+
# containers, the image stamp, the pi settings and the
|
|
48
|
+
# reviewer stub, and the state/status.json heartbeat the
|
|
49
|
+
# status collector reads. It also warns 14 days before the
|
|
50
|
+
# VM's ghcr pull token expires (the expiry that
|
|
51
|
+
# scripts/vm-ghcr-login.sh recorded).
|
|
52
|
+
#
|
|
53
|
+
# `sync`, `dev` and `rebuild` are gone (the on-VM rsync and build are deleted):
|
|
54
|
+
# they refuse with a pointer to `release`. A pre-release fix is a release.
|
|
55
|
+
#
|
|
56
|
+
# There are no systemd units for the daemon: quickreviewd is the container's
|
|
57
|
+
# command (`restart: unless-stopped`). The only host service this script cares
|
|
58
|
+
# about is cloudflared, and only when `cloudflared_enabled` is true -- the
|
|
59
|
+
# playbook installs it on exactly that condition.
|
|
60
|
+
#
|
|
61
|
+
# Guardrails (all pre-flight, all fatal):
|
|
62
|
+
# * refuses to deploy a dirty working tree -- group_vars and the pins are
|
|
63
|
+
# read from this checkout, so it must be a commit you can point at
|
|
64
|
+
# * refuses when config.toml's [pi] version and the ansible group_vars
|
|
65
|
+
# pi_version disagree, and refuses on the "PIN-ME" placeholder. The pin
|
|
66
|
+
# never floats and this script never bumps it.
|
|
67
|
+
# * refuses on OpenViking pin drift, the same way. Four pins:
|
|
68
|
+
# - [openviking] version == group_vars openviking_version
|
|
69
|
+
# - [openviking] llama_cpp_python_version
|
|
70
|
+
# == group_vars llama_cpp_python_version
|
|
71
|
+
# - [openviking] extension_commit == extensions/vendor/openviking-pi/
|
|
72
|
+
# upstream.json "commit"
|
|
73
|
+
# - [openviking] local_embedder_file/_url/_sha256/_port/_threads
|
|
74
|
+
# == group_vars openviking_local_embedder_*
|
|
75
|
+
# A release also holds the pi, openviking, llama-cpp-python and embedder
|
|
76
|
+
# file/sha256 pins to the MANIFEST (the pins the images were built with,
|
|
77
|
+
# which are the images' pin labels) -- a drift means the image is not the
|
|
78
|
+
# build the config and group_vars describe.
|
|
79
|
+
# * refuses endpoint drift, the same way: [openviking] endpoint ==
|
|
80
|
+
# group_vars openviking_endpoint. Under "remote" (the memory sidecar, a
|
|
81
|
+
# second container -- docker/memory/ -- beside the daemon's) the config's
|
|
82
|
+
# [openviking] host and port must also name the sidecar (group_vars
|
|
83
|
+
# memory_container_name, openviking_port). A shipping mode also REFUSES
|
|
84
|
+
# "local": the release image carries no OpenViking.
|
|
85
|
+
# * WARNS (release only, never refuses) when a config's explicit [tui]
|
|
86
|
+
# remote_sandbox_root is not the image's app root (group_vars app_root,
|
|
87
|
+
# /opt/qcr)
|
|
88
|
+
# * release refuses to ship without a config home: the located config.toml
|
|
89
|
+
# and the profiles/ beside it (never its secrets/) are the tar the updater
|
|
90
|
+
# takes. A config that lives only in the checkout is refused (`qcr install
|
|
91
|
+
# migrate` moves it), and so is a `[review] profile` -- or any profile it
|
|
92
|
+
# extends -- found neither in the home's profiles/ nor among the checkout's
|
|
93
|
+
# generic examples.
|
|
94
|
+
# * release refuses a run spec the VM does not hold: the fingerprint of
|
|
95
|
+
# /etc/qcr/update.conf (read by `qcr-update status --json`) must equal the
|
|
96
|
+
# one rendered from group_vars -- otherwise "re-run provision"
|
|
97
|
+
# * refuses a base-clone name ([github] clone_dir, else the repo slug's
|
|
98
|
+
# basename -- the same rule as githubCloneDir in src/config.ts) that is not
|
|
99
|
+
# one plain path segment or that names a runtime dir of the sandbox root
|
|
100
|
+
#
|
|
101
|
+
# This script performs no live run on its own beyond ansible/ssh; every live
|
|
102
|
+
# invocation belongs to the operator on a machine with VM access.
|
|
103
|
+
#
|
|
104
|
+
# Usage:
|
|
105
|
+
# scripts/deploy.sh [--dry-run] release <version> [--manifest FILE] [--cutover-ack]
|
|
106
|
+
# scripts/deploy.sh [--dry-run] provision
|
|
107
|
+
# scripts/deploy.sh [--dry-run] verify
|
|
108
|
+
#
|
|
109
|
+
# Env:
|
|
110
|
+
# QC_ANSIBLE_DIR ansible project dir (default: deploy/ansible in this checkout)
|
|
111
|
+
# QC_INVENTORY inventory file (default: $QC_ANSIBLE_DIR/inventory.ini)
|
|
112
|
+
# QC_SANDBOX_DIR sandbox working copy (default: this script's parent)
|
|
113
|
+
# QC_VM_HOST ssh target (default: the inventory's first host)
|
|
114
|
+
# QC_UPDATE_STATUS_CMD replaces the ssh `sudo qcr-update status --json` (tests)
|
|
115
|
+
# QC_UPDATE_RUN_CMD replaces the ssh `sudo qcr-update <args>`; run as
|
|
116
|
+
# `bash -c "$QC_UPDATE_RUN_CMD" _ <args...>` with the config
|
|
117
|
+
# tar on stdin (tests)
|
|
118
|
+
# QC_RELEASE_DOWNLOAD_CMD replaces `gh release download` of the manifest; run as
|
|
119
|
+
# `bash -c "$QC_RELEASE_DOWNLOAD_CMD" _ <version> <dir>` (tests)
|
|
120
|
+
# QC_TAG_DIGEST_CMD replaces the registry read of what <repo>:<version> resolves
|
|
121
|
+
# to; run as `bash -c "$QC_TAG_DIGEST_CMD" _ <repo>:<version>`,
|
|
122
|
+
# prints the index digest (tests)
|
|
123
|
+
# QC_NOW_EPOCH the clock for the token-expiry warning (tests)
|
|
124
|
+
# QC_NO_MEMORY_FORWARD skip the host memory-forward restart (tests)
|
|
125
|
+
# QCR_GHCR_EXPIRY_FILE the recorded token expiry (default: the install's
|
|
126
|
+
# state home, ghcr-token-expiry)
|
|
127
|
+
# QCR_CONFIG config.toml path (default: the install locator's answer --
|
|
128
|
+
# the install's config home's config.toml;
|
|
129
|
+
# scripts/install-locator.sh). Its directory is the
|
|
130
|
+
# config home a release ships; `verify` alone
|
|
131
|
+
# accepts a QCR_CONFIG inside the checkout
|
|
132
|
+
# QC_MEMORY_PROBE_CMD replaces the ssh read of the sidecar's /ready (tests)
|
|
133
|
+
# QC_MEMORY_SEARCH_CMD replaces the ssh run of the post-deploy search check (tests)
|
|
134
|
+
# QC_MEMORY_WAIT_S how long the sidecar checks wait for a cold boot (default 240)
|
|
135
|
+
# (secrets) provision's secrets_src is the install locator's answer --
|
|
136
|
+
# [github] secrets_dir, else <config home>/secrets
|
|
137
|
+
|
|
138
|
+
set -euo pipefail
|
|
139
|
+
|
|
140
|
+
SELF="$(basename "${BASH_SOURCE[0]}")"
|
|
141
|
+
DEFAULT_ANSIBLE_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)/deploy/ansible"
|
|
142
|
+
|
|
143
|
+
SANDBOX_DIR="${QC_SANDBOX_DIR:-$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)}"
|
|
144
|
+
ANSIBLE_DIR="${QC_ANSIBLE_DIR:-$DEFAULT_ANSIBLE_DIR}"
|
|
145
|
+
INVENTORY="${QC_INVENTORY:-$ANSIBLE_DIR/inventory.ini}"
|
|
146
|
+
# The pin/drift preflight reads the config the install locator names.
|
|
147
|
+
# Sourced from beside this script, not from $SANDBOX_DIR: the sandbox may be a
|
|
148
|
+
# scratch working copy.
|
|
149
|
+
. "$(dirname "${BASH_SOURCE[0]}")/install-locator.sh"
|
|
150
|
+
CONFIG="$(qcr_locate_config "$SANDBOX_DIR")" || exit 1
|
|
151
|
+
GROUP_VARS="$ANSIBLE_DIR/group_vars/all.yml"
|
|
152
|
+
PLAYBOOK="$ANSIBLE_DIR/playbook.yml"
|
|
153
|
+
OV_UPSTREAM="$SANDBOX_DIR/extensions/vendor/openviking-pi/upstream.json"
|
|
154
|
+
|
|
155
|
+
# The run-spec version this script renders and the images' qcr.runspec label
|
|
156
|
+
# must not exceed (docker/Dockerfile; tests/deploy.test.ts pins the two).
|
|
157
|
+
RUNSPEC_VERSION=2
|
|
158
|
+
|
|
159
|
+
# The release images' version spelling: X.Y.Z, optionally -prerelease. No "latest"
|
|
160
|
+
# exists. It names an image tag and the manifest.
|
|
161
|
+
RELEASE_VERSION_RE='^[0-9]+\.[0-9]+\.[0-9]+(-[0-9A-Za-z.]+)?$'
|
|
162
|
+
|
|
163
|
+
MODE=""
|
|
164
|
+
RELEASE_VERSION=""
|
|
165
|
+
DRY_RUN=0
|
|
166
|
+
MANIFEST_FILE=""
|
|
167
|
+
CUTOVER_ACK=0
|
|
168
|
+
|
|
169
|
+
die() { echo "$SELF: $*" >&2; exit 1; }
|
|
170
|
+
note() { echo "$SELF: $*" >&2; }
|
|
171
|
+
|
|
172
|
+
# ---- arguments ---------------------------------------------------------------
|
|
173
|
+
|
|
174
|
+
USAGE="usage: $SELF [--dry-run] release <version> [--manifest FILE] [--cutover-ack] | provision | verify"
|
|
175
|
+
while [[ $# -gt 0 ]]; do
|
|
176
|
+
case "$1" in
|
|
177
|
+
--dry-run|-n) DRY_RUN=1 ;;
|
|
178
|
+
sync|dev|rebuild)
|
|
179
|
+
die "'$1' is gone: nothing is rsynced, built or installed on the VM any more. A deploy is \`$SELF release <version>\` (the image CI built once from the release tag, pulled by digest); the host's packages, secrets and updater are \`$SELF provision\`" ;;
|
|
180
|
+
verify|provision)
|
|
181
|
+
[[ -n "$MODE" ]] && die "two modes given: $MODE and $1"
|
|
182
|
+
MODE="$1" ;;
|
|
183
|
+
release)
|
|
184
|
+
[[ -n "$MODE" ]] && die "two modes given: $MODE and $1"
|
|
185
|
+
MODE=release
|
|
186
|
+
[[ $# -ge 2 ]] || die "release needs a version (usage: $SELF release X.Y.Z)"
|
|
187
|
+
RELEASE_VERSION="$2"
|
|
188
|
+
shift ;;
|
|
189
|
+
--manifest)
|
|
190
|
+
[[ $# -ge 2 ]] || die "--manifest needs a file"
|
|
191
|
+
MANIFEST_FILE="$2"
|
|
192
|
+
shift ;;
|
|
193
|
+
--cutover-ack) CUTOVER_ACK=1 ;;
|
|
194
|
+
-h|--help) sed -n '2,/^$/p' "${BASH_SOURCE[0]}" | sed 's/^# \{0,1\}//'; exit 0 ;;
|
|
195
|
+
*) die "unknown argument '$1' ($USAGE)" ;;
|
|
196
|
+
esac
|
|
197
|
+
shift
|
|
198
|
+
done
|
|
199
|
+
[[ -n "$MODE" ]] || die "no mode given ($USAGE)"
|
|
200
|
+
if [[ "$MODE" != release && ( -n "$MANIFEST_FILE" || $CUTOVER_ACK -eq 1 ) ]]; then
|
|
201
|
+
die "--manifest and --cutover-ack belong to \`release\`"
|
|
202
|
+
fi
|
|
203
|
+
if [[ "$MODE" == release ]]; then
|
|
204
|
+
[[ "$RELEASE_VERSION" != latest ]] || die "there is no latest tag: name the version (X.Y.Z)"
|
|
205
|
+
[[ "$RELEASE_VERSION" =~ $RELEASE_VERSION_RE ]] ||
|
|
206
|
+
die "release version '$RELEASE_VERSION' is not X.Y.Z (optionally -prerelease): it names an image tag"
|
|
207
|
+
# The acknowledgement the runbook (C.11/C.12) and scripts/cutover.sh give is the environment's
|
|
208
|
+
# QCR_CUTOVER_ACK=<version>, not only the flag. It acknowledges THIS release and no other: a stale
|
|
209
|
+
# one left over from another version must not authorize the cutover.
|
|
210
|
+
if [[ -n "${QCR_CUTOVER_ACK:-}" ]]; then
|
|
211
|
+
[[ "$QCR_CUTOVER_ACK" == "$RELEASE_VERSION" ]] ||
|
|
212
|
+
die "QCR_CUTOVER_ACK=$QCR_CUTOVER_ACK acknowledges another version than the one being released ($RELEASE_VERSION): set it to $RELEASE_VERSION, or unset it"
|
|
213
|
+
CUTOVER_ACK=1
|
|
214
|
+
fi
|
|
215
|
+
fi
|
|
216
|
+
|
|
217
|
+
# ---- helpers -----------------------------------------------------------------
|
|
218
|
+
|
|
219
|
+
# A `<key>: "<value>"` scalar out of the ansible group_vars.
|
|
220
|
+
group_vars_key() {
|
|
221
|
+
awk -v key="$1" '
|
|
222
|
+
$0 ~ "^[[:space:]]*" key "[[:space:]]*:" {
|
|
223
|
+
line = $0
|
|
224
|
+
sub(/^[^:]*:[[:space:]]*/, "", line)
|
|
225
|
+
gsub(/["'"'"']/, "", line)
|
|
226
|
+
sub(/[[:space:]]*(#.*)?$/, "", line)
|
|
227
|
+
print line
|
|
228
|
+
exit
|
|
229
|
+
}
|
|
230
|
+
' "$GROUP_VARS"
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
group_vars_pi_version() { group_vars_key pi_version; }
|
|
234
|
+
config_pi_version() { config_table_key pi version; }
|
|
235
|
+
|
|
236
|
+
# A `<key> = "<value>"` scalar out of a named config.toml table, without a
|
|
237
|
+
# TOML parser.
|
|
238
|
+
config_table_key() {
|
|
239
|
+
awk -v table="$1" -v key="$2" '
|
|
240
|
+
$0 ~ "^\\[" table "\\]" { in_t = 1; next }
|
|
241
|
+
/^\[/ { in_t = 0 }
|
|
242
|
+
in_t && $0 ~ "^[[:space:]]*" key "[[:space:]]*=" {
|
|
243
|
+
line = $0
|
|
244
|
+
sub(/^[^=]*=[[:space:]]*/, "", line)
|
|
245
|
+
gsub(/["'"'"']/, "", line)
|
|
246
|
+
sub(/[[:space:]]*(#.*)?$/, "", line)
|
|
247
|
+
print line
|
|
248
|
+
exit
|
|
249
|
+
}
|
|
250
|
+
' "$CONFIG"
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
# A `<key> = ["a", "b"]` ARRAY out of a named config.toml table, normalised to
|
|
254
|
+
# the comma-joined spelling group_vars carries (`openviking_provision_users`).
|
|
255
|
+
# One line only: every list this compares is one line by convention.
|
|
256
|
+
config_list_key() {
|
|
257
|
+
local raw
|
|
258
|
+
raw="$(config_table_key "$1" "$2")"
|
|
259
|
+
[[ -n "$raw" ]] || return 0
|
|
260
|
+
raw="${raw#[}"
|
|
261
|
+
raw="${raw%]}"
|
|
262
|
+
echo "$raw" | tr -d ' ' | sed 's/,$//'
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
# upstream.json's "commit" — the vendored fork's base commit (mirror 2 of 2).
|
|
266
|
+
upstream_commit() {
|
|
267
|
+
[[ -f "$OV_UPSTREAM" ]] || return 0
|
|
268
|
+
awk -F'"' '/^[[:space:]]*"commit"[[:space:]]*:/ { print $4; exit }' "$OV_UPSTREAM"
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
# One pin, two spellings that must agree. Empty on EITHER side is fatal: a pin
|
|
272
|
+
# we cannot read is a pin we cannot verify, and this script never guesses.
|
|
273
|
+
check_pin() {
|
|
274
|
+
local label="$1" a_where="$2" a="$3" b_where="$4" b="$5"
|
|
275
|
+
[[ -n "$a" ]] || die "$a_where has no $label pin — cannot verify it"
|
|
276
|
+
[[ -n "$b" ]] || die "$b_where has no $label pin — cannot verify it"
|
|
277
|
+
if [[ "$a" != "$b" ]]; then
|
|
278
|
+
cat >&2 <<EOF
|
|
279
|
+
$SELF: $label pin mismatch — refusing to deploy.
|
|
280
|
+
|
|
281
|
+
$a_where = "$a"
|
|
282
|
+
$b_where = "$b"
|
|
283
|
+
|
|
284
|
+
Deploying would change a pinned version on the VM. That is a deliberate act:
|
|
285
|
+
reconcile the two by hand and cut a new release, so CI builds the image against
|
|
286
|
+
the new pin.
|
|
287
|
+
EOF
|
|
288
|
+
exit 1
|
|
289
|
+
fi
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
# A `key=value` HOST VAR on the inventory's first host line, or nothing. The
|
|
293
|
+
# onboarding wizard writes an operator's answers there precisely so the tracked
|
|
294
|
+
# group_vars stay clean; in Ansible's precedence a host var outranks
|
|
295
|
+
# group_vars/all, so the EFFECTIVE value of a knob is the host var when one is
|
|
296
|
+
# set and the group_vars line otherwise (`effective_var`).
|
|
297
|
+
inventory_host_var() {
|
|
298
|
+
[[ -f "$INVENTORY" ]] || return 0
|
|
299
|
+
awk -v key="$1" '
|
|
300
|
+
/^[[:space:]]*[#;]/ { next }
|
|
301
|
+
/^[[:space:]]*\[/ { if (seen_group) exit; seen_group = 1; next }
|
|
302
|
+
NF && seen_group {
|
|
303
|
+
for (i = 2; i <= NF; i++) {
|
|
304
|
+
split($i, kv, "=")
|
|
305
|
+
if (kv[1] == key) { print substr($i, length(key) + 2); exit }
|
|
306
|
+
}
|
|
307
|
+
exit
|
|
308
|
+
}' "$INVENTORY"
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
effective_var() {
|
|
312
|
+
local v
|
|
313
|
+
v="$(inventory_host_var "$1" || true)"
|
|
314
|
+
[[ -n "$v" ]] && { echo "$v"; return 0; }
|
|
315
|
+
group_vars_key "$1"
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
# The effective value, or a default when neither the inventory nor group_vars names it.
|
|
319
|
+
effective_var_or() {
|
|
320
|
+
local v
|
|
321
|
+
v="$(effective_var "$1" || true)"
|
|
322
|
+
echo "${v:-$2}"
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
# Drift diagnostics name the source whose precedence actually supplied the value.
|
|
326
|
+
effective_var_source() {
|
|
327
|
+
if [[ -n "$(inventory_host_var "$1" || true)" ]]; then
|
|
328
|
+
echo inventory
|
|
329
|
+
else
|
|
330
|
+
echo group_vars
|
|
331
|
+
fi
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
# The base clone's directory name, derived exactly as githubCloneDir
|
|
335
|
+
# (src/config.ts) derives it: `[github] clone_dir` when set, else the repo
|
|
336
|
+
# slug's basename (`.git` and trailing slashes stripped), else "repo".
|
|
337
|
+
# tests/deploy.test.ts pins the two derivations against each other.
|
|
338
|
+
base_clone_dir() {
|
|
339
|
+
local explicit repo tail
|
|
340
|
+
explicit="$(config_table_key github clone_dir || true)"
|
|
341
|
+
explicit="$(echo "$explicit" | sed 's/^[[:space:]]*//; s/[[:space:]]*$//')"
|
|
342
|
+
if [[ -n "$explicit" ]]; then echo "$explicit"; return 0; fi
|
|
343
|
+
repo="$(config_table_key github repo || true)"
|
|
344
|
+
while [[ "$repo" == */ ]]; do repo="${repo%/}"; done
|
|
345
|
+
repo="${repo%.git}"
|
|
346
|
+
tail="${repo##*/}"
|
|
347
|
+
echo "${tail:-repo}"
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
first_inventory_host() {
|
|
351
|
+
[[ -f "$INVENTORY" ]] || return 0
|
|
352
|
+
awk '/^[[:space:]]*[#;]/ { next }
|
|
353
|
+
/^[[:space:]]*\[/ { next }
|
|
354
|
+
NF { print $1; exit }' "$INVENTORY"
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
# ---- pre-flight --------------------------------------------------------------
|
|
358
|
+
|
|
359
|
+
[[ -f "$CONFIG" ]] || die "no config at $CONFIG — the install's config home holds none (run the onboarding wizard, or \`qcr install migrate\` to move a checkout config.toml there: the checkout's is no longer read)"
|
|
360
|
+
# A release ships the install's config home — the located config and the
|
|
361
|
+
# profiles/ beside it — to the VM as the tar the updater takes (never secrets/),
|
|
362
|
+
# and the container reads it there ($QCR_CONFIG names the shipped file). So the
|
|
363
|
+
# config must live in such a home: a $QCR_CONFIG naming a file inside the
|
|
364
|
+
# checkout is refused, not shipped. `provision` and `verify` ship nothing and
|
|
365
|
+
# still read whatever the locator found.
|
|
366
|
+
SHIPPED_CONFIG="$(qcr_shipped_config "$SANDBOX_DIR")" || exit 1
|
|
367
|
+
if [[ "$MODE" == release && -z "$SHIPPED_CONFIG" ]]; then
|
|
368
|
+
die "no config home to ship — the live config is $CONFIG, inside the checkout. A release ships the install's config home ($(qcr_config_home 2>/dev/null || echo '$XDG_CONFIG_HOME/qcr/<install>'), or the directory \$QCR_CONFIG names) to the VM, never the checkout's config.toml: move it with \`qcr install migrate\`, then re-run"
|
|
369
|
+
fi
|
|
370
|
+
CONFIG_HOME_SRC=""
|
|
371
|
+
CONFIG_NAME=""
|
|
372
|
+
if [[ -n "$SHIPPED_CONFIG" ]]; then
|
|
373
|
+
CONFIG_HOME_SRC="$(dirname "$SHIPPED_CONFIG")"
|
|
374
|
+
CONFIG_NAME="$(basename "$SHIPPED_CONFIG")"
|
|
375
|
+
# The tar is built from these paths and handed through ssh: a plain path
|
|
376
|
+
# and a plain file name, so nothing in them is read as shell or as a glob.
|
|
377
|
+
if [[ "$MODE" == release && ( "$CONFIG_HOME_SRC" =~ [[:space:]\"\'{}] || "$CONFIG_NAME" =~ [[:space:]\"\'{}] ) ]]; then
|
|
378
|
+
die "the config home path '$SHIPPED_CONFIG' contains whitespace, quotes or braces — set XDG_CONFIG_HOME (or QCR_CONFIG) to a plain path"
|
|
379
|
+
fi
|
|
380
|
+
# The file ships as config.toml whatever it is called here, but a name with
|
|
381
|
+
# a wildcard or a leading dash is never a file anyone meant to ship.
|
|
382
|
+
if [[ "$MODE" == release && ! "$CONFIG_NAME" =~ ^[A-Za-z0-9][A-Za-z0-9._-]*$ ]]; then
|
|
383
|
+
die "the config file name '$CONFIG_NAME' is not a plain file name — letters, digits, '.', '_' and '-' only; rename it, or point QCR_CONFIG at one that is"
|
|
384
|
+
fi
|
|
385
|
+
fi
|
|
386
|
+
# The repo profile the config names, and every profile it `extends`,
|
|
387
|
+
# must be found where the VM's daemon will look for it (src/install.ts
|
|
388
|
+
# locateProfile, in the container): profiles/ beside the shipped config first,
|
|
389
|
+
# then the image's own profiles/ — for a generic example only
|
|
390
|
+
# (`qcr_is_example_profile`, pinned to EXAMPLE_PROFILES). A profile
|
|
391
|
+
# found in neither fails the VM's boot, so it fails here. Only presence is checked; the daemon's
|
|
392
|
+
# resolver refuses a cycle, so the walk just stops at a name it has seen.
|
|
393
|
+
profile_extends() {
|
|
394
|
+
awk '
|
|
395
|
+
/^[[:space:]]*\[/ { exit }
|
|
396
|
+
/^[[:space:]]*extends[[:space:]]*=/ {
|
|
397
|
+
line = $0
|
|
398
|
+
sub(/^[^=]*=[[:space:]]*/, "", line)
|
|
399
|
+
gsub(/["'"'"']/, "", line)
|
|
400
|
+
sub(/[[:space:]]*(#.*)?$/, "", line)
|
|
401
|
+
print line
|
|
402
|
+
exit
|
|
403
|
+
}
|
|
404
|
+
' "$1" 2>/dev/null
|
|
405
|
+
}
|
|
406
|
+
# The shipped directory holding profile <name>, or nothing.
|
|
407
|
+
shipped_profile_dir() {
|
|
408
|
+
[[ -d "$CONFIG_HOME_SRC/profiles/$1" ]] && { echo "$CONFIG_HOME_SRC/profiles/$1"; return 0; }
|
|
409
|
+
qcr_is_example_profile "$1" && [[ -d "$SANDBOX_DIR/profiles/$1" ]] && echo "$SANDBOX_DIR/profiles/$1"
|
|
410
|
+
return 0
|
|
411
|
+
}
|
|
412
|
+
# A profile name is one plain path segment (the daemon's assertPathSegment):
|
|
413
|
+
# anything that could climb out of profiles/ is refused before any lookup.
|
|
414
|
+
check_profile_name() {
|
|
415
|
+
if [[ ! "$1" =~ ^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$ || "$1" == *..* ]]; then
|
|
416
|
+
die "$2 \"$1\" is not a plain profile name — letters, digits, '.', '_' and '-' only, no path separators or '..'"
|
|
417
|
+
fi
|
|
418
|
+
}
|
|
419
|
+
DEPLOY_PROFILE="$(config_table_key review profile)"
|
|
420
|
+
PROFILE_CHAIN=""
|
|
421
|
+
if [[ "$MODE" == release && -n "$DEPLOY_PROFILE" ]]; then
|
|
422
|
+
check_profile_name "$DEPLOY_PROFILE" "config [review] profile"
|
|
423
|
+
chain_at="$DEPLOY_PROFILE"
|
|
424
|
+
chain_dir="$(shipped_profile_dir "$chain_at")"
|
|
425
|
+
[[ -n "$chain_dir" ]] ||
|
|
426
|
+
die "config [review] profile = \"$DEPLOY_PROFILE\", but the config home does not hold it — looked in $CONFIG_HOME_SRC/profiles/$DEPLOY_PROFILE and the checkout's generic examples. The VM's daemon refuses to boot without its profile: put it in $CONFIG_HOME_SRC/profiles/ (\`qcr install migrate\` moves one left in the checkout), or fix the name"
|
|
427
|
+
chain_seen=" $DEPLOY_PROFILE "
|
|
428
|
+
PROFILE_CHAIN="$DEPLOY_PROFILE"
|
|
429
|
+
while chain_base="$(profile_extends "$chain_dir/profile.toml")" && [[ -n "$chain_base" ]]; do
|
|
430
|
+
[[ "$chain_seen" == *" $chain_base "* ]] && break
|
|
431
|
+
check_profile_name "$chain_base" "profile \"$chain_at\" extends"
|
|
432
|
+
chain_dir="$(shipped_profile_dir "$chain_base")"
|
|
433
|
+
[[ -n "$chain_dir" ]] ||
|
|
434
|
+
die "profile \"$chain_at\" extends \"$chain_base\", but the config home does not hold it — looked in $CONFIG_HOME_SRC/profiles/$chain_base and the checkout's generic examples. The VM's daemon refuses to boot without every profile in the chain"
|
|
435
|
+
chain_seen+="$chain_base "
|
|
436
|
+
PROFILE_CHAIN+=" → $chain_base"
|
|
437
|
+
chain_at="$chain_base"
|
|
438
|
+
done
|
|
439
|
+
fi
|
|
440
|
+
[[ -d "$ANSIBLE_DIR" ]] || die "no ansible dir at $ANSIBLE_DIR (set QC_ANSIBLE_DIR)"
|
|
441
|
+
[[ -f "$GROUP_VARS" ]] || die "no group_vars at $GROUP_VARS"
|
|
442
|
+
[[ -f "$PLAYBOOK" ]] || die "no playbook at $PLAYBOOK"
|
|
443
|
+
|
|
444
|
+
# 1. clean tree
|
|
445
|
+
if ! git -C "$SANDBOX_DIR" rev-parse --git-dir >/dev/null 2>&1; then
|
|
446
|
+
die "$SANDBOX_DIR is not a git repository — refusing to deploy an untracked tree"
|
|
447
|
+
fi
|
|
448
|
+
|
|
449
|
+
# 1a. the base clone. Its name becomes a directory under the sandbox root, a
|
|
450
|
+
# mount into the container and a run-spec value, so it must be one plain path
|
|
451
|
+
# segment.
|
|
452
|
+
CLONE_DIR="$(base_clone_dir)"
|
|
453
|
+
if [[ ! "$CLONE_DIR" =~ ^[A-Za-z0-9._-]+$ || "$CLONE_DIR" == "." || "$CLONE_DIR" == ".." ]]; then
|
|
454
|
+
die "base clone name '$CLONE_DIR' ([github] clone_dir, else the repo's basename) is not one plain directory name — fix config.toml"
|
|
455
|
+
fi
|
|
456
|
+
# Nor may it name a directory the daemon already owns at the sandbox root:
|
|
457
|
+
# a repo called `notes` or `wt` would otherwise be cloned into (or over) a
|
|
458
|
+
# runtime dir, and one named for something the release image keeps under the app
|
|
459
|
+
# root would be mounted over it. tests/deploy.test.ts pins this list against the
|
|
460
|
+
# updater's mounts and the image's COPY lines.
|
|
461
|
+
BASE_CLONE_RESERVED=(.git .claude node_modules state wt archive commands digest inject verdict memory notes ledger.md AGENTS.override.md pihome dist packages package.json package-lock.json src extensions agents prompts seeds profiles schemas scripts config.example.toml docker docker-compose.yml tests)
|
|
462
|
+
for reserved in "${BASE_CLONE_RESERVED[@]}"; do
|
|
463
|
+
if [[ "$CLONE_DIR" == "$reserved" ]]; then
|
|
464
|
+
die "base clone name '$CLONE_DIR' is a runtime or build directory of the sandbox root — set [github] clone_dir to a free name"
|
|
465
|
+
fi
|
|
466
|
+
done
|
|
467
|
+
|
|
468
|
+
# 1b. the secrets source: the install locator's answer — the
|
|
469
|
+
# configured [github] secrets_dir, else <config home>/secrets — handed to
|
|
470
|
+
# provision's secrets copy as secrets_src, so group_vars' hand-run default is
|
|
471
|
+
# never what ships.
|
|
472
|
+
SECRETS_SRC="$(qcr_locate_secrets "$(config_table_key github secrets_dir || true)")" || exit 1
|
|
473
|
+
|
|
474
|
+
DIRTY="$(git -C "$SANDBOX_DIR" status --porcelain)"
|
|
475
|
+
if [[ -n "$DIRTY" ]]; then
|
|
476
|
+
echo "$SELF: refusing to deploy — the working tree is dirty:" >&2
|
|
477
|
+
echo "$DIRTY" | sed 's/^/ /' >&2
|
|
478
|
+
echo "$SELF: commit (locally — never push, never master/main) and re-run." >&2
|
|
479
|
+
exit 1
|
|
480
|
+
fi
|
|
481
|
+
HEAD_SHA="$(git -C "$SANDBOX_DIR" rev-parse --short HEAD 2>/dev/null || echo "no-commits")"
|
|
482
|
+
|
|
483
|
+
# The dry_run flag as the shipped config carries it — shown in the plan, never
|
|
484
|
+
# changed: a release ships the config home, so its flag is the VM's flag.
|
|
485
|
+
CFG_DRY_RUN="$(awk '/^\[/ { exit } /^[[:space:]]*dry_run[[:space:]]*=/ { sub(/^[^=]*=[[:space:]]*/, ""); sub(/[[:space:]]*(#.*)?$/, ""); print; exit }' "$CONFIG")"
|
|
486
|
+
|
|
487
|
+
# 2. the pi pin must agree, and must not be the placeholder
|
|
488
|
+
CFG_PI="$(config_pi_version || true)"
|
|
489
|
+
GV_PI="$(group_vars_pi_version || true)"
|
|
490
|
+
[[ -n "$CFG_PI" ]] || die "config.toml has no [pi] version key — cannot verify the pin"
|
|
491
|
+
[[ -n "$GV_PI" ]] || die "group_vars/all.yml has no pi_version key — cannot verify the pin"
|
|
492
|
+
|
|
493
|
+
if [[ "$GV_PI" == "PIN-ME" ]]; then
|
|
494
|
+
cat >&2 <<EOF
|
|
495
|
+
$SELF: the ansible pin is still the placeholder.
|
|
496
|
+
|
|
497
|
+
$GROUP_VARS
|
|
498
|
+
pi_version: "PIN-ME" -> pi_version: "$CFG_PI"
|
|
499
|
+
|
|
500
|
+
Confirm the package name against assets/findings.md, set the exact
|
|
501
|
+
version by hand, then re-run. The image never floats and this script never
|
|
502
|
+
bumps a pin for you.
|
|
503
|
+
EOF
|
|
504
|
+
exit 1
|
|
505
|
+
fi
|
|
506
|
+
|
|
507
|
+
if [[ "$CFG_PI" != "$GV_PI" ]]; then
|
|
508
|
+
cat >&2 <<EOF
|
|
509
|
+
$SELF: pi version mismatch — refusing to deploy.
|
|
510
|
+
|
|
511
|
+
config.toml [pi] version = "$CFG_PI"
|
|
512
|
+
group_vars pi_version = "$GV_PI"
|
|
513
|
+
|
|
514
|
+
Deploying would change the pinned pi version on the VM. That is a deliberate
|
|
515
|
+
act: reconcile the two by hand (and package.json's "qcrPi" block) and cut a new
|
|
516
|
+
release, so CI builds the image against the new pin.
|
|
517
|
+
EOF
|
|
518
|
+
exit 1
|
|
519
|
+
fi
|
|
520
|
+
|
|
521
|
+
# 2b. the OpenViking pins must agree too. Same discipline as pi: the
|
|
522
|
+
# memory engine never floats, and a rebase of the vendored fork that nobody
|
|
523
|
+
# recorded in config.toml is exactly the drift `extension_commit` exists to
|
|
524
|
+
# catch. `[openviking]` is fully defaulted in code, so an older config.toml may
|
|
525
|
+
# genuinely carry no block — in that case there is nothing to compare and the
|
|
526
|
+
# check stands down rather than inventing a mismatch.
|
|
527
|
+
CFG_OV="$(config_table_key openviking version || true)"
|
|
528
|
+
if [[ -n "$CFG_OV" ]]; then
|
|
529
|
+
check_pin "openviking" \
|
|
530
|
+
'config.toml [openviking] version' "$CFG_OV" \
|
|
531
|
+
'group_vars openviking_version' "$(group_vars_key openviking_version || true)"
|
|
532
|
+
check_pin "llama-cpp-python" \
|
|
533
|
+
'config.toml [openviking] llama_cpp_python_version' "$(config_table_key openviking llama_cpp_python_version || true)" \
|
|
534
|
+
'group_vars llama_cpp_python_version' "$(group_vars_key llama_cpp_python_version || true)"
|
|
535
|
+
# The auth posture is mirrored the same way the pins are, and for the same
|
|
536
|
+
# reason — the entrypoint renders group_vars, so a config.toml that says
|
|
537
|
+
# "api_key" while group_vars says "dev" would ship a server with no
|
|
538
|
+
# per-user confinement while every doc claims otherwise. Same for the secret
|
|
539
|
+
# file names the template interpolates and the user list it provisions:
|
|
540
|
+
# drift there is a memory outage or an unprovisioned user, silently.
|
|
541
|
+
CFG_OV_AUTH="$(config_table_key openviking auth_mode || true)"
|
|
542
|
+
if [[ -n "$CFG_OV_AUTH" ]]; then
|
|
543
|
+
check_pin "openviking auth_mode" \
|
|
544
|
+
'config.toml [openviking] auth_mode' "$CFG_OV_AUTH" \
|
|
545
|
+
"$(effective_var_source openviking_auth_mode) openviking_auth_mode" "$(effective_var openviking_auth_mode || true)"
|
|
546
|
+
check_pin "openviking root key file" \
|
|
547
|
+
'config.toml [openviking] root_key_file' "$(config_table_key openviking root_key_file || true)" \
|
|
548
|
+
"$(effective_var_source openviking_root_key_file) openviking_root_key_file" "$(effective_var openviking_root_key_file || true)"
|
|
549
|
+
check_pin "openviking reviewer key file" \
|
|
550
|
+
'config.toml [openviking] reviewer_key_file' "$(config_table_key openviking reviewer_key_file || true)" \
|
|
551
|
+
"$(effective_var_source openviking_reviewer_key_file) openviking_reviewer_key_file" "$(effective_var openviking_reviewer_key_file || true)"
|
|
552
|
+
check_pin "openviking provisioned users" \
|
|
553
|
+
'config.toml [openviking] provision_users' "$(config_list_key openviking provision_users || true)" \
|
|
554
|
+
"$(effective_var_source openviking_provision_users) openviking_provision_users" "$(effective_var openviking_provision_users || true)"
|
|
555
|
+
fi
|
|
556
|
+
# The embedder is mirrored the same way, against the value the boot
|
|
557
|
+
# will actually render — a host var on the inventory line (how an opt-in is
|
|
558
|
+
# written) outranks group_vars. This one is not a pin but a vector space: a
|
|
559
|
+
# config.toml that says "openai" over a boot that renders "local" would make
|
|
560
|
+
# the entrypoint's migration check reset the vector store for the wrong
|
|
561
|
+
# embedder on the next start, so the two are refused apart before anything
|
|
562
|
+
# ships.
|
|
563
|
+
CFG_OV_EMBED="$(config_table_key openviking embedding_provider || true)"
|
|
564
|
+
if [[ -n "$CFG_OV_EMBED" ]]; then
|
|
565
|
+
CFG_OV_EMBED_MODEL="$(config_table_key openviking embedding_model || true)"
|
|
566
|
+
CFG_OV_EMBED_DIM="$(config_table_key openviking embedding_dimension || true)"
|
|
567
|
+
# Read config.toml the way the daemon's parser does (src/config.ts):
|
|
568
|
+
# under `local`, the older spelling (the engine's built-in Chinese-first
|
|
569
|
+
# model, 512 dims, `RETIRED_LOCAL_EMBEDDING`) means the local English
|
|
570
|
+
# embedder (`LOCAL_EMBEDDING`). So a config.toml with the older spelling
|
|
571
|
+
# needs no edit, and the same file keeps loading on a release that
|
|
572
|
+
# refuses the new spelling — a rollback cannot strand it. Both
|
|
573
|
+
# pairs are pinned to config.ts by tests/deploy.test.ts.
|
|
574
|
+
RETIRED_LOCAL_EMBED_MODEL="bge-small-zh-v1.5-f16"
|
|
575
|
+
RETIRED_LOCAL_EMBED_DIM="512"
|
|
576
|
+
LOCAL_EMBED_MODEL="bge-small-en-v1.5-f16"
|
|
577
|
+
LOCAL_EMBED_DIM="384"
|
|
578
|
+
if [[ "$CFG_OV_EMBED" == "local" && "$CFG_OV_EMBED_MODEL" == "$RETIRED_LOCAL_EMBED_MODEL" \
|
|
579
|
+
&& ( -z "$CFG_OV_EMBED_DIM" || "$CFG_OV_EMBED_DIM" == "$RETIRED_LOCAL_EMBED_DIM" ) ]]; then
|
|
580
|
+
note "config.toml spells the retired local embedder ($RETIRED_LOCAL_EMBED_MODEL/${CFG_OV_EMBED_DIM:-default}) — read as $LOCAL_EMBED_MODEL/$LOCAL_EMBED_DIM, exactly as the daemon's parser reads it"
|
|
581
|
+
CFG_OV_EMBED_MODEL="$LOCAL_EMBED_MODEL"
|
|
582
|
+
CFG_OV_EMBED_DIM="$LOCAL_EMBED_DIM"
|
|
583
|
+
fi
|
|
584
|
+
check_pin "openviking embedding provider" \
|
|
585
|
+
'config.toml [openviking] embedding_provider' "$CFG_OV_EMBED" \
|
|
586
|
+
'ansible (host var, else group_vars) openviking_embedding_provider' "$(effective_var openviking_embedding_provider || true)"
|
|
587
|
+
check_pin "openviking embedding model" \
|
|
588
|
+
'config.toml [openviking] embedding_model' "$CFG_OV_EMBED_MODEL" \
|
|
589
|
+
'ansible (host var, else group_vars) openviking_embedding_model' "$(effective_var openviking_embedding_model || true)"
|
|
590
|
+
check_pin "openviking embedding dimension" \
|
|
591
|
+
'config.toml [openviking] embedding_dimension' "$CFG_OV_EMBED_DIM" \
|
|
592
|
+
'ansible (host var, else group_vars) openviking_embedding_dimension' "$(effective_var openviking_embedding_dimension || true)"
|
|
593
|
+
check_pin "openviking embeddings key file" \
|
|
594
|
+
'config.toml [openviking] embedding_key_file' "$(config_table_key openviking embedding_key_file || true)" \
|
|
595
|
+
'ansible (host var, else group_vars) openviking_embedding_key_file' "$(effective_var openviking_embedding_key_file || true)"
|
|
596
|
+
fi
|
|
597
|
+
# The local embedder's model file is a pin like `version` —
|
|
598
|
+
# config.toml == group_vars == the image's model layer (which interpolates
|
|
599
|
+
# these vars, so agreeing here means agreeing in the image). Its port and
|
|
600
|
+
# threads are mirrored the same way: the boot's provisioning step runs the
|
|
601
|
+
# service and renders ov.conf's api_base from the ansible values, the daemon
|
|
602
|
+
# supervises it from config.toml's — a port they disagree on is a search
|
|
603
|
+
# nothing serves. A config.toml that predates the pin names none of the five
|
|
604
|
+
# keys: the code defaults are the pin then (held to group_vars by
|
|
605
|
+
# tests/openviking.test.ts) and the check stands down, said aloud. One that
|
|
606
|
+
# names any of them is checked key by key — every named key against
|
|
607
|
+
# group_vars first, so a drift reads as the drift it is — and a partial block
|
|
608
|
+
# is then refused, never half-verified.
|
|
609
|
+
CFG_OV_LE_FILE="$(config_table_key openviking local_embedder_file || true)"
|
|
610
|
+
CFG_OV_LE_URL="$(config_table_key openviking local_embedder_url || true)"
|
|
611
|
+
CFG_OV_LE_SHA="$(config_table_key openviking local_embedder_sha256 || true)"
|
|
612
|
+
CFG_OV_LE_PORT="$(config_table_key openviking local_embedder_port || true)"
|
|
613
|
+
CFG_OV_LE_THREADS="$(config_table_key openviking local_embedder_threads || true)"
|
|
614
|
+
if [[ -n "$CFG_OV_LE_FILE$CFG_OV_LE_URL$CFG_OV_LE_SHA$CFG_OV_LE_PORT$CFG_OV_LE_THREADS" ]]; then
|
|
615
|
+
GV_LE_DIR="$(effective_var openviking_local_embedder_dir || true)"
|
|
616
|
+
GV_LE_MODEL="$(effective_var openviking_local_embedder_model || true)"
|
|
617
|
+
GV_LE_FILE=""
|
|
618
|
+
[[ -n "$GV_LE_DIR" && -n "$GV_LE_MODEL" ]] && GV_LE_FILE="$GV_LE_DIR/$GV_LE_MODEL.gguf"
|
|
619
|
+
[[ -z "$CFG_OV_LE_FILE" ]] || check_pin "local embedder model file" \
|
|
620
|
+
'config.toml [openviking] local_embedder_file' "$CFG_OV_LE_FILE" \
|
|
621
|
+
'ansible openviking_local_embedder_dir/<model>.gguf' "$GV_LE_FILE"
|
|
622
|
+
[[ -z "$CFG_OV_LE_URL" ]] || check_pin "local embedder model url" \
|
|
623
|
+
'config.toml [openviking] local_embedder_url' "$CFG_OV_LE_URL" \
|
|
624
|
+
'ansible openviking_local_embedder_url' "$(effective_var openviking_local_embedder_url || true)"
|
|
625
|
+
[[ -z "$CFG_OV_LE_SHA" ]] || check_pin "local embedder model sha256" \
|
|
626
|
+
'config.toml [openviking] local_embedder_sha256' "$CFG_OV_LE_SHA" \
|
|
627
|
+
'ansible openviking_local_embedder_sha256' "$(effective_var openviking_local_embedder_sha256 || true)"
|
|
628
|
+
[[ -z "$CFG_OV_LE_PORT" ]] || check_pin "local embedder port" \
|
|
629
|
+
'config.toml [openviking] local_embedder_port' "$CFG_OV_LE_PORT" \
|
|
630
|
+
'ansible openviking_local_embedder_port' "$(effective_var openviking_local_embedder_port || true)"
|
|
631
|
+
[[ -z "$CFG_OV_LE_THREADS" ]] || check_pin "local embedder threads" \
|
|
632
|
+
'config.toml [openviking] local_embedder_threads' "$CFG_OV_LE_THREADS" \
|
|
633
|
+
'ansible openviking_local_embedder_threads' "$(effective_var openviking_local_embedder_threads || true)"
|
|
634
|
+
LE_MISSING=""
|
|
635
|
+
[[ -n "$CFG_OV_LE_FILE" ]] || LE_MISSING+=" local_embedder_file"
|
|
636
|
+
[[ -n "$CFG_OV_LE_URL" ]] || LE_MISSING+=" local_embedder_url"
|
|
637
|
+
[[ -n "$CFG_OV_LE_SHA" ]] || LE_MISSING+=" local_embedder_sha256"
|
|
638
|
+
[[ -n "$CFG_OV_LE_PORT" ]] || LE_MISSING+=" local_embedder_port"
|
|
639
|
+
[[ -n "$CFG_OV_LE_THREADS" ]] || LE_MISSING+=" local_embedder_threads"
|
|
640
|
+
[[ -z "$LE_MISSING" ]] || die "config.toml [openviking] names only part of the local embedder pin — it lacks${LE_MISSING}. Name all five (the block in config.example.toml) or none, so this preflight can verify them"
|
|
641
|
+
else
|
|
642
|
+
note "config.toml names no [openviking] local_embedder_* pin — the code defaults apply (tests hold them to group_vars); add the block from config.example.toml to have this preflight verify it"
|
|
643
|
+
fi
|
|
644
|
+
CFG_OV_EXT="$(config_table_key openviking extension_commit || true)"
|
|
645
|
+
if [[ -n "$CFG_OV_EXT" ]]; then
|
|
646
|
+
check_pin "openviking pi-extension base commit" \
|
|
647
|
+
'config.toml [openviking] extension_commit' "$CFG_OV_EXT" \
|
|
648
|
+
'upstream.json "commit"' "$(upstream_commit || true)"
|
|
649
|
+
fi
|
|
650
|
+
else
|
|
651
|
+
note "config.toml carries no [openviking] block — memory-engine pins not verified"
|
|
652
|
+
fi
|
|
653
|
+
|
|
654
|
+
# 2c. who runs OpenViking. Mirrored like the pins: the entrypoint (which skips
|
|
655
|
+
# the memory boot under "remote") follows group_vars, the daemon (which probes a
|
|
656
|
+
# sidecar instead of supervising a child) follows config.toml, and the two
|
|
657
|
+
# disagreeing is either two writers on one store or a daemon probing a server
|
|
658
|
+
# nobody started. Both default to "local" (a config.toml or group_vars that
|
|
659
|
+
# predates the sidecar names neither key).
|
|
660
|
+
CFG_OV_ENDPOINT="$(config_table_key openviking endpoint || true)"
|
|
661
|
+
CFG_OV_ENDPOINT="${CFG_OV_ENDPOINT:-local}"
|
|
662
|
+
OV_ENDPOINT="$(effective_var openviking_endpoint || true)"
|
|
663
|
+
OV_ENDPOINT="${OV_ENDPOINT:-local}"
|
|
664
|
+
[[ "$OV_ENDPOINT" == local || "$OV_ENDPOINT" == remote ]] ||
|
|
665
|
+
die "openviking_endpoint is \"$OV_ENDPOINT\" ($(effective_var_source openviking_endpoint)) — it must be \"local\" or \"remote\""
|
|
666
|
+
check_pin "openviking endpoint" \
|
|
667
|
+
'config.toml [openviking] endpoint' "$CFG_OV_ENDPOINT" \
|
|
668
|
+
"$(effective_var_source openviking_endpoint) openviking_endpoint" "$OV_ENDPOINT"
|
|
669
|
+
# The daemon container's name: group_vars/inventory image_name, so an install
|
|
670
|
+
# that predates the default (another name on the host) keeps its container.
|
|
671
|
+
CONTAINER="$(effective_var_or image_name quickreview-sandbox)"
|
|
672
|
+
[[ "$CONTAINER" =~ ^[A-Za-z0-9][A-Za-z0-9_.-]*$ ]] ||
|
|
673
|
+
die "image_name \"$CONTAINER\" is not a plain container name — letters, digits, '.', '_' and '-' only"
|
|
674
|
+
MEMORY_CONTAINER="$(effective_var memory_container_name || true)"
|
|
675
|
+
MEMORY_CONTAINER="${MEMORY_CONTAINER:-quickreview-memory}"
|
|
676
|
+
OV_PORT="$(effective_var openviking_port || true)"
|
|
677
|
+
OV_PORT="${OV_PORT:-1933}"
|
|
678
|
+
OV_REVIEWER_KEY_FILE="$(effective_var openviking_reviewer_key_file || true)"
|
|
679
|
+
OV_REVIEWER_KEY_FILE="${OV_REVIEWER_KEY_FILE:-openviking_reviewer_key}"
|
|
680
|
+
if [[ "$OV_ENDPOINT" == remote ]]; then
|
|
681
|
+
# The name reaches docker's DNS and the verify commands' shell.
|
|
682
|
+
[[ "$MEMORY_CONTAINER" =~ ^[A-Za-z0-9][A-Za-z0-9_.-]*$ ]] ||
|
|
683
|
+
die "memory_container_name \"$MEMORY_CONTAINER\" is not a plain container name — letters, digits, '.', '_' and '-' only"
|
|
684
|
+
[[ "$MEMORY_CONTAINER" != "$CONTAINER" ]] ||
|
|
685
|
+
die "memory_container_name is the daemon's own container name ($CONTAINER) — the sidecar needs a name of its own"
|
|
686
|
+
check_pin "memory sidecar host" \
|
|
687
|
+
'config.toml [openviking] host' "$(config_table_key openviking host || true)" \
|
|
688
|
+
"$(effective_var_source memory_container_name) memory_container_name" "$MEMORY_CONTAINER"
|
|
689
|
+
check_pin "memory sidecar port" \
|
|
690
|
+
'config.toml [openviking] port' "$(CFG_P="$(config_table_key openviking port || true)"; echo "${CFG_P:-1933}")" \
|
|
691
|
+
"$(effective_var_source openviking_port) openviking_port" "$OV_PORT"
|
|
692
|
+
fi
|
|
693
|
+
|
|
694
|
+
# 2c'. the daemon container's name: the verify commands and the run spec name
|
|
695
|
+
# it here, group_vars' image_name is the playbook's. One name or none.
|
|
696
|
+
GV_IMAGE_NAME="$(effective_var image_name || true)"
|
|
697
|
+
[[ -z "$GV_IMAGE_NAME" || "$GV_IMAGE_NAME" == "$CONTAINER" ]] ||
|
|
698
|
+
die "image_name is \"$GV_IMAGE_NAME\" ($(effective_var_source image_name)) but this script and the run spec name the daemon container $CONTAINER"
|
|
699
|
+
|
|
700
|
+
# 2d. the release image runs the daemon and nothing else: OpenViking is the
|
|
701
|
+
# sidecar's. A shipping mode on a "local" endpoint would boot a daemon that
|
|
702
|
+
# supervises a server the image does not have.
|
|
703
|
+
if [[ "$MODE" != verify && "$OV_ENDPOINT" != remote ]]; then
|
|
704
|
+
die "openviking endpoint is \"$OV_ENDPOINT\" — the release image carries no OpenViking, so $MODE needs the memory sidecar: set group_vars/inventory openviking_endpoint AND config.toml [openviking] endpoint to \"remote\" (and [openviking] host to the sidecar's name). The way back to \"local\" is the legacy image and the old playbook (deploy/ansible/README.md, Rollback)"
|
|
705
|
+
fi
|
|
706
|
+
|
|
707
|
+
# 2e. the TUI finds the app root inside the container by itself: it reads the
|
|
708
|
+
# running container's qcr.layout label (src/tui/layout.ts), release = the
|
|
709
|
+
# image's app root, so no [tui] key is edited for a release or for the cutover.
|
|
710
|
+
# An EXPLICIT [tui] remote_sandbox_root wins over that, so one that names another
|
|
711
|
+
# root points the TUI at a path the release container does not have: warn, do
|
|
712
|
+
# not refuse (the key is the operator's, and the release does not read it).
|
|
713
|
+
APP_ROOT="$(effective_var app_root || true)"
|
|
714
|
+
APP_ROOT="${APP_ROOT:-/opt/qcr}"
|
|
715
|
+
if [[ "$MODE" == release ]]; then
|
|
716
|
+
CFG_TUI_ROOT="$(config_table_key tui remote_sandbox_root || true)"
|
|
717
|
+
if [[ -n "$CFG_TUI_ROOT" && "$CFG_TUI_ROOT" != "$APP_ROOT" ]]; then
|
|
718
|
+
cat >&2 <<EOF
|
|
719
|
+
$SELF: warning: [tui] remote_sandbox_root = "$CFG_TUI_ROOT" is set explicitly in $CONFIG, and the release image keeps the app at $APP_ROOT.
|
|
720
|
+
An explicit key wins over the TUI's own layout detection, so the TUI will look for the deploy marker,
|
|
721
|
+
the operator channel, messages and the digest under "$CFG_TUI_ROOT". Delete the key (or set it to
|
|
722
|
+
"$APP_ROOT") unless that is what you want.
|
|
723
|
+
EOF
|
|
724
|
+
fi
|
|
725
|
+
fi
|
|
726
|
+
|
|
727
|
+
# Slack tokens ride the secrets mount under their configured flat filenames.
|
|
728
|
+
SLACK_ENABLED="$(config_table_key slack enabled)"
|
|
729
|
+
SLACK_BOT_TOKEN_FILE="$(config_table_key slack bot_token_file)"
|
|
730
|
+
SLACK_APP_TOKEN_FILE="$(config_table_key slack app_token_file)"
|
|
731
|
+
SLACK_BOT_TOKEN_FILE="${SLACK_BOT_TOKEN_FILE:-slack_bot_token}"
|
|
732
|
+
SLACK_APP_TOKEN_FILE="${SLACK_APP_TOKEN_FILE:-slack_app_token}"
|
|
733
|
+
if [[ "$MODE" == provision ]]; then
|
|
734
|
+
for token_key in bot_token_file app_token_file; do
|
|
735
|
+
token_name="$SLACK_BOT_TOKEN_FILE"
|
|
736
|
+
[[ "$token_key" == app_token_file ]] && token_name="$SLACK_APP_TOKEN_FILE"
|
|
737
|
+
if [[ ! "$token_name" =~ ^[A-Za-z0-9_.-]+$ || "$token_name" == . || "$token_name" == .. ]]; then
|
|
738
|
+
if [[ "$SLACK_ENABLED" == true ]]; then
|
|
739
|
+
die "[slack] $token_key must be a flat filename for VM deployment; move the token into the configured secrets directory and use its filename"
|
|
740
|
+
fi
|
|
741
|
+
# Disabled Slack's unused host paths never widen the secrets copy.
|
|
742
|
+
if [[ "$token_key" == app_token_file ]]; then
|
|
743
|
+
SLACK_APP_TOKEN_FILE=slack_app_token
|
|
744
|
+
else
|
|
745
|
+
SLACK_BOT_TOKEN_FILE=slack_bot_token
|
|
746
|
+
fi
|
|
747
|
+
fi
|
|
748
|
+
done
|
|
749
|
+
fi
|
|
750
|
+
|
|
751
|
+
# ---- plan --------------------------------------------------------------------
|
|
752
|
+
|
|
753
|
+
# provision is the only mode that runs ansible, and the whole playbook runs: the
|
|
754
|
+
# host packages, Docker, cloudflared, the secrets, the directory layout, the
|
|
755
|
+
# updater and its run spec. No tags, no start-at-task. The run spec is rendered
|
|
756
|
+
# here (render_runspec) into a file the playbook installs as /etc/qcr/update.conf.
|
|
757
|
+
WORK_DIR="$(mktemp -d "${TMPDIR:-/tmp}/qcr-deploy.XXXXXX")"
|
|
758
|
+
trap 'rm -rf "$WORK_DIR"' EXIT
|
|
759
|
+
UPDATE_CONF_SRC="$WORK_DIR/update.conf"
|
|
760
|
+
ANSIBLE_CMD=()
|
|
761
|
+
if [[ "$MODE" == provision ]]; then
|
|
762
|
+
ANSIBLE_CMD=(ansible-playbook -i "$INVENTORY" "$PLAYBOOK"
|
|
763
|
+
-e "base_clone_dir=$CLONE_DIR" -e "secrets_src=$SECRETS_SRC"
|
|
764
|
+
-e "slack_bot_token_file=$SLACK_BOT_TOKEN_FILE" -e "slack_app_token_file=$SLACK_APP_TOKEN_FILE"
|
|
765
|
+
-e "update_conf_src=$UPDATE_CONF_SRC")
|
|
766
|
+
fi
|
|
767
|
+
|
|
768
|
+
# Ansible's `| bool`: its BOOLEANS_TRUE set (quoted or not, any case) is true,
|
|
769
|
+
# everything else false — the same reading src/onboard/steps/inventory.ts's
|
|
770
|
+
# ansibleBool gives the onboarding check.
|
|
771
|
+
ansible_bool() {
|
|
772
|
+
local v="$1"
|
|
773
|
+
v="${v#\"}"; v="${v%\"}"; v="${v#\'}"; v="${v%\'}"
|
|
774
|
+
v="$(printf '%s' "$v" | tr '[:upper:]' '[:lower:]')"
|
|
775
|
+
case "$v" in
|
|
776
|
+
y|yes|on|1|true|t|1.0) return 0 ;;
|
|
777
|
+
*) return 1 ;;
|
|
778
|
+
esac
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
# The playbook installs and starts cloudflared only under `cloudflared_enabled`
|
|
782
|
+
# (an inventory host var over the group_vars default), so the service is
|
|
783
|
+
# required exactly when that effective value is true. An unreadable value
|
|
784
|
+
# counts as the tracked default, true.
|
|
785
|
+
CLOUDFLARED_ENABLED="$(effective_var cloudflared_enabled || true)"
|
|
786
|
+
CLOUDFLARED_ENABLED="${CLOUDFLARED_ENABLED:-true}"
|
|
787
|
+
VM_HOST="${QC_VM_HOST:-$(first_inventory_host)}"
|
|
788
|
+
VERIFY_CMDS=()
|
|
789
|
+
if ansible_bool "$CLOUDFLARED_ENABLED"; then
|
|
790
|
+
VERIFY_CMDS+=("systemctl is-active cloudflared")
|
|
791
|
+
fi
|
|
792
|
+
VERIFY_CMDS+=(
|
|
793
|
+
"systemctl is-active docker"
|
|
794
|
+
"sudo docker ps --filter name=$CONTAINER --format '{{.Names}} {{.Status}}'"
|
|
795
|
+
)
|
|
796
|
+
# The memory sidecar is a second container: it must be listed AND running (a
|
|
797
|
+
# `docker ps` that finds nothing still exits 0, so the check reads the state).
|
|
798
|
+
if [[ "$OV_ENDPOINT" == remote ]]; then
|
|
799
|
+
VERIFY_CMDS+=(
|
|
800
|
+
"sudo docker ps --filter name=$MEMORY_CONTAINER --format '{{.Names}} {{.Status}}'"
|
|
801
|
+
"test \"\$(sudo docker container inspect -f '{{.State.Running}}' $MEMORY_CONTAINER)\" = true"
|
|
802
|
+
)
|
|
803
|
+
fi
|
|
804
|
+
# What runs, and that it is the release layout: the image's build stamp (the
|
|
805
|
+
# deploy tab reads the same label), then — once the heartbeat shows the
|
|
806
|
+
# entrypoint has finished — that pi's settings carry no global bash-guard entry
|
|
807
|
+
# (the guard is a factory on every session; the entrypoint prunes the one
|
|
808
|
+
# an older image left on the pihome volume), and the 12-line reviewer stub
|
|
809
|
+
# every session loads instead of the contributor guide.
|
|
810
|
+
SANDBOX_ROOT_EFF="$(effective_var_or sandbox_root /opt/quickreview)"
|
|
811
|
+
VERIFY_CMDS+=(
|
|
812
|
+
"sudo docker inspect -f '{{.Config.Image}} build {{index .Config.Labels \"qcr.build-sha\"}}' $CONTAINER"
|
|
813
|
+
"sudo docker inspect -f '{{.Config.Image}} {{index .Config.Labels \"qcr.build-sha\"}}' $CONTAINER | grep -Eq ' [0-9a-f]{7,40}\$'"
|
|
814
|
+
"sudo docker exec $CONTAINER tmux ls || echo 'no tmux sessions yet'"
|
|
815
|
+
"cat $SANDBOX_ROOT_EFF/state/status.json | head -20 # heartbeat for the status collector"
|
|
816
|
+
)
|
|
817
|
+
BOOT_CMDS=(
|
|
818
|
+
"sudo docker exec $CONTAINER sh -c '! grep -q bash-guard /home/qcr/.pi/agent/settings.json'"
|
|
819
|
+
"sudo docker exec $CONTAINER sh -c 'test \"\$(stat -c %a $APP_ROOT/AGENTS.override.md)\" = 444 && grep -q \"session context\" $APP_ROOT/AGENTS.override.md'"
|
|
820
|
+
)
|
|
821
|
+
|
|
822
|
+
# The sidecar's post-deploy checks, run over ssh like the others. /ready is the
|
|
823
|
+
# server's own readiness (the embedder it probes included); the search runs from
|
|
824
|
+
# the DAEMON's container, as a round would make it: the sidecar's name resolves,
|
|
825
|
+
# the reviewer key authenticates, a vector search answers
|
|
826
|
+
# (scripts/memory-search-check.mjs, which the image carries).
|
|
827
|
+
memory_ready_cmd() {
|
|
828
|
+
echo "sudo docker exec $MEMORY_CONTAINER curl -fsS http://127.0.0.1:$OV_PORT/ready"
|
|
829
|
+
}
|
|
830
|
+
memory_search_cmd() {
|
|
831
|
+
echo "sudo docker exec $CONTAINER node $APP_ROOT/scripts/memory-search-check.mjs --base http://$MEMORY_CONTAINER:$OV_PORT --key-file /opt/quickreview/secrets/$OV_REVIEWER_KEY_FILE"
|
|
832
|
+
}
|
|
833
|
+
|
|
834
|
+
# ---- the run spec ---------------------------------------------------------------
|
|
835
|
+
# /etc/qcr/update.conf on the VM, rendered HERE from group_vars (and the
|
|
836
|
+
# inventory's host vars over them, as effective_var reads) so the playbook has
|
|
837
|
+
# nothing to template and the controller can recompute the same bytes: a release
|
|
838
|
+
# compares this fingerprint with the one the VM's updater reports, and refuses a
|
|
839
|
+
# drift with "re-run provision". KEY=VALUE only, every value from the plain
|
|
840
|
+
# charset qcr-update reads. The fingerprint is the sha256 of every other
|
|
841
|
+
# KEY=VALUE line, in this order. The defaults are group_vars/all.yml's.
|
|
842
|
+
|
|
843
|
+
runspec_value() { effective_var_or "$1" "$2"; }
|
|
844
|
+
|
|
845
|
+
runspec_body() {
|
|
846
|
+
local v
|
|
847
|
+
v() { runspec_value "$1" "$2"; }
|
|
848
|
+
local tz openviking_home
|
|
849
|
+
tz="$(v timezone Europe/Warsaw)"
|
|
850
|
+
openviking_home="$(v openviking_home /opt/quickreview/state/openviking)"
|
|
851
|
+
printf 'RUNSPEC_VERSION=%s\n' "$RUNSPEC_VERSION"
|
|
852
|
+
printf 'SANDBOX_ROOT=%s\n' "$(v sandbox_root /opt/quickreview)"
|
|
853
|
+
printf 'APP_ROOT=%s\n' "$APP_ROOT"
|
|
854
|
+
printf 'DAEMON_CONTAINER=%s\n' "$CONTAINER"
|
|
855
|
+
printf 'MEMORY_CONTAINER=%s\n' "$MEMORY_CONTAINER"
|
|
856
|
+
printf 'DAEMON_IMAGE_REPO=%s\n' "$(v release_image ghcr.io/quickchatai/quickreview)"
|
|
857
|
+
printf 'MEMORY_IMAGE_REPO=%s\n' "$(v release_memory_image ghcr.io/quickchatai/quickreview-memory)"
|
|
858
|
+
printf 'MEMORY_NETWORK=%s\n' "$(v memory_network quickreview-memory)"
|
|
859
|
+
printf 'WEBHOOK_PORT=%s\n' "$(v webhook_port 8787)"
|
|
860
|
+
printf 'CONTAINER_UID=%s\n' "$(v container_uid 1001)"
|
|
861
|
+
printf 'TOOL_UID=%s\n' "$(v tool_uid 65534)"
|
|
862
|
+
printf 'TOOL_GID=%s\n' "$(v tool_gid 4749)"
|
|
863
|
+
printf 'TZ=%s\n' "$tz"
|
|
864
|
+
printf 'DAEMON_CPUS=%s\n' "$(v container_cpus 2)"
|
|
865
|
+
printf 'DAEMON_MEMORY=%s\n' "$(v container_memory 8g)"
|
|
866
|
+
printf 'MEMORY_CPUS=%s\n' "$(v memory_container_cpus 1)"
|
|
867
|
+
printf 'MEMORY_MEMORY=%s\n' "$(v memory_container_memory 3g)"
|
|
868
|
+
printf 'MEMORY_STATE_DIR=%s\n' "$(v memory_state_dir /opt/quickreview/memory-state)"
|
|
869
|
+
printf 'OPENVIKING_HOME=%s\n' "$openviking_home"
|
|
870
|
+
printf 'OPENVIKING_PORT=%s\n' "$OV_PORT"
|
|
871
|
+
printf 'BASE_CLONE_DIR=%s\n' "$CLONE_DIR"
|
|
872
|
+
printf 'LEGACY_SANDBOX_ROOT=%s\n' "$(v legacy_sandbox_root /opt/quickreview/sandbox)"
|
|
873
|
+
printf 'BACKUP_DIR=%s\n' "$(v backup_dir /var/backups/qcr)"
|
|
874
|
+
printf 'UPDATE_STATE_DIR=%s\n' "$(v update_state_dir /var/lib/qcr-update)"
|
|
875
|
+
printf 'MEMORY_READY_TIMEOUT_S=%s\n' "$(v memory_ready_timeout_s 1800)"
|
|
876
|
+
# The sidecar's boot knobs: docker/memory/entrypoint.sh renders ov.conf from
|
|
877
|
+
# these, the values config.toml [openviking] is held equal to above.
|
|
878
|
+
printf 'SIDECAR_ENV_TZ=%s\n' "$tz"
|
|
879
|
+
printf 'SIDECAR_ENV_QCR_OPENVIKING_HOME=%s\n' "$openviking_home"
|
|
880
|
+
printf 'SIDECAR_ENV_QCR_OPENVIKING_PORT=%s\n' "$OV_PORT"
|
|
881
|
+
printf 'SIDECAR_ENV_QCR_OPENVIKING_BIND_HOST=0.0.0.0\n'
|
|
882
|
+
printf 'SIDECAR_ENV_QCR_OPENVIKING_WORKERS=%s\n' "$(v openviking_workers 1)"
|
|
883
|
+
printf 'SIDECAR_ENV_QCR_OPENVIKING_QUEUE_CONCURRENCY=%s\n' "$(v openviking_queue_concurrency 2)"
|
|
884
|
+
printf 'SIDECAR_ENV_QCR_OPENVIKING_AUTH_MODE=%s\n' "$(v openviking_auth_mode api_key)"
|
|
885
|
+
printf 'SIDECAR_ENV_QCR_OPENVIKING_EMBEDDING_PROVIDER=%s\n' "$(v openviking_embedding_provider local)"
|
|
886
|
+
printf 'SIDECAR_ENV_QCR_OPENVIKING_EMBEDDING_MODEL=%s\n' "$(v openviking_embedding_model bge-small-en-v1.5-f16)"
|
|
887
|
+
printf 'SIDECAR_ENV_QCR_OPENVIKING_EMBEDDING_DIMENSION=%s\n' "$(v openviking_embedding_dimension 384)"
|
|
888
|
+
printf 'SIDECAR_ENV_QCR_OPENVIKING_EMBEDDING_KEY_FILE=%s\n' "$(v openviking_embedding_key_file openai_embeddings_api_key)"
|
|
889
|
+
printf 'SIDECAR_ENV_QCR_OPENVIKING_LOCAL_EMBEDDER_MODEL=%s\n' "$(v openviking_local_embedder_model bge-small-en-v1.5-f16)"
|
|
890
|
+
printf 'SIDECAR_ENV_QCR_OPENVIKING_LOCAL_EMBEDDER_DIMENSION=%s\n' "$(v openviking_local_embedder_dimension 384)"
|
|
891
|
+
printf 'SIDECAR_ENV_QCR_OPENVIKING_LOCAL_EMBEDDER_DIR=%s\n' "$(v openviking_local_embedder_dir /opt/openviking/models)"
|
|
892
|
+
printf 'SIDECAR_ENV_QCR_OPENVIKING_LOCAL_EMBEDDER_SHA256=%s\n' "$(v openviking_local_embedder_sha256 f0b2fef971e8366438bfd2d9aefea1b0115919389448806d290237f638bae999)"
|
|
893
|
+
printf 'SIDECAR_ENV_QCR_OPENVIKING_LOCAL_EMBEDDER_PORT=%s\n' "$(v openviking_local_embedder_port 1934)"
|
|
894
|
+
printf 'SIDECAR_ENV_QCR_OPENVIKING_LOCAL_EMBEDDER_THREADS=%s\n' "$(v openviking_local_embedder_threads 1)"
|
|
895
|
+
printf 'SIDECAR_ENV_QCR_OPENVIKING_ROOT_KEY_FILE=%s\n' "$(v openviking_root_key_file openviking_root_key)"
|
|
896
|
+
printf 'SIDECAR_ENV_QCR_OPENVIKING_REVIEWER_KEY_FILE=%s\n' "$OV_REVIEWER_KEY_FILE"
|
|
897
|
+
printf 'SIDECAR_ENV_QCR_OPENVIKING_PROVISION_USERS=%s\n' "$(v openviking_provision_users reviewer,companion)"
|
|
898
|
+
}
|
|
899
|
+
|
|
900
|
+
# Rendered once, here, at the top level (a die inside a command substitution
|
|
901
|
+
# would only end the substitution): every line must be a KEY=VALUE qcr-update
|
|
902
|
+
# will read, so a group_vars value with a space, quote or `$` stops the run.
|
|
903
|
+
RUNSPEC_BODY=""
|
|
904
|
+
RUNSPEC_FP=""
|
|
905
|
+
if [[ "$MODE" != verify ]]; then
|
|
906
|
+
RUNSPEC_BODY="$(runspec_body)"
|
|
907
|
+
bad_line="$(printf '%s\n' "$RUNSPEC_BODY" | grep -vE '^[A-Z][A-Z0-9_]*=[A-Za-z0-9_./:@+,-]*$' | head -1 || true)"
|
|
908
|
+
[[ -z "$bad_line" ]] ||
|
|
909
|
+
die "the run spec cannot carry \"$bad_line\" — group_vars values ride /etc/qcr/update.conf, which allows letters, digits and _ . / : @ + , - only"
|
|
910
|
+
RUNSPEC_FP="$(printf '%s\n' "$RUNSPEC_BODY" | sha256sum | cut -d' ' -f1)"
|
|
911
|
+
fi
|
|
912
|
+
runspec_fingerprint() { echo "$RUNSPEC_FP"; }
|
|
913
|
+
|
|
914
|
+
runspec_file() {
|
|
915
|
+
echo "# /etc/qcr/update.conf -- rendered by scripts/deploy.sh provision from group_vars."
|
|
916
|
+
echo "# Root-owned. Do not edit: qcr-update checks RUNSPEC_FINGERPRINT."
|
|
917
|
+
printf '%s\n' "$RUNSPEC_BODY"
|
|
918
|
+
printf 'RUNSPEC_FINGERPRINT=%s\n' "$RUNSPEC_FP"
|
|
919
|
+
}
|
|
920
|
+
|
|
921
|
+
# ---- the deploy manifest (release) ---------------------------------------------------
|
|
922
|
+
# deploy-manifest-<version>.json: CI's record of what it built (the schema is
|
|
923
|
+
# deploy/manifest.schema.json, the parser scripts/deploy-manifest.mjs).
|
|
924
|
+
declare -A MF=()
|
|
925
|
+
load_manifest() {
|
|
926
|
+
[[ "$MODE" == release ]] || return 0
|
|
927
|
+
if [[ -z "$MANIFEST_FILE" && ! -f "./deploy-manifest-$RELEASE_VERSION.json" ]]; then
|
|
928
|
+
# No manifest named and none beside us: the release's own attached copy.
|
|
929
|
+
local dl="$WORK_DIR/manifest-download"
|
|
930
|
+
mkdir -p "$dl"
|
|
931
|
+
if [[ -n "${QC_RELEASE_DOWNLOAD_CMD:-}" ]]; then
|
|
932
|
+
bash -c "$QC_RELEASE_DOWNLOAD_CMD" _ "$RELEASE_VERSION" "$dl" >/dev/null 2>&1 || true
|
|
933
|
+
elif command -v gh >/dev/null 2>&1; then
|
|
934
|
+
gh release download "v$RELEASE_VERSION" --repo "${QC_RELEASE_REPO:-quickchatai/quickreview}" \
|
|
935
|
+
--pattern "deploy-manifest-$RELEASE_VERSION.json" --dir "$dl" >/dev/null 2>&1 || true
|
|
936
|
+
fi
|
|
937
|
+
[[ ! -f "$dl/deploy-manifest-$RELEASE_VERSION.json" ]] ||
|
|
938
|
+
MANIFEST_FILE="$dl/deploy-manifest-$RELEASE_VERSION.json"
|
|
939
|
+
fi
|
|
940
|
+
MANIFEST_FILE="${MANIFEST_FILE:-./deploy-manifest-$RELEASE_VERSION.json}"
|
|
941
|
+
[[ -f "$MANIFEST_FILE" ]] ||
|
|
942
|
+
die "no deploy manifest at $MANIFEST_FILE — the release workflow emits deploy-manifest-$RELEASE_VERSION.json next to the images (deploy/manifest.schema.json); download it (\`gh release download v$RELEASE_VERSION --pattern 'deploy-manifest-*.json'\`), or name it with --manifest"
|
|
943
|
+
local out line
|
|
944
|
+
out="$(node "$(dirname "${BASH_SOURCE[0]}")/deploy-manifest.mjs" "$MANIFEST_FILE" "$RELEASE_VERSION" 2>&1)" ||
|
|
945
|
+
die "refusing the release — $MANIFEST_FILE: $out"
|
|
946
|
+
while IFS= read -r line; do MF["${line%%=*}"]="${line#*=}"; done <<<"$out"
|
|
947
|
+
|
|
948
|
+
# The images are the ones group_vars names.
|
|
949
|
+
check_pin "daemon image repository" \
|
|
950
|
+
"manifest $MANIFEST_FILE" "${MF[DAEMON_REPOSITORY]}" \
|
|
951
|
+
'group_vars release_image' "$(effective_var_or release_image ghcr.io/quickchatai/quickreview)"
|
|
952
|
+
check_pin "memory image repository" \
|
|
953
|
+
"manifest $MANIFEST_FILE" "${MF[MEMORY_REPOSITORY]}" \
|
|
954
|
+
'group_vars release_memory_image' "$(effective_var_or release_memory_image ghcr.io/quickchatai/quickreview-memory)"
|
|
955
|
+
# Rule 7, kept: the pins the images were BUILT with (their pin labels, copied
|
|
956
|
+
# into the manifest) must be the ones config.toml and group_vars hold. A drift
|
|
957
|
+
# here is an image that is not the build this config describes.
|
|
958
|
+
check_pin "pi (manifest)" \
|
|
959
|
+
"manifest pins.pi" "${MF[PIN_PI]}" \
|
|
960
|
+
'config.toml [pi] version' "$CFG_PI"
|
|
961
|
+
if [[ -n "$CFG_OV" ]]; then
|
|
962
|
+
check_pin "openviking (manifest)" \
|
|
963
|
+
"manifest pins.openviking" "${MF[PIN_OPENVIKING]}" \
|
|
964
|
+
'config.toml [openviking] version' "$CFG_OV"
|
|
965
|
+
check_pin "llama-cpp-python (manifest)" \
|
|
966
|
+
"manifest pins.llama_cpp_python" "${MF[PIN_LLAMA_CPP_PYTHON]}" \
|
|
967
|
+
'config.toml [openviking] llama_cpp_python_version' "$(config_table_key openviking llama_cpp_python_version || true)"
|
|
968
|
+
fi
|
|
969
|
+
local gv_dir gv_model gv_sha
|
|
970
|
+
gv_dir="$(effective_var openviking_local_embedder_dir || true)"
|
|
971
|
+
gv_model="$(effective_var openviking_local_embedder_model || true)"
|
|
972
|
+
gv_sha="$(effective_var openviking_local_embedder_sha256 || true)"
|
|
973
|
+
if [[ -n "$gv_dir" && -n "$gv_model" ]]; then
|
|
974
|
+
check_pin "local embedder model file (manifest)" \
|
|
975
|
+
"manifest pins.embedder_file" "${MF[PIN_EMBEDDER_FILE]}" \
|
|
976
|
+
'ansible openviking_local_embedder_dir/<model>.gguf' "$gv_dir/$gv_model.gguf"
|
|
977
|
+
fi
|
|
978
|
+
if [[ -n "$gv_sha" ]]; then
|
|
979
|
+
check_pin "local embedder model sha256 (manifest)" \
|
|
980
|
+
"manifest pins.embedder_sha256" "${MF[PIN_EMBEDDER_SHA256]}" \
|
|
981
|
+
'ansible openviking_local_embedder_sha256' "$gv_sha"
|
|
982
|
+
fi
|
|
983
|
+
if (( MF[PIN_RUNSPEC] > RUNSPEC_VERSION )); then
|
|
984
|
+
die "refusing the release — its images need run spec v${MF[PIN_RUNSPEC]} but this checkout renders v$RUNSPEC_VERSION: update this checkout, then re-run provision"
|
|
985
|
+
fi
|
|
986
|
+
}
|
|
987
|
+
|
|
988
|
+
# ---- what the VM runs now (the updater's own report) -----------------------------------
|
|
989
|
+
# `sudo qcr-update status --json`, over ssh: the layout, the run spec the VM
|
|
990
|
+
# holds (version + fingerprint) and the drain. QC_UPDATE_STATUS_CMD stands in
|
|
991
|
+
# for the ssh (tests). Unreadable is UNKNOWN: a plan says so, a real run refuses.
|
|
992
|
+
VM_STATUS=""
|
|
993
|
+
VM_LAYOUT="unknown"
|
|
994
|
+
VM_FINGERPRINT=""
|
|
995
|
+
VM_DRAINING=""
|
|
996
|
+
VM_ROUNDS=""
|
|
997
|
+
vm_status_read() {
|
|
998
|
+
[[ "$MODE" == release ]] || return 0
|
|
999
|
+
if [[ -n "${QC_UPDATE_STATUS_CMD:-}" ]]; then
|
|
1000
|
+
VM_STATUS="$(bash -c "$QC_UPDATE_STATUS_CMD" 2>/dev/null)" || VM_STATUS=""
|
|
1001
|
+
elif [[ -n "$VM_HOST" ]]; then
|
|
1002
|
+
VM_STATUS="$(ssh -o BatchMode=yes -o ConnectTimeout=10 "$VM_HOST" 'sudo qcr-update status --json' 2>/dev/null)" || VM_STATUS=""
|
|
1003
|
+
fi
|
|
1004
|
+
grep -q '"layout"' <<<"$VM_STATUS" || { VM_STATUS=""; return 0; }
|
|
1005
|
+
VM_LAYOUT="$(grep -o '"layout":"[^"]*"' <<<"$VM_STATUS" | head -1 | cut -d'"' -f4)"
|
|
1006
|
+
VM_FINGERPRINT="$(grep -o '"fingerprint":"[^"]*"' <<<"$VM_STATUS" | head -1 | cut -d'"' -f4)"
|
|
1007
|
+
VM_DRAINING="$(grep -o '"draining":[a-z]*' <<<"$VM_STATUS" | head -1 | cut -d: -f2)"
|
|
1008
|
+
VM_ROUNDS="$(grep -o '"running_rounds":[0-9a-z]*' <<<"$VM_STATUS" | head -1 | cut -d: -f2)"
|
|
1009
|
+
}
|
|
1010
|
+
|
|
1011
|
+
# A run spec the VM does not hold is fatal in any run (the plan included): the
|
|
1012
|
+
# updater would refuse the image, or run a stale container shape.
|
|
1013
|
+
EXPECTED_FINGERPRINT=""
|
|
1014
|
+
runspec_drift() {
|
|
1015
|
+
[[ "$MODE" == release ]] || return 0
|
|
1016
|
+
EXPECTED_FINGERPRINT="$(runspec_fingerprint)"
|
|
1017
|
+
[[ -n "$VM_STATUS" ]] || return 0
|
|
1018
|
+
if [[ "$VM_FINGERPRINT" != "$EXPECTED_FINGERPRINT" ]]; then
|
|
1019
|
+
die "refusing the release — the VM's run spec (fingerprint ${VM_FINGERPRINT:-none}) differs from the one group_vars renders ($EXPECTED_FINGERPRINT): re-run provision (\`$SELF provision\`). Nothing was changed"
|
|
1020
|
+
fi
|
|
1021
|
+
}
|
|
1022
|
+
|
|
1023
|
+
# What the version tag resolves to in the registry must be the manifest's index
|
|
1024
|
+
# digest: the VM runs exactly the bytes CI built and smoked once. A different
|
|
1025
|
+
# digest is fatal in any run (the tag moved, or the manifest is not this
|
|
1026
|
+
# release's); a registry that cannot be read is UNKNOWN (a plan says so, a real
|
|
1027
|
+
# run refuses, see release_problem). The updater pulls by digest either way.
|
|
1028
|
+
TAG_DIGEST_UNREAD=""
|
|
1029
|
+
tag_digest() {
|
|
1030
|
+
local ref="$1"
|
|
1031
|
+
if [[ -n "${QC_TAG_DIGEST_CMD:-}" ]]; then
|
|
1032
|
+
bash -c "$QC_TAG_DIGEST_CMD" _ "$ref" 2>/dev/null
|
|
1033
|
+
elif command -v docker >/dev/null 2>&1; then
|
|
1034
|
+
# The plain listing's Digest line: not every buildx honours --format.
|
|
1035
|
+
docker buildx imagetools inspect "$ref" 2>/dev/null | awk '/^Digest:/ { print $2; exit }'
|
|
1036
|
+
else
|
|
1037
|
+
return 1
|
|
1038
|
+
fi
|
|
1039
|
+
}
|
|
1040
|
+
verify_tag_digests() {
|
|
1041
|
+
[[ "$MODE" == release ]] || return 0
|
|
1042
|
+
local which ref want got
|
|
1043
|
+
for which in DAEMON MEMORY; do
|
|
1044
|
+
ref="${MF[${which}_REPOSITORY]}:$RELEASE_VERSION"
|
|
1045
|
+
want="${MF[${which}_INDEX_DIGEST]}"
|
|
1046
|
+
got="$(tag_digest "$ref" | head -1 | tr -d '[:space:]')" || got=""
|
|
1047
|
+
if [[ -z "$got" ]]; then
|
|
1048
|
+
TAG_DIGEST_UNREAD="${TAG_DIGEST_UNREAD:+$TAG_DIGEST_UNREAD, }$ref"
|
|
1049
|
+
elif [[ "$got" != "$want" ]]; then
|
|
1050
|
+
die "refusing the release — $ref resolves to $got but the manifest says $want: the tag moved or the manifest is not this release's. The VM must run exactly the bytes CI built. Nothing was changed"
|
|
1051
|
+
fi
|
|
1052
|
+
done
|
|
1053
|
+
}
|
|
1054
|
+
|
|
1055
|
+
# Things a real run refuses and a plan reports (the updater refuses them too):
|
|
1056
|
+
# the layout needs the ack, the board must be drained and idle. Prints the first.
|
|
1057
|
+
release_problem() {
|
|
1058
|
+
[[ "$MODE" == release ]] || return 0
|
|
1059
|
+
if [[ -z "$VM_STATUS" ]]; then
|
|
1060
|
+
echo "the VM's \`qcr-update status\` could not be read (ssh, or provision never installed the updater), so its layout, run spec and drain are unknown"
|
|
1061
|
+
return 0
|
|
1062
|
+
fi
|
|
1063
|
+
if [[ -n "$TAG_DIGEST_UNREAD" ]]; then
|
|
1064
|
+
echo "the registry could not be read for $TAG_DIGEST_UNREAD, so the manifest's digests are not checked against the tags (the docker CLI, a ghcr login)"
|
|
1065
|
+
return 0
|
|
1066
|
+
fi
|
|
1067
|
+
if [[ "$VM_LAYOUT" == legacy && $CUTOVER_ACK -eq 0 ]]; then
|
|
1068
|
+
echo "the VM still runs the legacy layout: this deploy is the cutover (the first boot migrates forward). Re-run with --cutover-ack"
|
|
1069
|
+
return 0
|
|
1070
|
+
fi
|
|
1071
|
+
# no daemon container: a first install has nothing to drain (the updater decides
|
|
1072
|
+
# whether the state on the VM makes it one)
|
|
1073
|
+
if [[ "$VM_LAYOUT" == none ]]; then
|
|
1074
|
+
return 0
|
|
1075
|
+
fi
|
|
1076
|
+
if [[ "$VM_DRAINING" != true ]]; then
|
|
1077
|
+
echo "the bot is not drained (draining=${VM_DRAINING:-unknown}): run \`qcr drain on\`, wait for running_rounds 0, then deploy"
|
|
1078
|
+
return 0
|
|
1079
|
+
fi
|
|
1080
|
+
if [[ "$VM_ROUNDS" != 0 ]]; then
|
|
1081
|
+
echo "the board is busy (running_rounds=${VM_ROUNDS:-unknown}): wait for running_rounds 0, then deploy"
|
|
1082
|
+
return 0
|
|
1083
|
+
fi
|
|
1084
|
+
}
|
|
1085
|
+
|
|
1086
|
+
# The VM's ghcr pull token expires (1 year, by ruling), and a lapsed one fails
|
|
1087
|
+
# the updater's pull. scripts/vm-ghcr-login.sh records the expiry on this
|
|
1088
|
+
# controller; release and verify read it back. Within GHCR_WARN_DAYS of it
|
|
1089
|
+
# (or past it) the plan line starts with WARNING. Warn only, never a refusal.
|
|
1090
|
+
GHCR_WARN_DAYS=14
|
|
1091
|
+
GHCR_EXPIRY_LINE=""
|
|
1092
|
+
GHCR_EXPIRY_WARNS=0
|
|
1093
|
+
ghcr_expiry_status() {
|
|
1094
|
+
[[ "$MODE" == verify || "$MODE" == release ]] || return 0
|
|
1095
|
+
local file iso epoch now days
|
|
1096
|
+
file="$(qcr_ghcr_expiry_file 2>/dev/null)" || file=""
|
|
1097
|
+
if [[ -z "$file" || ! -s "$file" ]]; then
|
|
1098
|
+
GHCR_EXPIRY_LINE="no recorded expiry for the VM's ghcr pull token (scripts/vm-ghcr-login.sh records it when the owner logs the VM in)"
|
|
1099
|
+
return 0
|
|
1100
|
+
fi
|
|
1101
|
+
iso="$(head -1 "$file" | tr -d '[:space:]')"
|
|
1102
|
+
epoch="$(date -u -d "$iso" +%s 2>/dev/null || date -u -j -f '%Y-%m-%dT%H:%M:%SZ' "$iso" +%s 2>/dev/null || true)"
|
|
1103
|
+
if [[ -z "$epoch" ]]; then
|
|
1104
|
+
GHCR_EXPIRY_LINE="WARNING — the recorded ghcr token expiry in $file ('$iso') is not a date; re-run scripts/vm-ghcr-login.sh"
|
|
1105
|
+
GHCR_EXPIRY_WARNS=1
|
|
1106
|
+
return 0
|
|
1107
|
+
fi
|
|
1108
|
+
now="${QC_NOW_EPOCH:-$(date -u +%s)}"
|
|
1109
|
+
days=$(( (epoch - now) / 86400 ))
|
|
1110
|
+
if (( epoch <= now )); then
|
|
1111
|
+
GHCR_EXPIRY_LINE="WARNING — the VM's ghcr pull token EXPIRED at $iso: create a new classic read:packages token and run scripts/vm-ghcr-login.sh"
|
|
1112
|
+
GHCR_EXPIRY_WARNS=1
|
|
1113
|
+
elif (( days <= GHCR_WARN_DAYS )); then
|
|
1114
|
+
GHCR_EXPIRY_LINE="WARNING — the VM's ghcr pull token expires at $iso, in $days day(s) (warned from $GHCR_WARN_DAYS days out): create a new classic read:packages token and run scripts/vm-ghcr-login.sh"
|
|
1115
|
+
GHCR_EXPIRY_WARNS=1
|
|
1116
|
+
else
|
|
1117
|
+
GHCR_EXPIRY_LINE="the VM's ghcr pull token expires at $iso, in $days days"
|
|
1118
|
+
fi
|
|
1119
|
+
}
|
|
1120
|
+
|
|
1121
|
+
# The updater's argv, minus `sudo qcr-update`.
|
|
1122
|
+
update_args() {
|
|
1123
|
+
local args=("$RELEASE_VERSION@${MF[DAEMON_INDEX_DIGEST]}" --memory-digest "${MF[MEMORY_INDEX_DIGEST]}")
|
|
1124
|
+
[[ $CUTOVER_ACK -eq 0 ]] || args+=(--cutover-ack "$RELEASE_VERSION")
|
|
1125
|
+
printf '%s ' "${args[@]}"
|
|
1126
|
+
}
|
|
1127
|
+
|
|
1128
|
+
print_plan() {
|
|
1129
|
+
echo "plan — $SELF $MODE${RELEASE_VERSION:+ $RELEASE_VERSION}"
|
|
1130
|
+
echo " sandbox $SANDBOX_DIR @ $HEAD_SHA (clean)"
|
|
1131
|
+
echo " ansible $ANSIBLE_DIR"
|
|
1132
|
+
echo " inventory $INVENTORY"
|
|
1133
|
+
echo " pi pin $CFG_PI (config.toml == group_vars, not bumped)"
|
|
1134
|
+
if [[ -n "$CFG_OV" ]]; then
|
|
1135
|
+
echo " memory pins openviking $CFG_OV (config.toml == group_vars == image layer), extension ${CFG_OV_EXT:-<unset>} (config.toml == upstream.json)"
|
|
1136
|
+
echo " memory auth auth_mode ${CFG_OV_AUTH:-<unset>} (config.toml == group_vars; 'dev' means NO per-user confinement)"
|
|
1137
|
+
echo " memory embed ${CFG_OV_EMBED:-<unset>} (config.toml == ansible effective value; 'local' means no memory text leaves the VM)"
|
|
1138
|
+
echo " local model ${GV_LE_MODEL:-<code default>} sha256 ${CFG_OV_LE_SHA:-<code default>} (config.toml == group_vars == image layer; a changed pin needs a new release)"
|
|
1139
|
+
else
|
|
1140
|
+
echo " memory pins <no [openviking] block in config.toml — unverified>"
|
|
1141
|
+
fi
|
|
1142
|
+
if [[ "$OV_ENDPOINT" == remote ]]; then
|
|
1143
|
+
echo " containers $CONTAINER (daemon) + $MEMORY_CONTAINER (memory sidecar: OpenViking + local embedder, network $(effective_var_or memory_network quickreview-memory), port $OV_PORT unpublished)"
|
|
1144
|
+
echo " memory endpoint remote (config.toml == ansible): the daemon only probes $MEMORY_CONTAINER:$OV_PORT; the sidecar owns provisioning and the re-embed"
|
|
1145
|
+
echo " memory store the sidecar's own copy at $(effective_var_or memory_state_dir /opt/quickreview/memory-state), copied from the daemon's and checksum-verified ONCE by the updater on the legacy layout (the daemon container stopped for it), never wiped; the old directory is the rollback"
|
|
1146
|
+
else
|
|
1147
|
+
echo " containers $CONTAINER only (endpoint local: OpenViking is the daemon's child; a shipping mode refuses this — the release image carries no OpenViking)"
|
|
1148
|
+
fi
|
|
1149
|
+
echo " vm host ${VM_HOST:-<unset — set QC_VM_HOST>}"
|
|
1150
|
+
echo " base clone /$CLONE_DIR (mounted from the sandbox root, outside every image)"
|
|
1151
|
+
if [[ "$MODE" == release ]]; then
|
|
1152
|
+
echo " config home $CONFIG_HOME_SRC ($CONFIG_NAME as config.toml + profiles/ -> the tar on stdin; the VM's read-only /opt/quickreview/config-home, which QCR_CONFIG names; secrets/ never rides it)"
|
|
1153
|
+
echo " profile ${PROFILE_CHAIN:-<none — the generic registry>}"
|
|
1154
|
+
fi
|
|
1155
|
+
case "$MODE" in
|
|
1156
|
+
provision)
|
|
1157
|
+
echo " secrets $SECRETS_SRC (copied to the VM's read-only secrets mount)"
|
|
1158
|
+
echo " action the whole playbook: host packages, Docker, cloudflared, secrets, the directory layout, /usr/local/sbin/qcr-update and /etc/qcr/update.conf. It pulls and starts no image"
|
|
1159
|
+
echo " run spec v$RUNSPEC_VERSION, fingerprint $(runspec_fingerprint) (rendered from group_vars into $UPDATE_CONF_SRC)" ;;
|
|
1160
|
+
release)
|
|
1161
|
+
echo " manifest $MANIFEST_FILE: version ${MF[VERSION]}, revision ${MF[REVISION]} (checkout HEAD $(git -C "$SANDBOX_DIR" rev-parse HEAD 2>/dev/null || echo unknown))"
|
|
1162
|
+
echo " images daemon ${MF[DAEMON_REPOSITORY]}@${MF[DAEMON_INDEX_DIGEST]}"
|
|
1163
|
+
echo " memory ${MF[MEMORY_REPOSITORY]}@${MF[MEMORY_INDEX_DIGEST]}"
|
|
1164
|
+
echo " manifest pins pi ${MF[PIN_PI]}, openviking ${MF[PIN_OPENVIKING]}, llama-cpp-python ${MF[PIN_LLAMA_CPP_PYTHON]}, embedder ${MF[PIN_EMBEDDER_FILE]} sha256 ${MF[PIN_EMBEDDER_SHA256]}, run spec v${MF[PIN_RUNSPEC]} (== config.toml == group_vars)"
|
|
1165
|
+
echo " run spec v$RUNSPEC_VERSION, fingerprint $EXPECTED_FINGERPRINT (== the VM's: ${VM_FINGERPRINT:-unread})"
|
|
1166
|
+
echo " vm now layout $VM_LAYOUT, draining ${VM_DRAINING:-?}, running_rounds ${VM_ROUNDS:-?}"
|
|
1167
|
+
echo " steps 1. read the manifest (this script) 2. tar config.toml profiles/ | ssh ${VM_HOST:-<vm>} sudo qcr-update $(update_args)"
|
|
1168
|
+
echo " on the VM, in order:"
|
|
1169
|
+
echo " flock; refuse unless draining=true and running_rounds=0; legacy layout needs --cutover-ack"
|
|
1170
|
+
echo " pull both images by digest and check them (qcr.layout, qcr.build-sha, qcr.preflight, qcr.runspec <= update.conf)"
|
|
1171
|
+
echo " stage the config to config-home.next; the NEW image's \`qcr config check --json\` on it"
|
|
1172
|
+
echo " stop the daemon container; snapshot state.db(-wal/-shm) and state/pi-sessions to $(effective_var_or backup_dir /var/backups/qcr)/<version>-<ts>/"
|
|
1173
|
+
echo " the new image's \`daemon --preflight\` on a scratch copy of the snapshot (--network none)"
|
|
1174
|
+
echo " seed the sidecar store once (legacy layout only; copy + checksum verify)"
|
|
1175
|
+
echo " swap the config (current -> .prev, .next -> current)"
|
|
1176
|
+
echo " start the memory sidecar, wait for /ready; recreate the daemon container on the new image — THE POINT OF NO RETURN on the legacy layout (the first boot migrates state.db forward; there is no way back to tmux or processes)"
|
|
1177
|
+
echo " any failure from the stop to the recreate is RESCUED: previous config back, old containers started, drain re-sent and confirmed, non-zero exit"
|
|
1178
|
+
echo " no rsync, no build, no install on the VM"
|
|
1179
|
+
local problem
|
|
1180
|
+
problem="$(release_problem)"
|
|
1181
|
+
if [[ -n "$problem" ]]; then
|
|
1182
|
+
echo " REFUSED in a real run: $problem"
|
|
1183
|
+
elif [[ "$VM_LAYOUT" == legacy ]]; then
|
|
1184
|
+
echo " cutover first run on the legacy layout, acknowledged (--cutover-ack $RELEASE_VERSION): the first boot migrates forward"
|
|
1185
|
+
fi
|
|
1186
|
+
echo " rollback the legacy image quickreview-sandbox:v1 stays on the VM until the owner removes it; the updater also keeps the previous container as $CONTAINER-prev and the state snapshot under $(effective_var_or backup_dir /var/backups/qcr)/. A release is undone by 'release <previous version>' (drain first)" ;;
|
|
1187
|
+
verify) echo " action post-deploy checks over ssh only (no ansible)" ;;
|
|
1188
|
+
esac
|
|
1189
|
+
if [[ "$MODE" == release ]]; then
|
|
1190
|
+
echo " container release image in daemon mode as uid $(effective_var_or container_uid 1001): app root $APP_ROOT read-only; state, pihome, secrets, config-home and wt/archive/commands/digest/inject/verdict/$CLONE_DIR mounted from /opt/quickreview"
|
|
1191
|
+
fi
|
|
1192
|
+
if [[ ${#ANSIBLE_CMD[@]} -gt 0 ]]; then
|
|
1193
|
+
echo " command ${ANSIBLE_CMD[*]}"
|
|
1194
|
+
fi
|
|
1195
|
+
[[ -z "$GHCR_EXPIRY_LINE" ]] || echo " ghcr login $GHCR_EXPIRY_LINE"
|
|
1196
|
+
if [[ "$MODE" != provision ]]; then
|
|
1197
|
+
echo " then verify ssh ${VM_HOST:-<vm>}:"
|
|
1198
|
+
local c
|
|
1199
|
+
for c in "${VERIFY_CMDS[@]}"; do echo " $c"; done
|
|
1200
|
+
for c in "${BOOT_CMDS[@]}"; do echo " (after the heartbeat) $c"; done
|
|
1201
|
+
if [[ "$OV_ENDPOINT" == remote ]]; then
|
|
1202
|
+
echo " (polled) $(memory_ready_cmd)"
|
|
1203
|
+
echo " (polled) $(memory_search_cmd)"
|
|
1204
|
+
fi
|
|
1205
|
+
ansible_bool "$CLOUDFLARED_ENABLED" ||
|
|
1206
|
+
echo " (cloudflared not checked — cloudflared_enabled=$CLOUDFLARED_ENABLED, so the playbook does not install it)"
|
|
1207
|
+
fi
|
|
1208
|
+
echo " dry_run ${CFG_DRY_RUN:-<unset>} in $CONFIG — config.toml dry_run is NOT touched by deploy — the cutover flip is a manual edit"
|
|
1209
|
+
if [[ "$MODE" == release ]]; then
|
|
1210
|
+
echo " stamp BUILD_SHA is CI's, in the image (qcr.build-sha label, $APP_ROOT/BUILD_SHA). The qcr tui deploy tab reads it in the container"
|
|
1211
|
+
fi
|
|
1212
|
+
}
|
|
1213
|
+
|
|
1214
|
+
load_manifest
|
|
1215
|
+
verify_tag_digests
|
|
1216
|
+
vm_status_read
|
|
1217
|
+
runspec_drift
|
|
1218
|
+
ghcr_expiry_status
|
|
1219
|
+
|
|
1220
|
+
if [[ $DRY_RUN -eq 1 ]]; then
|
|
1221
|
+
print_plan
|
|
1222
|
+
echo
|
|
1223
|
+
echo "(--dry-run: nothing executed)"
|
|
1224
|
+
exit 0
|
|
1225
|
+
fi
|
|
1226
|
+
|
|
1227
|
+
# ---- execute -----------------------------------------------------------------
|
|
1228
|
+
|
|
1229
|
+
print_plan >&2
|
|
1230
|
+
echo >&2
|
|
1231
|
+
|
|
1232
|
+
# The companion's memory-health watcher (docker/companion/memory-health.mjs)
|
|
1233
|
+
# alerts after 3 failed probes — and a deploy's container recreate takes the
|
|
1234
|
+
# memory chain down for longer than that, so without a grace window the
|
|
1235
|
+
# "memory unreachable" alert would fire on every deploy. The marker below opens
|
|
1236
|
+
# the watcher's deploy-grace window: it rides the alerts bind mount into the
|
|
1237
|
+
# companion container, the watcher holds the alert while the marker is
|
|
1238
|
+
# younger than 10 min, and refresh_memory_forward deletes it the moment the
|
|
1239
|
+
# chain answers again. Dotfile on purpose — AlertConsumer skips dotfiles.
|
|
1240
|
+
# (Path matches the config.ts default; an alerts_dir override in config.toml
|
|
1241
|
+
# would bypass the grace, visibly — the alert would simply return.)
|
|
1242
|
+
COMPANION_ALERTS_DIR="$SANDBOX_DIR/state/companion/alerts"
|
|
1243
|
+
DEPLOY_MARKER_FILE="$COMPANION_ALERTS_DIR/.deploy-window.json"
|
|
1244
|
+
|
|
1245
|
+
if [[ "$MODE" == provision ]]; then
|
|
1246
|
+
command -v ansible-playbook >/dev/null 2>&1 || die "ansible-playbook not on PATH — run this from the controller machine"
|
|
1247
|
+
[[ -f "$INVENTORY" ]] || die "no inventory at $INVENTORY (cp inventory.example.ini inventory.ini, then edit; or set QC_INVENTORY)"
|
|
1248
|
+
runspec_file >"$UPDATE_CONF_SRC"
|
|
1249
|
+
note "running: ${ANSIBLE_CMD[*]}"
|
|
1250
|
+
"${ANSIBLE_CMD[@]}"
|
|
1251
|
+
note "provision ok ($HEAD_SHA): the updater and run spec v$RUNSPEC_VERSION are on the VM; no image was pulled or started. Deploy with: $SELF release <version>"
|
|
1252
|
+
exit 0
|
|
1253
|
+
fi
|
|
1254
|
+
|
|
1255
|
+
if [[ "$MODE" == release ]]; then
|
|
1256
|
+
[[ -n "$VM_HOST" ]] || die "no VM host resolved — set QC_VM_HOST: the release runs qcr-update over ssh"
|
|
1257
|
+
release_refusal="$(release_problem)"
|
|
1258
|
+
[[ -z "$release_refusal" ]] || die "refusing the release — $release_refusal"
|
|
1259
|
+
# The config home as the tar the updater takes: config.toml and profiles/,
|
|
1260
|
+
# dereferenced (a symlinked profile ships as the files it points at), never
|
|
1261
|
+
# secrets/. Staged first so the tar is exactly these two names.
|
|
1262
|
+
STAGE="$WORK_DIR/config-home"
|
|
1263
|
+
mkdir -p "$STAGE"
|
|
1264
|
+
cp -L "$CONFIG_HOME_SRC/$CONFIG_NAME" "$STAGE/config.toml"
|
|
1265
|
+
TAR_NAMES=(config.toml)
|
|
1266
|
+
if [[ -d "$CONFIG_HOME_SRC/profiles" ]]; then
|
|
1267
|
+
cp -RL "$CONFIG_HOME_SRC/profiles" "$STAGE/profiles"
|
|
1268
|
+
TAR_NAMES+=(profiles)
|
|
1269
|
+
fi
|
|
1270
|
+
# Open the deploy-grace window before the disruptive phase starts.
|
|
1271
|
+
mkdir -p "$COMPANION_ALERTS_DIR"
|
|
1272
|
+
printf '{"since":"%s","by":"deploy.sh","mode":"%s"}\n' "$(date -u +%Y-%m-%dT%H:%M:%SZ)" "$MODE" > "$DEPLOY_MARKER_FILE"
|
|
1273
|
+
read -r -a UPDATE_ARGV <<<"$(update_args)"
|
|
1274
|
+
note "running: tar ${TAR_NAMES[*]} | ssh $VM_HOST sudo qcr-update ${UPDATE_ARGV[*]}"
|
|
1275
|
+
if [[ -n "${QC_UPDATE_RUN_CMD:-}" ]]; then
|
|
1276
|
+
tar -C "$STAGE" -cf - "${TAR_NAMES[@]}" | bash -c "$QC_UPDATE_RUN_CMD" _ "${UPDATE_ARGV[@]}" ||
|
|
1277
|
+
die "qcr-update failed — read its output above: any step before the recreate was rescued (the previous version runs, the bot is drained)"
|
|
1278
|
+
else
|
|
1279
|
+
tar -C "$STAGE" -cf - "${TAR_NAMES[@]}" | ssh -o BatchMode=yes "$VM_HOST" "sudo qcr-update ${UPDATE_ARGV[*]}" ||
|
|
1280
|
+
die "qcr-update failed — read its output above: any step before the recreate was rescued (the previous version runs, the bot is drained)"
|
|
1281
|
+
fi
|
|
1282
|
+
fi
|
|
1283
|
+
|
|
1284
|
+
[[ -n "$VM_HOST" ]] || die "no VM host resolved — set QC_VM_HOST to run the post-deploy checks"
|
|
1285
|
+
note "post-deploy checks on $VM_HOST"
|
|
1286
|
+
FAILED=0
|
|
1287
|
+
for cmd in "${VERIFY_CMDS[@]}"; do
|
|
1288
|
+
echo "--- $cmd"
|
|
1289
|
+
if ! ssh -o BatchMode=yes "$VM_HOST" "$cmd"; then
|
|
1290
|
+
FAILED=1
|
|
1291
|
+
echo "$SELF: CHECK FAILED: $cmd" >&2
|
|
1292
|
+
fi
|
|
1293
|
+
done
|
|
1294
|
+
|
|
1295
|
+
# The heartbeat must come from this boot (scripts/heartbeat-fresh.mjs says
|
|
1296
|
+
# why: the status.json the cat above shows may be the old daemon's). Polled,
|
|
1297
|
+
# because a cold boot spends ~40s on OpenViking before the daemon starts, and a sync's restart re-runs the
|
|
1298
|
+
# entrypoint's dist rebuild. QC_HEARTBEAT_PROBE_CMD stands in for the ssh
|
|
1299
|
+
# read (tests); QC_HEARTBEAT_WAIT_S bounds the wait.
|
|
1300
|
+
heartbeat_probe() {
|
|
1301
|
+
if [[ -n "${QC_HEARTBEAT_PROBE_CMD:-}" ]]; then
|
|
1302
|
+
bash -c "$QC_HEARTBEAT_PROBE_CMD"
|
|
1303
|
+
else
|
|
1304
|
+
ssh -o BatchMode=yes "$VM_HOST" \
|
|
1305
|
+
"sudo docker inspect -f '{{.State.StartedAt}}' $CONTAINER; date -u +%Y-%m-%dT%H:%M:%SZ; sudo cat $SANDBOX_ROOT_EFF/state/status.json"
|
|
1306
|
+
fi
|
|
1307
|
+
}
|
|
1308
|
+
check_heartbeat_fresh() {
|
|
1309
|
+
local wait_s="${QC_HEARTBEAT_WAIT_S:-240}" waited=0 verdict=""
|
|
1310
|
+
echo "--- heartbeat written by this boot (waits up to ${wait_s}s)"
|
|
1311
|
+
while :; do
|
|
1312
|
+
verdict="$(heartbeat_probe 2>/dev/null | node "$(dirname "${BASH_SOURCE[0]}")/heartbeat-fresh.mjs")" && {
|
|
1313
|
+
echo "$verdict"
|
|
1314
|
+
return 0
|
|
1315
|
+
}
|
|
1316
|
+
(( waited >= wait_s )) && break
|
|
1317
|
+
sleep 10
|
|
1318
|
+
waited=$((waited + 10))
|
|
1319
|
+
done
|
|
1320
|
+
echo "$verdict"
|
|
1321
|
+
echo "$SELF: CHECK FAILED: no fresh heartbeat after ${waited}s — the daemon is not running on this boot; read 'sudo docker logs --tail 100 $CONTAINER'" >&2
|
|
1322
|
+
return 1
|
|
1323
|
+
}
|
|
1324
|
+
check_heartbeat_fresh || FAILED=1
|
|
1325
|
+
|
|
1326
|
+
# The checks that need the entrypoint to have run: it prunes the global bash-guard entry from
|
|
1327
|
+
# pi's settings and re-installs the stub, both before it starts the daemon.
|
|
1328
|
+
for cmd in "${BOOT_CMDS[@]}"; do
|
|
1329
|
+
echo "--- $cmd"
|
|
1330
|
+
if ! ssh -o BatchMode=yes "$VM_HOST" "$cmd"; then
|
|
1331
|
+
FAILED=1
|
|
1332
|
+
echo "$SELF: CHECK FAILED: $cmd" >&2
|
|
1333
|
+
fi
|
|
1334
|
+
done
|
|
1335
|
+
|
|
1336
|
+
# The memory sidecar answers /ready and a reviewer-key search works from the
|
|
1337
|
+
# daemon's container. Polled like the heartbeat: a cold sidecar spends minutes
|
|
1338
|
+
# on the embedder before /ready passes. QC_MEMORY_PROBE_CMD / QC_MEMORY_SEARCH_CMD
|
|
1339
|
+
# stand in for the ssh reads (tests); QC_MEMORY_WAIT_S bounds the wait.
|
|
1340
|
+
memory_probe() {
|
|
1341
|
+
local which="$1" cmd
|
|
1342
|
+
if [[ "$which" == ready ]]; then
|
|
1343
|
+
cmd="${QC_MEMORY_PROBE_CMD:-}"
|
|
1344
|
+
[[ -n "$cmd" ]] && { bash -c "$cmd"; return; }
|
|
1345
|
+
ssh -o BatchMode=yes "$VM_HOST" "$(memory_ready_cmd)"
|
|
1346
|
+
else
|
|
1347
|
+
cmd="${QC_MEMORY_SEARCH_CMD:-}"
|
|
1348
|
+
[[ -n "$cmd" ]] && { bash -c "$cmd"; return; }
|
|
1349
|
+
ssh -o BatchMode=yes "$VM_HOST" "$(memory_search_cmd)"
|
|
1350
|
+
fi
|
|
1351
|
+
}
|
|
1352
|
+
check_memory_sidecar() {
|
|
1353
|
+
local wait_s="${QC_MEMORY_WAIT_S:-240}" waited=0 which out=""
|
|
1354
|
+
for which in ready search; do
|
|
1355
|
+
echo "--- memory sidecar $which (${wait_s}s budget shared by both checks, $((wait_s - waited))s left)"
|
|
1356
|
+
while :; do
|
|
1357
|
+
out="$(memory_probe "$which" 2>&1)" && { echo "$out"; break; }
|
|
1358
|
+
if (( waited >= wait_s )); then
|
|
1359
|
+
echo "$out"
|
|
1360
|
+
echo "$SELF: CHECK FAILED: memory sidecar $which — the daemon would run memory-less; read 'sudo docker logs --tail 100 $MEMORY_CONTAINER'" >&2
|
|
1361
|
+
return 1
|
|
1362
|
+
fi
|
|
1363
|
+
sleep 10
|
|
1364
|
+
waited=$((waited + 10))
|
|
1365
|
+
done
|
|
1366
|
+
done
|
|
1367
|
+
}
|
|
1368
|
+
if [[ "$OV_ENDPOINT" == remote ]]; then
|
|
1369
|
+
check_memory_sidecar || FAILED=1
|
|
1370
|
+
fi
|
|
1371
|
+
|
|
1372
|
+
if [[ $FAILED -ne 0 ]]; then
|
|
1373
|
+
cat >&2 <<'EOF'
|
|
1374
|
+
One or more post-deploy checks failed. Triage order (README §fallback):
|
|
1375
|
+
1. cloudflared inactive (when enabled) -> systemctl restart cloudflared; journalctl -u cloudflared -n 50
|
|
1376
|
+
2. container not running -> docker logs quickreview-sandbox --tail 100
|
|
1377
|
+
3. stale/absent status.json -> the daemon is not writing its heartbeat; docker logs, then restart the container
|
|
1378
|
+
4. memory sidecar check failed -> docker logs quickreview-memory --tail 100
|
|
1379
|
+
5. wrong image / bad release -> 'release <previous version>', or the legacy image (deploy/ansible/README.md, Rollback);
|
|
1380
|
+
the old state, pihome and host directories are the same ones, untouched
|
|
1381
|
+
While the sandbox is down, run manual laptop ticks (`/auto-review <n>`) —
|
|
1382
|
+
NEVER concurrently with a live sandbox.
|
|
1383
|
+
EOF
|
|
1384
|
+
exit 1
|
|
1385
|
+
fi
|
|
1386
|
+
|
|
1387
|
+
# Refresh the host-side memory forward. Its long-lived ssh state goes stale
|
|
1388
|
+
# when the container underneath it is recreated (every relay exits 255 and the
|
|
1389
|
+
# TUI reports OpenViking down until a restart), so a deploy that touched the
|
|
1390
|
+
# container restarts the unit itself. Three deliberate softnesses: the unit
|
|
1391
|
+
# may not exist or may be inactive (the TUI can own the forward instead,
|
|
1392
|
+
# adopt-or-bind) — skip silently; the restart or the probe failing warns and
|
|
1393
|
+
# never fails the deploy (memory never blocks a deploy, same rule as the boot); verify-only
|
|
1394
|
+
# runs skip it (no container was touched).
|
|
1395
|
+
memory_forward_answers() { curl -s -m 5 -o /dev/null "http://198.51.100.1:1933/health"; }
|
|
1396
|
+
refresh_memory_forward() {
|
|
1397
|
+
[[ "$MODE" == "verify" ]] && return 0
|
|
1398
|
+
# tests: a stubbed run never restarts the developer's own user unit
|
|
1399
|
+
[[ -z "${QC_NO_MEMORY_FORWARD:-}" ]] || return 0
|
|
1400
|
+
command -v systemctl >/dev/null 2>&1 || return 0
|
|
1401
|
+
if ! systemctl --user is-active --quiet qcr-memory-forward 2>/dev/null; then
|
|
1402
|
+
note "memory forward: systemd unit not active (TUI-owned or absent) — not restarted"
|
|
1403
|
+
# Still close the grace window if the chain already answers (a TUI-owned
|
|
1404
|
+
# forward recovers by itself); otherwise the marker expires on its own.
|
|
1405
|
+
memory_forward_answers && rm -f "$DEPLOY_MARKER_FILE"
|
|
1406
|
+
return 0
|
|
1407
|
+
fi
|
|
1408
|
+
note "memory forward: restarting qcr-memory-forward (its ssh state predates this deploy's container)"
|
|
1409
|
+
if ! systemctl --user restart qcr-memory-forward 2>/dev/null; then
|
|
1410
|
+
note "WARNING — memory forward restart failed; memory stays degraded until 'systemctl --user restart qcr-memory-forward' succeeds"
|
|
1411
|
+
return 0
|
|
1412
|
+
fi
|
|
1413
|
+
# Poll, don't probe once: right after the ansible run the VM container is
|
|
1414
|
+
# still booting OpenViking (~40s) and the forward's first relays die against
|
|
1415
|
+
# it — a single probe would race that boot and warn on every deploy even
|
|
1416
|
+
# when the chain comes up a minute later. Up to 3 min covers the boot plus
|
|
1417
|
+
# relay churn; only then is a warning believable.
|
|
1418
|
+
local waited=0
|
|
1419
|
+
while (( waited < 180 )); do
|
|
1420
|
+
if memory_forward_answers; then
|
|
1421
|
+
note "memory forward: answering on the qcr-memory bridge (${waited}s after restart)"
|
|
1422
|
+
rm -f "$DEPLOY_MARKER_FILE"
|
|
1423
|
+
return 0
|
|
1424
|
+
fi
|
|
1425
|
+
sleep 10
|
|
1426
|
+
waited=$((waited + 10))
|
|
1427
|
+
done
|
|
1428
|
+
note "WARNING — memory forward still not answering after ${waited}s; check 'journalctl --user -u qcr-memory-forward -n 20' (deploy-grace marker left to expire so a real outage still alerts)"
|
|
1429
|
+
}
|
|
1430
|
+
refresh_memory_forward
|
|
1431
|
+
|
|
1432
|
+
# Said again at the end, where a long verify's output is read.
|
|
1433
|
+
[[ $GHCR_EXPIRY_WARNS -eq 0 ]] || note "$GHCR_EXPIRY_LINE"
|
|
1434
|
+
|
|
1435
|
+
note "deploy ok ($MODE, $HEAD_SHA)"
|