@bongos/core 1.18.0 → 1.18.18

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 (553) hide show
  1. package/.bongos-core.json +1740 -640
  2. package/.claude/skills/builder-box/SKILL.md +6 -0
  3. package/.claude/skills/builder-sequence/SKILL.md +97 -0
  4. package/.claude/skills/builder-ship/SKILL.md +8 -2
  5. package/.claude/skills/builder-start/SKILL.md +11 -0
  6. package/.claude/skills/builder-sync/SKILL.md +41 -0
  7. package/.claude/skills/fix-task/SKILL.md +64 -0
  8. package/.claude/skills/goal-create/SKILL.md +158 -0
  9. package/.claude/skills/goal-review/SKILL.md +3 -1
  10. package/.claude/skills/idea-triage/SKILL.md +14 -8
  11. package/.claude/skills/merge-mode/SKILL.md +2 -2
  12. package/.claude/skills/new-project/SKILL.md +1 -0
  13. package/.claude/skills/planning-session/SKILL.md +45 -18
  14. package/.env.local.example +4 -3
  15. package/README.md +5 -3
  16. package/bin/bongos.js +1 -0
  17. package/clients/bongos-client/README.md +2 -2
  18. package/clients/bongos-client/bongos-client.global.js +82 -18
  19. package/clients/bongos-client/index.cjs +82 -18
  20. package/clients/bongos-client/index.d.ts +131 -29
  21. package/clients/bongos-client/index.mjs +82 -18
  22. package/config/branding.neutral.json +11 -2
  23. package/config/governance.neutral.json +21 -0
  24. package/docs/adr/0043-git-ssh-trust-boundary-and-rank-floor-on-permission-paths.md +24 -0
  25. package/docs/adr/0090-metic-task-authoring.md +1 -1
  26. package/docs/adr/0142-project-context-is-prose-only.md +41 -0
  27. package/docs/adr/0143-onboarding-repo-picker-oauth-scope.md +35 -0
  28. package/docs/adr/0144-devbox-rehome-onto-cloudbongos-plane.md +41 -0
  29. package/docs/adr/0144-federated-single-logout-backchannel.md +42 -0
  30. package/docs/adr/0145-devbox-app-branding-driven-module.md +44 -0
  31. package/docs/adr/0145-free-hosted-project-tier-isolation-and-domain-separation.md +62 -0
  32. package/docs/adr/0146-configurable-reward-mode-cost-plus-only.md +53 -0
  33. package/docs/adr/0147-standalone-live-docs-regen.md +39 -0
  34. package/docs/adr/0148-task-scoped-box-source-access.md +48 -0
  35. package/docs/adr/0149-bongos-dev-local-launcher.md +57 -0
  36. package/docs/adr/0150-box-first-boot-bringup-vendored-instances.md +68 -0
  37. package/docs/adr/0151-governance-permissions-as-atom-ranks-as-roles.md +229 -0
  38. package/docs/adr/0152-landing-gate-served-by-the-core-app.md +92 -0
  39. package/docs/adr/0152-metic-task-abandonment.md +67 -0
  40. package/docs/adr/0153-sequential-chain-runner.md +51 -0
  41. package/docs/adr/0154-goal-owner-criterion-authoring.md +86 -0
  42. package/docs/adr/README.md +15 -2
  43. package/docs/api/openapi.json +2648 -483
  44. package/docs/api-reference.md +73 -13
  45. package/docs/architecture.md +4 -2
  46. package/docs/branding-contract.md +3 -1
  47. package/docs/canonical-permissions.md +21 -8
  48. package/docs/file-map.md +15 -7
  49. package/docs/onboarding/diagrams/01-task-lifecycle.mmd +2 -2
  50. package/docs/onboarding/diagrams/02-rank-ladder.mmd +3 -1
  51. package/docs/onboarding/diagrams/05-versioning.mmd +1 -3
  52. package/docs/onboarding/diagrams/assertions.json +4 -7
  53. package/docs/onboarding/drachmae-and-karma.md +3 -3
  54. package/docs/onboarding/primer.md +17 -16
  55. package/docs/onboarding/slash-commands.md +124 -0
  56. package/docs/recipes/builder-box-lifecycle.md +23 -0
  57. package/docs/recipes/devbox-app-release.md +14 -4
  58. package/docs/recipes/instance-first-run.md +16 -0
  59. package/docs/recipes/local-dev.md +81 -0
  60. package/docs/recipes/ops-gotchas.md +19 -1
  61. package/docs/recipes/overnight-builder.md +1 -1
  62. package/docs/recipes/packaging-the-core.md +13 -0
  63. package/docs/recipes/parallel-goal-waves.md +174 -0
  64. package/docs/recipes/ship-and-regrade.md +2 -2
  65. package/docs/recipes/standalone-live-docs.md +48 -0
  66. package/docs/recipes/upgrading-the-core.md +5 -2
  67. package/docs/recipes/work-on-a-task-from-your-dev-box.md +108 -0
  68. package/migrations/003_pms.sql +6 -7
  69. package/migrations/006_pms_v2.sql +3 -31
  70. package/migrations/039_newcomer_friendly_column.sql +5 -0
  71. package/migrations/102_remove_box_first_provision_approval.sql +4 -0
  72. package/migrations/core_192_github_oauth_tokens.sql +33 -0
  73. package/migrations/core_193_task_needs_rebase.sql +28 -0
  74. package/migrations/core_194_confirmed_backlog_clawback.sql +63 -0
  75. package/migrations/core_195_idea_triage_provenance.sql +57 -0
  76. package/migrations/core_196_session_reward_topup.sql +69 -0
  77. package/modules/autonomy/pollers/routine-timers.js +9 -8
  78. package/modules/builder-settings/wandering-prefs.js +61 -18
  79. package/modules/dev-box/app/README.md +73 -0
  80. package/modules/dev-box/app/assets/tray/mac-alertTemplate.png +0 -0
  81. package/modules/dev-box/app/assets/tray/mac-alertTemplate@2x.png +0 -0
  82. package/modules/dev-box/app/assets/tray/mac-busyTemplate.png +0 -0
  83. package/modules/dev-box/app/assets/tray/mac-busyTemplate@2x.png +0 -0
  84. package/modules/dev-box/app/assets/tray/mac-offTemplate.png +0 -0
  85. package/modules/dev-box/app/assets/tray/mac-offTemplate@2x.png +0 -0
  86. package/modules/dev-box/app/assets/tray/mac-onTemplate.png +0 -0
  87. package/modules/dev-box/app/assets/tray/mac-onTemplate@2x.png +0 -0
  88. package/modules/dev-box/app/assets/tray/win-alert.png +0 -0
  89. package/modules/dev-box/app/assets/tray/win-busy.png +0 -0
  90. package/modules/dev-box/app/assets/tray/win-off.png +0 -0
  91. package/modules/dev-box/app/assets/tray/win-on.png +0 -0
  92. package/modules/dev-box/app/build-resources/dmg-background.png +0 -0
  93. package/modules/dev-box/app/build-resources/dmg-background@2x.png +0 -0
  94. package/modules/dev-box/app/build-resources/icon.png +0 -0
  95. package/modules/dev-box/app/electron-builder.tmpl.yml +95 -0
  96. package/modules/dev-box/app/electron-builder.yml +95 -0
  97. package/modules/dev-box/app/package-lock.json +5291 -0
  98. package/modules/dev-box/app/package.json +30 -0
  99. package/modules/dev-box/app/renderer/app.js +319 -0
  100. package/modules/dev-box/app/renderer/index.html +45 -0
  101. package/modules/dev-box/app/renderer/styles.css +246 -0
  102. package/modules/dev-box/app/renderer/theme.generated.css +16 -0
  103. package/modules/dev-box/app/scripts/apply-dmg-backdrop.sh +82 -0
  104. package/modules/dev-box/app/scripts/dmgbuild-settings.py +39 -0
  105. package/modules/dev-box/app/scripts/gen-brand.js +138 -0
  106. package/modules/dev-box/app/scripts/gen-dmg-backdrop.py +323 -0
  107. package/modules/dev-box/app/scripts/gen-icons.js +286 -0
  108. package/modules/dev-box/app/scripts/vendor-client.js +23 -0
  109. package/modules/dev-box/app/src/box-controller.js +287 -0
  110. package/modules/dev-box/app/src/brand.generated.json +13 -0
  111. package/modules/dev-box/app/src/brand.js +16 -0
  112. package/modules/dev-box/app/src/config.js +19 -0
  113. package/modules/dev-box/app/src/gds-client.js +90 -0
  114. package/modules/dev-box/app/src/main.js +610 -0
  115. package/modules/dev-box/app/src/pairing.js +57 -0
  116. package/modules/dev-box/app/src/preload.js +29 -0
  117. package/modules/dev-box/app/src/session-store.js +58 -0
  118. package/modules/dev-box/app/src/ssh-setup.js +569 -0
  119. package/modules/dev-box/app/src/task-picker.js +70 -0
  120. package/modules/dev-box/app/src/tray.js +99 -0
  121. package/modules/dev-box/app/src/updater.js +55 -0
  122. package/modules/dev-box/app/src/vendor/bongos-client.cjs +698 -0
  123. package/modules/dev-box/app/test/unit.mjs +526 -0
  124. package/modules/dev-box/box-access.js +121 -4
  125. package/modules/dev-box/boxes.js +20 -0
  126. package/modules/dev-box/module.json +1 -1
  127. package/modules/dev-box/routes/box.js +87 -3
  128. package/modules/economy/credits.js +116 -25
  129. package/modules/economy/reward.js +8 -0
  130. package/modules/economy/streak.js +2 -1
  131. package/modules/governance/CLAUDE.md +15 -0
  132. package/modules/governance/catalog.js +419 -0
  133. package/modules/governance/config.js +174 -0
  134. package/modules/governance/db.js +125 -0
  135. package/modules/governance/migrations/governance_001_tables.sql +81 -0
  136. package/modules/governance/migrations/governance_002_seed_and_backfill.sql +103 -0
  137. package/modules/governance/migrations/governance_003_task_abandon_metic.sql +28 -0
  138. package/modules/governance/migrations/governance_004_criterion_create.sql +39 -0
  139. package/modules/governance/migrations/governance_005_resync_builder_rank_roles.sql +56 -0
  140. package/modules/governance/module.json +10 -0
  141. package/modules/governance/protected-surfaces.js +88 -0
  142. package/modules/governance/protected-surfaces.json +80 -0
  143. package/modules/governance/rank-roles.js +83 -0
  144. package/modules/governance/resolver.js +221 -0
  145. package/modules/governance/routes/governance.js +89 -0
  146. package/modules/governance/routes/role-sync.js +49 -0
  147. package/modules/governance/session-scopes.js +127 -0
  148. package/modules/governance/session-scopes.json +38 -0
  149. package/modules/hall-ui/public/atlas.css +117 -0
  150. package/modules/hall-ui/public/atlas.html +3 -114
  151. package/modules/hall-ui/public/brand-apply.js +34 -6
  152. package/modules/hall-ui/public/builders.js +187 -219
  153. package/modules/hall-ui/public/diagrams.html +2 -0
  154. package/modules/hall-ui/public/dom-utils.js +27 -0
  155. package/modules/hall-ui/public/drachmae.html +2 -0
  156. package/modules/hall-ui/public/drachmae.js +54 -1
  157. package/modules/hall-ui/public/gate.css +98 -0
  158. package/modules/hall-ui/public/gate.html +3 -95
  159. package/modules/hall-ui/public/goal-inbox.js +4 -1
  160. package/modules/hall-ui/public/goals-page.js +626 -114
  161. package/modules/hall-ui/public/goals.css +79 -1
  162. package/modules/hall-ui/public/goals.html +38 -11
  163. package/modules/hall-ui/public/hall-widgets.js +62 -52
  164. package/modules/hall-ui/public/harbor.css +123 -0
  165. package/modules/hall-ui/public/harbor.html +3 -120
  166. package/modules/hall-ui/public/index.html +11 -4
  167. package/modules/hall-ui/public/instances-panel.js +4 -1
  168. package/modules/hall-ui/public/modules.css +63 -0
  169. package/modules/hall-ui/public/modules.html +3 -60
  170. package/modules/hall-ui/public/my-projects.html +78 -0
  171. package/modules/hall-ui/public/my-projects.js +142 -0
  172. package/modules/hall-ui/public/new-project-wizard.js +7 -3
  173. package/modules/hall-ui/public/not-ready.html +2 -0
  174. package/modules/hall-ui/public/pair.css +73 -0
  175. package/modules/hall-ui/public/pair.html +3 -70
  176. package/modules/hall-ui/public/palette.js +302 -0
  177. package/modules/hall-ui/public/primer.css +13 -0
  178. package/modules/hall-ui/public/primer.html +2 -0
  179. package/modules/hall-ui/public/profile.html +2 -0
  180. package/modules/hall-ui/public/ranks.html +2 -0
  181. package/modules/hall-ui/public/roadmap.css +179 -0
  182. package/modules/hall-ui/public/roadmap.html +9 -172
  183. package/modules/hall-ui/public/roadmap.js +329 -316
  184. package/modules/hall-ui/public/scouting.html +45 -0
  185. package/modules/hall-ui/public/scouting.js +73 -0
  186. package/modules/hall-ui/public/sessions.css +16 -0
  187. package/modules/hall-ui/public/sessions.html +3 -13
  188. package/modules/hall-ui/public/settings.html +2 -0
  189. package/modules/hall-ui/public/settings.js +4 -4
  190. package/modules/hall-ui/public/shell.js +143 -18
  191. package/modules/hall-ui/public/style.css +231 -39
  192. package/modules/hall-ui/public/task-detail.js +347 -26
  193. package/modules/hall-ui/public/task.html +6 -1
  194. package/modules/hall-ui/public/watch.html +2 -0
  195. package/modules/hall-ui/public/work.html +8 -6
  196. package/modules/hall-ui/public/work.js +1 -1
  197. package/modules/ideas/inbox.js +61 -11
  198. package/modules/ideas/routes/inbox.js +23 -0
  199. package/modules/lifecycle/claim-eligibility.js +72 -0
  200. package/modules/lifecycle/db.js +519 -139
  201. package/modules/lifecycle/done-when.js +18 -3
  202. package/modules/lifecycle/github-push.js +19 -2
  203. package/modules/lifecycle/goal-advisory.js +114 -0
  204. package/modules/lifecycle/goal-authz.js +62 -0
  205. package/modules/lifecycle/lifecycle.js +9 -0
  206. package/modules/lifecycle/publish-reconciler.js +109 -6
  207. package/modules/lifecycle/routes/claims.js +68 -23
  208. package/modules/lifecycle/routes/done-when.js +110 -15
  209. package/modules/lifecycle/routes/goals.js +8 -38
  210. package/modules/lifecycle/routes/tasks.js +38 -6
  211. package/modules/lifecycle/ship-card.js +6 -0
  212. package/modules/onboarding/onboarding-state.js +1 -1
  213. package/modules/platform-identity/CLAUDE.md +15 -0
  214. package/modules/platform-identity/hub-device.js +147 -0
  215. package/modules/platform-identity/hub-keys.js +151 -0
  216. package/modules/platform-identity/migrations/platform_identity_001_tables.sql +114 -0
  217. package/modules/platform-identity/migrations/platform_identity_002_client_origin_index.sql +18 -0
  218. package/modules/platform-identity/migrations/platform_identity_003_project_memberships.sql +42 -0
  219. package/modules/platform-identity/migrations/platform_identity_004_projects.sql +44 -0
  220. package/modules/platform-identity/migrations/platform_identity_005_builder_activity.sql +42 -0
  221. package/modules/platform-identity/module.json +18 -0
  222. package/modules/platform-identity/platform-identity.js +531 -0
  223. package/modules/platform-identity/pollers/code-sweep.js +43 -0
  224. package/modules/platform-identity/routes/my-projects.js +140 -0
  225. package/modules/platform-identity/routes/projects.js +107 -0
  226. package/modules/platform-identity/routes/scouting.js +35 -0
  227. package/modules/platform-identity/routes/sso.js +402 -0
  228. package/modules/platform-identity/tests/platform-identity.mjs +388 -0
  229. package/modules/platform-identity/ui/.gitkeep +0 -0
  230. package/modules/security/routes/reports.js +10 -0
  231. package/modules/sessions/db.js +166 -21
  232. package/modules/sessions/module.json +1 -1
  233. package/modules/sessions/pollers/session-reward-reconcile.js +78 -0
  234. package/modules/sessions/routes/sessions.js +33 -0
  235. package/modules/status-ui/public/explore.html +54 -0
  236. package/modules/status-ui/public/explore.js +67 -0
  237. package/package-lock.json +2 -2
  238. package/package.json +1 -1
  239. package/public-landing/assets/anim/idle/frame_00.webp +0 -0
  240. package/public-landing/assets/anim/idle/frame_01.webp +0 -0
  241. package/public-landing/assets/anim/idle/frame_02.webp +0 -0
  242. package/public-landing/assets/anim/idle/frame_03.webp +0 -0
  243. package/public-landing/assets/anim/idle/frame_04.webp +0 -0
  244. package/public-landing/assets/anim/idle/frame_05.webp +0 -0
  245. package/public-landing/assets/anim/idle/frame_06.webp +0 -0
  246. package/public-landing/assets/anim/idle/frame_07.webp +0 -0
  247. package/public-landing/assets/anim/idle/frame_08.webp +0 -0
  248. package/public-landing/assets/anim/idle/frame_09.webp +0 -0
  249. package/public-landing/assets/anim/idle/frame_10.webp +0 -0
  250. package/public-landing/assets/anim/idle/frame_11.webp +0 -0
  251. package/public-landing/assets/anim/idle/frame_12.webp +0 -0
  252. package/public-landing/assets/anim/idle/frame_13.webp +0 -0
  253. package/public-landing/assets/anim/idle/frame_14.webp +0 -0
  254. package/public-landing/assets/anim/idle/frame_15.webp +0 -0
  255. package/public-landing/assets/anim/idle/frame_16.webp +0 -0
  256. package/public-landing/assets/anim/idle/frame_17.webp +0 -0
  257. package/public-landing/assets/anim/idle/frame_18.webp +0 -0
  258. package/public-landing/assets/anim/idle/frame_19.webp +0 -0
  259. package/public-landing/assets/anim/idle/frame_20.webp +0 -0
  260. package/public-landing/assets/anim/idle/frame_21.webp +0 -0
  261. package/public-landing/assets/anim/idle/frame_22.webp +0 -0
  262. package/public-landing/assets/anim/idle/frame_23.webp +0 -0
  263. package/public-landing/assets/anim/idle/frame_24.webp +0 -0
  264. package/public-landing/assets/anim/idle/frame_25.webp +0 -0
  265. package/public-landing/assets/anim/idle/frame_26.webp +0 -0
  266. package/public-landing/assets/anim/idle/frame_27.webp +0 -0
  267. package/public-landing/assets/anim/idle/frame_28.webp +0 -0
  268. package/public-landing/assets/anim/idle/frame_29.webp +0 -0
  269. package/public-landing/assets/anim/idle/frame_30.webp +0 -0
  270. package/public-landing/assets/anim/idle/frame_31.webp +0 -0
  271. package/public-landing/assets/anim/idle/frame_32.webp +0 -0
  272. package/public-landing/assets/anim/idle/frame_33.webp +0 -0
  273. package/public-landing/assets/anim/idle/frame_34.webp +0 -0
  274. package/public-landing/assets/anim/idle/frame_35.webp +0 -0
  275. package/public-landing/assets/anim/idle/frame_36.webp +0 -0
  276. package/public-landing/assets/anim/idle/frame_37.webp +0 -0
  277. package/public-landing/assets/anim/idle/frame_38.webp +0 -0
  278. package/public-landing/assets/anim/idle/frame_39.webp +0 -0
  279. package/public-landing/assets/anim/idle/frame_40.webp +0 -0
  280. package/public-landing/assets/anim/idle/frame_41.webp +0 -0
  281. package/public-landing/assets/anim/idle/frame_42.webp +0 -0
  282. package/public-landing/assets/anim/idle/frame_43.webp +0 -0
  283. package/public-landing/create.html +252 -62
  284. package/public-landing/gate/README.md +137 -0
  285. package/public-landing/gate/dispmaps/bar.png +0 -0
  286. package/public-landing/gate/dispmaps/flat.png +0 -0
  287. package/public-landing/gate/dispmaps/key.png +0 -0
  288. package/public-landing/gate/index.html +587 -0
  289. package/public-landing/gate/previews/cloud-bongos.jpg +0 -0
  290. package/public-landing/gate/previews/off-the-boats.jpg +0 -0
  291. package/public-landing/gate/space.css +799 -0
  292. package/public-landing/gate/space.js +1826 -0
  293. package/public-landing/gate/sprites/enter.png +0 -0
  294. package/public-landing/gate/sprites/search.png +0 -0
  295. package/public-landing/gate/sprites/terminal-blank.png +0 -0
  296. package/public-landing/gate/sprites/tile.png +0 -0
  297. package/public-landing/world/assets/clouds/cloud-island-large.png +0 -0
  298. package/public-landing/world/assets/clouds/cloud-puff.png +0 -0
  299. package/public-landing/world/assets/creatures/creature-whale-cargo.png +0 -0
  300. package/public-landing/world/assets/creatures/creature-whale-glide.png +0 -0
  301. package/public-landing/world/assets/plates/plate-clouds-near.png +0 -0
  302. package/public-landing/world/assets/plates/scene-establishing.png +0 -0
  303. package/scripts/gds/abandon.js +6 -4
  304. package/scripts/gds/api.js +10 -5
  305. package/scripts/gds/api.ps1 +26 -0
  306. package/scripts/gds/api.sh +6 -0
  307. package/scripts/gds/audit-ships.js +7 -1
  308. package/scripts/gds/backfill-sessions.js +78 -18
  309. package/scripts/gds/backup.js +23 -1
  310. package/scripts/gds/box-connect-lib.js +124 -31
  311. package/scripts/gds/box-sync.js +66 -0
  312. package/scripts/gds/box.js +225 -53
  313. package/scripts/gds/cf-tunnel.js +145 -6
  314. package/scripts/gds/claim.js +34 -3
  315. package/scripts/gds/cli-lib.js +161 -21
  316. package/scripts/gds/code.js +6 -2
  317. package/scripts/gds/deploy-divergence.js +209 -0
  318. package/scripts/gds/dev-lib.js +309 -0
  319. package/scripts/gds/dev.js +322 -0
  320. package/scripts/gds/doctor.js +90 -61
  321. package/scripts/gds/fetch-art-key.js +1 -1
  322. package/scripts/gds/fetch-sound-prefs.js +2 -2
  323. package/scripts/gds/fitness.js +228 -12
  324. package/scripts/gds/gds-db-backup.service +16 -1
  325. package/scripts/gds/gds-db-offsite.service +9 -1
  326. package/scripts/gds/gen-api-client.js +1 -1
  327. package/scripts/gds/gen-api-docs.js +36 -14
  328. package/scripts/gds/gen-diagrams.js +45 -28
  329. package/scripts/gds/gen-file-map.js +2 -1
  330. package/scripts/gds/gen-freshness.js +32 -0
  331. package/scripts/gds/gen-repo-map.js +3 -2
  332. package/scripts/gds/gen-session-index.js +14 -5
  333. package/scripts/gds/hooks/pre-push.js +2 -2
  334. package/scripts/gds/init.js +60 -3
  335. package/scripts/gds/install-git-hooks.js +1 -1
  336. package/scripts/gds/key-card.js +15 -6
  337. package/scripts/gds/key-status.js +1 -1
  338. package/scripts/gds/linkify-refs.js +35 -9
  339. package/scripts/gds/lint-migrations.js +102 -0
  340. package/scripts/gds/login.js +58 -11
  341. package/scripts/gds/mirror-redact.js +5 -0
  342. package/scripts/gds/package-core.js +263 -4
  343. package/scripts/gds/paste-token.js +7 -1
  344. package/scripts/gds/preflight.js +126 -0
  345. package/scripts/gds/provision.js +268 -19
  346. package/scripts/gds/publish-manifest.js +22 -1
  347. package/scripts/gds/reauth.js +7 -3
  348. package/scripts/gds/regen-instance-docs.js +47 -0
  349. package/scripts/gds/run-unit-tests.js +42 -16
  350. package/scripts/gds/seed-bongos-v1-seo-tasks.js +104 -0
  351. package/scripts/gds/seed-bv1-profiles-tasks.js +126 -0
  352. package/scripts/gds/sequence.js +425 -0
  353. package/scripts/gds/session-cost.js +10 -1
  354. package/scripts/gds/setup.js +72 -5
  355. package/scripts/gds/shell.js +5 -3
  356. package/scripts/gds/ship.js +433 -59
  357. package/scripts/gds/smoke-gds.js +139 -8
  358. package/scripts/gds/start.js +171 -22
  359. package/scripts/gds/status.js +1 -1
  360. package/scripts/gds/triage.js +10 -9
  361. package/scripts/gds/upgrade.js +271 -7
  362. package/scripts/migrate.sh +10 -0
  363. package/scripts/status-mirror-sync.js +114 -84
  364. package/src/bongos/api-errors.js +1 -0
  365. package/src/bongos/auth.js +809 -107
  366. package/src/bongos/db-kernel.js +27 -0
  367. package/src/bongos/db.js +105 -0
  368. package/src/bongos/github-repos.js +93 -0
  369. package/src/bongos/module-scope-map.js +27 -1
  370. package/src/bongos/permission-path-check.js +136 -102
  371. package/src/bongos/repo-info.js +47 -11
  372. package/src/bongos/route-rank-check.js +429 -46
  373. package/src/bongos/routes/_helpers.js +12 -1
  374. package/src/bongos/routes/auth.js +312 -13
  375. package/src/bongos/routes/backup.js +58 -2
  376. package/src/bongos/routes/github.js +90 -0
  377. package/src/bongos/routes/instance.js +15 -3
  378. package/src/bongos/routes/me.js +35 -1
  379. package/src/bongos/routes.js +2 -0
  380. package/src/bongos/serve-internal.js +84 -15
  381. package/src/branding.js +49 -2
  382. package/src/instance-config.js +46 -0
  383. package/src/module-api.js +140 -1
  384. package/src/platform-server.js +86 -9
  385. package/tests/abandon_task.mjs +59 -3
  386. package/tests/api_cli_flags.mjs +53 -0
  387. package/tests/auth_page_gate.mjs +13 -0
  388. package/tests/backfill_sessions.mjs +127 -1
  389. package/tests/backup_cli.mjs +54 -0
  390. package/tests/bongos_box_connect.mjs +77 -0
  391. package/tests/box_access.mjs +130 -1
  392. package/tests/box_credential.mjs +125 -0
  393. package/tests/box_host_keys.mjs +24 -0
  394. package/tests/box_infra_resolve.mjs +149 -0
  395. package/tests/box_scope_session.mjs +6 -2
  396. package/tests/box_sync.mjs +55 -0
  397. package/tests/box_task_scope.mjs +99 -0
  398. package/tests/branding.mjs +41 -23
  399. package/tests/cf_ruleset_preflight.mjs +142 -0
  400. package/tests/cf_tunnel.mjs +37 -0
  401. package/tests/claim_gate_rebase.mjs +98 -0
  402. package/tests/clawback_confirmed_backlog.mjs +103 -0
  403. package/tests/cli_lib_formatters.mjs +20 -0
  404. package/tests/confirm_attribution.mjs +30 -46
  405. package/tests/credit_grant.mjs +198 -10
  406. package/tests/criterion_authoring_gate.mjs +272 -0
  407. package/tests/criterion_progress.mjs +33 -1
  408. package/tests/dev_lib.mjs +253 -0
  409. package/tests/devbox_artifact_gate.mjs +75 -0
  410. package/tests/devbox_dmg_backdrop.mjs +137 -0
  411. package/tests/devbox_icons.mjs +304 -0
  412. package/tests/devbox_installer_brand.mjs +232 -0
  413. package/tests/devbox_key_register.mjs +234 -0
  414. package/tests/docs_openapi_route.mjs +110 -0
  415. package/tests/doctor_hooks.mjs +141 -0
  416. package/tests/extract_json.mjs +140 -0
  417. package/tests/fitness.mjs +89 -0
  418. package/tests/gen_freshness.mjs +96 -0
  419. package/tests/github_repos_helpers.mjs +91 -0
  420. package/tests/github_token_store.mjs +121 -0
  421. package/tests/goal_advisory.mjs +251 -0
  422. package/tests/goal_criterion_confirm_ui.mjs +269 -0
  423. package/tests/goal_routes.mjs +59 -4
  424. package/tests/goal_tier.mjs +12 -3
  425. package/tests/governance_catalog.mjs +129 -0
  426. package/tests/governance_claim_eligibility.mjs +262 -0
  427. package/tests/governance_config.mjs +187 -0
  428. package/tests/governance_ownership_scope.mjs +410 -0
  429. package/tests/governance_page_gates.mjs +324 -0
  430. package/tests/governance_parity.mjs +336 -0
  431. package/tests/governance_principals.mjs +232 -0
  432. package/tests/governance_protected_surfaces.mjs +208 -0
  433. package/tests/governance_require_permission.mjs +223 -0
  434. package/tests/governance_resolver.mjs +84 -0
  435. package/tests/governance_role_sync.mjs +231 -0
  436. package/tests/governance_seed.mjs +127 -0
  437. package/tests/governance_session_scope.mjs +434 -0
  438. package/tests/governance_vanilla_boot.mjs +203 -0
  439. package/tests/grade_attribution.mjs +7 -7
  440. package/tests/hall_audit.mjs +223 -0
  441. package/tests/hall_nav.mjs +191 -0
  442. package/tests/hall_palette.mjs +206 -0
  443. package/tests/hall_signout.mjs +124 -0
  444. package/tests/hook_user_agents.mjs +10 -0
  445. package/tests/idea_triage.mjs +162 -0
  446. package/tests/idp_device_exchange.mjs +198 -0
  447. package/tests/idp_federation.mjs +100 -0
  448. package/tests/init.mjs +51 -0
  449. package/tests/instance_config.mjs +65 -0
  450. package/tests/instance_mismatch_guard.mjs +23 -6
  451. package/tests/instance_release_config.mjs +104 -0
  452. package/tests/linkify_refs.mjs +15 -2
  453. package/tests/migrations_forward_safe.mjs +86 -0
  454. package/tests/module_api.mjs +1 -1
  455. package/tests/module_contributions.mjs +55 -0
  456. package/tests/module_loader.mjs +10 -5
  457. package/tests/module_scope_active_claims.mjs +94 -0
  458. package/tests/needs_rebase.mjs +92 -0
  459. package/tests/one_active_claim_per_session.mjs +76 -0
  460. package/tests/override_attribution.mjs +10 -8
  461. package/tests/override_request_ownership.mjs +8 -4
  462. package/tests/package_core.mjs +246 -1
  463. package/tests/parse_cookie_decode.mjs +42 -0
  464. package/tests/pay_on_land.mjs +129 -0
  465. package/tests/platform_boot.mjs +100 -1
  466. package/tests/post_login_landing.mjs +42 -0
  467. package/tests/preflight_checks.mjs +72 -0
  468. package/tests/provision.mjs +289 -7
  469. package/tests/pruned_lockfile_integrity.mjs +79 -0
  470. package/tests/publish_branch_route.mjs +53 -0
  471. package/tests/publish_reconciler.mjs +187 -2
  472. package/tests/repo_info_binding.mjs +248 -0
  473. package/tests/reward_gate.mjs +11 -2
  474. package/tests/reward_mode_cost_plus_only.mjs +116 -0
  475. package/tests/reward_mode_surfacing.mjs +168 -0
  476. package/tests/route_permission_check.mjs +301 -0
  477. package/tests/route_rank_check.mjs +254 -12
  478. package/tests/route_validation.mjs +30 -0
  479. package/tests/sequence.mjs +267 -0
  480. package/tests/session_digest.mjs +44 -0
  481. package/tests/session_index.mjs +18 -6
  482. package/tests/session_records.mjs +39 -1
  483. package/tests/session_reward_on_land.mjs +381 -0
  484. package/tests/session_reward_reconcile.mjs +229 -0
  485. package/tests/ship_api_client_pathspec.mjs +100 -0
  486. package/tests/ship_base_flag.mjs +93 -0
  487. package/tests/ship_ci_deploy.mjs +140 -3
  488. package/tests/ship_upstream_deletion.mjs +57 -0
  489. package/tests/smoke_target.mjs +233 -0
  490. package/tests/start_env_guard.mjs +87 -0
  491. package/tests/start_rebase_warning.mjs +103 -0
  492. package/tests/status_mirror_sync.mjs +122 -0
  493. package/tests/stop_refcheck.mjs +19 -1
  494. package/tests/task_goal_link.mjs +15 -6
  495. package/tests/upgrade.mjs +393 -5
  496. package/tests/web_admission_status.mjs +162 -0
  497. package/tests/write_validation_gaps.mjs +110 -0
  498. package/.gitignore +0 -91
  499. package/docs/adr/0139-platform-history-migration-to-cloudbongos.md +0 -94
  500. package/docs/adr/0140-cloudbongos-standalone-reprovision.md +0 -43
  501. package/scripts/hall-preview/fixtures/access-requests.json +0 -20
  502. package/scripts/hall-preview/fixtures/achievements.json +0 -76
  503. package/scripts/hall-preview/fixtures/analytics__builder.json +0 -251
  504. package/scripts/hall-preview/fixtures/audit-log.json +0 -68
  505. package/scripts/hall-preview/fixtures/blockers__detail.json +0 -12
  506. package/scripts/hall-preview/fixtures/box__me.json +0 -5
  507. package/scripts/hall-preview/fixtures/boxes__cost-ledger.json +0 -69
  508. package/scripts/hall-preview/fixtures/builders__active-claims.json +0 -1
  509. package/scripts/hall-preview/fixtures/builders__directory.json +0 -81
  510. package/scripts/hall-preview/fixtures/builders__roster.json +0 -48
  511. package/scripts/hall-preview/fixtures/goals.json +0 -502
  512. package/scripts/hall-preview/fixtures/goals__25.json +0 -96
  513. package/scripts/hall-preview/fixtures/goals__26.json +0 -67
  514. package/scripts/hall-preview/fixtures/goals__3.json +0 -103
  515. package/scripts/hall-preview/fixtures/goals__35.json +0 -49
  516. package/scripts/hall-preview/fixtures/goals__conflicts.json +0 -154
  517. package/scripts/hall-preview/fixtures/goals__detail.json +0 -96
  518. package/scripts/hall-preview/fixtures/goals__inbox.json +0 -5
  519. package/scripts/hall-preview/fixtures/goals__requests.json +0 -3
  520. package/scripts/hall-preview/fixtures/grades__by-builder.json +0 -32
  521. package/scripts/hall-preview/fixtures/inbox.json +0 -330
  522. package/scripts/hall-preview/fixtures/inbox__detail.json +0 -15
  523. package/scripts/hall-preview/fixtures/instance.json +0 -124
  524. package/scripts/hall-preview/fixtures/leaderboard.json +0 -184
  525. package/scripts/hall-preview/fixtures/me.json +0 -476
  526. package/scripts/hall-preview/fixtures/me__art-key__own.json +0 -6
  527. package/scripts/hall-preview/fixtures/me__event-sound-prefs.json +0 -209
  528. package/scripts/hall-preview/fixtures/me__recent-ships.json +0 -304
  529. package/scripts/hall-preview/fixtures/me__render.json +0 -5
  530. package/scripts/hall-preview/fixtures/me__skill-prefs.json +0 -143
  531. package/scripts/hall-preview/fixtures/me__sound-prefs.json +0 -67
  532. package/scripts/hall-preview/fixtures/me__wandering.json +0 -43
  533. package/scripts/hall-preview/fixtures/override-requests.json +0 -15
  534. package/scripts/hall-preview/fixtures/provisioning__fleet.json +0 -1
  535. package/scripts/hall-preview/fixtures/provisioning__fleet__cost-ledger.json +0 -1
  536. package/scripts/hall-preview/fixtures/provisioning__instances.json +0 -1
  537. package/scripts/hall-preview/fixtures/public__analytics__ecosystem.json +0 -516
  538. package/scripts/hall-preview/fixtures/public__bounty-table.json +0 -37
  539. package/scripts/hall-preview/fixtures/public__cost-summary.json +0 -742
  540. package/scripts/hall-preview/fixtures/public__estimation-drift.json +0 -41
  541. package/scripts/hall-preview/fixtures/public__grades.json +0 -50
  542. package/scripts/hall-preview/fixtures/public__progress.json +0 -124
  543. package/scripts/hall-preview/fixtures/public__recent-shipped.json +0 -426
  544. package/scripts/hall-preview/fixtures/public__repo-health.json +0 -160
  545. package/scripts/hall-preview/fixtures/public__repo-info.json +0 -5
  546. package/scripts/hall-preview/fixtures/security__docs.json +0 -10
  547. package/scripts/hall-preview/fixtures/security__reports.json +0 -31
  548. package/scripts/hall-preview/fixtures/sessions__search.json +0 -9
  549. package/scripts/hall-preview/fixtures/tasks.json +0 -2420
  550. package/scripts/hall-preview/fixtures/tasks__claimable.json +0 -2555
  551. package/scripts/hall-preview/fixtures/tasks__detail.json +0 -111
  552. package/scripts/hall-preview/fixtures/tasks__peer-votes__feed.json +0 -330
  553. package/scripts/hall-preview/fixtures/versions.json +0 -89
@@ -10,10 +10,12 @@
10
10
  // work another session already shipped — instead of leaving it in 'ready' as
11
11
  // claimable noise.
12
12
  //
13
- // Archon-only (the route is requireRank('archon')). Refuses on a shipped task
14
- // (immutable history) and on a task with an active claim (release it first);
15
- // idempotent on an already-abandoned task. The abandon + reason are recorded in
16
- // audit_log by the server's writing middleware, so the ledger stays complete.
13
+ // Metic+ (the route is requireRank('metic', 'archon') as of ADR 0152; Archon-only
14
+ // before that). Refuses on a shipped task (immutable history) and on a task with an
15
+ // active claim (release it first); idempotent on an already-abandoned task. The
16
+ // abandon + reason are recorded in audit_log by the server's writing middleware, so
17
+ // the ledger stays complete. Retired the wrong task? POST /tasks/<id>/promote
18
+ // restores it to backlog at the same rank (db.restoreTask) — no Archon SQL needed.
17
19
 
18
20
  const { cliClient, requireSession, arg } = require('./cli-lib');
19
21
 
@@ -18,17 +18,21 @@
18
18
  // on a 2xx, 1 on any non-2xx or transport error, 2 on bad invocation.
19
19
 
20
20
  const fs = require('node:fs');
21
- const { cliClient, arg, isBoxScope, printBoxScopeGuidanceAndExit } = require('./cli-lib');
21
+ const { cliClient, arg, stripBom, resolveQuiet, isBoxScope, printBoxScopeGuidanceAndExit } = require('./cli-lib');
22
22
 
23
23
 
24
24
  (async () => {
25
25
  const method = (process.argv[2] || '').toUpperCase();
26
26
  const urlPath = process.argv[3];
27
27
  if (!method || !urlPath || !urlPath.startsWith('/')) {
28
- console.error("usage: node scripts/gds/api.js <METHOD> <path> [--body '<json>' | --body-file <file|->]");
28
+ console.error("usage: node scripts/gds/api.js <METHOD> <path> [--body '<json>' | --body-file <file|->] [--quiet]");
29
29
  process.exit(2);
30
30
  }
31
31
 
32
+ // --quiet / GDS_QUIET: suppress the stderr status banner below. Default OFF —
33
+ // existing pipelines that scrape stderr for the status line are unaffected.
34
+ const quiet = resolveQuiet();
35
+
32
36
  // Resolve an optional JSON request body. `--body-file -` reads stdin (the
33
37
  // robust way to pass a body with awkward characters without shell quoting).
34
38
  let body = null;
@@ -36,8 +40,9 @@ const { cliClient, arg, isBoxScope, printBoxScopeGuidanceAndExit } = require('./
36
40
  const bodyInline = arg('--body');
37
41
  try {
38
42
  let raw = null;
39
- if (bodyFile != null) raw = bodyFile === '-' ? fs.readFileSync(0, 'utf8') : fs.readFileSync(bodyFile, 'utf8');
40
- else if (bodyInline != null) raw = bodyInline;
43
+ if (bodyFile != null) {
44
+ raw = stripBom(bodyFile === '-' ? fs.readFileSync(0, 'utf8') : fs.readFileSync(bodyFile, 'utf8'));
45
+ } else if (bodyInline != null) raw = bodyInline;
41
46
  if (raw != null && raw.trim()) body = JSON.parse(raw);
42
47
  } catch (e) {
43
48
  console.error(`api: could not read/parse JSON body: ${e.message}`);
@@ -59,7 +64,7 @@ const { cliClient, arg, isBoxScope, printBoxScopeGuidanceAndExit } = require('./
59
64
  console.error(`api: request failed: ${e.message}`);
60
65
  process.exit(1);
61
66
  }
62
- console.error(`${method} ${urlPath} → ${res.status}`);
67
+ if (!quiet) console.error(`${method} ${urlPath} → ${res.status}`);
63
68
  // task 1501: on a dev box the baked session is box-scoped (ADR 0053) and every
64
69
  // non-bootstrap call (e.g. /me, /inbox) returns 403 box_scope. We pass
65
70
  // allowUnauthenticated above (so a 401 stays a plain error, not the setup
@@ -0,0 +1,26 @@
1
+ # scripts/gds/api.ps1 — PowerShell 5.1-safe wrapper around api.js.
2
+ #
3
+ # PS 5.1 wraps a native command's stderr into NativeCommandError records and sets
4
+ # $? false even on a clean exit (api.js always prints its status banner to
5
+ # stderr), so a plain `node scripts/gds/api.js ... 2>&1` looks like a failure when
6
+ # it isn't. This wrapper discards stderr, strips a leading UTF-8 BOM, and writes
7
+ # only the raw JSON to stdout so callers can `| ConvertFrom-Json` it directly.
8
+ #
9
+ # Usage: scripts/gds/api.ps1 <METHOD> <path> [--body '<json>' | --body-file <file>]
10
+
11
+ $apiJs = Join-Path $PSScriptRoot 'api.js'
12
+ $out = & node $apiJs @args 2>$null
13
+ $exitCode = $LASTEXITCODE
14
+
15
+ $text = $out -join "`n"
16
+ if ($text.Length -gt 0 -and $text[0] -eq [char]0xFEFF) {
17
+ $text = $text.Substring(1)
18
+ }
19
+
20
+ if ($exitCode -ne 0) {
21
+ Write-Output "GDS-ERROR: scripts/gds/api.ps1 exited $exitCode"
22
+ exit $exitCode
23
+ }
24
+
25
+ Write-Output $text
26
+ exit $exitCode
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env bash
2
+ # scripts/gds/api.sh — Git Bash/Linux passthrough wrapper around api.js.
3
+ # MSYS_NO_PATHCONV avoids Git Bash mangling API paths (e.g. /api/gds/inbox) into
4
+ # Windows paths before node ever sees them.
5
+ export MSYS_NO_PATHCONV=1
6
+ exec node "$(dirname "$0")/api.js" "$@"
@@ -102,8 +102,14 @@ function downstreamImpact(sha, files) {
102
102
  }
103
103
 
104
104
  async function fetchShipped(days) {
105
+ // Derive the public read host from branding (statusOrigin → publicOrigin), never a
106
+ // hardcoded founder host (was https://status.amazonprimea.com — task 1002234). The
107
+ // recent-shipped feed is a public endpoint served on the status dashboard host.
108
+ const b = (() => { try { return require('../../src/branding').branding(); } catch (_) { return {}; } })();
109
+ const d = b.domains || {};
110
+ const statusBase = String(d.statusOrigin || d.publicOrigin || '').replace(/\/+$/, '');
105
111
  const r = await fetch(
106
- `https://status.amazonprimea.com/api/gds/public/recent-shipped?days=${days}&limit=500`
112
+ `${statusBase}/api/gds/public/recent-shipped?days=${days}&limit=500`
107
113
  );
108
114
  const data = await r.json();
109
115
  return data.tasks || [];
@@ -44,7 +44,8 @@ const path = require('path');
44
44
  const os = require('os');
45
45
 
46
46
  const { cliClient, loadSession } = require('./cli-lib');
47
- const { buildSessionDigest } = require('./ship');
47
+ const { buildSessionDigest, encodeProjectPath } = require('./ship');
48
+ const { looksLikeGraderTranscript } = require('./session-cost');
48
49
 
49
50
  // ---- args ----------------------------------------------------------------
50
51
  const ARGV = process.argv.slice(2);
@@ -68,7 +69,17 @@ const PROJECTS_DIR = path.join(os.homedir(), '.claude', 'projects');
68
69
  // Project-path families this project has lived under (the cwd is encoded into
69
70
  // the dir name by Claude Code; the project was renamed over its life). A dir is
70
71
  // in-scope if its name contains any of these tokens.
72
+ //
73
+ // The first token is DERIVED from this checkout, not hardcoded: the legacy names
74
+ // below are the owner's old machine, so on every other clone inScope() matched
75
+ // nothing and the script reported "0 transcripts" while exiting 0 — the failure
76
+ // looked like success. encodeProjectPath is the same encoder Claude Code (and
77
+ // resolveTranscriptPath) uses, so the derived token IS the real dir name, and a
78
+ // per-task worktree dir (<encoded-root>--claude-worktrees-task-N) still matches
79
+ // on the prefix. The legacy tokens stay so the owner's older corpus keeps working.
80
+ const REPO_ROOT = path.resolve(__dirname, '..', '..');
71
81
  const PROJECT_TOKENS = [
82
+ encodeProjectPath(REPO_ROOT),
72
83
  'Offtheboats',
73
84
  'Claude-OTB',
74
85
  'LarsCode-Amazonprimea',
@@ -80,11 +91,13 @@ const PROJECT_TOKENS = [
80
91
  // invocation, a claim/release slash command, or the builder-claim skill's
81
92
  // "Claimed #N" confirmation. NOT markdown "#N" links, NOT API list responses
82
93
  // (those carry many incidental task_id fields), NOT "/tasks/N/..." paths.
94
+ // Ids are 7 digits on a live instance (1002471); the original {1,4} matched none
95
+ // of them, so every modern session extracted zero task_ids and could never pay.
83
96
  const TASK_MARKERS = [
84
- /\bship\.js\s+(\d{1,4})\b/g, // node scripts/gds/ship.js 681 [--flags]
85
- /\bbuilder-claim\s+(\d{1,4})\b/g, // /builder-claim 681
86
- /\bbuilder-release\s+(\d{1,4})\b/g, // /builder-release 681
87
- /\bClaimed\s+(?:task\s+)?#(\d{1,4})\b/g, // "Claimed #681" / "Claimed task #681"
97
+ /\bship\.js\s+(\d{1,7})\b/g, // node scripts/gds/ship.js 1002471 [--flags]
98
+ /\bbuilder-claim\s+(\d{1,7})\b/g, // /builder-claim 1002471
99
+ /\bbuilder-release\s+(\d{1,7})\b/g, // /builder-release 1002471
100
+ /\bClaimed\s+(?:task\s+)?#(\d{1,7})\b/g, // "Claimed #1002471" / "Claimed task #1002471"
88
101
  ];
89
102
 
90
103
  // Decode the human-and-tool text of one transcript entry: assistant text +
@@ -119,13 +132,53 @@ function extractTaskIds(entries) {
119
132
  let m;
120
133
  while ((m = re.exec(t)) !== null) {
121
134
  const n = parseInt(m[1], 10);
122
- if (n > 0 && n < 10000) found.add(n);
135
+ // No upper clamp: the old `n < 10000` dropped every 7-digit id a SECOND
136
+ // time, so widening TASK_MARKERS alone would still have extracted nothing.
137
+ //
138
+ // Over-capture cannot pay for work you did not do. The id set here is a
139
+ // REQUEST; the server intersects it with the caller's own claims before
140
+ // storing — modules/sessions/routes/sessions.js calls
141
+ // db.filterTaskIdsClaimedBy(req.builder.id, requestedTaskIds), which is
142
+ // `SELECT DISTINCT task_id FROM claims WHERE builder_id = $1 AND task_id =
143
+ // ANY($2)` (modules/sessions/db.js). builder_id comes from the authenticated
144
+ // caller, never the body, so a forged transcript naming an unclaimed task
145
+ // yields an EMPTY task_ids — and the cost-plus reward is gated on a stored
146
+ // id reaching status='shipped' (ADR 0120), so it can never book.
147
+ // Confirmed empirically on the 2026-07-28 recovery: ids 2, 5, 600, 2615 and
148
+ // 999999 were extracted from quoted docs and every one was dropped server-side.
149
+ if (n > 0) found.add(n);
123
150
  }
124
151
  }
125
152
  }
126
153
  return [...found].sort((a, b) => a - b);
127
154
  }
128
155
 
156
+ // buildUploadPayload — the POST /sessions body for one planned session. Pure +
157
+ // exported so the field set is unit-tested; the money rides on `model_usage`.
158
+ //
159
+ // model_usage MUST be top-level. The route promotes it to its own column and
160
+ // deletes it from the metrics jsonb (modules/sessions/routes/sessions.js), so it
161
+ // reads body.model_usage and never looks inside metrics. Omitting it left every
162
+ // backfilled reward on the task-1676 fallback, which prices the whole token total
163
+ // at the opus cache_read rate — deliberately conservative, i.e. an under-payment.
164
+ // Sent exactly as ship.js sends it so a backfilled row prices identically to one
165
+ // written at ship time.
166
+ function buildUploadPayload(p) {
167
+ const digest = (p && p.digest) || {};
168
+ const metrics = digest.metrics || {};
169
+ return {
170
+ session_id: p.sessionId,
171
+ task_ids: p.taskIds,
172
+ total_tokens: digest.totalTokens,
173
+ duration_seconds: digest.durationSeconds,
174
+ started_at: digest.startedAt,
175
+ ended_at: digest.endedAt,
176
+ detail: digest.detail,
177
+ metrics,
178
+ model_usage: metrics.model_usage || {},
179
+ };
180
+ }
181
+
129
182
  // ---- transcript discovery + parse ----------------------------------------
130
183
  function inScope(dirName) {
131
184
  return PROJECT_TOKENS.some((tok) => dirName.includes(tok));
@@ -218,6 +271,7 @@ async function main() {
218
271
  let skippedPresent = 0;
219
272
  let skippedEmpty = 0;
220
273
  let skippedActive = 0;
274
+ let skippedGrader = 0;
221
275
  for (const file of files) {
222
276
  const sessionId = path.basename(file, '.jsonl');
223
277
  if (!/^[A-Za-z0-9_-]+$/.test(sessionId)) continue; // server format guard
@@ -229,6 +283,18 @@ async function main() {
229
283
  skippedPresent++;
230
284
  continue;
231
285
  }
286
+ // The ship-time subagent grader panel persists each worker as its OWN
287
+ // top-level transcript in this same project dir (they are the MAJORITY of
288
+ // files here — 36 of 52 in one real main dir, 146 of 151 across worktrees).
289
+ // A worker reads the diff, so it quotes the very ship markers above and would
290
+ // be uploaded as a session that shipped a real task — earning a cost-plus
291
+ // reward the live ship path never pays, for spend already captured by the
292
+ // worker cost envelope. Same exclusion, same reason as session-cost.js's
293
+ // ADR 0028 non-double-count invariant; reuse its detector.
294
+ if (looksLikeGraderTranscript(file)) {
295
+ skippedGrader++;
296
+ continue;
297
+ }
232
298
  const entries = parseEntries(file);
233
299
  const digest = buildSessionDigest(entries, { extraSecrets });
234
300
  // Skip sessions with no real LLM work. A 0-token digest is either an aborted
@@ -252,7 +318,8 @@ async function main() {
252
318
  const totalTasks = plan.reduce((n, p) => n + p.taskIds.length, 0);
253
319
  console.log(
254
320
  `\nPlan: ${plan.length} new sessions to upload ` +
255
- `(${skippedPresent} already present, ${skippedEmpty} empty/no-turns skipped` +
321
+ `(${skippedPresent} already present, ${skippedEmpty} empty/no-turns skipped, ` +
322
+ `${skippedGrader} grader-worker transcripts skipped` +
256
323
  `${skippedActive ? `, ${skippedActive} current-session skipped` : ''}).`
257
324
  );
258
325
  console.log(`Task-id associations extracted: ${totalTasks} across ${plan.filter((p) => p.taskIds.length).length} sessions.\n`);
@@ -283,16 +350,7 @@ async function main() {
283
350
  const api = await cliClient();
284
351
  for (let i = 0; i < capped; i++) {
285
352
  const p = plan[i];
286
- const payload = {
287
- session_id: p.sessionId,
288
- task_ids: p.taskIds,
289
- total_tokens: p.digest.totalTokens,
290
- duration_seconds: p.digest.durationSeconds,
291
- started_at: p.digest.startedAt,
292
- ended_at: p.digest.endedAt,
293
- detail: p.digest.detail,
294
- metrics: p.digest.metrics,
295
- };
353
+ const payload = buildUploadPayload(p);
296
354
  let attempts = 0;
297
355
  for (;;) {
298
356
  attempts++;
@@ -341,7 +399,9 @@ function finish(ok, failed, failures) {
341
399
 
342
400
  // Pure helpers are exported for unit testing (matches the ship.js / session-eval
343
401
  // convention). The script only executes when run directly, not when required.
344
- module.exports = { entryText, extractTaskIds, inScope, TASK_MARKERS, PROJECT_TOKENS };
402
+ module.exports = {
403
+ entryText, extractTaskIds, inScope, buildUploadPayload, TASK_MARKERS, PROJECT_TOKENS,
404
+ };
345
405
 
346
406
  if (require.main === module) {
347
407
  main().catch((e) => {
@@ -26,19 +26,41 @@ async function fetchStatus() {
26
26
  return r.data;
27
27
  }
28
28
 
29
+ function fmtAge(hours) {
30
+ if (hours == null) return '(unknown age)';
31
+ if (hours < 48) return `${hours.toFixed(1)}h old`;
32
+ return `${Math.floor(hours / 24)} days old`;
33
+ }
34
+
29
35
  function printStatus(data) {
30
36
  const d = data.latest_dump;
31
37
  if (!d) {
32
38
  console.log(`Backup dir: ${data.backup_dir}`);
33
39
  console.log('No dumps found yet.');
40
+ console.log('');
41
+ console.log(' ⚠️ NO BACKUP EXISTS. There is no restore point for this database.');
34
42
  return;
35
43
  }
36
44
  console.log(`Backup dir: ${data.backup_dir}`);
37
45
  console.log(`Latest dump: ${d.filename}`);
38
- console.log(` Timestamp: ${d.timestamp}`);
46
+ console.log(` Timestamp: ${d.timestamp} (${fmtAge(d.age_hours)})`);
39
47
  console.log(` Size: ${fmtBytes(d.size_bytes)}`);
40
48
  console.log(` Retained: ${d.count} dump(s)`);
41
49
  if (data.trigger_running) console.log(' ⏳ A backup is currently running.');
50
+ // Loud on purpose. A stale backup previously showed as an ordinary timestamp
51
+ // the reader had to date themselves — which is how 13 days of failure went
52
+ // unnoticed on cloudbongos (task 1002479).
53
+ if (d.stale) {
54
+ console.log('');
55
+ console.log(` ⚠️ STALE — the newest dump is ${fmtAge(d.age_hours)}, past the ${d.stale_after_hours}h threshold.`);
56
+ console.log(' The nightly backup is probably failing. Check the unit:');
57
+ console.log(' systemctl status gds-db-backup.service');
58
+ console.log(' journalctl -u gds-db-backup.service -n 50 --no-pager');
59
+ console.log(' A 203/EXEC there means the script lost its exec bit — the unit');
60
+ console.log(' should invoke it via `/usr/bin/env bash <script>`.');
61
+ } else {
62
+ console.log(` ✅ Fresh (threshold ${d.stale_after_hours}h).`);
63
+ }
42
64
  }
43
65
 
44
66
  async function trigger() {
@@ -27,6 +27,7 @@
27
27
  const fs = require('node:fs');
28
28
  const os = require('node:os');
29
29
  const path = require('node:path');
30
+ const crypto = require('node:crypto');
30
31
  const { spawnSync } = require('node:child_process');
31
32
  const ic = require('../../src/instance-config');
32
33
  const { cliClient } = require('./cli-lib');
@@ -90,13 +91,49 @@ function upsertSshConfigText(existing, { hostname, identityFile }) {
90
91
  return `${base}${gap}${block}\n`;
91
92
  }
92
93
 
93
- // Authoritative known_hosts upsert: drop any stale line for THIS host (exact
94
- // field-1 match), keep every other host, then append the box's reported keys.
95
- // hostKeysRaw is the newline-joined `<keytype> <base64>` string from /box/me.
96
- function buildKnownHostsUpsert(existing, hostname, hostKeysRaw) {
94
+ // Does a known_hosts line name any of `names`? Field 1 is either a plain
95
+ // `host` / `host,ip` LIST, or — with HashKnownHosts on (the Debian/Ubuntu default)
96
+ // — the opaque `|1|<b64 salt>|<b64 hash>` form, hash = HMAC-SHA1(salt) of the name.
97
+ //
98
+ // (This duplicates ssh-setup.knownHostsLineMatchesHost on purpose: the Dev Box app
99
+ // under modules/dev-box/app/ is a separately-packaged subproject with ZERO requires
100
+ // into the repo — see modules/dev-box/CLAUDE.md — so the two cannot share a module.)
101
+ function knownHostsLineNamesAny(line, names) {
102
+ const t = String(line || '').trim();
103
+ if (!t || t.startsWith('#')) return false;
104
+ const field = t.split(/\s+/)[0] || '';
105
+ const wanted = names.filter(Boolean).map(String);
106
+ if (!wanted.length) return false;
107
+ if (!field.startsWith('|1|')) {
108
+ const listed = field.split(',');
109
+ return wanted.some((n) => listed.includes(n));
110
+ }
111
+ const [, , salt, hash] = field.split('|');
112
+ if (!salt || !hash) return false;
113
+ try {
114
+ const key = Buffer.from(salt, 'base64');
115
+ return wanted.some((n) => crypto.createHmac('sha1', key).update(n).digest('base64') === hash);
116
+ } catch (_) {
117
+ return false; // unparseable salt → leave the line alone rather than guess
118
+ }
119
+ }
120
+
121
+ // Authoritative known_hosts upsert: drop EVERY stale line for this box, keep every
122
+ // other host, then append the box's reported keys. hostKeysRaw is the newline-joined
123
+ // `<keytype> <base64>` string from /box/me; `ip` is optional and drops an IP-keyed
124
+ // line for the same box.
125
+ //
126
+ // It used to compare field 1 to the hostname with `!==`, which missed the two forms
127
+ // OpenSSH actually writes — `host,1.2.3.4` (what `StrictHostKeyChecking=accept-new`
128
+ // records) and the hashed `|1|salt|hash`. Those lines survived the "replace", so
129
+ // every re-pin ACCUMULATED another entry and the client kept verifying against the
130
+ // first, now-wrong one: "Host denied (verification failed)" on a box that had just
131
+ // re-reported valid keys. Comments and blank lines are preserved.
132
+ function buildKnownHostsUpsert(existing, hostname, hostKeysRaw, ip = null) {
133
+ const names = [hostname, ip];
97
134
  const keep = (existing || '')
98
135
  .split('\n')
99
- .filter((line) => line.trim() && line.split(/\s+/)[0] !== hostname);
136
+ .filter((line) => line.trim() && !knownHostsLineNamesAny(line, names));
100
137
  const added = (hostKeysRaw || '')
101
138
  .split('\n')
102
139
  .map((k) => k.trim())
@@ -106,16 +143,35 @@ function buildKnownHostsUpsert(existing, hostname, hostKeysRaw) {
106
143
  return all.length ? all.join('\n') + '\n' : '';
107
144
  }
108
145
 
146
+ // The on-box re-scope STEP (BV1.R101 / goal 1000051) — no `cd`, the caller
147
+ // positions cwd. Re-runs the box source-fetch so /workspace holds exactly the
148
+ // builder's ACTIVE-claim scope: "claim → open → the scoped folder." box-sync.js
149
+ // re-runs infra/box-source-fetch.sh, which calls GET /box/source-access —
150
+ // claim-driven since R94 — so a fresh claim (or a switch) re-scopes the ONE
151
+ // working folder in place (the owner's one-folder decision). Best-effort (`|| true`):
152
+ // a sync hiccup must never block the connection.
153
+ const RESYNC_STEP = `echo '↪ Syncing your box to your active claim…'; node scripts/gds/box-sync.js || true`;
154
+
109
155
  // The remote command `ssh -t root@host …` runs. Lands the builder in /workspace;
110
- // on the first connection to this box it runs an interactive `claude` first so the
111
- // Trusted-Devices /login enrollment (interactive-only by design — ADR 0038) is
156
+ // with `sync` (BV1.R101) it re-scopes that folder to the active claim first, so a
157
+ // builder who just claimed/switched arrives in a folder holding exactly that task's
158
+ // code. On the first connection to this box it then runs an interactive `claude` so
159
+ // the Trusted-Devices /login enrollment (interactive-only by design — ADR 0038) is
112
160
  // completed once, then drops to a login shell. Silent on every later connect.
113
- function buildRemoteShellCommand({ firstRun = false, workdir = WORKSPACE } = {}) {
161
+ function buildRemoteShellCommand({ firstRun = false, sync = false, workdir = WORKSPACE } = {}) {
114
162
  const cd = `cd ${workdir} 2>/dev/null || true`;
163
+ const resync = sync ? `${RESYNC_STEP}; ` : '';
115
164
  const login = firstRun
116
165
  ? `echo '↪ First time on this box — complete /login in claude once (browser device flow), then exit to a shell.'; claude || true; `
117
166
  : '';
118
- return `${cd}; ${login}exec bash -l`;
167
+ return `${cd}; ${resync}${login}exec bash -l`;
168
+ }
169
+
170
+ // The full one-shot remote command for `bongos code` (VS Code Remote-SSH opens the
171
+ // folder directly rather than running a login shell, so the re-scope rides a
172
+ // separate one-shot ssh before the window opens): cd + the re-scope step, no shell.
173
+ function buildRemoteResyncCommand({ workdir = WORKSPACE } = {}) {
174
+ return `cd ${workdir} 2>/dev/null || true; ${RESYNC_STEP}`;
119
175
  }
120
176
 
121
177
  // VS Code Remote-SSH authority for a host (VS Code resolves it via the ~/.ssh
@@ -226,32 +282,47 @@ function ensureSshKey({ log = console.log } = {}) {
226
282
  return { keyPath, pubKey, created };
227
283
  }
228
284
 
229
- // Register the public key with the GDS if it isn't already. Registers
230
- // unconditionally when we just generated it (fresh laptop); otherwise only when
231
- // the server's key list doesn't already carry this fingerprint — so re-runs don't
232
- // spam the 10-key-per-builder cap. Best-effort: a failure here is surfaced as a
233
- // warning, not fatal (SSH will report a genuine auth problem clearly).
285
+ // PURE (unit tested): should we POST this key? The old inline version FAILED OPEN —
286
+ // when the fingerprint couldn't be computed (ssh-keygen missing from a leaner PATH)
287
+ // or the key list couldn't be fetched, it decided "don't register" and connected
288
+ // anyway, so a builder whose key was never on the server got a bare "Permission
289
+ // denied" with nothing to act on. Uncertainty now means REGISTER.
290
+ //
291
+ // A redundant POST costs nothing: POST /box/ssh-key upserts by fingerprint
292
+ // (modules/dev-box/routes/box.js — an existing fingerprint is explicitly exempt from
293
+ // MAX_SSH_KEYS_PER_BUILDER), so re-registering can neither add a row nor trip the
294
+ // cap. Do not "optimize" the skip back in.
295
+ function decideKeyRegistration({ created, fingerprint, listedKeys, listFailed } = {}) {
296
+ if (created) return { register: true, why: 'created' };
297
+ if (listFailed || !Array.isArray(listedKeys)) return { register: true, why: 'list_unavailable' };
298
+ if (!fingerprint) return { register: true, why: 'fingerprint_unknown' };
299
+ if (listedKeys.some((k) => k && k.fingerprint === fingerprint)) return { register: false, why: 'already_registered' };
300
+ return { register: true, why: 'not_listed' };
301
+ }
302
+
303
+ // Register the public key with the GDS unless the server already demonstrably has
304
+ // it. Best-effort: a failure here is surfaced as a warning, not fatal (SSH will
305
+ // report a genuine auth problem clearly).
234
306
  async function ensureKeyRegistered({ created, keyPath, pubKey, log = console.log } = {}) {
235
307
  const api = await cliClient();
236
- let needed = created;
237
- if (!needed) {
238
- const fp = localKeyFingerprint(`${keyPath}.pub`);
239
- let listed = null;
308
+ let listedKeys = null;
309
+ let listFailed = false;
310
+ if (!created) {
240
311
  try {
241
312
  const res = await api.box.getBoxSshKey();
242
- if (res.ok && res.data && Array.isArray(res.data.keys)) listed = res.data.keys;
313
+ if (res.ok && res.data && Array.isArray(res.data.keys)) listedKeys = res.data.keys;
314
+ else listFailed = true;
243
315
  } catch (_) {
244
- /* tolerate — fall through to the heuristics below */
245
- }
246
- if (fp && Array.isArray(listed)) {
247
- needed = !listed.some((k) => k && k.fingerprint === fp);
248
- } else if (fp == null && Array.isArray(listed) && listed.length > 0) {
249
- needed = false; // can't compare, but keys exist → assume registered
250
- } else {
251
- needed = false; // no info → don't spam; SSH will surface a real auth failure
316
+ listFailed = true;
252
317
  }
253
318
  }
254
- if (!needed) return { registered: false };
319
+ const { register } = decideKeyRegistration({
320
+ created,
321
+ fingerprint: created ? null : localKeyFingerprint(`${keyPath}.pub`),
322
+ listedKeys,
323
+ listFailed,
324
+ });
325
+ if (!register) return { registered: false };
255
326
  try {
256
327
  const label = `bongos ${process.platform}`.slice(0, 80);
257
328
  const res = await api.box.postBoxSshKey({ body: { public_key: pubKey, label } });
@@ -364,7 +435,7 @@ function writeSshAlias({ hostname, keyPath, log = console.log }) {
364
435
  }
365
436
  }
366
437
 
367
- function pinKnownHosts({ hostname, hostKeys, log = console.log }) {
438
+ function pinKnownHosts({ hostname, hostKeys, ip = null, log = console.log }) {
368
439
  if (!hostKeys) return; // box hasn't reported keys yet; accept-new covers first connect
369
440
  const khPath = path.join(sshDir(), 'known_hosts');
370
441
  let existing = '';
@@ -373,7 +444,9 @@ function pinKnownHosts({ hostname, hostKeys, log = console.log }) {
373
444
  } catch (_) {
374
445
  /* none yet */
375
446
  }
376
- const next = buildKnownHostsUpsert(existing, hostname, hostKeys);
447
+ // DNS is repointed on every wake, so an old line keyed by the PREVIOUS box's IP
448
+ // is stale too — pass it so the upsert drops it instead of leaving it to match.
449
+ const next = buildKnownHostsUpsert(existing, hostname, hostKeys, ip);
377
450
  if (next !== existing) {
378
451
  fs.mkdirSync(sshDir(), { recursive: true });
379
452
  fs.writeFileSync(khPath, next, { mode: 0o600 });
@@ -392,11 +465,26 @@ async function prepareConnection({ log = console.log } = {}) {
392
465
  const { keyPath, pubKey, created } = ensureSshKey({ log });
393
466
  await ensureKeyRegistered({ created, keyPath, pubKey, log });
394
467
  const box = await ensureBoxActive({ log });
395
- pinKnownHosts({ hostname: box.hostname, hostKeys: box.host_keys, log });
468
+ pinKnownHosts({ hostname: box.hostname, hostKeys: box.host_keys, ip: box.ip, log });
396
469
  writeSshAlias({ hostname: box.hostname, keyPath, log });
397
470
  return { box, keyPath };
398
471
  }
399
472
 
473
+ // Re-scope /workspace to the builder's active claim over a one-shot ssh, BEFORE
474
+ // `bongos code` opens the VS Code window (BV1.R101). Best-effort: a failure just
475
+ // means the folder holds its last-synced scope — the builder can /builder-sync
476
+ // inside the VS Code terminal — so it never blocks the open. Returns true on a
477
+ // clean run. Mirrors shell.js's ssh args (explicit key + host-key policy).
478
+ function resyncBoxOverSsh({ host, keyPath, log = console.log }) {
479
+ const args = [
480
+ '-i', keyPath, '-o', 'IdentitiesOnly=yes', '-o', 'StrictHostKeyChecking=accept-new',
481
+ `${BOX_USER}@${host}`, buildRemoteResyncCommand(),
482
+ ];
483
+ log(` Scoping ${WORKSPACE} to your active claim…`);
484
+ const r = spawnSync('ssh', args, { stdio: 'inherit' });
485
+ return !!r && !r.error && (r.status === 0 || r.status == null);
486
+ }
487
+
400
488
  // First-run marker (per box hostname). /login enrollment persists on the box, so
401
489
  // we only force the interactive claude handoff the first time we connect to a
402
490
  // given box from this laptop.
@@ -430,7 +518,11 @@ module.exports = {
430
518
  buildSshAliasBlock,
431
519
  upsertSshConfigText,
432
520
  buildKnownHostsUpsert,
521
+ knownHostsLineNamesAny,
522
+ decideKeyRegistration,
433
523
  buildRemoteShellCommand,
524
+ buildRemoteResyncCommand, // BV1.R101 — one-shot re-scope for `bongos code`
525
+ RESYNC_STEP,
434
526
  remoteAuthority,
435
527
  parseEnsure,
436
528
  // toolchain / paths
@@ -446,6 +538,7 @@ module.exports = {
446
538
  writeSshAlias,
447
539
  pinKnownHosts,
448
540
  prepareConnection,
541
+ resyncBoxOverSsh, // BV1.R101 — re-scope /workspace before `bongos code` opens
449
542
  // first-run login handoff
450
543
  loginMarkerPath,
451
544
  isFirstLogin,