@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
@@ -15,8 +15,64 @@ const api = require('../../../src/module-api');
15
15
  const auth = api;
16
16
  const doneWhen = require('../done-when');
17
17
  const db = require('../db');
18
+ // The goal authority PREDICATE (ADR 0154) — the same owner|manager test routes/goals.js
19
+ // uses for visibility / invites / roles, so "who may manage this goal" has
20
+ // one definition. A module sibling, like ../db.
21
+ const { isGoalOwnerOrManager } = require('../goal-authz');
18
22
  const { validateOrRespond, parseId, asyncHandler, LIMITS } = api;
19
23
 
24
+ // authorizeCriterionCreate — the ORDERED, FAIL-CLOSED decision for a non-Archon
25
+ // POST /versions/:id/done-when (ADR 0154). PURE + exported so every rejection is
26
+ // pinned by a DB-free unit test (the authorizeGoalTaskCreate precedent in
27
+ // routes/goals.js). The route resolves goal/version first — their absence is a
28
+ // 404/400 there — so this gate only decides AUTHORITY, in this order:
29
+ // archon → allow · goal named? · owner|manager? (MASKED) · goal open? · version open?
30
+ // Authority sits ahead of both state checks on purpose — see the no-leak note below.
31
+ // An Archon short-circuits: version-level (goal-less) criteria and any goal in any
32
+ // state stay theirs. Returns { ok:true }, { ok:false, status, body }, or
33
+ // { ok:false, maskAsBadGoalId:true } (the route renders the identical 400).
34
+ //
35
+ // "Version open" = `planning` or `building`. NOT just `building`: a planning session
36
+ // scopes a version at status='planning', which is the main moment criteria get
37
+ // written. `shipped` is closed (the same `version_closed` refusal
38
+ // authorizeGoalTaskCreate uses), and `frozen` means the scope is deliberately
39
+ // locked — for a SCOPE write that is a refusal, even though task authoring allows it.
40
+ const CRITERION_OPEN_VERSION_STATUSES = ['planning', 'building'];
41
+
42
+ function authorizeCriterionCreate({ goal, version, actorId, actorRank, actorMembership }) {
43
+ if (actorRank === 'archon') return { ok: true };
44
+ if (!goal) {
45
+ return { ok: false, status: 403, body: {
46
+ error: 'goal_id_required',
47
+ message: 'Name the goal this criterion belongs to — a version-level criterion is Archon-only (ADR 0154).',
48
+ } };
49
+ }
50
+ // NO-LEAK ORDERING — authority BEFORE state, and the refusal is MASKED.
51
+ // Before ADR 0154 this route was Archon-only, and an Archon can already read
52
+ // every goal, so no probe existed. Now a Metic may call it, and distinguishable
53
+ // refusals (400 "no such goal" vs 403 "not yours" vs 409 "not open") would turn
54
+ // it into an oracle for which goal ids exist on a version — including PRIVATE
55
+ // goals (ADR 0112). So a caller who is not the owner/manager gets byte-identical
56
+ // `bad_goal_id` to a caller naming a goal that does not exist. Only someone who
57
+ // already holds the goal learns anything further about it.
58
+ if (!isGoalOwnerOrManager({ goal, actorId, actorRank, actorMembership })) {
59
+ return { ok: false, maskAsBadGoalId: true };
60
+ }
61
+ if (goal.status !== 'open') {
62
+ return { ok: false, status: 409, body: {
63
+ error: 'goal_not_open', status: goal.status,
64
+ message: 'Only an open goal can be given new criteria.',
65
+ } };
66
+ }
67
+ if (!version || !CRITERION_OPEN_VERSION_STATUSES.includes(version.status)) {
68
+ return { ok: false, status: 409, body: {
69
+ error: 'version_closed', status: version ? version.status : null,
70
+ message: 'Only an Archon may add a criterion to a shipped or frozen version.',
71
+ } };
72
+ }
73
+ return { ok: true };
74
+ }
75
+
20
76
  module.exports = function buildDoneWhenRouter() {
21
77
  const router = express.Router();
22
78
 
@@ -35,16 +91,32 @@ module.exports = function buildDoneWhenRouter() {
35
91
  res.json({ version_id: versionId, criteria, goals });
36
92
  }, { errorCode: 'list_failed' }));
37
93
 
38
- // Auth (Archon): create a done-when criterion at RUNTIME. ADR 0105 moved
39
- // scope-truth seeds (versions/goals/criteria) out of the normal migration path,
40
- // so a brand-new criterion on a RUNNING instance can no longer be added by a
41
- // plain migration (migrations/instance/ applies only under the owner opt-in
42
- // GDS_APPLY_INSTANCE_SEEDS=1, a DR step). This is the vector a planning session
43
- // uses to add a criterion to a live version WITHOUT a droplet migrate. Archon-
44
- // only — done_when_criteria is scope-shaping (mirrors the criterion-proposal
45
- // ratify gate). Pinned in route-rank-check.MODULE_EXPECTED_RANKS.
46
- // rank: archon — scope-truth write (a new criterion on a version).
47
- router.post('/versions/:id/done-when', auth.requireBuilder, auth.requireRank('archon'), asyncHandler('POST /versions/:id/done-when', async (req, res) => {
94
+ // Auth (Metic+, then the goal's own authority): create a done-when criterion at
95
+ // RUNTIME. ADR 0105 moved scope-truth seeds (versions/goals/criteria) out of the
96
+ // normal migration path, so a brand-new criterion on a RUNNING instance can no
97
+ // longer be added by a plain migration (migrations/instance/ applies only under
98
+ // the owner opt-in GDS_APPLY_INSTANCE_SEEDS=1, a DR step). This is the vector a
99
+ // planning session uses to add a criterion to a live version WITHOUT a droplet
100
+ // migrate.
101
+ //
102
+ // ADR 0154 — authority over a GOAL's criteria follows GOAL OWNERSHIP, not global
103
+ // rank. A Metic who may create the goal (POST /goals) and author the tasks under
104
+ // it (POST /tasks, ADR 0090) may now write the criteria those tasks gate — but
105
+ // only inside a goal they own or manage. Deliberately NOT a flat re-rank:
106
+ // - An unsatisfied criterion permanently blocks version close (version-close.js
107
+ // counts satisfied === total per VERSION, regardless of goal) and goal
108
+ // auto-achieve (db.achieveGoalIfComplete). There is NO delete route, so it
109
+ // cannot be undone via the API — the same backward ratchet as unsatisfy,
110
+ // which stays Archon.
111
+ // - So a non-Archon must name a goal_id (version-level criteria stay Archon),
112
+ // must be that goal's OWNER or MANAGER (isGoalOwnerOrManager — the same
113
+ // predicate behind visibility/invite/role management), and the goal must be
114
+ // open on a planning/building version. That bounds the blast radius to a
115
+ // workspace they already hold. An unauthorized caller is masked behind
116
+ // bad_goal_id so the route is not an existence oracle (ADR 0112).
117
+ // Pinned in route-rank-check.MODULE_EXPECTED_RANKS.
118
+ // rank: metic+archon — criterion create, walled by goal ownership (ADR 0154).
119
+ router.post('/versions/:id/done-when', auth.requireBuilder, auth.requireRank('metic', 'archon'), asyncHandler('POST /versions/:id/done-when', async (req, res) => {
48
120
  const versionId = String(req.params.id || '').trim();
49
121
  if (!versionId) return res.fail('version_id_required', 400);
50
122
  if (validateOrRespond(req, res, {
@@ -55,12 +127,29 @@ module.exports = function buildDoneWhenRouter() {
55
127
  })) return;
56
128
  const version = await db.getVersion(versionId);
57
129
  if (!version) return res.fail('version_not_found', { status: 404, message: `No version '${versionId}'.` });
130
+ // The ONE bad_goal_id refusal, used both for a genuinely absent/foreign goal
131
+ // and for the masked unauthorized case below — same code, same message, same
132
+ // envelope, so the two are indistinguishable on the wire.
133
+ const badGoalId = () => res.fail('bad_goal_id', { status: 400, message: `goal_id ${req.body.goal_id} does not exist on version '${versionId}'.` });
58
134
  // A goal_id, if given, must exist AND belong to this version.
135
+ let goal = null;
59
136
  if (req.body.goal_id != null) {
60
- const goal = await db.getGoal(req.body.goal_id);
61
- if (!goal || String(goal.version_id) !== String(versionId)) {
62
- return res.fail('bad_goal_id', { status: 400, message: `goal_id ${req.body.goal_id} does not exist on version '${versionId}'.` });
63
- }
137
+ goal = await db.getGoal(req.body.goal_id);
138
+ if (!goal || String(goal.version_id) !== String(versionId)) return badGoalId();
139
+ }
140
+ // ADR 0154: the authority wall. Checked AFTER the goal's version linkage so a
141
+ // bad ref is still the clearer 400. Membership is only read when a goal was
142
+ // named and the caller isn't an Archon — a primary-key hit on
143
+ // goal_members (goal_id, builder_id) (migration 160), on a low-frequency route.
144
+ const actorMembership = (goal && req.builder.rank !== 'archon')
145
+ ? await db.getGoalMember({ goalId: goal.id, builderId: req.builder.id })
146
+ : null;
147
+ const decision = authorizeCriterionCreate({
148
+ goal, version, actorId: req.builder.id, actorRank: req.builder.rank, actorMembership,
149
+ });
150
+ if (!decision.ok) {
151
+ if (decision.maskAsBadGoalId) return badGoalId();
152
+ return res.status(decision.status).json(decision.body);
64
153
  }
65
154
  try {
66
155
  const criterion = await db.createCriterion({
@@ -98,7 +187,8 @@ module.exports = function buildDoneWhenRouter() {
98
187
  // criterion belongs to a goal and was its LAST unsatisfied one, the goal auto-
99
188
  // flips open→achieved (ADR 0086 §6 / BV1.R63). Re-ranked Archon→Metic+ so the
100
189
  // planner — not only the Archon — closes the criterion/goal loop; the route is
101
- // pinned in route-rank-check.EXPECTED_RANKS so a further downgrade is caught.
190
+ // pinned in route-rank-check.MODULE_EXPECTED_RANKS.lifecycle so a further
191
+ // downgrade is caught.
102
192
  // rank: metic+archon — criterion confirmation + goal auto-achieve (the closing loop).
103
193
  router.post('/done-when/:criterionId/satisfy', auth.requireBuilder, auth.requireRank('metic', 'archon'), asyncHandler('POST /done-when/:criterionId/satisfy', async (req, res) => {
104
194
  if (validateOrRespond(req, res, {
@@ -134,3 +224,8 @@ module.exports = function buildDoneWhenRouter() {
134
224
 
135
225
  return router;
136
226
  };
227
+
228
+ // ADR 0154: the pure ordered gate for POST /versions/:id/done-when — exported for
229
+ // tests/criterion_authoring_gate.mjs (DB-free), the authorizeGoalTaskCreate
230
+ // precedent in routes/goals.js.
231
+ module.exports.authorizeCriterionCreate = authorizeCriterionCreate;
@@ -38,6 +38,11 @@ const { isModuleKey, scopeIncludesProtected, moduleKeys } = api.moduleScopeMap;
38
38
  const goalScopeCheck = api.goalScopeCheck;
39
39
  // The pure conflict detector (task 1736) — a module sibling, like ../db.
40
40
  const goalConflicts = require('../goal-conflicts');
41
+ // The goal authority model (task 1002538 / ADR 0154) — moved to a module sibling
42
+ // so done-when.js can share it verbatim. authorizeGoalManagement is re-exported at
43
+ // the bottom of this file so the existing DB-free unit tests keep importing it from
44
+ // here; isGoalOwner is used internally only and is exported from ../goal-authz.
45
+ const { isGoalOwner, authorizeGoalManagement } = require('../goal-authz');
41
46
 
42
47
  const VALID_GOAL_STATUSES = ['open', 'achieved', 'archived'];
43
48
  const VALID_MEMBER_ROLES = ['lead', 'member'];
@@ -122,18 +127,9 @@ function authorizeGoalTaskCreate({ goal, version, isMember, builderRank, touches
122
127
  return { ok: true };
123
128
  }
124
129
 
125
- // The goal AUTHORITY MODEL (task 1735). Two layers, deliberately different kinds:
126
- // - OWNER — goals.created_by, the fixed authority. Not a role row: it is set at
127
- // create time (the creator) and moves only via the explicit transfer route.
128
- // The owner holds every goal power.
129
- // - MANAGER — the goal_members.role 'lead' value (the hall renders it
130
- // "manager"). Owner-equivalent EXCEPT: a manager cannot DEMOTE a fellow
131
- // manager (owner-only), and cannot transfer ownership.
132
- // A goal with created_by NULL (the migration-160 system defaults) has no owner;
133
- // Archons act as one — the archon arm of both gates below.
134
- function isGoalOwner(goal, builderId) {
135
- return goal.created_by != null && String(goal.created_by) === String(builderId);
136
- }
130
+ // The goal AUTHORITY MODEL (owner vs manager) now lives in ../goal-authz.js —
131
+ // isGoalOwner + authorizeGoalManagement are imported at the top of this file and
132
+ // re-exported at the bottom (task 1002538 / ADR 0154).
137
133
 
138
134
  // authorizeMemberRoleChange — the ORDERED, FAIL-CLOSED authorization decision for
139
135
  // PATCH /goals/:id/members/:builderId (task 1735). Today the only role-assignment
@@ -201,32 +197,6 @@ function authorizeMemberRoleChange({ goal, actorId, actorRank, actorMembership,
201
197
  return { ok: true };
202
198
  }
203
199
 
204
- // authorizeGoalManagement — the shared ORDERED, FAIL-CLOSED prefix for actions
205
- // a goal's own authority performs (task 1736: conflict resolution; the same
206
- // three steps open authorizeMemberRoleChange above): goal exists → goal open →
207
- // actor is owner|manager|Archon. Returns { ok:true } or { ok:false, status,
208
- // body }. PURE + exported, same precedent as its siblings.
209
- function authorizeGoalManagement({ goal, actorId, actorRank, actorMembership }) {
210
- if (!goal) {
211
- return { ok: false, status: 404, body: { error: 'goal_not_found' } };
212
- }
213
- if (goal.status !== 'open') {
214
- return { ok: false, status: 409, body: {
215
- error: 'goal_not_open', status: goal.status,
216
- message: 'Only an open goal can be managed.',
217
- } };
218
- }
219
- const actorIsOwner = isGoalOwner(goal, actorId);
220
- const actorIsManager = !!actorMembership && actorMembership.role === 'lead';
221
- if (actorRank !== 'archon' && !actorIsOwner && !actorIsManager) {
222
- return { ok: false, status: 403, body: {
223
- error: 'not_owner_or_manager',
224
- message: 'Only this goal\'s owner or a manager (or an Archon) may do this.',
225
- } };
226
- }
227
- return { ok: true };
228
- }
229
-
230
200
  // authorizeOwnershipTransfer — the ORDERED, FAIL-CLOSED decision for
231
201
  // POST /goals/:id/transfer (task 1735): the owner hands the goal to another
232
202
  // builder (the old owner keeps their manager row). Ownership is creator-
@@ -8,6 +8,8 @@ const auth = api;
8
8
  const db = require('../db');
9
9
  const hierarchy = require('../hierarchy');
10
10
  const { suggestCriteria } = require('../criterion-suggest');
11
+ // The orphan-task advisory (task 1002539) — pure, fail-open; see goal-advisory.js.
12
+ const { buildGoalAdvisory } = require('../goal-advisory');
11
13
  // grading carved to modules/grading/ (BV1.R73 / ADR 0091 §4): the lifecycle no
12
14
  // longer imports the grader — it resolves the `grade` kernel PORT at grade time
13
15
  // (seams.resolveOptional('grade'), in POST /tasks/:id/grade below).
@@ -79,7 +81,7 @@ function detectsMigration(...texts) {
79
81
  // Same deliberate-locality call as the MIGRATION_KEYWORD_RE mirror above.
80
82
 
81
83
  // VOTABLE_TASK_STATUSES — a task is votable once it's verified work the project
82
- // keeps (confirmed = past the grader, credits landed; or shipped = deployed);
84
+ // keeps (confirmed = past the grader, not yet paid; or shipped = deployed + paid);
83
85
  // backlog/ready/completed are not. Imported above from ../task-classifier (D1).
84
86
 
85
87
  // #853 (ADR 0041): temporary, default-OFF global grader bypass. When the env
@@ -216,6 +218,14 @@ module.exports = function buildTasksRouter() {
216
218
  }
217
219
  }
218
220
  const tasks = await db.listTasks({ versionId: version, status, kind, discipline, goalId });
221
+ // ?include=deps (hall redesign task 1002439) — enrich each task with its
222
+ // dependency summary (`blocked_by[]` with per-target satisfied state, and a
223
+ // `blocks` count) so the Goals rollup + Tasks dependency view can paint
224
+ // ready/blocked without an N+1 of /tasks/:id fetches. Opt-in so the default
225
+ // list shape stays unchanged for every other caller. Two aggregate queries.
226
+ if (String(req.query.include || '').split(',').includes('deps')) {
227
+ await db.attachTaskDepSummaries(tasks);
228
+ }
219
229
  res.json({ tasks });
220
230
  });
221
231
 
@@ -709,12 +719,17 @@ module.exports = function buildTasksRouter() {
709
719
  console.error('[gds] POST /tasks suggest-criteria (non-blocking)', e && e.message);
710
720
  }
711
721
  }
722
+ // task 1002539 — the orphan-task advisory (the /goal-create auto-pickup
723
+ // hook). Nothing 4xxs when a task has no goal, so surface it here instead of
724
+ // blocking. Non-blocking by construction — see goal-advisory.js.
725
+ const goal_advisory = await buildGoalAdvisory({ db, versionId: body.version_id, task });
712
726
  res.status(201).json({
713
727
  task,
714
728
  credits_reward: task.credits_reward,
715
729
  needs_migration: needsMigration,
716
730
  criteria,
717
731
  suggested_criteria,
732
+ goal_advisory,
718
733
  });
719
734
  } catch (err) {
720
735
  console.error('[gds] POST /tasks', err);
@@ -955,13 +970,26 @@ module.exports = function buildTasksRouter() {
955
970
  });
956
971
 
957
972
  // rank: metic+archon — backlog→ready promotion; scope mutation; ADR 0090 (was archon).
973
+ //
974
+ // Also the UN-ABANDON path (ADR 0152): 'abandoned' → 'backlog'. When abandon moved
975
+ // down to Metic+ it needed an undo at the same rank, or a Metic's mis-retire would
976
+ // be unrecoverable without Archon SQL (no other route restores an abandoned task —
977
+ // PATCH refuses `status` outright). A restore lands in **backlog**, never straight
978
+ // to ready: the task was retired for a reason, so it re-enters as un-workable and
979
+ // takes a second, deliberate promote to become claimable again.
958
980
  router.post('/tasks/:id/promote', auth.requireBuilder, auth.requireRank('metic', 'archon'), async (req, res) => {
959
981
  const id = Number(req.params.id);
960
982
  const task = await db.getTask(id);
961
983
  if (!task) return res.fail('task_not_found', 404);
962
- if (!['backlog', 'blocked'].includes(task.status)) {
984
+ if (!['backlog', 'blocked', 'abandoned'].includes(task.status)) {
963
985
  return res.fail('cannot_promote', 409, { current_status: task.status });
964
986
  }
987
+ // The restore branch returns early: no reward gate (that is a promote-to-WORKABLE
988
+ // concern and backlog is the pre-workable state — same reasoning as /demote).
989
+ if (task.status === 'abandoned') {
990
+ const restored = await db.restoreTask({ taskId: id });
991
+ return res.json({ ok: true, restored: true, status: restored.taskStatus });
992
+ }
965
993
  // task 1670 / ADR 0096 + task 1673 amendment (2026-06-28): a task must never
966
994
  // become workable paying 0 drachmae — but the gate AUTO-ASSIGNS rather than
967
995
  // BLOCKS. If credits_reward is 0/NULL, assign the capped suggestion (from
@@ -1113,10 +1141,14 @@ module.exports = function buildTasksRouter() {
1113
1141
  // on a task with an active claim (release it first); idempotent on an already-
1114
1142
  // abandoned task. The abandon + its `reason` land in audit_log via the writing
1115
1143
  // middleware, so the ledger keeps a record of who retired what.
1116
- // rank: archon — terminal lifecycle mutation / scope removal (ADR 0016). This
1117
- // is the inverse of /promote and belongs to the same archon-gated scope-control
1118
- // surface, never self-serviceable by a lower rank.
1119
- router.post('/tasks/:id/abandon', auth.requireBuilder, auth.requireRank('archon'), async (req, res) => {
1144
+ // rank: metic+archon — scope removal; the inverse of /promote, and re-ranked to
1145
+ // match it (ADR 0152, was archon under ADR 0090). A Metic who may author, shape,
1146
+ // and promote work may also retire it: leaving the kill switch an Archon rung
1147
+ // higher than the create switch only forced the owner to clean up after the
1148
+ // triage they already delegated. Not one-way any more — /promote restores an
1149
+ // abandoned task to backlog at the same rank — and the guards below (shipped
1150
+ // refused, active claim refused) plus the audit_log entry are unchanged.
1151
+ router.post('/tasks/:id/abandon', auth.requireBuilder, auth.requireRank('metic', 'archon'), async (req, res) => {
1120
1152
  if (validateOrRespond(req, res, {
1121
1153
  reason: { type: 'string' }, // free-text retire reason (sliced to 2000 below)
1122
1154
  })) return;
@@ -247,6 +247,12 @@ function buildCardModel(input = {}) {
247
247
  } else if (credits.awarded != null) {
248
248
  const total = fmtInt(credits.total);
249
249
  creditsText = total ? `+${credits.awarded} → ${total} ${brand.currency}` : `+${credits.awarded} ${brand.currency}`;
250
+ // cost-plus-only (ADR 0146): the per-task estimate is withheld, so a `+0`
251
+ // here is correct. Say why on the card itself — unexplained it reads as a
252
+ // broken payout (task 1002469). `perTaskCredit` is injected by the caller
253
+ // (which resolves economy's rewardPolicy); absent ⇒ assume the paying
254
+ // default and print nothing, so other instances are untouched.
255
+ if (credits.perTaskCredit === false) creditsText += ' · est. not paid (cost-plus only)';
250
256
  }
251
257
  // Usage — lead with the dollar cost (the honest measure of the work), then
252
258
  // split the token count into 'new' (fresh input + Claude's output + the
@@ -129,7 +129,7 @@ const STAGE_HINTS = {
129
129
  auth_complete: 'First obtain the repo — clone it (or open thy hosted dev box, where it is already cloned) — then sign in via GitHub Device Flow or the web sign-in.',
130
130
  pick_disciplines: 'Pick the work that calls thee — engineer, artist, ideator, or any mix.',
131
131
  first_start: 'Run /builder-start to see claimable works.',
132
- first_claim: 'Claim a task with /builder-claim N.',
132
+ first_claim: 'Claim a task with /builder-claim N — then on thy dev box run /builder-sync, and thy box pulls exactly that task\'s code (naught else) within seconds.',
133
133
  ship_three: 'Carry three tasks through with /builder-ship — the third makes thee a Thetes.',
134
134
  graduated: '',
135
135
  };
@@ -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._
@@ -0,0 +1,147 @@
1
+ // modules/platform-identity/hub-device.js — the hub's GitHub device flow for CLI
2
+ // federation (ADR 0141 §1, task #1002108).
3
+ //
4
+ // This is a DELIBERATELY SELF-CONTAINED GitHub device flow — it does NOT reuse
5
+ // core auth's startDeviceFlow/pollDeviceFlow, because those mint a hub BUILDER
6
+ // session (and run the hub's invite-only admission gate). Federation must instead
7
+ // establish a platform ACCOUNT — open to anyone with a GitHub account, since a
8
+ // platform account is pure identity and project admission stays LOCAL (ADR 0141:
9
+ // identity federated, authority local). So the flow here runs GitHub's device
10
+ // endpoints against the hub's own OAuth app and returns the raw GitHub identity;
11
+ // the route then upserts the platform account and mints an assertion. The ~40
12
+ // lines of GitHub mechanics duplicated from core auth are boundary-forced
13
+ // duplication (ADR 0091 §2) — the module cannot import core internals.
14
+ 'use strict';
15
+
16
+ const api = require('../../src/module-api');
17
+
18
+ const { resolveEnv, userAgent } = api;
19
+ const SCOPE = 'read:user';
20
+
21
+ // In-memory device_code -> { client_id, interval, expires_at, last_polled_at }.
22
+ // Ephemeral, like core auth's deviceFlows: a hub restart drops pending flows (the
23
+ // CLI just re-runs). The client_id is BOUND here at start (resolved from the
24
+ // user-typed origin by the route) so the poll mints for exactly that client — the
25
+ // CLI never gets to name the audience (ADR 0141 §1 security fix).
26
+ const hubDeviceFlows = new Map();
27
+
28
+ // Absolute cap on concurrent pending flows — a backstop against memory growth from
29
+ // a flood of /sso/device/start calls (the per-IP write limiter is the primary
30
+ // control; this bounds a distributed flood). Old expired entries are reaped first,
31
+ // so a legitimate load never hits this.
32
+ const MAX_FLOWS = 2000;
33
+
34
+ // The hub's own GitHub OAuth-app client id (public). Device flow needs only the
35
+ // client_id (no secret) for both start and the token poll.
36
+ function hubGithubClientId() {
37
+ return resolveEnv('GITHUB_CLIENT_ID') || null;
38
+ }
39
+
40
+ function hubDeviceConfigured() {
41
+ return !!hubGithubClientId();
42
+ }
43
+
44
+ // Start a GitHub device flow against the hub's app, BOUND to `federatedClientId`
45
+ // (the client the route resolved from the user-typed origin). Returns the codes to
46
+ // show the user. Throws with an actionable message on a GitHub OAuth-app misconfig.
47
+ async function startHubGithubDevice(federatedClientId) {
48
+ const clientId = hubGithubClientId();
49
+ if (!clientId) throw new Error('hub GitHub app not configured');
50
+ if (!federatedClientId) throw new Error('federated client_id required');
51
+ // Reap expired first, then refuse a NEW start past the cap (never evict a live one).
52
+ for (const [k, v] of hubDeviceFlows) if (v.expires_at < Date.now()) hubDeviceFlows.delete(k);
53
+ if (hubDeviceFlows.size >= MAX_FLOWS) throw new Error('too many device flows in flight; try again shortly');
54
+ const res = await fetch('https://github.com/login/device/code', {
55
+ method: 'POST',
56
+ headers: { 'Content-Type': 'application/x-www-form-urlencoded', Accept: 'application/json' },
57
+ body: new URLSearchParams({ client_id: clientId, scope: SCOPE }).toString(),
58
+ });
59
+ if (!res.ok) {
60
+ const text = await res.text().catch(() => '');
61
+ throw new Error(`github device/code failed (${res.status}): ${text.slice(0, 160)}`);
62
+ }
63
+ const data = await res.json();
64
+ if (data.error) {
65
+ const hint = data.error === 'device_flow_disabled'
66
+ ? ' — enable Device Flow on the hub GitHub app'
67
+ : '';
68
+ throw new Error(`github rejected device-flow start: ${data.error}${hint}`);
69
+ }
70
+ if (!data.device_code) throw new Error('github device/code returned no device_code');
71
+ hubDeviceFlows.set(data.device_code, {
72
+ client_id: federatedClientId,
73
+ interval: data.interval || 5,
74
+ expires_at: Date.now() + (data.expires_in || 900) * 1000,
75
+ last_polled_at: 0,
76
+ });
77
+ return {
78
+ device_code: data.device_code,
79
+ user_code: data.user_code,
80
+ verification_uri: data.verification_uri,
81
+ interval: data.interval || 5,
82
+ expires_in: data.expires_in || 900,
83
+ };
84
+ }
85
+
86
+ // Poll GitHub for the device flow's completion. Returns:
87
+ // { state: 'pending' } | { state: 'slow_down', interval } | { state: 'expired' }
88
+ // | { state: 'denied' } | { state: 'ok', identity: { github_id, github_login,
89
+ // display_name, avatar_url } }
90
+ // Identity ONLY — no session, no rank. The route mints the assertion from it.
91
+ async function pollHubGithubDevice(deviceCode) {
92
+ const flow = hubDeviceFlows.get(deviceCode);
93
+ if (!flow) return { state: 'expired' };
94
+ if (flow.expires_at < Date.now()) { hubDeviceFlows.delete(deviceCode); return { state: 'expired' }; }
95
+ const now = Date.now();
96
+ if (now - flow.last_polled_at < flow.interval * 1000) return { state: 'slow_down', interval: flow.interval };
97
+ flow.last_polled_at = now;
98
+
99
+ const clientId = hubGithubClientId();
100
+ const res = await fetch('https://github.com/login/oauth/access_token', {
101
+ method: 'POST',
102
+ headers: { 'Content-Type': 'application/x-www-form-urlencoded', Accept: 'application/json' },
103
+ body: new URLSearchParams({
104
+ client_id: clientId,
105
+ device_code: deviceCode,
106
+ grant_type: 'urn:ietf:params:oauth:grant-type:device_code',
107
+ }).toString(),
108
+ });
109
+ const data = await res.json();
110
+ if (data.error === 'authorization_pending') return { state: 'pending' };
111
+ if (data.error === 'slow_down') { flow.interval = (flow.interval || 5) + 5; return { state: 'slow_down', interval: flow.interval }; }
112
+ if (data.error === 'expired_token') { hubDeviceFlows.delete(deviceCode); return { state: 'expired' }; }
113
+ if (data.error === 'access_denied') { hubDeviceFlows.delete(deviceCode); return { state: 'denied' }; }
114
+ if (data.access_token) {
115
+ hubDeviceFlows.delete(deviceCode);
116
+ const ghRes = await fetch('https://api.github.com/user', {
117
+ headers: {
118
+ Authorization: `Bearer ${data.access_token}`,
119
+ 'User-Agent': userAgent(),
120
+ Accept: 'application/vnd.github+json',
121
+ },
122
+ });
123
+ if (!ghRes.ok) throw new Error(`github /user failed (${ghRes.status})`);
124
+ const u = await ghRes.json();
125
+ return {
126
+ state: 'ok',
127
+ // client_id is the one BOUND at start (from the user-typed origin) — the CLI
128
+ // never names the audience; the mint uses this (ADR 0141 §1 security fix).
129
+ client_id: flow.client_id,
130
+ identity: {
131
+ github_id: u.id,
132
+ github_login: u.login,
133
+ display_name: u.name || u.login,
134
+ avatar_url: u.avatar_url,
135
+ },
136
+ };
137
+ }
138
+ return { state: 'pending' };
139
+ }
140
+
141
+ module.exports = {
142
+ hubGithubClientId,
143
+ hubDeviceConfigured,
144
+ startHubGithubDevice,
145
+ pollHubGithubDevice,
146
+ _flows: hubDeviceFlows, // exposed for tests
147
+ };