@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
@@ -96,6 +96,11 @@ const STARTER_SPARSE_PATHS = [
96
96
  // stamped, and the "Connect to thy dev box" onboarding step never ticks. No
97
97
  // secrets live in infra/ (ADR 0022).
98
98
  'infra',
99
+ // tests/ — so a starter box can RUN the suite for the code it holds (BV1.R99,
100
+ // goal 1000052; same rationale as BASE_SPARSE_PATHS). Onboarding is "bare
101
+ // machine → first ship", and a newcomer proving their first change needs the
102
+ // top-level tests/ (tests/*.mjs + helpers.mjs), which sits beside src, not in it.
103
+ 'tests',
99
104
  ];
100
105
 
101
106
  // States in which a box is (or is becoming) a running droplet that can actually
@@ -117,21 +122,99 @@ function cloneSpecForScope(scope) {
117
122
  return { scope: 'starter', mode: 'sparse', sparsePaths: [...STARTER_SPARSE_PATHS] };
118
123
  }
119
124
 
125
+ // ---------------------------------------------------------------------------
126
+ // PURE: the TASK-scoped surface (ADR 0148 / goal 1000051) — layered on BASE
127
+ // ---------------------------------------------------------------------------
128
+
129
+ // BASE_SPARSE_PATHS — the "pre-existing Bongos instance" every box carries no
130
+ // matter what the builder claimed: enough for Claude to hold methodology context
131
+ // and run the /builder-* CLI, but NONE of the product/game surface. It is the
132
+ // NON-PRODUCT subset of STARTER_SPARSE_PATHS — .devcontainer, .claude, scripts,
133
+ // src/bongos, docs, infra. (Top-level files like CLAUDE.md/server.js + the src/*.js
134
+ // root files come AUTOMATICALLY via cone-mode ancestor inclusion, because src/bongos
135
+ // is listed — see the STARTER note above.) A task's own module code (including the
136
+ // product/game when a game task is claimed) layers on top via sparsePathsForScopeKeys.
137
+ // This is the ADR 0148 move: scope the box to the code the claimed task needs, not
138
+ // the whole repo.
139
+ //
140
+ // BEHAVIOR vs ADR 0052 (sandbox-for-everyone): BASE deliberately drops the game
141
+ // (src/world, src/rooms, public) + art + modules/status-ui — a task-scoped box only
142
+ // carries the live sandbox when the claimed work is scoped to the `game` module. The
143
+ // pre-ADR-0148 "every box runs the sandbox" survives on the RANK fallback path
144
+ // (decideSourceAccess with NO scopeKeys), so a vanilla instance without the lifecycle
145
+ // port is unchanged.
146
+ const BASE_SPARSE_PATHS = [
147
+ '.devcontainer', '.claude', 'scripts', 'src/bongos', 'docs', 'infra',
148
+ // tests/ — so a pulled module's build/test commands actually RUN on the box
149
+ // (BV1.R99, goal 1000052). Module tests live in the TOP-LEVEL tests/ dir
150
+ // (tests/box_*.mjs + the tests/helpers.mjs harness), NOT inside modules/<key>/,
151
+ // so a box holding the module source but not tests/ cannot verify a change.
152
+ // A cone-mode directory; it carries no product runtime, only test code.
153
+ 'tests',
154
+ ];
155
+
156
+ // sparsePathsForScopeKeys — PURE. Given the module KEYS a builder's active claims
157
+ // put in scope (from the lifecycle port `moduleScopeForActiveClaims`, R92) and the
158
+ // doorway `moduleScopeMap` (PASSED IN by the route — box-access imports no core
159
+ // internal), compute the git cone-mode sparse-checkout set: BASE ∪ the DIRECTORY
160
+ // globs of every scoped module.
161
+ //
162
+ // Cone-mode constraint (load-bearing, ADR 0148): `git sparse-checkout set` accepts
163
+ // ONLY directories — a file path aborts the whole set. A module's scope_modules →
164
+ // globs come in two shapes: directory globs (trailing '/', e.g. 'modules/dev-box/')
165
+ // and file-list globs (exact paths, e.g. the kernel key's 'src/bongos/db.js'). We
166
+ // KEEP the directory globs (normalized to no trailing slash, matching the BASE
167
+ // style) and DROP the file globs — every file glob in the map sits under a BASE
168
+ // directory (src/bongos, scripts) or is a top-level/ancestor file cone mode pulls
169
+ // automatically, so base already materializes it. (A future non-cone sparse move
170
+ // could pull individual files; out of scope — cone mode stays.) Deterministic:
171
+ // BASE in declared order, then the module dirs sorted + deduped.
172
+ function sparsePathsForScopeKeys(scopeKeys, moduleScopeMap) {
173
+ const base = [...BASE_SPARSE_PATHS];
174
+ const seen = new Set(base);
175
+ const extra = [];
176
+ for (const key of Array.isArray(scopeKeys) ? scopeKeys : []) {
177
+ const entry = moduleScopeMap && typeof moduleScopeMap.scopeForModule === 'function'
178
+ ? moduleScopeMap.scopeForModule(key)
179
+ : null;
180
+ if (!entry || !Array.isArray(entry.globs)) continue;
181
+ for (const glob of entry.globs) {
182
+ if (typeof glob !== 'string' || !glob.endsWith('/')) continue; // dirs only (cone mode)
183
+ const dir = glob.replace(/\/+$/, '');
184
+ if (!seen.has(dir)) { seen.add(dir); extra.push(dir); }
185
+ }
186
+ }
187
+ extra.sort();
188
+ return [...base, ...extra];
189
+ }
190
+
120
191
  // ---------------------------------------------------------------------------
121
192
  // PURE: the per-fetch access decision (consumed by GET /box/source-access)
122
193
  // ---------------------------------------------------------------------------
123
194
 
124
195
  // Decide what source access a builder gets RIGHT NOW. The route reads the live
125
196
  // rank+status from the DB and passes them in; this stays pure + unit-tested so the
126
- // route and any future caller enforce the SAME rule. The scope is derived from the
127
- // LIVE rank (not the box's stored scope), so a just-graduated builder gets 'full'
128
- // on their very next fetch — before the slower box reconcile flips the stored column.
197
+ // route and any future caller enforce the SAME rule.
198
+ //
199
+ // Rank still governs ALLOW/DENY + the provision floor (an inactive/below-floor
200
+ // builder is still 403, unchanged). What rank NO LONGER decides — once a claim
201
+ // drives it — is the module BREADTH (ADR 0148 / goal 1000051). The optional
202
+ // `scopeKeys` (from the lifecycle port `moduleScopeForActiveClaims`, R92, resolved
203
+ // + passed by the route in R94) selects the surface, three ways:
204
+ // - scopeKeys is a NON-EMPTY array → TASK scope: BASE ∪ the claims' module dirs
205
+ // (via sparsePathsForScopeKeys + the passed-in `moduleScopeMap`).
206
+ // - scopeKeys is an EMPTY array → BASE-only (a live box with no active claim /
207
+ // no scoped goal — the pre-existing Bongos instance, nothing extra).
208
+ // - scopeKeys is ABSENT (undefined) → the pre-ADR-0148 RANK fallback (full for
209
+ // Metic+, else the starter sparse surface). This is the path a vanilla instance
210
+ // with no lifecycle port takes, and it preserves the old behavior EXACTLY — so
211
+ // shipping R93 before R94 wires the route changes nothing in production.
129
212
  //
130
213
  // Returns one of:
131
214
  // { allowed:false, reason:'BUILDER_INACTIVE' }
132
215
  // { allowed:false, reason:'INSUFFICIENT_RANK', required, actual }
133
216
  // { allowed:true, scope, spec }
134
- function decideSourceAccess({ rank, status, floor = DEFAULT_PROVISION_FLOOR } = {}) {
217
+ function decideSourceAccess({ rank, status, floor = DEFAULT_PROVISION_FLOOR, scopeKeys, moduleScopeMap } = {}) {
135
218
  if (status && status !== 'active') {
136
219
  return { allowed: false, reason: 'BUILDER_INACTIVE' };
137
220
  }
@@ -143,10 +226,41 @@ function decideSourceAccess({ rank, status, floor = DEFAULT_PROVISION_FLOOR } =
143
226
  actual: rank ?? null,
144
227
  };
145
228
  }
229
+ // Claim-driven task scope (present → the box asks for exactly its work's code).
230
+ if (Array.isArray(scopeKeys)) {
231
+ const sparsePaths = sparsePathsForScopeKeys(scopeKeys, moduleScopeMap);
232
+ const scope = scopeKeys.length > 0 ? 'task' : 'base';
233
+ return { allowed: true, scope, spec: { scope, mode: 'sparse', sparsePaths } };
234
+ }
235
+ // No claim signal (no lifecycle port on this instance): the pre-ADR-0148 rank fallback.
146
236
  const scope = boxScopeForRank(rank);
147
237
  return { allowed: true, scope, spec: cloneSpecForScope(scope) };
148
238
  }
149
239
 
240
+ // scopeMetadata — PURE. The credential-FREE projection of a source-access
241
+ // decision, for the read-only GET /box/scope companion (BV1.R100 / goal 1000051):
242
+ // the laptop app (and R104's proof) needs to KNOW what a box holds — which module
243
+ // dirs, which sparse set — WITHOUT being issued the pull credential. It is computed
244
+ // from the SAME decideSourceAccess result the real fetch uses, so the answer can
245
+ // never drift from the actual checkout.
246
+ //
247
+ // `modules` reports the resolved module KEYS a task-scope pulls (the raw scopeKeys
248
+ // from moduleScopeForActiveClaims, deduped + sorted for a stable display) — or null
249
+ // when the decision came from the RANK fallback (scopeKeys absent), where breadth is
250
+ // rank-driven, not module-driven, so there is no per-module list to name.
251
+ function scopeMetadata({ decision, scopeKeys } = {}) {
252
+ if (!decision || !decision.allowed) return null;
253
+ const modules = Array.isArray(scopeKeys)
254
+ ? [...new Set(scopeKeys.filter((k) => typeof k === 'string'))].sort()
255
+ : null;
256
+ return {
257
+ scope: decision.scope,
258
+ mode: decision.spec ? decision.spec.mode : null,
259
+ modules,
260
+ sparse_paths: decision.spec ? decision.spec.sparsePaths : null,
261
+ };
262
+ }
263
+
150
264
  // ---------------------------------------------------------------------------
151
265
  // PURE: the clawback / reconcile decision (consumed by `box.js reconcile`)
152
266
  // ---------------------------------------------------------------------------
@@ -262,9 +376,12 @@ async function flagBoxReconcileForBuilder(db, builderId, { rank, status, floor =
262
376
  module.exports = {
263
377
  // pure — surface catalog
264
378
  STARTER_SPARSE_PATHS,
379
+ BASE_SPARSE_PATHS, // ADR 0148 / goal 1000051 — the non-product base a task-scoped box layers on
265
380
  SOURCE_HOLDING_STATES,
266
381
  boxCanHoldSource,
267
382
  cloneSpecForScope,
383
+ sparsePathsForScopeKeys, // ADR 0148 — BASE ∪ the claims' module dirs (cone-mode)
384
+ scopeMetadata, // BV1.R100 — credential-free projection for GET /box/scope
268
385
  // pure — decisions
269
386
  decideSourceAccess,
270
387
  reconcileSourceAccess,
@@ -783,6 +783,24 @@ function computeCodeStaleness({ state, provisionedAt, codeReportedAt, codeCommit
783
783
  return { stale: false, reason: 'current' };
784
784
  }
785
785
 
786
+ // PURE: are the box's REPORTED SSH host keys older than the droplet now running?
787
+ // A rebuild mints fresh host keys, and until the new box re-reports (~10 min) the
788
+ // row can still carry its predecessor's identity — pinning that is the "Host denied
789
+ // (verification failed)" lockout. A report that predates `active_since` therefore
790
+ // describes a droplet that no longer exists; so does a missing timestamp on either
791
+ // side. Callers must fall back to a live keyscan rather than pin a dead identity.
792
+ function hostKeysAreStale({ hostKeysAt, activeSince } = {}) {
793
+ const toMs = (x) => {
794
+ if (x == null) return null;
795
+ const t = x instanceof Date ? x.getTime() : new Date(x).getTime();
796
+ return Number.isFinite(t) ? t : null;
797
+ };
798
+ const reported = toMs(hostKeysAt);
799
+ const activated = toMs(activeSince);
800
+ if (reported == null || activated == null) return true;
801
+ return reported < activated;
802
+ }
803
+
786
804
  module.exports = {
787
805
  // pure — rank
788
806
  RANK_ORDER,
@@ -812,6 +830,8 @@ module.exports = {
812
830
  computeCodeStaleness,
813
831
  CODE_REPORT_GRACE_MS,
814
832
  CODE_STALE_LAG_MS,
833
+ // pure — host-key staleness (task 1002509)
834
+ hostKeysAreStale,
815
835
  // pure — droplet⇄row drift (task 1289)
816
836
  DROPLET_HOLDING_STATES,
817
837
  loginFromDroplet,
@@ -2,7 +2,7 @@
2
2
  "key": "dev-box",
3
3
  "title": "Dev boxes",
4
4
  "description": "Per-builder cloud dev environments (DigitalOcean + Cloudflare tunnels), sandbox preview, server-mediated branch publish.",
5
- "coreVersion": "^1.2.0",
5
+ "coreVersion": "^1.12.0",
6
6
  "default": false,
7
7
  "contributes": {
8
8
  "routes": ["box"],
@@ -152,6 +152,10 @@ function publicBox(row) {
152
152
  hostname: row.hostname,
153
153
  last_activity_at: row.last_activity_at,
154
154
  provisioned_at: row.provisioned_at,
155
+ // When the CURRENT droplet came up. provisioned_at is preserved across rebuilds,
156
+ // so this is the only timestamp a client can compare host_keys_at against to
157
+ // tell a fresh identity report from a dead box's (boxes.hostKeysAreStale).
158
+ active_since: row.active_since || null,
155
159
  parked_at: row.parked_at,
156
160
  compute_cost_usd: Number(row.compute_cost_usd || 0),
157
161
  claude_active: row.claude_active ?? false,
@@ -429,7 +433,26 @@ module.exports = function buildBoxRouter() {
429
433
  const builder = await gdsDb.getBuilderById(req.builder.id);
430
434
  const rank = builder ? builder.rank : null;
431
435
  const status = builder ? builder.status : 'inactive';
432
- const decision = boxAccess.decideSourceAccess({ rank, status });
436
+
437
+ // R94 (ADR 0148 §4 / goal 1000051): scope the box to the CODE THE BUILDER'S
438
+ // CLAIMED TASK NEEDS. Resolve the lifecycle port (OPTIONAL — resolveOptional
439
+ // returns null on a vanilla instance without it) and read the set-union of the
440
+ // builder's active claims' module scope. scopeKeys drives the sparse BREADTH:
441
+ // - array present → TASK scope (BASE ∪ the claims' module dirs; empty → BASE-only)
442
+ // - undefined → decideSourceAccess falls back to the RANK scope (the
443
+ // pre-ADR-0148 behavior, so a port-less instance is unchanged).
444
+ // NEVER fail the source fetch on a scope-read hiccup — degrade to rank scope.
445
+ const lifecycle = api.resolveOptional('lifecycle');
446
+ let scopeKeys;
447
+ if (lifecycle && typeof lifecycle.moduleScopeForActiveClaims === 'function') {
448
+ try {
449
+ scopeKeys = await lifecycle.moduleScopeForActiveClaims(req.builder.id);
450
+ } catch (scopeErr) {
451
+ console.error('[gds] source-access scope read (non-fatal, → rank scope)', scopeErr && scopeErr.message ? scopeErr.message : scopeErr);
452
+ scopeKeys = undefined;
453
+ }
454
+ }
455
+ const decision = boxAccess.decideSourceAccess({ rank, status, scopeKeys, moduleScopeMap: api.moduleScopeMap });
433
456
  const box = await boxes.getBoxByBuilderId(pool, req.builder.id);
434
457
  const auditDeny = (reason, extra) =>
435
458
  box && boxAccess.recordSourceEvent(pool, {
@@ -452,7 +475,16 @@ module.exports = function buildBoxRouter() {
452
475
  }
453
476
 
454
477
  // Mint the scope-appropriate credential (operator-supplied; may be unconfigured).
455
- const cred = boxCredential.defaultProvider.issue(decision.scope, { nowMs: Date.now() });
478
+ // ADR 0148 C4 leaves the TOKEN MODEL untouched: the credential (which repo +
479
+ // which token) stays RANK-based, exactly as before task-scoping. decision.scope
480
+ // may now be 'task'/'base' (the sparse BREADTH the box ASKS for), but the
481
+ // credential must still be the rank one — issue() only special-cases 'full', so
482
+ // passing 'task'/'base' would hand even a Metic+ the STARTER repo/token, which on
483
+ // a separate-starter-repo instance can't reach the task's module. Task-scope is
484
+ // the CLIENT-SIDE sparse ask; a hard per-claim credential ceiling is the deferred
485
+ // hardening (ADR 0148 Consequences).
486
+ const credScope = boxes.boxScopeForRank(rank); // 'starter' | 'full' — unchanged posture
487
+ const cred = boxCredential.defaultProvider.issue(credScope, { nowMs: Date.now() });
456
488
 
457
489
  // Audit the grant — scope + configured flag only, NEVER the token. Only a
458
490
  // REAL (configured) grant is logged: an unconfigured fetch issued no
@@ -465,7 +497,7 @@ module.exports = function buildBoxRouter() {
465
497
  if (box && cred.configured) {
466
498
  await boxAccess.recordSourceEvent(pool, {
467
499
  box, builderId: req.builder.id, event: 'source_grant',
468
- detail: `scope=${decision.scope} mode=${decision.spec.mode}`,
500
+ detail: `scope=${decision.scope} cred=${credScope} mode=${decision.spec.mode}`,
469
501
  actor: 'api',
470
502
  }).catch(() => {});
471
503
  }
@@ -490,6 +522,58 @@ module.exports = function buildBoxRouter() {
490
522
  }
491
523
  });
492
524
 
525
+ // GET /box/scope — the credential-FREE companion to /box/source-access
526
+ // (BV1.R100 / goal 1000051). Answers "what code does my box hold RIGHT NOW?" —
527
+ // the resolved sparse set + the module keys of the builder's active claims —
528
+ // WITHOUT issuing the pull credential. The laptop app reads it to confirm
529
+ // "your box now has <module> for task N" after a pick+claim; R104's proof reads
530
+ // it to VERIFY the scope is minimal (excludes other modules). Same live rank+
531
+ // status + lifecycle-port scope logic as source-access, so the answer can't
532
+ // drift from the actual checkout — it just omits the secret.
533
+ //
534
+ // Deliberately NOT gated on a live box (boxCanHoldSource): the SCOPE is metadata,
535
+ // so a builder can ask "what WOULD my box hold for my current claims" the instant
536
+ // they claim, before the box has even synced. The ALLOW/DENY still comes from the
537
+ // live rank+status (a demoted/inactive builder gets the same 403 as source-access).
538
+ // allowBoxScope so it works both from the laptop app (full session) and on the box
539
+ // (box-scoped session) — safe here precisely because no credential is returned.
540
+ router.get('/box/scope', auth.allowBoxScope, auth.requireBuilder, async (req, res) => {
541
+ try {
542
+ // ONE consistent rank+status snapshot (the /box/source-access TOCTOU note).
543
+ const builder = await gdsDb.getBuilderById(req.builder.id);
544
+ const rank = builder ? builder.rank : null;
545
+ const status = builder ? builder.status : 'inactive';
546
+
547
+ // Resolve the claim-driven module scope exactly as source-access does
548
+ // (optional lifecycle port; degrade to rank scope on any hiccup).
549
+ const lifecycle = api.resolveOptional('lifecycle');
550
+ let scopeKeys;
551
+ if (lifecycle && typeof lifecycle.moduleScopeForActiveClaims === 'function') {
552
+ try {
553
+ scopeKeys = await lifecycle.moduleScopeForActiveClaims(req.builder.id);
554
+ } catch (scopeErr) {
555
+ console.error('[gds] /box/scope scope read (non-fatal, → rank scope)', scopeErr && scopeErr.message ? scopeErr.message : scopeErr);
556
+ scopeKeys = undefined;
557
+ }
558
+ }
559
+ const decision = boxAccess.decideSourceAccess({ rank, status, scopeKeys, moduleScopeMap: api.moduleScopeMap });
560
+ if (!decision.allowed) {
561
+ return res.fail('SOURCE_ACCESS_REVOKED', 403, {
562
+ reason: decision.reason,
563
+ required: decision.required ?? null,
564
+ actual: decision.actual ?? null,
565
+ });
566
+ }
567
+ const meta = boxAccess.scopeMetadata({ decision, scopeKeys });
568
+ // task_scope true only when a claim actually drove the breadth (scopeKeys is
569
+ // an array). false → the rank fallback (no active claim / no lifecycle port).
570
+ return res.json({ ...meta, task_scope: Array.isArray(scopeKeys) });
571
+ } catch (err) {
572
+ console.error('[gds] GET /box/scope', err);
573
+ res.fail('box_scope_failed', { status: 500, message: 'internal error' });
574
+ }
575
+ });
576
+
493
577
  // POST /box/heartbeat — the box reports it is alive, resetting the idle clock.
494
578
  // rank: any authenticated builder (own resource write). Optional body:
495
579
  // { claude_active: bool } — when provided, records whether a `claude` process
@@ -135,14 +135,45 @@ const IDEA_BONUS_FRACTION = 0.25; // 25% slice to the idea's original capturer
135
135
  // is `branding.currency.label`.
136
136
  const REWARD_MARGIN_PCT = 20; // cost-plus margin, percent — DEFAULT (task 1005)
137
137
 
138
+ // Reward MODE — which reward streams an instance pays (task 1002270, ADR 0146).
139
+ // The two streams (ADR 0054 "two drachma conventions, one ledger") are:
140
+ // (1) the per-task author ESTIMATE — credits_reward × kind multiplier (ADR 0023),
141
+ // a unitless bounty set when the task is authored; and
142
+ // (2) the COST-PLUS session reward — round(true_cost_usd × 1.20), 1 drachma = $1
143
+ // (ADR 0054), grounded in the session's real API spend.
144
+ // mode picks which of the two an instance actually PAYS:
145
+ // 'cost-plus-and-estimate' (DEFAULT) — pay both, the historical behavior. Every
146
+ // existing instance keeps paying exactly as before on upgrade.
147
+ // 'cost-plus-only' — the cost-plus session reward is the SOLE equity reward; the
148
+ // per-task estimate PAYOUT is withheld (and its idea-promotion slice, being a
149
+ // fraction of it, cascades to 0). credits_reward still rides the task row for
150
+ // display + the no-zero-reward claim gate (ADR 0096) — only the payout is
151
+ // suppressed, so a credit means one dollar of cost-plus value and nothing
152
+ // else. Cloud Bongos's choice (owner decision, 2026-07-16).
153
+ // Unknown/garbage mode → treated as the default (pay both): a config typo must
154
+ // never silently zero out every builder's per-task reward.
155
+ const REWARD_MODE_DEFAULT = 'cost-plus-and-estimate';
156
+ const REWARD_MODE_COST_PLUS_ONLY = 'cost-plus-only';
157
+
158
+ // Pure policy resolver — maps a raw branding.reward object to the effective reward
159
+ // policy. Exported so a test can assert the mapping without touching the memoized
160
+ // branding() cache (mirrors computeSessionTokenReward's pure/impure split). The
161
+ // derived `perTaskCredit` boolean is what the per-task credit writer gates on.
162
+ function rewardPolicy(rawReward) {
163
+ const r = rawReward || {};
164
+ const marginPct = Number.isFinite(Number(r.marginPct)) ? Number(r.marginPct) : REWARD_MARGIN_PCT;
165
+ const usdPeg = Number.isFinite(Number(r.usdPeg)) && Number(r.usdPeg) > 0 ? Number(r.usdPeg) : 1;
166
+ const mode = r.mode === REWARD_MODE_COST_PLUS_ONLY ? REWARD_MODE_COST_PLUS_ONLY : REWARD_MODE_DEFAULT;
167
+ const perTaskCredit = mode !== REWARD_MODE_COST_PLUS_ONLY;
168
+ return { marginPct, usdPeg, mode, perTaskCredit };
169
+ }
170
+
138
171
  // Per-instance reward policy, resolved over the code defaults. Best-effort: a
139
172
  // branding hiccup falls back to the defaults so reward accrual never crashes.
140
173
  function rewardConfig() {
141
174
  let r = {};
142
175
  try { r = (branding().reward) || {}; } catch { r = {}; }
143
- const marginPct = Number.isFinite(Number(r.marginPct)) ? Number(r.marginPct) : REWARD_MARGIN_PCT;
144
- const usdPeg = Number.isFinite(Number(r.usdPeg)) && Number(r.usdPeg) > 0 ? Number(r.usdPeg) : 1;
145
- return { marginPct, usdPeg };
176
+ return rewardPolicy(r);
146
177
  }
147
178
 
148
179
  // Pure helper — exported for tests. Returns { trueCostUsd, marginPct, drachmae,
@@ -182,18 +213,38 @@ function applyKindMultiplier(rawCredits, kind) {
182
213
  }
183
214
 
184
215
  // Write the primary task-ship credit_log row, applying the kind multiplier.
185
- // Caller must already have verified that no prior 'task.confirmed' row exists
186
- // for this task. Returns { creditsAwarded, multiplier, raw } where
216
+ // Caller must already have verified that no prior row with the SAME `reason`
217
+ // exists for this task. Returns { creditsAwarded, multiplier, raw } where
187
218
  // creditsAwarded is the multiplier-adjusted integer that landed.
188
- async function insertMultipliedConfirmCredit(client, builderId, taskId, rawCredits, kind) {
219
+ //
220
+ // `reason` is a parameter (default 'task.confirmed' for back-compat) so the
221
+ // pay-on-land move (ADR 0120) can book the per-task credit at the SHIPPED
222
+ // transition with reason='task.shipped' — the credit only exists once the code
223
+ // is on `main`. The idempotency guard the caller runs must key on the SAME
224
+ // reason it passes here.
225
+ //
226
+ // `config` (default: the resolved per-instance rewardConfig()) carries the reward
227
+ // MODE (ADR 0146). Injectable so a test can drive the cost-plus-only gate without
228
+ // touching the memoized branding() cache; the sole live caller (lifecycle
229
+ // awardLandingCredit) omits it and gets the resolved instance policy per call.
230
+ async function insertMultipliedConfirmCredit(client, builderId, taskId, rawCredits, kind, reason = 'task.confirmed', config = rewardConfig()) {
189
231
  const result = applyKindMultiplier(rawCredits, kind);
232
+ // Reward-mode gate (ADR 0146): in 'cost-plus-only' mode this instance pays no
233
+ // per-task estimate credit — the cost-plus session reward is the sole equity
234
+ // reward. Return the same zero-shape the effective<=0 path returns (so the
235
+ // caller's idea-promotion slice, a fraction of creditsAwarded, cascades to 0)
236
+ // but write NO credit_log row. The task's credits_reward still exists for
237
+ // display + the claim gate; only the payout is withheld.
238
+ if (config && config.perTaskCredit === false) {
239
+ return { creditsAwarded: 0, multiplier: result.multiplier, raw: result.raw, skipped: REWARD_MODE_COST_PLUS_ONLY };
240
+ }
190
241
  if (result.effective <= 0) {
191
242
  return { creditsAwarded: 0, multiplier: result.multiplier, raw: result.raw };
192
243
  }
193
244
  await client.query(
194
245
  `INSERT INTO credit_log (builder_id, task_id, delta, reason, description)
195
- VALUES ($1, $2, $3, 'task.confirmed', $4)`,
196
- [builderId, taskId, result.effective, result.description]
246
+ VALUES ($1, $2, $3, $4, $5)`,
247
+ [builderId, taskId, result.effective, reason, result.description]
197
248
  );
198
249
  return {
199
250
  creditsAwarded: result.effective,
@@ -277,9 +328,17 @@ async function maybeAwardIdeaPromotionBonus(client, taskId, shipperBuilderId, mu
277
328
  // empty/unpriceable model_usage → $0 reward; ADR 0097) still pays
278
329
  // the real cost-plus amount. This is NOT a fabricated dollar value
279
330
  // — it prices REAL token counts via the same locked PRICES table.
280
- // Returns { drachmae, trueCostUsd, awarded, unknownModels, pricedFromTotalTokens }.
281
- // awarded=false means a prior reward row for this session already existed
282
- // (re-upload / re-ship) — a true no-op, no second credit, no double payout.
331
+ // Returns { drachmae, trueCostUsd, awarded, unknownModels, pricedFromTotalTokens,
332
+ // settled? }. `drachmae` is the amount BOOKED BY THIS CALL, which for a session that
333
+ // has already been paid something is the INCREMENT, not the session's whole value.
334
+ //
335
+ // The reward tops up (task 1002474). A session's cost keeps growing after its first
336
+ // ship, and multi-ship sessions are normal — one claim at a time, but ship -> new
337
+ // worktree -> claim again. So each landed ship books the difference between what the
338
+ // whole session is now worth and what it has already been paid. awarded=false means
339
+ // nothing was owed this time; `settled` says why — 'up_to_date' (already paid in
340
+ // full, the ordinary idempotent case) or 'legacy' (a row predating reward_basis,
341
+ // deliberately left for a signed-off backfill rather than paid out on deploy).
283
342
  async function awardSessionTokenReward(client, builderId, sessionId, modelUsage, totalTokens = 0) {
284
343
  const { true_cost_usd: pricedCostUsd, unknown_models: unknownModels } =
285
344
  priceModelUsage(modelUsage);
@@ -306,29 +365,58 @@ async function awardSessionTokenReward(client, builderId, sessionId, modelUsage,
306
365
  return { drachmae: 0, trueCostUsd, awarded: false, unknownModels, pricedFromTotalTokens };
307
366
  }
308
367
 
309
- // Fast-path idempotency guard (the partial-unique index is the hard backstop
310
- // against a race). Keyed on the literal reason + session_id.
311
- const { rows: existing } = await client.query(
312
- `SELECT 1 FROM credit_log
313
- WHERE reason = 'session.token_reward' AND session_id = $1 LIMIT 1`,
368
+ // Watermark accounting. `drachmae` is what the WHOLE session is worth right now;
369
+ // book only the part not yet paid. Before this, any existing row made the whole
370
+ // call a no-op, so a session that shipped a second task was paid only for the cost
371
+ // it had at its FIRST ship — measured at ~65% of a three-ship session unrewarded.
372
+ //
373
+ // `legacy` counts rows booked before reward_basis existed (they default to 0).
374
+ // Topping those up would be an uncontrolled retroactive payout across every
375
+ // historical session, so we deliberately leave them settled; reconstructing what
376
+ // they are owed is a separate, owner-signed-off backfill.
377
+ const { rows: prior } = await client.query(
378
+ `SELECT COALESCE(SUM(delta), 0)::int AS paid,
379
+ COUNT(*) FILTER (WHERE reward_basis = 0)::int AS legacy
380
+ FROM credit_log
381
+ WHERE reason = 'session.token_reward' AND session_id = $1`,
314
382
  [sessionId]
315
383
  );
316
- if (existing.length > 0) {
317
- return { drachmae: 0, trueCostUsd, awarded: false, unknownModels, pricedFromTotalTokens };
384
+ const paid = Number(prior[0] && prior[0].paid) || 0;
385
+ const legacy = Number(prior[0] && prior[0].legacy) || 0;
386
+ if (legacy > 0) {
387
+ return {
388
+ drachmae: 0, trueCostUsd, awarded: false, unknownModels, pricedFromTotalTokens,
389
+ settled: 'legacy',
390
+ };
391
+ }
392
+
393
+ // Owed − paid. Zero when nothing changed (the idempotent case) and negative when a
394
+ // smaller/stale digest arrives late — never claw back, never write a negative row.
395
+ const owed = drachmae - paid;
396
+ if (owed <= 0) {
397
+ return {
398
+ drachmae: 0, trueCostUsd, awarded: false, unknownModels, pricedFromTotalTokens,
399
+ settled: 'up_to_date',
400
+ };
318
401
  }
319
402
 
403
+ const rowDescription = paid > 0
404
+ ? `${description} — top-up: +${owed} on ${paid} already paid`
405
+ : description;
406
+
320
407
  // Append-only ledger row. task_id = NULL (session-level, not task-level).
321
- // ON CONFLICT DO NOTHING on the partial-unique index closes the race window:
322
- // if a parallel upload inserted between our SELECT and here, we no-op cleanly.
408
+ // ON CONFLICT DO NOTHING still closes the race window, now keyed on
409
+ // (session_id, reward_basis): a parallel upload at the SAME cumulative basis
410
+ // collides and no-ops, while a genuinely larger basis books its own increment.
323
411
  const { rows } = await client.query(
324
- `INSERT INTO credit_log (builder_id, task_id, delta, reason, description, session_id)
325
- VALUES ($1, NULL, $2, 'session.token_reward', $3, $4)
326
- ON CONFLICT (session_id) WHERE reason = 'session.token_reward' DO NOTHING
412
+ `INSERT INTO credit_log (builder_id, task_id, delta, reason, description, session_id, reward_basis)
413
+ VALUES ($1, NULL, $2, 'session.token_reward', $3, $4, $5)
414
+ ON CONFLICT (session_id, reward_basis) WHERE reason = 'session.token_reward' DO NOTHING
327
415
  RETURNING id`,
328
- [builderId, drachmae, description, sessionId]
416
+ [builderId, owed, rowDescription, sessionId, drachmae]
329
417
  );
330
418
  const awarded = rows.length > 0;
331
- return { drachmae: awarded ? drachmae : 0, trueCostUsd, awarded, unknownModels, pricedFromTotalTokens };
419
+ return { drachmae: awarded ? owed : 0, trueCostUsd, awarded, unknownModels, pricedFromTotalTokens };
332
420
  }
333
421
 
334
422
  // net-negative ship bonus — V3.R93 (#354). Simplification reward.
@@ -425,6 +513,9 @@ module.exports = {
425
513
  KIND_MULTIPLIERS,
426
514
  IDEA_BONUS_FRACTION,
427
515
  REWARD_MARGIN_PCT,
516
+ REWARD_MODE_DEFAULT,
517
+ REWARD_MODE_COST_PLUS_ONLY,
518
+ rewardPolicy,
428
519
  rewardConfig,
429
520
  computeSessionTokenReward,
430
521
  applyKindMultiplier,
@@ -37,6 +37,14 @@ module.exports = {
37
37
  // pure helpers + constants (display / tests)
38
38
  applyKindMultiplier: (...a) => credits.applyKindMultiplier(...a),
39
39
  computeSessionTokenReward: (...a) => credits.computeSessionTokenReward(...a),
40
+ // The resolved per-instance reward POLICY (ADR 0146). Exposed on the port so
41
+ // display surfaces can ASK which streams this instance pays instead of
42
+ // re-deriving `mode !== 'cost-plus-only'` for themselves — the knob lives
43
+ // inside economy, and that is the whole point of the ADR. `rewardConfig()`
44
+ // resolves branding; `rewardPolicy(raw)` is the pure mapping for callers that
45
+ // already hold the raw object.
46
+ rewardConfig: (...a) => credits.rewardConfig(...a),
47
+ rewardPolicy: (...a) => credits.rewardPolicy(...a),
40
48
  get KIND_MULTIPLIERS() { return credits.KIND_MULTIPLIERS; },
41
49
  get IDEA_BONUS_FRACTION() { return credits.IDEA_BONUS_FRACTION; },
42
50
  get REWARD_MARGIN_PCT() { return credits.REWARD_MARGIN_PCT; },
@@ -7,7 +7,8 @@
7
7
  // shipped >=1 qualifying task.
8
8
  // - A "qualifying ship" requires:
9
9
  // * claim.outcome = 'shipped' (builder declared done)
10
- // * task.status IN ('confirmed','shipped') (verification passed; credits landed)
10
+ // * task.status IN ('confirmed','shipped') (verification passed; credits land
11
+ // at 'shipped' — ADR 0120 pays on land)
11
12
  // * task.est_minutes >= 15 (anti-gaming floor)
12
13
  // NOTE: cleanup/refactor tasks DO now count (V3.R93 #354). The original
13
14
  // PMS-V2 rule excluded kind='cleanup', which mildly penalized the exact
@@ -0,0 +1,15 @@
1
+ # @bongos/core — installed platform dependency (not your project's CLAUDE.md)
2
+
3
+ This file is a placeholder shipped inside the **@bongos/core** package. The core's own
4
+ internal agent notes are intentionally omitted from the published artifact so they cannot
5
+ load into your instance's context.
6
+
7
+ **It does not govern this repository.** Your own root `CLAUDE.md`, your `.claude/`
8
+ directory, and the live Bongos database are the source of truth for how work happens here
9
+ and who may do what.
10
+
11
+ You are seeing this only because an agent opened a file under `node_modules/@bongos/core/`.
12
+ Nothing in here needs to be read to use the platform — run `bongos help` for commands.
13
+
14
+ _To work ON the platform core itself, use the standalone @bongos/core source repository,
15
+ not this vendored copy._