@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.
Files changed (1443) hide show
  1. package/LICENSE +23 -0
  2. package/README.md +385 -2
  3. package/THIRD-PARTY-NOTICES.md +134 -0
  4. package/agents/pass-abstraction.md +64 -0
  5. package/agents/pass-ask-human.md +134 -0
  6. package/agents/pass-backcompat.md +67 -0
  7. package/agents/pass-baseline.md +61 -0
  8. package/agents/pass-behaviour-delta.md +64 -0
  9. package/agents/pass-bounds.md +36 -0
  10. package/agents/pass-claims.md +31 -0
  11. package/agents/pass-concurrency.md +168 -0
  12. package/agents/pass-consistency.md +57 -0
  13. package/agents/pass-contracts.md +30 -0
  14. package/agents/pass-data-safety.md +54 -0
  15. package/agents/pass-deferred.md +35 -0
  16. package/agents/pass-delivery.md +37 -0
  17. package/agents/pass-duplication.md +60 -0
  18. package/agents/pass-intent.md +63 -0
  19. package/agents/pass-lint-conformance.md +46 -0
  20. package/agents/pass-maintainability.md +67 -0
  21. package/agents/pass-placement.md +68 -0
  22. package/agents/pass-prod-risk.md +24 -0
  23. package/agents/pass-rollout.md +32 -0
  24. package/agents/pass-roundtrip.md +46 -0
  25. package/agents/pass-security.md +31 -0
  26. package/agents/pass-simplicity.md +68 -0
  27. package/agents/pass-statelessness.md +27 -0
  28. package/agents/pass-style.md +37 -0
  29. package/agents/pass-sweep.md +57 -0
  30. package/agents/pass-tests.md +58 -0
  31. package/agents/pass-ui.md +23 -0
  32. package/agents/pass-validate.md +50 -0
  33. package/agents/pass-widened.md +46 -0
  34. package/agents/scan/pass-scan.md +86 -0
  35. package/bin/qcr.js +11 -0
  36. package/config.example.toml +1067 -0
  37. package/deploy/ansible/README.md +356 -0
  38. package/deploy/ansible/group_vars/all.yml +212 -0
  39. package/deploy/ansible/inventory.example.ini +25 -0
  40. package/deploy/ansible/playbook.yml +360 -0
  41. package/deploy/collect-secrets.sh +289 -0
  42. package/deploy/collect-slack-secrets.mjs +1190 -0
  43. package/deploy/github-app-manifest.json +31 -0
  44. package/deploy/manifest.schema.json +75 -0
  45. package/deploy/qcr-update.sh +803 -0
  46. package/deploy/reviewer/AGENTS.override.md +12 -0
  47. package/dist/extensions/cli/bash-guard-ambient.js +42 -0
  48. package/dist/extensions/cli/bash-guard-ambient.js.map +1 -0
  49. package/dist/extensions/cli/bash-guard.js +5 -0
  50. package/dist/extensions/cli/bash-guard.js.map +1 -0
  51. package/dist/extensions/cli/companion-guard.js +5 -0
  52. package/dist/extensions/cli/companion-guard.js.map +1 -0
  53. package/dist/extensions/cli/debug-round.js +8 -0
  54. package/dist/extensions/cli/debug-round.js.map +1 -0
  55. package/dist/extensions/cli/inject-bridge.js +5 -0
  56. package/dist/extensions/cli/inject-bridge.js.map +1 -0
  57. package/dist/extensions/cli/ledger-pin.js +5 -0
  58. package/dist/extensions/cli/ledger-pin.js.map +1 -0
  59. package/dist/extensions/cli/lens.js +13 -0
  60. package/dist/extensions/cli/lens.js.map +1 -0
  61. package/dist/extensions/cli/muted-theme.js +5 -0
  62. package/dist/extensions/cli/muted-theme.js.map +1 -0
  63. package/dist/extensions/cli/onboarding.js +5 -0
  64. package/dist/extensions/cli/onboarding.js.map +1 -0
  65. package/dist/extensions/cli/setup.js +5 -0
  66. package/dist/extensions/cli/setup.js.map +1 -0
  67. package/dist/extensions/cli/tui-view.js +5 -0
  68. package/dist/extensions/cli/tui-view.js.map +1 -0
  69. package/dist/extensions/companion-guard.js +361 -0
  70. package/dist/extensions/companion-guard.js.map +1 -0
  71. package/dist/extensions/debug-round.js +204 -0
  72. package/dist/extensions/debug-round.js.map +1 -0
  73. package/dist/extensions/inject-bridge.js +603 -0
  74. package/dist/extensions/inject-bridge.js.map +1 -0
  75. package/dist/extensions/muted-theme.js +12 -0
  76. package/dist/extensions/muted-theme.js.map +1 -0
  77. package/dist/extensions/onboarding.js +175 -0
  78. package/dist/extensions/onboarding.js.map +1 -0
  79. package/dist/extensions/setup-replay.js +269 -0
  80. package/dist/extensions/setup-replay.js.map +1 -0
  81. package/dist/extensions/setup-scan.js +998 -0
  82. package/dist/extensions/setup-scan.js.map +1 -0
  83. package/dist/extensions/setup-triage.js +175 -0
  84. package/dist/extensions/setup-triage.js.map +1 -0
  85. package/dist/extensions/setup.js +1160 -0
  86. package/dist/extensions/setup.js.map +1 -0
  87. package/dist/extensions/tui-view.js +389 -0
  88. package/dist/extensions/tui-view.js.map +1 -0
  89. package/dist/extensions/vendor/openviking-pi/client.js +376 -0
  90. package/dist/extensions/vendor/openviking-pi/client.js.map +1 -0
  91. package/dist/extensions/vendor/openviking-pi/config.js +189 -0
  92. package/dist/extensions/vendor/openviking-pi/config.js.map +1 -0
  93. package/dist/extensions/vendor/openviking-pi/index.js +421 -0
  94. package/dist/extensions/vendor/openviking-pi/index.js.map +1 -0
  95. package/dist/extensions/vendor/openviking-pi/lib/capture-adapter.mjs +86 -0
  96. package/dist/extensions/vendor/openviking-pi/lib/capture-adapter.mjs.map +1 -0
  97. package/dist/extensions/vendor/openviking-pi/lib/takeover-core.mjs +408 -0
  98. package/dist/extensions/vendor/openviking-pi/lib/takeover-core.mjs.map +1 -0
  99. package/dist/extensions/vendor/openviking-pi/lib/uri-guard-adapter.mjs +41 -0
  100. package/dist/extensions/vendor/openviking-pi/lib/uri-guard-adapter.mjs.map +1 -0
  101. package/dist/extensions/vendor/openviking-pi/qcr-hint.js +290 -0
  102. package/dist/extensions/vendor/openviking-pi/qcr-hint.js.map +1 -0
  103. package/dist/extensions/vendor/openviking-pi/qcr-honest.js +148 -0
  104. package/dist/extensions/vendor/openviking-pi/qcr-honest.js.map +1 -0
  105. package/dist/extensions/vendor/openviking-pi/qcr-hooks.js +68 -0
  106. package/dist/extensions/vendor/openviking-pi/qcr-hooks.js.map +1 -0
  107. package/dist/extensions/vendor/openviking-pi/qcr-leaf.js +77 -0
  108. package/dist/extensions/vendor/openviking-pi/qcr-leaf.js.map +1 -0
  109. package/dist/extensions/vendor/openviking-pi/qcr-lesson.js +216 -0
  110. package/dist/extensions/vendor/openviking-pi/qcr-lesson.js.map +1 -0
  111. package/dist/extensions/vendor/openviking-pi/qcr-namespaces.js +154 -0
  112. package/dist/extensions/vendor/openviking-pi/qcr-namespaces.js.map +1 -0
  113. package/dist/extensions/vendor/openviking-pi/qcr-recall.js +254 -0
  114. package/dist/extensions/vendor/openviking-pi/qcr-recall.js.map +1 -0
  115. package/dist/extensions/vendor/openviking-pi/qcr-shim.js +94 -0
  116. package/dist/extensions/vendor/openviking-pi/qcr-shim.js.map +1 -0
  117. package/dist/extensions/vendor/openviking-pi/qcr-write.js +333 -0
  118. package/dist/extensions/vendor/openviking-pi/qcr-write.js.map +1 -0
  119. package/dist/extensions/vendor/openviking-pi/recall.js +108 -0
  120. package/dist/extensions/vendor/openviking-pi/recall.js.map +1 -0
  121. package/dist/extensions/vendor/openviking-pi/shared/capture-utils.mjs +529 -0
  122. package/dist/extensions/vendor/openviking-pi/shared/capture-utils.mjs.map +1 -0
  123. package/dist/extensions/vendor/openviking-pi/shared/credentials.mjs +205 -0
  124. package/dist/extensions/vendor/openviking-pi/shared/credentials.mjs.map +1 -0
  125. package/dist/extensions/vendor/openviking-pi/shared/pending-queue.mjs +442 -0
  126. package/dist/extensions/vendor/openviking-pi/shared/pending-queue.mjs.map +1 -0
  127. package/dist/extensions/vendor/openviking-pi/shared/profile-inject.mjs +286 -0
  128. package/dist/extensions/vendor/openviking-pi/shared/profile-inject.mjs.map +1 -0
  129. package/dist/extensions/vendor/openviking-pi/shared/recall-compress-core.mjs +189 -0
  130. package/dist/extensions/vendor/openviking-pi/shared/recall-compress-core.mjs.map +1 -0
  131. package/dist/extensions/vendor/openviking-pi/shared/recall-core.mjs +580 -0
  132. package/dist/extensions/vendor/openviking-pi/shared/recall-core.mjs.map +1 -0
  133. package/dist/extensions/vendor/openviking-pi/shared/retryable.mjs +11 -0
  134. package/dist/extensions/vendor/openviking-pi/shared/retryable.mjs.map +1 -0
  135. package/dist/extensions/vendor/openviking-pi/shared/session-model.mjs +63 -0
  136. package/dist/extensions/vendor/openviking-pi/shared/session-model.mjs.map +1 -0
  137. package/dist/extensions/vendor/openviking-pi/shared/uri-guard.mjs +58 -0
  138. package/dist/extensions/vendor/openviking-pi/shared/uri-guard.mjs.map +1 -0
  139. package/dist/extensions/vendor/openviking-pi/shared/workspace-peer.mjs +16 -0
  140. package/dist/extensions/vendor/openviking-pi/shared/workspace-peer.mjs.map +1 -0
  141. package/dist/extensions/vendor/openviking-pi/sync.js +127 -0
  142. package/dist/extensions/vendor/openviking-pi/sync.js.map +1 -0
  143. package/dist/extensions/vendor/openviking-pi/takeover.js +25 -0
  144. package/dist/extensions/vendor/openviking-pi/takeover.js.map +1 -0
  145. package/dist/extensions/vendor/openviking-pi/tools.js +417 -0
  146. package/dist/extensions/vendor/openviking-pi/tools.js.map +1 -0
  147. package/dist/src/anomalies.js +483 -0
  148. package/dist/src/anomalies.js.map +1 -0
  149. package/dist/src/answerworker.js +180 -0
  150. package/dist/src/answerworker.js.map +1 -0
  151. package/dist/src/appstamp.js +68 -0
  152. package/dist/src/appstamp.js.map +1 -0
  153. package/dist/src/archive/admission.js +3 -0
  154. package/dist/src/archive/admission.js.map +1 -0
  155. package/dist/src/archive/archivedb.js +474 -0
  156. package/dist/src/archive/archivedb.js.map +1 -0
  157. package/dist/src/archive/archiver.js +1770 -0
  158. package/dist/src/archive/archiver.js.map +1 -0
  159. package/dist/src/archive/archivewrite.js +198 -0
  160. package/dist/src/archive/archivewrite.js.map +1 -0
  161. package/dist/src/archive/bundle.js +266 -0
  162. package/dist/src/archive/bundle.js.map +1 -0
  163. package/dist/src/archive/dbmove.js +638 -0
  164. package/dist/src/archive/dbmove.js.map +1 -0
  165. package/dist/src/archive/duplicatecounts.js +80 -0
  166. package/dist/src/archive/duplicatecounts.js.map +1 -0
  167. package/dist/src/archive/eligibility.js +113 -0
  168. package/dist/src/archive/eligibility.js.map +1 -0
  169. package/dist/src/archive/extract.js +288 -0
  170. package/dist/src/archive/extract.js.map +1 -0
  171. package/dist/src/archive/fsutil.js +330 -0
  172. package/dist/src/archive/fsutil.js.map +1 -0
  173. package/dist/src/archive/gzfile.js +277 -0
  174. package/dist/src/archive/gzfile.js.map +1 -0
  175. package/dist/src/archive/kinds.js +22 -0
  176. package/dist/src/archive/kinds.js.map +1 -0
  177. package/dist/src/archive/livecheckpoint.js +178 -0
  178. package/dist/src/archive/livecheckpoint.js.map +1 -0
  179. package/dist/src/archive/liveretention.js +145 -0
  180. package/dist/src/archive/liveretention.js.map +1 -0
  181. package/dist/src/archive/manifest.js +284 -0
  182. package/dist/src/archive/manifest.js.map +1 -0
  183. package/dist/src/archive/overseer.js +283 -0
  184. package/dist/src/archive/overseer.js.map +1 -0
  185. package/dist/src/archive/piglobal.js +129 -0
  186. package/dist/src/archive/piglobal.js.map +1 -0
  187. package/dist/src/archive/plan.js +398 -0
  188. package/dist/src/archive/plan.js.map +1 -0
  189. package/dist/src/archive/prarchive.js +831 -0
  190. package/dist/src/archive/prarchive.js.map +1 -0
  191. package/dist/src/archive/prfiles.js +303 -0
  192. package/dist/src/archive/prfiles.js.map +1 -0
  193. package/dist/src/archive/report.js +169 -0
  194. package/dist/src/archive/report.js.map +1 -0
  195. package/dist/src/archive/restore.js +276 -0
  196. package/dist/src/archive/restore.js.map +1 -0
  197. package/dist/src/archive/rows.js +106 -0
  198. package/dist/src/archive/rows.js.map +1 -0
  199. package/dist/src/archive/seal.js +480 -0
  200. package/dist/src/archive/seal.js.map +1 -0
  201. package/dist/src/archive/status.js +528 -0
  202. package/dist/src/archive/status.js.map +1 -0
  203. package/dist/src/archive/storepass.js +409 -0
  204. package/dist/src/archive/storepass.js.map +1 -0
  205. package/dist/src/backfill.js +217 -0
  206. package/dist/src/backfill.js.map +1 -0
  207. package/dist/src/bench/cost.js +87 -0
  208. package/dist/src/bench/exec.js +76 -0
  209. package/dist/src/bench/values.js +14 -0
  210. package/dist/src/botcontrol.js +127 -0
  211. package/dist/src/botcontrol.js.map +1 -0
  212. package/dist/src/botworker.js +805 -0
  213. package/dist/src/botworker.js.map +1 -0
  214. package/dist/src/capture.js +234 -0
  215. package/dist/src/capture.js.map +1 -0
  216. package/dist/src/cases.js +1491 -0
  217. package/dist/src/cases.js.map +1 -0
  218. package/dist/src/channelfs.js +27 -0
  219. package/dist/src/channelfs.js.map +1 -0
  220. package/dist/src/checklist.js +312 -0
  221. package/dist/src/checklist.js.map +1 -0
  222. package/dist/src/ciretry.js +91 -0
  223. package/dist/src/ciretry.js.map +1 -0
  224. package/dist/src/cli.js +1916 -0
  225. package/dist/src/cli.js.map +1 -0
  226. package/dist/src/cliops.js +333 -0
  227. package/dist/src/cliops.js.map +1 -0
  228. package/dist/src/clonedir.js +15 -0
  229. package/dist/src/clonedir.js.map +1 -0
  230. package/dist/src/commandeffects.js +43 -0
  231. package/dist/src/commandeffects.js.map +1 -0
  232. package/dist/src/commands.js +1312 -0
  233. package/dist/src/commands.js.map +1 -0
  234. package/dist/src/companioninbox.js +203 -0
  235. package/dist/src/companioninbox.js.map +1 -0
  236. package/dist/src/compare.js +673 -0
  237. package/dist/src/compare.js.map +1 -0
  238. package/dist/src/config.js +1027 -0
  239. package/dist/src/config.js.map +1 -0
  240. package/dist/src/configcheck.js +111 -0
  241. package/dist/src/configcheck.js.map +1 -0
  242. package/dist/src/coreext.js +43 -0
  243. package/dist/src/coreext.js.map +1 -0
  244. package/dist/src/daemon.js +929 -0
  245. package/dist/src/daemon.js.map +1 -0
  246. package/dist/src/debuground.js +731 -0
  247. package/dist/src/debuground.js.map +1 -0
  248. package/dist/src/debugroundpi.js +219 -0
  249. package/dist/src/debugroundpi.js.map +1 -0
  250. package/dist/src/diagnostics.js +14 -0
  251. package/dist/src/diagnostics.js.map +1 -0
  252. package/dist/src/digest.js +139 -0
  253. package/dist/src/digest.js.map +1 -0
  254. package/dist/src/dispatch.js +1726 -0
  255. package/dist/src/dispatch.js.map +1 -0
  256. package/dist/src/engine/answerguard.js +18 -0
  257. package/dist/src/engine/answerguard.js.map +1 -0
  258. package/dist/src/engine/botsdk.js +262 -0
  259. package/dist/src/engine/botsdk.js.map +1 -0
  260. package/dist/src/engine/caserunner-sdk.js +332 -0
  261. package/dist/src/engine/caserunner-sdk.js.map +1 -0
  262. package/dist/src/engine/debugpi.js +194 -0
  263. package/dist/src/engine/debugpi.js.map +1 -0
  264. package/dist/src/eod.js +473 -0
  265. package/dist/src/eod.js.map +1 -0
  266. package/dist/src/eodpinwatch.js +391 -0
  267. package/dist/src/eodpinwatch.js.map +1 -0
  268. package/dist/src/escalation.js +1009 -0
  269. package/dist/src/escalation.js.map +1 -0
  270. package/dist/src/eventloop.js +779 -0
  271. package/dist/src/eventloop.js.map +1 -0
  272. package/dist/src/execboundary.js +430 -0
  273. package/dist/src/execboundary.js.map +1 -0
  274. package/dist/src/feedback.js +915 -0
  275. package/dist/src/feedback.js.map +1 -0
  276. package/dist/src/findingscheck.js +200 -0
  277. package/dist/src/findingscheck.js.map +1 -0
  278. package/dist/src/fsdenied.js +26 -0
  279. package/dist/src/fsdenied.js.map +1 -0
  280. package/dist/src/ghread.js +181 -0
  281. package/dist/src/ghread.js.map +1 -0
  282. package/dist/src/github.js +1063 -0
  283. package/dist/src/github.js.map +1 -0
  284. package/dist/src/homes.js +103 -0
  285. package/dist/src/homes.js.map +1 -0
  286. package/dist/src/hostroute.js +24 -0
  287. package/dist/src/hostroute.js.map +1 -0
  288. package/dist/src/improvements.js +625 -0
  289. package/dist/src/improvements.js.map +1 -0
  290. package/dist/src/install.js +946 -0
  291. package/dist/src/install.js.map +1 -0
  292. package/dist/src/installkind.js +40 -0
  293. package/dist/src/installkind.js.map +1 -0
  294. package/dist/src/ipc/capability.js +286 -0
  295. package/dist/src/ipc/capability.js.map +1 -0
  296. package/dist/src/ipc/client.js +226 -0
  297. package/dist/src/ipc/client.js.map +1 -0
  298. package/dist/src/ipc/control.js +70 -0
  299. package/dist/src/ipc/control.js.map +1 -0
  300. package/dist/src/ipc/ghreadshim.js +77 -0
  301. package/dist/src/ipc/ghreadshim.js.map +1 -0
  302. package/dist/src/ipc/launcher.js +65 -0
  303. package/dist/src/ipc/launcher.js.map +1 -0
  304. package/dist/src/ipc/permits.js +293 -0
  305. package/dist/src/ipc/permits.js.map +1 -0
  306. package/dist/src/ipc/postshim.js +137 -0
  307. package/dist/src/ipc/postshim.js.map +1 -0
  308. package/dist/src/ipc/protocol.js +239 -0
  309. package/dist/src/ipc/protocol.js.map +1 -0
  310. package/dist/src/ipc/server.js +312 -0
  311. package/dist/src/ipc/server.js.map +1 -0
  312. package/dist/src/ipc/workerspec.js +148 -0
  313. package/dist/src/ipc/workerspec.js.map +1 -0
  314. package/dist/src/learner/learner.js +1272 -0
  315. package/dist/src/learner/learner.js.map +1 -0
  316. package/dist/src/learner/miner.js +948 -0
  317. package/dist/src/learner/miner.js.map +1 -0
  318. package/dist/src/learner/packet.js +216 -0
  319. package/dist/src/learner/packet.js.map +1 -0
  320. package/dist/src/learner/receipt.js +135 -0
  321. package/dist/src/learner/receipt.js.map +1 -0
  322. package/dist/src/learner/status.js +38 -0
  323. package/dist/src/learner/status.js.map +1 -0
  324. package/dist/src/ledgercheck.js +120 -0
  325. package/dist/src/ledgercheck.js.map +1 -0
  326. package/dist/src/lessonledger.js +451 -0
  327. package/dist/src/lessonledger.js.map +1 -0
  328. package/dist/src/lessonselect.js +639 -0
  329. package/dist/src/lessonselect.js.map +1 -0
  330. package/dist/src/lessonshadow.js +540 -0
  331. package/dist/src/lessonshadow.js.map +1 -0
  332. package/dist/src/levers.js +302 -0
  333. package/dist/src/levers.js.map +1 -0
  334. package/dist/src/live/projection.js +450 -0
  335. package/dist/src/live/projection.js.map +1 -0
  336. package/dist/src/live/publish.js +83 -0
  337. package/dist/src/live/publish.js.map +1 -0
  338. package/dist/src/live/read.js +60 -0
  339. package/dist/src/live/read.js.map +1 -0
  340. package/dist/src/livecli.js +288 -0
  341. package/dist/src/livecli.js.map +1 -0
  342. package/dist/src/loginhint.js +42 -0
  343. package/dist/src/loginhint.js.map +1 -0
  344. package/dist/src/loginrefresh/atomic.js +66 -0
  345. package/dist/src/loginrefresh/atomic.js.map +1 -0
  346. package/dist/src/loginrefresh/lock.js +93 -0
  347. package/dist/src/loginrefresh/lock.js.map +1 -0
  348. package/dist/src/loginrefresh/main.js +58 -0
  349. package/dist/src/loginrefresh/main.js.map +1 -0
  350. package/dist/src/loginrefresh/pirefresh.js +49 -0
  351. package/dist/src/loginrefresh/pirefresh.js.map +1 -0
  352. package/dist/src/loginrefresh/refresher.js +286 -0
  353. package/dist/src/loginrefresh/refresher.js.map +1 -0
  354. package/dist/src/loginrefresh/supervisor.js +253 -0
  355. package/dist/src/loginrefresh/supervisor.js.map +1 -0
  356. package/dist/src/loginrefresh/types.js +4 -0
  357. package/dist/src/loginrefresh/types.js.map +1 -0
  358. package/dist/src/memory/context.js +251 -0
  359. package/dist/src/memory/context.js.map +1 -0
  360. package/dist/src/memory/datafence.js +5 -0
  361. package/dist/src/memory/datafence.js.map +1 -0
  362. package/dist/src/memory/delivery.js +131 -0
  363. package/dist/src/memory/delivery.js.map +1 -0
  364. package/dist/src/memory/eventtags.js +81 -0
  365. package/dist/src/memory/eventtags.js.map +1 -0
  366. package/dist/src/memory/health.js +713 -0
  367. package/dist/src/memory/health.js.map +1 -0
  368. package/dist/src/memory/import.js +504 -0
  369. package/dist/src/memory/import.js.map +1 -0
  370. package/dist/src/memory/layout.js +252 -0
  371. package/dist/src/memory/layout.js.map +1 -0
  372. package/dist/src/memory/legacy-file.js +100 -0
  373. package/dist/src/memory/legacy-file.js.map +1 -0
  374. package/dist/src/memory/lessons.js +370 -0
  375. package/dist/src/memory/lessons.js.map +1 -0
  376. package/dist/src/memory/ovsession.js +449 -0
  377. package/dist/src/memory/ovsession.js.map +1 -0
  378. package/dist/src/memory/relayout.js +259 -0
  379. package/dist/src/memory/relayout.js.map +1 -0
  380. package/dist/src/memory/roundactivity.js +180 -0
  381. package/dist/src/memory/roundactivity.js.map +1 -0
  382. package/dist/src/memory/selection.js +690 -0
  383. package/dist/src/memory/selection.js.map +1 -0
  384. package/dist/src/memory/shadowstatus.js +69 -0
  385. package/dist/src/memory/shadowstatus.js.map +1 -0
  386. package/dist/src/memory/store.js +83 -0
  387. package/dist/src/memory/store.js.map +1 -0
  388. package/dist/src/memory/textidentity.js +8 -0
  389. package/dist/src/memory/textidentity.js.map +1 -0
  390. package/dist/src/metrics.js +773 -0
  391. package/dist/src/metrics.js.map +1 -0
  392. package/dist/src/missingconfig.js +8 -0
  393. package/dist/src/missingconfig.js.map +1 -0
  394. package/dist/src/npmverbs.js +226 -0
  395. package/dist/src/npmverbs.js.map +1 -0
  396. package/dist/src/onboard/choices.js +191 -0
  397. package/dist/src/onboard/choices.js.map +1 -0
  398. package/dist/src/onboard/engine.js +344 -0
  399. package/dist/src/onboard/engine.js.map +1 -0
  400. package/dist/src/onboard/launch.js +83 -0
  401. package/dist/src/onboard/launch.js.map +1 -0
  402. package/dist/src/onboard/steps/appslug.js +211 -0
  403. package/dist/src/onboard/steps/appslug.js.map +1 -0
  404. package/dist/src/onboard/steps/bringup.js +408 -0
  405. package/dist/src/onboard/steps/bringup.js.map +1 -0
  406. package/dist/src/onboard/steps/compose.js +343 -0
  407. package/dist/src/onboard/steps/compose.js.map +1 -0
  408. package/dist/src/onboard/steps/configdelivery.js +50 -0
  409. package/dist/src/onboard/steps/configdelivery.js.map +1 -0
  410. package/dist/src/onboard/steps/configfill.js +1148 -0
  411. package/dist/src/onboard/steps/configfill.js.map +1 -0
  412. package/dist/src/onboard/steps/deploysh.js +80 -0
  413. package/dist/src/onboard/steps/deploysh.js.map +1 -0
  414. package/dist/src/onboard/steps/embeddings.js +472 -0
  415. package/dist/src/onboard/steps/embeddings.js.map +1 -0
  416. package/dist/src/onboard/steps/escalation.js +571 -0
  417. package/dist/src/onboard/steps/escalation.js.map +1 -0
  418. package/dist/src/onboard/steps/githubapp.js +1092 -0
  419. package/dist/src/onboard/steps/githubapp.js.map +1 -0
  420. package/dist/src/onboard/steps/golive.js +506 -0
  421. package/dist/src/onboard/steps/golive.js.map +1 -0
  422. package/dist/src/onboard/steps/index.js +67 -0
  423. package/dist/src/onboard/steps/index.js.map +1 -0
  424. package/dist/src/onboard/steps/ingress.js +762 -0
  425. package/dist/src/onboard/steps/ingress.js.map +1 -0
  426. package/dist/src/onboard/steps/inventory.js +420 -0
  427. package/dist/src/onboard/steps/inventory.js.map +1 -0
  428. package/dist/src/onboard/steps/llmauth.js +893 -0
  429. package/dist/src/onboard/steps/llmauth.js.map +1 -0
  430. package/dist/src/onboard/steps/memory.js +1060 -0
  431. package/dist/src/onboard/steps/memory.js.map +1 -0
  432. package/dist/src/onboard/steps/memorykeys.js +43 -0
  433. package/dist/src/onboard/steps/memorykeys.js.map +1 -0
  434. package/dist/src/onboard/steps/preflight.js +172 -0
  435. package/dist/src/onboard/steps/preflight.js.map +1 -0
  436. package/dist/src/onboard/steps/proof.js +502 -0
  437. package/dist/src/onboard/steps/proof.js.map +1 -0
  438. package/dist/src/onboard/steps/ssh.js +300 -0
  439. package/dist/src/onboard/steps/ssh.js.map +1 -0
  440. package/dist/src/onboard/steps/topology.js +291 -0
  441. package/dist/src/onboard/steps/topology.js.map +1 -0
  442. package/dist/src/onboard/steps/transport.js +511 -0
  443. package/dist/src/onboard/steps/transport.js.map +1 -0
  444. package/dist/src/onboard/steps/verifyup.js +362 -0
  445. package/dist/src/onboard/steps/verifyup.js.map +1 -0
  446. package/dist/src/onboard/steps/vmdeploy.js +368 -0
  447. package/dist/src/onboard/steps/vmdeploy.js.map +1 -0
  448. package/dist/src/onboard/toml.js +53 -0
  449. package/dist/src/onboard/toml.js.map +1 -0
  450. package/dist/src/opencli.js +508 -0
  451. package/dist/src/opencli.js.map +1 -0
  452. package/dist/src/openpr.js +457 -0
  453. package/dist/src/openpr.js.map +1 -0
  454. package/dist/src/openviking/client.js +821 -0
  455. package/dist/src/openviking/client.js.map +1 -0
  456. package/dist/src/openviking/context.js +218 -0
  457. package/dist/src/openviking/context.js.map +1 -0
  458. package/dist/src/openviking/embedder.js +457 -0
  459. package/dist/src/openviking/embedder.js.map +1 -0
  460. package/dist/src/openviking/gapsink.js +38 -0
  461. package/dist/src/openviking/gapsink.js.map +1 -0
  462. package/dist/src/openviking/health.js +69 -0
  463. package/dist/src/openviking/health.js.map +1 -0
  464. package/dist/src/openviking/index.js +24 -0
  465. package/dist/src/openviking/index.js.map +1 -0
  466. package/dist/src/openviking/lessonfind.js +54 -0
  467. package/dist/src/openviking/lessonfind.js.map +1 -0
  468. package/dist/src/openviking/probe.js +89 -0
  469. package/dist/src/openviking/probe.js.map +1 -0
  470. package/dist/src/openviking/proc.js +95 -0
  471. package/dist/src/openviking/proc.js.map +1 -0
  472. package/dist/src/openviking/provisionreport.js +144 -0
  473. package/dist/src/openviking/provisionreport.js.map +1 -0
  474. package/dist/src/openviking/reembed.js +317 -0
  475. package/dist/src/openviking/reembed.js.map +1 -0
  476. package/dist/src/openviking/relayout.js +153 -0
  477. package/dist/src/openviking/relayout.js.map +1 -0
  478. package/dist/src/openviking/sidecar-main.js +39 -0
  479. package/dist/src/openviking/sidecar-main.js.map +1 -0
  480. package/dist/src/openviking/sidecar.js +79 -0
  481. package/dist/src/openviking/sidecar.js.map +1 -0
  482. package/dist/src/openviking/store.js +1758 -0
  483. package/dist/src/openviking/store.js.map +1 -0
  484. package/dist/src/openviking/supervisor.js +422 -0
  485. package/dist/src/openviking/supervisor.js.map +1 -0
  486. package/dist/src/openviking/types.js +5 -0
  487. package/dist/src/openviking/types.js.map +1 -0
  488. package/dist/src/operator-recovery.js +172 -0
  489. package/dist/src/operator-recovery.js.map +1 -0
  490. package/dist/src/operatormessage.js +44 -0
  491. package/dist/src/operatormessage.js.map +1 -0
  492. package/dist/src/outcomes.js +1361 -0
  493. package/dist/src/outcomes.js.map +1 -0
  494. package/dist/src/piauthcopy.js +5 -0
  495. package/dist/src/piauthcopy.js.map +1 -0
  496. package/dist/src/picli.js +45 -0
  497. package/dist/src/picli.js.map +1 -0
  498. package/dist/src/picwd.js +25 -0
  499. package/dist/src/picwd.js.map +1 -0
  500. package/dist/src/pilogin.js +35 -0
  501. package/dist/src/pilogin.js.map +1 -0
  502. package/dist/src/pingpong.js +1028 -0
  503. package/dist/src/pingpong.js.map +1 -0
  504. package/dist/src/posting-journal.js +441 -0
  505. package/dist/src/posting-journal.js.map +1 -0
  506. package/dist/src/posting-outbox.js +662 -0
  507. package/dist/src/posting-outbox.js.map +1 -0
  508. package/dist/src/posting-receipt.js +203 -0
  509. package/dist/src/posting-receipt.js.map +1 -0
  510. package/dist/src/posting.js +348 -0
  511. package/dist/src/posting.js.map +1 -0
  512. package/dist/src/postops.js +637 -0
  513. package/dist/src/postops.js.map +1 -0
  514. package/dist/src/pr-activity.js +11 -0
  515. package/dist/src/pr-activity.js.map +1 -0
  516. package/dist/src/pr-author.js +5 -0
  517. package/dist/src/pr-author.js.map +1 -0
  518. package/dist/src/pr-conversation.js +355 -0
  519. package/dist/src/pr-conversation.js.map +1 -0
  520. package/dist/src/pr-session-lifecycle.js +542 -0
  521. package/dist/src/pr-session-lifecycle.js.map +1 -0
  522. package/dist/src/preflight.js +341 -0
  523. package/dist/src/preflight.js.map +1 -0
  524. package/dist/src/priorresolve.js +275 -0
  525. package/dist/src/priorresolve.js.map +1 -0
  526. package/dist/src/qcrbin.js +180 -0
  527. package/dist/src/qcrbin.js.map +1 -0
  528. package/dist/src/recheck.js +1398 -0
  529. package/dist/src/recheck.js.map +1 -0
  530. package/dist/src/reconcile.js +820 -0
  531. package/dist/src/reconcile.js.map +1 -0
  532. package/dist/src/recordedfindings.js +15 -0
  533. package/dist/src/recordedfindings.js.map +1 -0
  534. package/dist/src/regression.js +546 -0
  535. package/dist/src/regression.js.map +1 -0
  536. package/dist/src/replay.js +598 -0
  537. package/dist/src/replay.js.map +1 -0
  538. package/dist/src/requiredpasses.js +113 -0
  539. package/dist/src/requiredpasses.js.map +1 -0
  540. package/dist/src/resumecheck.js +168 -0
  541. package/dist/src/resumecheck.js.map +1 -0
  542. package/dist/src/reviewcli.js +671 -0
  543. package/dist/src/reviewcli.js.map +1 -0
  544. package/dist/src/reviewcontext.js +22 -0
  545. package/dist/src/reviewcontext.js.map +1 -0
  546. package/dist/src/reviewdetach.js +574 -0
  547. package/dist/src/reviewdetach.js.map +1 -0
  548. package/dist/src/reviewhistory.js +284 -0
  549. package/dist/src/reviewhistory.js.map +1 -0
  550. package/dist/src/reviewhome.js +26 -0
  551. package/dist/src/reviewhome.js.map +1 -0
  552. package/dist/src/reviewmemory.js +110 -0
  553. package/dist/src/reviewmemory.js.map +1 -0
  554. package/dist/src/reviewrendercli.js +104 -0
  555. package/dist/src/reviewrendercli.js.map +1 -0
  556. package/dist/src/round-work.js +20 -0
  557. package/dist/src/round-work.js.map +1 -0
  558. package/dist/src/runhost.js +72 -0
  559. package/dist/src/runhost.js.map +1 -0
  560. package/dist/src/scanauth.js +25 -0
  561. package/dist/src/scanauth.js.map +1 -0
  562. package/dist/src/scanbox.js +846 -0
  563. package/dist/src/scanbox.js.map +1 -0
  564. package/dist/src/scancli.js +1074 -0
  565. package/dist/src/scancli.js.map +1 -0
  566. package/dist/src/scandriverlog.js +30 -0
  567. package/dist/src/scandriverlog.js.map +1 -0
  568. package/dist/src/session-bridge.js +75 -0
  569. package/dist/src/session-bridge.js.map +1 -0
  570. package/dist/src/session-command-payload.js +29 -0
  571. package/dist/src/session-command-payload.js.map +1 -0
  572. package/dist/src/session-command-supervisor.js +939 -0
  573. package/dist/src/session-command-supervisor.js.map +1 -0
  574. package/dist/src/sessionjsonl.js +85 -0
  575. package/dist/src/sessionjsonl.js.map +1 -0
  576. package/dist/src/sessions.js +541 -0
  577. package/dist/src/sessions.js.map +1 -0
  578. package/dist/src/setup/adapters.js +330 -0
  579. package/dist/src/setup/adapters.js.map +1 -0
  580. package/dist/src/setup/baselines.js +64 -0
  581. package/dist/src/setup/baselines.js.map +1 -0
  582. package/dist/src/setup/caserunner.js +554 -0
  583. package/dist/src/setup/caserunner.js.map +1 -0
  584. package/dist/src/setup/configcommit.js +358 -0
  585. package/dist/src/setup/configcommit.js.map +1 -0
  586. package/dist/src/setup/engine.js +672 -0
  587. package/dist/src/setup/engine.js.map +1 -0
  588. package/dist/src/setup/facts.js +593 -0
  589. package/dist/src/setup/facts.js.map +1 -0
  590. package/dist/src/setup/findings.js +948 -0
  591. package/dist/src/setup/findings.js.map +1 -0
  592. package/dist/src/setup/history.js +338 -0
  593. package/dist/src/setup/history.js.map +1 -0
  594. package/dist/src/setup/interview.js +185 -0
  595. package/dist/src/setup/interview.js.map +1 -0
  596. package/dist/src/setup/ledger.js +207 -0
  597. package/dist/src/setup/ledger.js.map +1 -0
  598. package/dist/src/setup/pins.js +43 -0
  599. package/dist/src/setup/pins.js.map +1 -0
  600. package/dist/src/setup/portrait.js +432 -0
  601. package/dist/src/setup/portrait.js.map +1 -0
  602. package/dist/src/setup/presentation.js +9 -0
  603. package/dist/src/setup/presentation.js.map +1 -0
  604. package/dist/src/setup/profile.js +417 -0
  605. package/dist/src/setup/profile.js.map +1 -0
  606. package/dist/src/setup/replay.js +240 -0
  607. package/dist/src/setup/replay.js.map +1 -0
  608. package/dist/src/setup/replaybuild.js +311 -0
  609. package/dist/src/setup/replaybuild.js.map +1 -0
  610. package/dist/src/setup/replayrun.js +806 -0
  611. package/dist/src/setup/replayrun.js.map +1 -0
  612. package/dist/src/setup/replaysource.js +169 -0
  613. package/dist/src/setup/replaysource.js.map +1 -0
  614. package/dist/src/setup/retune.js +333 -0
  615. package/dist/src/setup/retune.js.map +1 -0
  616. package/dist/src/setup/retunelog.js +51 -0
  617. package/dist/src/setup/retunelog.js.map +1 -0
  618. package/dist/src/setup/scan.js +725 -0
  619. package/dist/src/setup/scan.js.map +1 -0
  620. package/dist/src/setup/scanbuild.js +133 -0
  621. package/dist/src/setup/scanbuild.js.map +1 -0
  622. package/dist/src/setup/scanfacts.js +96 -0
  623. package/dist/src/setup/scanfacts.js.map +1 -0
  624. package/dist/src/setup/scanlease.js +251 -0
  625. package/dist/src/setup/scanlease.js.map +1 -0
  626. package/dist/src/setup/scanreport.js +289 -0
  627. package/dist/src/setup/scanreport.js.map +1 -0
  628. package/dist/src/setup/scanrun.js +1733 -0
  629. package/dist/src/setup/scanrun.js.map +1 -0
  630. package/dist/src/setup/scantree.js +257 -0
  631. package/dist/src/setup/scantree.js.map +1 -0
  632. package/dist/src/setup/stage2.js +617 -0
  633. package/dist/src/setup/stage2.js.map +1 -0
  634. package/dist/src/setup/stage2request.js +259 -0
  635. package/dist/src/setup/stage2request.js.map +1 -0
  636. package/dist/src/setup/survey.js +307 -0
  637. package/dist/src/setup/survey.js.map +1 -0
  638. package/dist/src/setup/surveyrun.js +549 -0
  639. package/dist/src/setup/surveyrun.js.map +1 -0
  640. package/dist/src/setup/textsniff.js +89 -0
  641. package/dist/src/setup/textsniff.js.map +1 -0
  642. package/dist/src/setup/trackersink.js +395 -0
  643. package/dist/src/setup/trackersink.js.map +1 -0
  644. package/dist/src/setup/triage.js +636 -0
  645. package/dist/src/setup/triage.js.map +1 -0
  646. package/dist/src/setup/tuning.js +585 -0
  647. package/dist/src/setup/tuning.js.map +1 -0
  648. package/dist/src/setup/unitcut.js +437 -0
  649. package/dist/src/setup/unitcut.js.map +1 -0
  650. package/dist/src/singleflight.js +72 -0
  651. package/dist/src/singleflight.js.map +1 -0
  652. package/dist/src/slack/announce.js +725 -0
  653. package/dist/src/slack/announce.js.map +1 -0
  654. package/dist/src/slack/api.js +227 -0
  655. package/dist/src/slack/api.js.map +1 -0
  656. package/dist/src/slack/messages.js +158 -0
  657. package/dist/src/slack/messages.js.map +1 -0
  658. package/dist/src/slack/origin.js +550 -0
  659. package/dist/src/slack/origin.js.map +1 -0
  660. package/dist/src/slack/policy.js +225 -0
  661. package/dist/src/slack/policy.js.map +1 -0
  662. package/dist/src/slack/reply.js +153 -0
  663. package/dist/src/slack/reply.js.map +1 -0
  664. package/dist/src/slack/router.js +189 -0
  665. package/dist/src/slack/router.js.map +1 -0
  666. package/dist/src/slack/socket.js +323 -0
  667. package/dist/src/slack/socket.js.map +1 -0
  668. package/dist/src/slack/types.js +11 -0
  669. package/dist/src/slack/types.js.map +1 -0
  670. package/dist/src/sqlitehint.js +30 -0
  671. package/dist/src/sqlitehint.js.map +1 -0
  672. package/dist/src/ssh-observation.js +25 -0
  673. package/dist/src/ssh-observation.js.map +1 -0
  674. package/dist/src/state.js +5869 -0
  675. package/dist/src/state.js.map +1 -0
  676. package/dist/src/statemachine.js +220 -0
  677. package/dist/src/statemachine.js.map +1 -0
  678. package/dist/src/stubs.js +52 -0
  679. package/dist/src/stubs.js.map +1 -0
  680. package/dist/src/takeover.js +506 -0
  681. package/dist/src/takeover.js.map +1 -0
  682. package/dist/src/takeoverpi.js +249 -0
  683. package/dist/src/takeoverpi.js.map +1 -0
  684. package/dist/src/terminal-failure.js +26 -0
  685. package/dist/src/terminal-failure.js.map +1 -0
  686. package/dist/src/thread-resolution-refusal.js +4 -0
  687. package/dist/src/thread-resolution-refusal.js.map +1 -0
  688. package/dist/src/tmux.js +126 -0
  689. package/dist/src/tmux.js.map +1 -0
  690. package/dist/src/trackerid.js +43 -0
  691. package/dist/src/trackerid.js.map +1 -0
  692. package/dist/src/transcriptindex.js +512 -0
  693. package/dist/src/transcriptindex.js.map +1 -0
  694. package/dist/src/tui/actions.js +404 -0
  695. package/dist/src/tui/actions.js.map +1 -0
  696. package/dist/src/tui/alerts.js +523 -0
  697. package/dist/src/tui/alerts.js.map +1 -0
  698. package/dist/src/tui/app.js +1835 -0
  699. package/dist/src/tui/app.js.map +1 -0
  700. package/dist/src/tui/archiveops.js +164 -0
  701. package/dist/src/tui/archiveops.js.map +1 -0
  702. package/dist/src/tui/autofile.js +191 -0
  703. package/dist/src/tui/autofile.js.map +1 -0
  704. package/dist/src/tui/board.js +1143 -0
  705. package/dist/src/tui/board.js.map +1 -0
  706. package/dist/src/tui/candidates.js +699 -0
  707. package/dist/src/tui/candidates.js.map +1 -0
  708. package/dist/src/tui/cellwidth.js +151 -0
  709. package/dist/src/tui/cellwidth.js.map +1 -0
  710. package/dist/src/tui/changelog-store.js +404 -0
  711. package/dist/src/tui/changelog-store.js.map +1 -0
  712. package/dist/src/tui/chord.js +26 -0
  713. package/dist/src/tui/chord.js.map +1 -0
  714. package/dist/src/tui/clipboard.js +81 -0
  715. package/dist/src/tui/clipboard.js.map +1 -0
  716. package/dist/src/tui/cmdbar.js +253 -0
  717. package/dist/src/tui/cmdbar.js.map +1 -0
  718. package/dist/src/tui/companion.js +1275 -0
  719. package/dist/src/tui/companion.js.map +1 -0
  720. package/dist/src/tui/companionplan.js +200 -0
  721. package/dist/src/tui/companionplan.js.map +1 -0
  722. package/dist/src/tui/companiontab.js +61 -0
  723. package/dist/src/tui/companiontab.js.map +1 -0
  724. package/dist/src/tui/confighome.js +105 -0
  725. package/dist/src/tui/confighome.js.map +1 -0
  726. package/dist/src/tui/connect.js +44 -0
  727. package/dist/src/tui/connect.js.map +1 -0
  728. package/dist/src/tui/deploy-ceremony.js +75 -0
  729. package/dist/src/tui/deploy-ceremony.js.map +1 -0
  730. package/dist/src/tui/deploy-drain.js +93 -0
  731. package/dist/src/tui/deploy-drain.js.map +1 -0
  732. package/dist/src/tui/deploy-process.js +234 -0
  733. package/dist/src/tui/deploy-process.js.map +1 -0
  734. package/dist/src/tui/deploytab.js +1263 -0
  735. package/dist/src/tui/deploytab.js.map +1 -0
  736. package/dist/src/tui/design.js +111 -0
  737. package/dist/src/tui/design.js.map +1 -0
  738. package/dist/src/tui/digesttab.js +148 -0
  739. package/dist/src/tui/digesttab.js.map +1 -0
  740. package/dist/src/tui/emptystate.js +32 -0
  741. package/dist/src/tui/emptystate.js.map +1 -0
  742. package/dist/src/tui/evidence.js +790 -0
  743. package/dist/src/tui/evidence.js.map +1 -0
  744. package/dist/src/tui/feed.js +415 -0
  745. package/dist/src/tui/feed.js.map +1 -0
  746. package/dist/src/tui/feedbacktab.js +907 -0
  747. package/dist/src/tui/feedbacktab.js.map +1 -0
  748. package/dist/src/tui/filters.js +72 -0
  749. package/dist/src/tui/filters.js.map +1 -0
  750. package/dist/src/tui/ghread.js +272 -0
  751. package/dist/src/tui/ghread.js.map +1 -0
  752. package/dist/src/tui/historypull.js +61 -0
  753. package/dist/src/tui/historypull.js.map +1 -0
  754. package/dist/src/tui/inboxpull.js +197 -0
  755. package/dist/src/tui/inboxpull.js.map +1 -0
  756. package/dist/src/tui/index.js +1425 -0
  757. package/dist/src/tui/index.js.map +1 -0
  758. package/dist/src/tui/issuesink.js +206 -0
  759. package/dist/src/tui/issuesink.js.map +1 -0
  760. package/dist/src/tui/jsonfile.js +32 -0
  761. package/dist/src/tui/jsonfile.js.map +1 -0
  762. package/dist/src/tui/keys.js +97 -0
  763. package/dist/src/tui/keys.js.map +1 -0
  764. package/dist/src/tui/layout.js +70 -0
  765. package/dist/src/tui/layout.js.map +1 -0
  766. package/dist/src/tui/learning.js +469 -0
  767. package/dist/src/tui/learning.js.map +1 -0
  768. package/dist/src/tui/lessonswitch.js +103 -0
  769. package/dist/src/tui/lessonswitch.js.map +1 -0
  770. package/dist/src/tui/livecontrol.js +167 -0
  771. package/dist/src/tui/livecontrol.js.map +1 -0
  772. package/dist/src/tui/memforward.js +310 -0
  773. package/dist/src/tui/memforward.js.map +1 -0
  774. package/dist/src/tui/memincident.js +287 -0
  775. package/dist/src/tui/memincident.js.map +1 -0
  776. package/dist/src/tui/memory.js +485 -0
  777. package/dist/src/tui/memory.js.map +1 -0
  778. package/dist/src/tui/memtab.js +513 -0
  779. package/dist/src/tui/memtab.js.map +1 -0
  780. package/dist/src/tui/metricstab.js +979 -0
  781. package/dist/src/tui/metricstab.js.map +1 -0
  782. package/dist/src/tui/model.js +104 -0
  783. package/dist/src/tui/model.js.map +1 -0
  784. package/dist/src/tui/mouse.js +83 -0
  785. package/dist/src/tui/mouse.js.map +1 -0
  786. package/dist/src/tui/msg.js +196 -0
  787. package/dist/src/tui/msg.js.map +1 -0
  788. package/dist/src/tui/nativecompanion.js +321 -0
  789. package/dist/src/tui/nativecompanion.js.map +1 -0
  790. package/dist/src/tui/openurl.js +17 -0
  791. package/dist/src/tui/openurl.js.map +1 -0
  792. package/dist/src/tui/paint.js +501 -0
  793. package/dist/src/tui/paint.js.map +1 -0
  794. package/dist/src/tui/panes.js +74 -0
  795. package/dist/src/tui/panes.js.map +1 -0
  796. package/dist/src/tui/pipane.js +166 -0
  797. package/dist/src/tui/pipane.js.map +1 -0
  798. package/dist/src/tui/poll-hooks.js +11 -0
  799. package/dist/src/tui/poll-hooks.js.map +1 -0
  800. package/dist/src/tui/poll.js +113 -0
  801. package/dist/src/tui/poll.js.map +1 -0
  802. package/dist/src/tui/proposals.js +715 -0
  803. package/dist/src/tui/proposals.js.map +1 -0
  804. package/dist/src/tui/prpause.js +132 -0
  805. package/dist/src/tui/prpause.js.map +1 -0
  806. package/dist/src/tui/reload.js +351 -0
  807. package/dist/src/tui/reload.js.map +1 -0
  808. package/dist/src/tui/remote.js +312 -0
  809. package/dist/src/tui/remote.js.map +1 -0
  810. package/dist/src/tui/rollover.js +767 -0
  811. package/dist/src/tui/rollover.js.map +1 -0
  812. package/dist/src/tui/scantab.js +1053 -0
  813. package/dist/src/tui/scantab.js.map +1 -0
  814. package/dist/src/tui/setupentry.js +81 -0
  815. package/dist/src/tui/setupentry.js.map +1 -0
  816. package/dist/src/tui/sidebar.js +602 -0
  817. package/dist/src/tui/sidebar.js.map +1 -0
  818. package/dist/src/tui/sshtriage.js +393 -0
  819. package/dist/src/tui/sshtriage.js.map +1 -0
  820. package/dist/src/tui/startup.js +172 -0
  821. package/dist/src/tui/startup.js.map +1 -0
  822. package/dist/src/tui/tabs.js +41 -0
  823. package/dist/src/tui/tabs.js.map +1 -0
  824. package/dist/src/tui/termmirror.js +217 -0
  825. package/dist/src/tui/termmirror.js.map +1 -0
  826. package/dist/src/tui/theme.js +95 -0
  827. package/dist/src/tui/theme.js.map +1 -0
  828. package/dist/src/tui/ticketstab.js +224 -0
  829. package/dist/src/tui/ticketstab.js.map +1 -0
  830. package/dist/src/tui/tmuxsocket.js +101 -0
  831. package/dist/src/tui/tmuxsocket.js.map +1 -0
  832. package/dist/src/tui/trackertickets.js +77 -0
  833. package/dist/src/tui/trackertickets.js.map +1 -0
  834. package/dist/src/tui/unwedge.js +245 -0
  835. package/dist/src/tui/unwedge.js.map +1 -0
  836. package/dist/src/tui/viewer.js +479 -0
  837. package/dist/src/tui/viewer.js.map +1 -0
  838. package/dist/src/tui/viewerinput.js +125 -0
  839. package/dist/src/tui/viewerinput.js.map +1 -0
  840. package/dist/src/updatenotice.js +143 -0
  841. package/dist/src/updatenotice.js.map +1 -0
  842. package/dist/src/usage.js +1007 -0
  843. package/dist/src/usage.js.map +1 -0
  844. package/dist/src/verdicts.js +3848 -0
  845. package/dist/src/verdicts.js.map +1 -0
  846. package/dist/src/version.js +58 -0
  847. package/dist/src/version.js.map +1 -0
  848. package/dist/src/wakes.js +411 -0
  849. package/dist/src/wakes.js.map +1 -0
  850. package/dist/src/webhook.js +725 -0
  851. package/dist/src/webhook.js.map +1 -0
  852. package/dist/src/webhookheal.js +238 -0
  853. package/dist/src/webhookheal.js.map +1 -0
  854. package/dist/src/wire.js +1801 -0
  855. package/dist/src/wire.js.map +1 -0
  856. package/dist/src/worker-session-host.js +531 -0
  857. package/dist/src/worker-session-host.js.map +1 -0
  858. package/dist/src/workerbreaker.js +112 -0
  859. package/dist/src/workerbreaker.js.map +1 -0
  860. package/dist/src/workercontrol.js +49 -0
  861. package/dist/src/workercontrol.js.map +1 -0
  862. package/dist/src/workerlease.js +351 -0
  863. package/dist/src/workerlease.js.map +1 -0
  864. package/dist/src/workerliveness.js +84 -0
  865. package/dist/src/workerliveness.js.map +1 -0
  866. package/dist/src/workerlog.js +65 -0
  867. package/dist/src/workerlog.js.map +1 -0
  868. package/dist/src/workerruntime.js +10 -0
  869. package/dist/src/workerruntime.js.map +1 -0
  870. package/dist/src/workersupervisor.js +1206 -0
  871. package/dist/src/workersupervisor.js.map +1 -0
  872. package/dist/src/workerusage.js +23 -0
  873. package/dist/src/workerusage.js.map +1 -0
  874. package/dist/src/workerworld.js +332 -0
  875. package/dist/src/workerworld.js.map +1 -0
  876. package/dist/src/worktrees.js +984 -0
  877. package/dist/src/worktrees.js.map +1 -0
  878. package/docker/Dockerfile +284 -0
  879. package/docker/Dockerfile.dockerignore +35 -0
  880. package/docker/README.md +221 -0
  881. package/docker/companion/Dockerfile +139 -0
  882. package/docker/companion/agents/evidence.md +34 -0
  883. package/docker/companion/agents/explore.md +26 -0
  884. package/docker/companion/agents/pass-survey.md +49 -0
  885. package/docker/companion/agents/study.md +26 -0
  886. package/docker/companion/entrypoint.sh +343 -0
  887. package/docker/companion/memory-health.mjs +269 -0
  888. package/docker/entrypoint.sh +782 -0
  889. package/docker/memory/Dockerfile +191 -0
  890. package/docker/memory/Dockerfile.dockerignore +20 -0
  891. package/docker/memory/README.md +60 -0
  892. package/docker/memory/entrypoint.sh +163 -0
  893. package/docker/setid/qcr-setid.c +109 -0
  894. package/docker/tunnel/Dockerfile.cloudflared +51 -0
  895. package/docker/tunnel/Dockerfile.ngrok +40 -0
  896. package/docker/tunnel/sidecar.mjs +411 -0
  897. package/docker/vm/tmux.conf +26 -0
  898. package/docker-compose.yml +348 -0
  899. package/extensions/cli/bash-guard-ambient.ts +71 -0
  900. package/extensions/cli/bash-guard.ts +12 -0
  901. package/extensions/cli/companion-guard.ts +8 -0
  902. package/extensions/cli/debug-round.ts +9 -0
  903. package/extensions/cli/inject-bridge.ts +8 -0
  904. package/extensions/cli/ledger-pin.ts +13 -0
  905. package/extensions/cli/lens.ts +22 -0
  906. package/extensions/cli/muted-theme.ts +9 -0
  907. package/extensions/cli/onboarding.ts +8 -0
  908. package/extensions/cli/setup.ts +8 -0
  909. package/extensions/cli/tui-view.ts +8 -0
  910. package/extensions/companion-guard.ts +411 -0
  911. package/extensions/debug-round.ts +281 -0
  912. package/extensions/inject-bridge.ts +630 -0
  913. package/extensions/inline-bash.ts +102 -0
  914. package/extensions/muted-theme.json +68 -0
  915. package/extensions/muted-theme.ts +23 -0
  916. package/extensions/onboarding.ts +233 -0
  917. package/extensions/setup-replay.ts +336 -0
  918. package/extensions/setup-scan.ts +1236 -0
  919. package/extensions/setup-triage.ts +190 -0
  920. package/extensions/setup.ts +1428 -0
  921. package/extensions/tui-view.ts +489 -0
  922. package/extensions/vendor/openviking-pi/LICENSE +201 -0
  923. package/extensions/vendor/openviking-pi/NOTICE +68 -0
  924. package/extensions/vendor/openviking-pi/README.md +550 -0
  925. package/extensions/vendor/openviking-pi/client.ts +588 -0
  926. package/extensions/vendor/openviking-pi/config.json +28 -0
  927. package/extensions/vendor/openviking-pi/config.ts +271 -0
  928. package/extensions/vendor/openviking-pi/index.ts +523 -0
  929. package/extensions/vendor/openviking-pi/lib/capture-adapter.d.mts +10 -0
  930. package/extensions/vendor/openviking-pi/lib/capture-adapter.mjs +88 -0
  931. package/extensions/vendor/openviking-pi/lib/takeover-core.d.mts +80 -0
  932. package/extensions/vendor/openviking-pi/lib/takeover-core.mjs +406 -0
  933. package/extensions/vendor/openviking-pi/lib/uri-guard-adapter.d.mts +1 -0
  934. package/extensions/vendor/openviking-pi/lib/uri-guard-adapter.mjs +42 -0
  935. package/extensions/vendor/openviking-pi/patches/0000-vendoring-and-build-conformance.patch +726 -0
  936. package/extensions/vendor/openviking-pi/patches/0001-remove-session-before-compact-handler.patch +101 -0
  937. package/extensions/vendor/openviking-pi/patches/0002-no-ambient-work-in-subagent-leaves.patch +202 -0
  938. package/extensions/vendor/openviking-pi/patches/0003-never-auto-create-openviking-sessions.patch +120 -0
  939. package/extensions/vendor/openviking-pi/patches/0004-viking-write-durable-batch-write.patch +521 -0
  940. package/extensions/vendor/openviking-pi/patches/0005-second-memory-namespace.patch +786 -0
  941. package/extensions/vendor/openviking-pi/patches/0006-writes-that-stick-and-honest-errors.patch +996 -0
  942. package/extensions/vendor/openviking-pi/patches/0007-bounded-recall-no-commit.patch +794 -0
  943. package/extensions/vendor/openviking-pi/patches/0008-pr-derived-recall.patch +379 -0
  944. package/extensions/vendor/openviking-pi/patches/0009-pr-session-tools-in-round-areas.patch +375 -0
  945. package/extensions/vendor/openviking-pi/patches/0010-typed-lessons.patch +448 -0
  946. package/extensions/vendor/openviking-pi/patches/0011-review-session-memory-boundary.patch +382 -0
  947. package/extensions/vendor/openviking-pi/patches/0012-typecheck-against-the-real-pi.patch +129 -0
  948. package/extensions/vendor/openviking-pi/patches/0013-createopenviking-factory.patch +1168 -0
  949. package/extensions/vendor/openviking-pi/qcr-hint.ts +313 -0
  950. package/extensions/vendor/openviking-pi/qcr-honest.ts +183 -0
  951. package/extensions/vendor/openviking-pi/qcr-hooks.ts +87 -0
  952. package/extensions/vendor/openviking-pi/qcr-leaf.ts +78 -0
  953. package/extensions/vendor/openviking-pi/qcr-lesson.ts +231 -0
  954. package/extensions/vendor/openviking-pi/qcr-namespaces.ts +198 -0
  955. package/extensions/vendor/openviking-pi/qcr-recall.ts +288 -0
  956. package/extensions/vendor/openviking-pi/qcr-shim.ts +142 -0
  957. package/extensions/vendor/openviking-pi/qcr-write.ts +427 -0
  958. package/extensions/vendor/openviking-pi/recall.ts +149 -0
  959. package/extensions/vendor/openviking-pi/shared/capture-utils.d.mts +9 -0
  960. package/extensions/vendor/openviking-pi/shared/capture-utils.mjs +515 -0
  961. package/extensions/vendor/openviking-pi/shared/credentials.d.mts +14 -0
  962. package/extensions/vendor/openviking-pi/shared/credentials.mjs +228 -0
  963. package/extensions/vendor/openviking-pi/shared/pending-queue.d.mts +19 -0
  964. package/extensions/vendor/openviking-pi/shared/pending-queue.mjs +458 -0
  965. package/extensions/vendor/openviking-pi/shared/profile-inject.d.mts +17 -0
  966. package/extensions/vendor/openviking-pi/shared/profile-inject.mjs +294 -0
  967. package/extensions/vendor/openviking-pi/shared/recall-compress-core.mjs +195 -0
  968. package/extensions/vendor/openviking-pi/shared/recall-core.d.mts +13 -0
  969. package/extensions/vendor/openviking-pi/shared/recall-core.mjs +609 -0
  970. package/extensions/vendor/openviking-pi/shared/retryable.mjs +9 -0
  971. package/extensions/vendor/openviking-pi/shared/session-model.d.mts +3 -0
  972. package/extensions/vendor/openviking-pi/shared/session-model.mjs +57 -0
  973. package/extensions/vendor/openviking-pi/shared/uri-guard.mjs +56 -0
  974. package/extensions/vendor/openviking-pi/shared/workspace-peer.d.mts +5 -0
  975. package/extensions/vendor/openviking-pi/shared/workspace-peer.mjs +16 -0
  976. package/extensions/vendor/openviking-pi/sync.ts +163 -0
  977. package/extensions/vendor/openviking-pi/takeover.ts +36 -0
  978. package/extensions/vendor/openviking-pi/tools.ts +479 -0
  979. package/extensions/vendor/openviking-pi/upstream.json +48 -0
  980. package/package.json +138 -4
  981. package/profiles/default/README.md +21 -0
  982. package/profiles/default/addenda/.gitkeep +0 -0
  983. package/profiles/default/profile.toml +17 -0
  984. package/profiles/example-django/README.md +21 -0
  985. package/profiles/example-django/addenda/10-migration-discipline.md +13 -0
  986. package/profiles/example-django/addenda/20-new-column-nullability.md +12 -0
  987. package/profiles/example-django/addenda/30-test-doctrine.md +12 -0
  988. package/profiles/example-django/addenda/40-orm-usage.md +19 -0
  989. package/profiles/example-django/addenda/50-declared-shape.md +10 -0
  990. package/profiles/example-django/addenda/60-config-placement.md +14 -0
  991. package/profiles/example-django/heuristics.toml +24 -0
  992. package/profiles/example-django/profile.toml +9 -0
  993. package/profiles/fast/README.md +24 -0
  994. package/profiles/fast/profile.toml +59 -0
  995. package/profiles/minimal/README.md +17 -0
  996. package/profiles/minimal/addenda/.gitkeep +0 -0
  997. package/profiles/minimal/profile.toml +8 -0
  998. package/profiles/snappy/README.md +24 -0
  999. package/profiles/snappy/addenda/10-race-as-question.md +6 -0
  1000. package/profiles/snappy/profile.toml +46 -0
  1001. package/prompts/companion-day-open.md +46 -0
  1002. package/prompts/companion-first-run.md +104 -0
  1003. package/prompts/companion.md +821 -0
  1004. package/prompts/conversation-system.md +117 -0
  1005. package/prompts/eod-sweep.md +120 -0
  1006. package/prompts/onboarding.md +74 -0
  1007. package/prompts/pass-common.md +52 -0
  1008. package/prompts/pass-quality-common.md +22 -0
  1009. package/prompts/round-first.md +874 -0
  1010. package/prompts/round-follow-up.md +991 -0
  1011. package/prompts/round-re-review.md +889 -0
  1012. package/prompts/round-recheck.md +196 -0
  1013. package/prompts/round-staleness.md +856 -0
  1014. package/schemas/review-result.v1.json +391 -0
  1015. package/schemas/scan-result.v1.json +421 -0
  1016. package/schemas/version.v1.json +57 -0
  1017. package/scripts/approve-pr.sh +16 -0
  1018. package/scripts/build-stamp.mjs +67 -0
  1019. package/scripts/commit-verdict.sh +155 -0
  1020. package/scripts/companion-e2e.sh +250 -0
  1021. package/scripts/companion-memory-forward.mjs +1966 -0
  1022. package/scripts/config-fingerprint.sh +28 -0
  1023. package/scripts/deploy-manifest.mjs +141 -0
  1024. package/scripts/deploy.sh +1435 -0
  1025. package/scripts/directory-lock.mjs +193 -0
  1026. package/scripts/heartbeat-fresh.mjs +54 -0
  1027. package/scripts/install-locator.sh +328 -0
  1028. package/scripts/install-wrapper.sh +76 -0
  1029. package/scripts/memory-search-check.mjs +98 -0
  1030. package/scripts/mint-token.sh +212 -0
  1031. package/scripts/openviking-embedder.py +392 -0
  1032. package/scripts/openviking-ov-conf.mjs +161 -0
  1033. package/scripts/openviking-provision.mjs +1122 -0
  1034. package/scripts/openviking-reembed.mjs +1154 -0
  1035. package/scripts/post-review.sh +16 -0
  1036. package/scripts/pr-checks.sh +256 -0
  1037. package/scripts/pr-context.py +174 -0
  1038. package/scripts/pr-context.sh +63 -0
  1039. package/scripts/pr-self.sh +144 -0
  1040. package/scripts/pr-threads.sh +145 -0
  1041. package/scripts/qcr +15 -0
  1042. package/scripts/qcr-host.sh +769 -0
  1043. package/scripts/render-review.sh +24 -0
  1044. package/scripts/reply-thread.sh +16 -0
  1045. package/scripts/required-passes.py +112 -0
  1046. package/scripts/smoke-companion.sh +211 -0
  1047. package/scripts/tui-deploy-runner.mjs +61 -0
  1048. package/scripts/verify-dist.mjs +201 -0
  1049. package/scripts/vm-ghcr-login.sh +188 -0
  1050. package/seeds/agents/pass-baseline.md +61 -0
  1051. package/seeds/agents/pass-contracts.md +30 -0
  1052. package/seeds/agents/pass-data-safety.md +54 -0
  1053. package/seeds/agents/pass-delivery.md +37 -0
  1054. package/seeds/agents/pass-prod-risk.md +24 -0
  1055. package/seeds/agents/pass-security.md +31 -0
  1056. package/seeds/agents/pass-statelessness.md +27 -0
  1057. package/seeds/agents/pass-ui.md +23 -0
  1058. package/seeds/agents/pass-validate.md +50 -0
  1059. package/seeds/prompts/conversation-system.md +117 -0
  1060. package/seeds/prompts/eod-sweep.md +120 -0
  1061. package/seeds/prompts/pass-common.md +52 -0
  1062. package/skills/companion/adjudicate/SKILL.md +247 -0
  1063. package/skills/companion/evidence/SKILL.md +150 -0
  1064. package/skills/companion/land/SKILL.md +200 -0
  1065. package/skills/companion/replay/SKILL.md +138 -0
  1066. package/skills/companion/setup/SKILL.md +167 -0
  1067. package/skills/companion/survey/SKILL.md +160 -0
  1068. package/skills/selfimprove/README.md +34 -0
  1069. package/skills/selfimprove/SKILL.md +127 -0
  1070. package/skills/selfimprove/agents/openai.yaml +2 -0
  1071. package/skills/selfimprove/commands/selfimprove.md +6 -0
  1072. package/skills/selfimprove/references/fix-loop.md +49 -0
  1073. package/skills/selfimprove/references/ledger.md +20 -0
  1074. package/skills/selfimprove/references/setup.md +40 -0
  1075. package/src/anomalies.ts +566 -0
  1076. package/src/answerworker.ts +251 -0
  1077. package/src/appstamp.ts +65 -0
  1078. package/src/archive/admission.ts +3 -0
  1079. package/src/archive/archivedb.ts +548 -0
  1080. package/src/archive/archiver.ts +1963 -0
  1081. package/src/archive/archivewrite.ts +279 -0
  1082. package/src/archive/bundle.ts +333 -0
  1083. package/src/archive/dbmove.ts +911 -0
  1084. package/src/archive/duplicatecounts.ts +98 -0
  1085. package/src/archive/eligibility.ts +181 -0
  1086. package/src/archive/extract.ts +342 -0
  1087. package/src/archive/fsutil.ts +415 -0
  1088. package/src/archive/gzfile.ts +370 -0
  1089. package/src/archive/kinds.ts +21 -0
  1090. package/src/archive/livecheckpoint.ts +217 -0
  1091. package/src/archive/liveretention.ts +180 -0
  1092. package/src/archive/manifest.ts +519 -0
  1093. package/src/archive/overseer.ts +336 -0
  1094. package/src/archive/piglobal.ts +174 -0
  1095. package/src/archive/plan.ts +612 -0
  1096. package/src/archive/prarchive.ts +984 -0
  1097. package/src/archive/prfiles.ts +406 -0
  1098. package/src/archive/report.ts +252 -0
  1099. package/src/archive/restore.ts +330 -0
  1100. package/src/archive/rows.ts +187 -0
  1101. package/src/archive/seal.ts +630 -0
  1102. package/src/archive/status.ts +719 -0
  1103. package/src/archive/storepass.ts +581 -0
  1104. package/src/backfill.ts +362 -0
  1105. package/src/bench/cost.ts +113 -0
  1106. package/src/bench/exec.ts +90 -0
  1107. package/src/bench/values.ts +13 -0
  1108. package/src/botcontrol.ts +176 -0
  1109. package/src/botworker.ts +953 -0
  1110. package/src/capture.ts +321 -0
  1111. package/src/cases.ts +1789 -0
  1112. package/src/channelfs.ts +26 -0
  1113. package/src/checklist.ts +506 -0
  1114. package/src/ciretry.ts +141 -0
  1115. package/src/cli.ts +2271 -0
  1116. package/src/cliops.ts +384 -0
  1117. package/src/clonedir.ts +15 -0
  1118. package/src/commandeffects.ts +57 -0
  1119. package/src/commands.ts +1678 -0
  1120. package/src/companioninbox.ts +239 -0
  1121. package/src/compare.ts +957 -0
  1122. package/src/config.ts +2050 -0
  1123. package/src/configcheck.ts +133 -0
  1124. package/src/coreext.ts +47 -0
  1125. package/src/daemon.ts +1302 -0
  1126. package/src/debuground.ts +927 -0
  1127. package/src/debugroundpi.ts +262 -0
  1128. package/src/diagnostics.ts +14 -0
  1129. package/src/digest.ts +183 -0
  1130. package/src/dispatch.ts +2250 -0
  1131. package/src/engine/answerguard.ts +27 -0
  1132. package/src/engine/botsdk.ts +336 -0
  1133. package/src/engine/caserunner-sdk.ts +423 -0
  1134. package/src/engine/debugpi.ts +235 -0
  1135. package/src/eod.ts +669 -0
  1136. package/src/eodpinwatch.ts +556 -0
  1137. package/src/escalation.ts +1253 -0
  1138. package/src/eventloop.ts +1045 -0
  1139. package/src/execboundary.ts +633 -0
  1140. package/src/feedback.ts +1162 -0
  1141. package/src/findingscheck.ts +298 -0
  1142. package/src/fsdenied.ts +24 -0
  1143. package/src/ghread.ts +242 -0
  1144. package/src/github.ts +1497 -0
  1145. package/src/homes.ts +175 -0
  1146. package/src/hostroute.ts +53 -0
  1147. package/src/improvements.ts +787 -0
  1148. package/src/install.ts +1103 -0
  1149. package/src/installkind.ts +50 -0
  1150. package/src/ipc/capability.ts +333 -0
  1151. package/src/ipc/client.ts +269 -0
  1152. package/src/ipc/control.ts +90 -0
  1153. package/src/ipc/ghreadshim.ts +94 -0
  1154. package/src/ipc/launcher.ts +65 -0
  1155. package/src/ipc/permits.ts +362 -0
  1156. package/src/ipc/postshim.ts +164 -0
  1157. package/src/ipc/protocol.ts +342 -0
  1158. package/src/ipc/server.ts +371 -0
  1159. package/src/ipc/workerspec.ts +218 -0
  1160. package/src/learner/learner.ts +1655 -0
  1161. package/src/learner/miner.ts +1290 -0
  1162. package/src/learner/packet.ts +340 -0
  1163. package/src/learner/receipt.ts +168 -0
  1164. package/src/learner/status.ts +107 -0
  1165. package/src/ledgercheck.ts +157 -0
  1166. package/src/lessonledger.ts +703 -0
  1167. package/src/lessonselect.ts +888 -0
  1168. package/src/lessonshadow.ts +810 -0
  1169. package/src/levers.ts +393 -0
  1170. package/src/live/projection.ts +491 -0
  1171. package/src/live/publish.ts +92 -0
  1172. package/src/live/read.ts +86 -0
  1173. package/src/livecli.ts +336 -0
  1174. package/src/loginhint.ts +56 -0
  1175. package/src/loginrefresh/atomic.ts +76 -0
  1176. package/src/loginrefresh/lock.ts +104 -0
  1177. package/src/loginrefresh/main.ts +59 -0
  1178. package/src/loginrefresh/pirefresh.ts +57 -0
  1179. package/src/loginrefresh/refresher.ts +326 -0
  1180. package/src/loginrefresh/supervisor.ts +273 -0
  1181. package/src/loginrefresh/types.ts +35 -0
  1182. package/src/memory/context.ts +385 -0
  1183. package/src/memory/datafence.ts +9 -0
  1184. package/src/memory/delivery.ts +171 -0
  1185. package/src/memory/eventtags.ts +80 -0
  1186. package/src/memory/health.ts +953 -0
  1187. package/src/memory/import.ts +664 -0
  1188. package/src/memory/layout.ts +261 -0
  1189. package/src/memory/legacy-file.ts +104 -0
  1190. package/src/memory/lessons.ts +447 -0
  1191. package/src/memory/ovsession.ts +601 -0
  1192. package/src/memory/relayout.ts +335 -0
  1193. package/src/memory/roundactivity.ts +209 -0
  1194. package/src/memory/selection.ts +989 -0
  1195. package/src/memory/shadowstatus.ts +125 -0
  1196. package/src/memory/store.ts +275 -0
  1197. package/src/memory/textidentity.ts +8 -0
  1198. package/src/metrics.ts +1056 -0
  1199. package/src/missingconfig.ts +7 -0
  1200. package/src/npmverbs.ts +264 -0
  1201. package/src/onboard/choices.ts +222 -0
  1202. package/src/onboard/engine.ts +518 -0
  1203. package/src/onboard/launch.ts +115 -0
  1204. package/src/onboard/steps/appslug.ts +265 -0
  1205. package/src/onboard/steps/bringup.ts +482 -0
  1206. package/src/onboard/steps/compose.ts +459 -0
  1207. package/src/onboard/steps/configdelivery.ts +58 -0
  1208. package/src/onboard/steps/configfill.ts +1352 -0
  1209. package/src/onboard/steps/deploysh.ts +105 -0
  1210. package/src/onboard/steps/embeddings.ts +550 -0
  1211. package/src/onboard/steps/escalation.ts +640 -0
  1212. package/src/onboard/steps/githubapp.ts +1414 -0
  1213. package/src/onboard/steps/golive.ts +577 -0
  1214. package/src/onboard/steps/index.ts +71 -0
  1215. package/src/onboard/steps/ingress.ts +907 -0
  1216. package/src/onboard/steps/inventory.ts +526 -0
  1217. package/src/onboard/steps/llmauth.ts +1095 -0
  1218. package/src/onboard/steps/memory.ts +1290 -0
  1219. package/src/onboard/steps/memorykeys.ts +85 -0
  1220. package/src/onboard/steps/preflight.ts +191 -0
  1221. package/src/onboard/steps/proof.ts +624 -0
  1222. package/src/onboard/steps/ssh.ts +377 -0
  1223. package/src/onboard/steps/topology.ts +337 -0
  1224. package/src/onboard/steps/transport.ts +665 -0
  1225. package/src/onboard/steps/verifyup.ts +448 -0
  1226. package/src/onboard/steps/vmdeploy.ts +442 -0
  1227. package/src/onboard/toml.ts +50 -0
  1228. package/src/opencli.ts +591 -0
  1229. package/src/openpr.ts +568 -0
  1230. package/src/openviking/client.ts +1131 -0
  1231. package/src/openviking/context.ts +263 -0
  1232. package/src/openviking/embedder.ts +516 -0
  1233. package/src/openviking/gapsink.ts +63 -0
  1234. package/src/openviking/health.ts +83 -0
  1235. package/src/openviking/index.ts +119 -0
  1236. package/src/openviking/lessonfind.ts +92 -0
  1237. package/src/openviking/probe.ts +129 -0
  1238. package/src/openviking/proc.ts +110 -0
  1239. package/src/openviking/provisionreport.ts +160 -0
  1240. package/src/openviking/reembed.ts +412 -0
  1241. package/src/openviking/relayout.ts +177 -0
  1242. package/src/openviking/sidecar-main.ts +38 -0
  1243. package/src/openviking/sidecar.ts +106 -0
  1244. package/src/openviking/store.ts +2025 -0
  1245. package/src/openviking/supervisor.ts +474 -0
  1246. package/src/openviking/types.ts +174 -0
  1247. package/src/operator-recovery.ts +237 -0
  1248. package/src/operatormessage.ts +52 -0
  1249. package/src/outcomes.ts +1670 -0
  1250. package/src/piauthcopy.ts +12 -0
  1251. package/src/picli.ts +49 -0
  1252. package/src/picwd.ts +25 -0
  1253. package/src/pilogin.ts +37 -0
  1254. package/src/pingpong.ts +1339 -0
  1255. package/src/posting-journal.ts +549 -0
  1256. package/src/posting-outbox.ts +802 -0
  1257. package/src/posting-receipt.ts +324 -0
  1258. package/src/posting.ts +603 -0
  1259. package/src/postops.ts +832 -0
  1260. package/src/pr-activity.ts +20 -0
  1261. package/src/pr-author.ts +6 -0
  1262. package/src/pr-conversation.ts +432 -0
  1263. package/src/pr-session-lifecycle.ts +631 -0
  1264. package/src/preflight.ts +391 -0
  1265. package/src/priorresolve.ts +343 -0
  1266. package/src/qcrbin.ts +186 -0
  1267. package/src/recheck.ts +1775 -0
  1268. package/src/reconcile.ts +1030 -0
  1269. package/src/recordedfindings.ts +27 -0
  1270. package/src/regression.ts +833 -0
  1271. package/src/replay.ts +816 -0
  1272. package/src/requiredpasses.ts +148 -0
  1273. package/src/resumecheck.ts +246 -0
  1274. package/src/reviewcli.ts +787 -0
  1275. package/src/reviewcontext.ts +35 -0
  1276. package/src/reviewdetach.ts +734 -0
  1277. package/src/reviewhistory.ts +427 -0
  1278. package/src/reviewhome.ts +36 -0
  1279. package/src/reviewmemory.ts +133 -0
  1280. package/src/reviewrendercli.ts +139 -0
  1281. package/src/round-work.ts +42 -0
  1282. package/src/runhost.ts +108 -0
  1283. package/src/scanauth.ts +36 -0
  1284. package/src/scanbox.ts +1081 -0
  1285. package/src/scancli.ts +1296 -0
  1286. package/src/scandriverlog.ts +32 -0
  1287. package/src/session-bridge.ts +139 -0
  1288. package/src/session-command-payload.ts +35 -0
  1289. package/src/session-command-supervisor.ts +1142 -0
  1290. package/src/sessionjsonl.ts +88 -0
  1291. package/src/sessions.ts +659 -0
  1292. package/src/setup/adapters.ts +335 -0
  1293. package/src/setup/baselines.ts +110 -0
  1294. package/src/setup/caserunner.ts +611 -0
  1295. package/src/setup/configcommit.ts +443 -0
  1296. package/src/setup/engine.ts +1001 -0
  1297. package/src/setup/facts.ts +627 -0
  1298. package/src/setup/findings.ts +1264 -0
  1299. package/src/setup/history.ts +443 -0
  1300. package/src/setup/interview.ts +243 -0
  1301. package/src/setup/ledger.ts +324 -0
  1302. package/src/setup/pins.ts +66 -0
  1303. package/src/setup/portrait.ts +635 -0
  1304. package/src/setup/presentation.ts +18 -0
  1305. package/src/setup/profile.ts +508 -0
  1306. package/src/setup/replay.ts +370 -0
  1307. package/src/setup/replaybuild.ts +392 -0
  1308. package/src/setup/replayrun.ts +1096 -0
  1309. package/src/setup/replaysource.ts +198 -0
  1310. package/src/setup/retune.ts +415 -0
  1311. package/src/setup/retunelog.ts +95 -0
  1312. package/src/setup/scan.ts +1033 -0
  1313. package/src/setup/scanbuild.ts +168 -0
  1314. package/src/setup/scanfacts.ts +107 -0
  1315. package/src/setup/scanlease.ts +297 -0
  1316. package/src/setup/scanreport.ts +373 -0
  1317. package/src/setup/scanrun.ts +2268 -0
  1318. package/src/setup/scantree.ts +259 -0
  1319. package/src/setup/stage2.ts +926 -0
  1320. package/src/setup/stage2request.ts +347 -0
  1321. package/src/setup/survey.ts +458 -0
  1322. package/src/setup/surveyrun.ts +758 -0
  1323. package/src/setup/textsniff.ts +95 -0
  1324. package/src/setup/trackersink.ts +433 -0
  1325. package/src/setup/triage.ts +768 -0
  1326. package/src/setup/tuning.ts +766 -0
  1327. package/src/setup/unitcut.ts +523 -0
  1328. package/src/singleflight.ts +86 -0
  1329. package/src/slack/announce.ts +911 -0
  1330. package/src/slack/api.ts +303 -0
  1331. package/src/slack/messages.ts +288 -0
  1332. package/src/slack/origin.ts +599 -0
  1333. package/src/slack/policy.ts +354 -0
  1334. package/src/slack/reply.ts +201 -0
  1335. package/src/slack/router.ts +285 -0
  1336. package/src/slack/socket.ts +384 -0
  1337. package/src/slack/types.ts +108 -0
  1338. package/src/sqlitehint.ts +32 -0
  1339. package/src/ssh-observation.ts +30 -0
  1340. package/src/state.ts +8243 -0
  1341. package/src/statemachine.ts +369 -0
  1342. package/src/stubs.ts +68 -0
  1343. package/src/takeover.ts +634 -0
  1344. package/src/takeoverpi.ts +293 -0
  1345. package/src/terminal-failure.ts +41 -0
  1346. package/src/thread-resolution-refusal.ts +2 -0
  1347. package/src/tmux.ts +153 -0
  1348. package/src/trackerid.ts +41 -0
  1349. package/src/transcriptindex.ts +665 -0
  1350. package/src/tui/actions.ts +484 -0
  1351. package/src/tui/alerts.ts +666 -0
  1352. package/src/tui/app.ts +1979 -0
  1353. package/src/tui/archiveops.ts +192 -0
  1354. package/src/tui/autofile.ts +233 -0
  1355. package/src/tui/board.ts +1432 -0
  1356. package/src/tui/candidates.ts +889 -0
  1357. package/src/tui/cellwidth.ts +148 -0
  1358. package/src/tui/changelog-store.ts +503 -0
  1359. package/src/tui/chord.ts +25 -0
  1360. package/src/tui/clipboard.ts +92 -0
  1361. package/src/tui/cmdbar.ts +296 -0
  1362. package/src/tui/companion.ts +1427 -0
  1363. package/src/tui/companionplan.ts +247 -0
  1364. package/src/tui/companiontab.ts +83 -0
  1365. package/src/tui/confighome.ts +155 -0
  1366. package/src/tui/connect.ts +68 -0
  1367. package/src/tui/deploy-ceremony.ts +104 -0
  1368. package/src/tui/deploy-drain.ts +129 -0
  1369. package/src/tui/deploy-process.ts +265 -0
  1370. package/src/tui/deploytab.ts +1523 -0
  1371. package/src/tui/design.ts +125 -0
  1372. package/src/tui/digesttab.ts +214 -0
  1373. package/src/tui/emptystate.ts +39 -0
  1374. package/src/tui/evidence.ts +999 -0
  1375. package/src/tui/feed.ts +604 -0
  1376. package/src/tui/feedbacktab.ts +1077 -0
  1377. package/src/tui/filters.ts +90 -0
  1378. package/src/tui/ghread.ts +321 -0
  1379. package/src/tui/historypull.ts +81 -0
  1380. package/src/tui/inboxpull.ts +236 -0
  1381. package/src/tui/index.ts +1565 -0
  1382. package/src/tui/issuesink.ts +238 -0
  1383. package/src/tui/jsonfile.ts +42 -0
  1384. package/src/tui/keys.ts +109 -0
  1385. package/src/tui/layout.ts +108 -0
  1386. package/src/tui/learning.ts +627 -0
  1387. package/src/tui/lessonswitch.ts +118 -0
  1388. package/src/tui/livecontrol.ts +228 -0
  1389. package/src/tui/memforward.ts +443 -0
  1390. package/src/tui/memincident.ts +363 -0
  1391. package/src/tui/memory.ts +620 -0
  1392. package/src/tui/memtab.ts +606 -0
  1393. package/src/tui/metricstab.ts +1177 -0
  1394. package/src/tui/model.ts +131 -0
  1395. package/src/tui/mouse.ts +98 -0
  1396. package/src/tui/msg.ts +268 -0
  1397. package/src/tui/nativecompanion.ts +417 -0
  1398. package/src/tui/openurl.ts +21 -0
  1399. package/src/tui/paint.ts +511 -0
  1400. package/src/tui/panes.ts +98 -0
  1401. package/src/tui/pipane.ts +219 -0
  1402. package/src/tui/poll-hooks.ts +12 -0
  1403. package/src/tui/poll.ts +147 -0
  1404. package/src/tui/proposals.ts +838 -0
  1405. package/src/tui/prpause.ts +150 -0
  1406. package/src/tui/reload.ts +436 -0
  1407. package/src/tui/remote.ts +413 -0
  1408. package/src/tui/rollover.ts +969 -0
  1409. package/src/tui/scantab.ts +1290 -0
  1410. package/src/tui/setupentry.ts +93 -0
  1411. package/src/tui/sidebar.ts +749 -0
  1412. package/src/tui/sshtriage.ts +510 -0
  1413. package/src/tui/startup.ts +231 -0
  1414. package/src/tui/tabs.ts +45 -0
  1415. package/src/tui/termmirror.ts +224 -0
  1416. package/src/tui/theme.ts +95 -0
  1417. package/src/tui/ticketstab.ts +310 -0
  1418. package/src/tui/tmuxsocket.ts +129 -0
  1419. package/src/tui/trackertickets.ts +84 -0
  1420. package/src/tui/unwedge.ts +286 -0
  1421. package/src/tui/viewer.ts +575 -0
  1422. package/src/tui/viewerinput.ts +173 -0
  1423. package/src/updatenotice.ts +178 -0
  1424. package/src/usage.ts +1154 -0
  1425. package/src/verdicts.ts +4689 -0
  1426. package/src/version.ts +80 -0
  1427. package/src/wakes.ts +476 -0
  1428. package/src/webhook.ts +942 -0
  1429. package/src/webhookheal.ts +276 -0
  1430. package/src/wire.ts +2153 -0
  1431. package/src/worker-session-host.ts +602 -0
  1432. package/src/workerbreaker.ts +156 -0
  1433. package/src/workercontrol.ts +104 -0
  1434. package/src/workerlease.ts +441 -0
  1435. package/src/workerliveness.ts +103 -0
  1436. package/src/workerlog.ts +79 -0
  1437. package/src/workerruntime.ts +30 -0
  1438. package/src/workersupervisor.ts +1538 -0
  1439. package/src/workerusage.ts +42 -0
  1440. package/src/workerworld.ts +427 -0
  1441. package/src/worktrees.ts +1138 -0
  1442. package/tests/fixtures/regression-cases.json +795 -0
  1443. 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)"