@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
@@ -0,0 +1,57 @@
1
+ -- core_195_idea_triage_provenance.sql — give a triage VERDICT somewhere to
2
+ -- record what it decided (task 1002447).
3
+ --
4
+ -- Triage has three verbs; only two of them had storage. `status` records THAT an
5
+ -- idea was discarded or merged, and `promoted_to_task_id` records the task a
6
+ -- promotion produced — but nothing recorded WHICH idea a merge folded into, or
7
+ -- WHY a discard happened. `promoted_to_task_id` cannot stand in for the merge
8
+ -- target: it is `REFERENCES tasks(id)`, so writing an idea id there either trips
9
+ -- the FK or silently points at an unrelated task. BOTH callers that documented
10
+ -- or performed that write are corrected alongside this migration — the
11
+ -- /idea-triage skill doc and the interactive walker `scripts/gds/triage.js`.
12
+ -- Without a target, "merge" is just "discard" wearing a different label.
13
+ --
14
+ -- CORE, not modules/ideas/migrations/, even though the ideas module owns every
15
+ -- query against this table: `idea_inbox` is a CORE table, and a module migration
16
+ -- may only touch its own <key>_-prefixed objects — `fitness.js`
17
+ -- checkModuleMigrationsAdditive hard-fails an `ALTER TABLE idea_inbox` from a
18
+ -- module (ADR 0083 §Decision #5). The module's "new ideas migration goes in
19
+ -- modules/ideas/migrations/" note applies to objects the module OWNS, and it
20
+ -- owns none.
21
+ --
22
+ -- Two additive, nullable columns. Forward-safe by construction: every existing
23
+ -- reader SELECTs an explicit column list, so old code on the new schema neither
24
+ -- sees nor needs these (tests/migrations_forward_safe.mjs).
25
+
26
+ BEGIN;
27
+
28
+ ALTER TABLE idea_inbox
29
+ ADD COLUMN IF NOT EXISTS merged_into_idea_id bigint REFERENCES idea_inbox(id);
30
+
31
+ ALTER TABLE idea_inbox
32
+ ADD COLUMN IF NOT EXISTS triage_note text;
33
+
34
+ -- A target exists IFF the idea is merged. Without this, two ordinary PATCHes
35
+ -- build a merge CHAIN: set A's target while leaving A open, then merge C into A
36
+ -- — whose status still reads 'open', so a status-based no-chains check waves it
37
+ -- through. One direction only (`target IS NULL OR status='merged'`), because
38
+ -- every pre-existing merged row has a NULL target and must stay valid.
39
+ -- CHECK constraints have no ADD ... IF NOT EXISTS, so guard the add (the
40
+ -- migration 042 idiom) and keep the file replay-safe.
41
+ DO $$
42
+ BEGIN
43
+ IF NOT EXISTS (
44
+ SELECT 1 FROM pg_constraint
45
+ WHERE conrelid = 'idea_inbox'::regclass
46
+ AND conname = 'idea_inbox_merge_target_needs_merged_status'
47
+ ) THEN
48
+ ALTER TABLE idea_inbox
49
+ ADD CONSTRAINT idea_inbox_merge_target_needs_merged_status
50
+ CHECK (merged_into_idea_id IS NULL OR status = 'merged');
51
+ END IF;
52
+ END $$;
53
+
54
+ INSERT INTO schema_migrations (version) VALUES ('core_195_idea_triage_provenance')
55
+ ON CONFLICT DO NOTHING;
56
+
57
+ COMMIT;
@@ -0,0 +1,69 @@
1
+ -- core_196_session_reward_topup.sql
2
+ -- Let a multi-ship session be paid for ALL of its cost, not just its first ship.
3
+ --
4
+ -- WHY THIS IS A CORE MIGRATION, NOT modules/economy/. credit_log is a CORE table — the
5
+ -- economy module owns the queries against it, not the table itself (ADR 0083 §Decision #5;
6
+ -- modules/economy/CLAUDE.md "Tables: None owned"). A module migration may only touch its own
7
+ -- economy_-namespaced objects, so this shipped as economy_002_* and reddened the fitness gate
8
+ -- three ways (destructive DDL + index + alter on a non-namespaced table). The real defect was
9
+ -- placement, not DDL: as a module migration these columns only existed when the economy module
10
+ -- happened to be ENABLED, which makes core schema depend on module enablement — the exact
11
+ -- coupling the rule forbids. Relocated per the core_195 precedent (task 1002447).
12
+ --
13
+ -- schema_migrations is stem-keyed, so this re-applies once under its new stem. That is safe
14
+ -- BECAUSE every statement below is IF [NOT] EXISTS — the re-run is a no-op on any database
15
+ -- that already had it as economy_002.
16
+ --
17
+ -- THE DEFECT. Migration 091 made the cost-plus session reward idempotent with a
18
+ -- PARTIAL UNIQUE INDEX on credit_log (session_id) WHERE reason='session.token_reward'
19
+ -- — "the hard DB-level backstop against a double payout". That is correct and stays.
20
+ -- But it was the ONLY control, so once a session booked its first reward row, every
21
+ -- later ship in the same session was a hard no-op. A session's cost keeps growing
22
+ -- after its first ship, and that increment had nowhere to land.
23
+ --
24
+ -- Measured: three tasks shipped from one session, priced $14.30 / $27.76 / $40.32,
25
+ -- paid 17 drachmae where the whole session was worth round(40.32 × 1.20) = 48.
26
+ -- Roughly 65% of real, shipped, graded work went unrewarded. Multi-ship sessions are
27
+ -- normal — one claim at a time, but ship -> new worktree -> claim again.
28
+ --
29
+ -- THE FIX is watermark accounting: each landed ship books the DIFFERENCE between what
30
+ -- the whole session is now worth and what it has already been paid. That needs more
31
+ -- than one row per session, so uniqueness moves from (session_id) to
32
+ -- (session_id, reward_basis) — still scoped to the same reason.
33
+ --
34
+ -- WHY A CUMULATIVE BASIS AND NOT THE DELTA. Two concurrent uploads at the SAME cost
35
+ -- level must still collide and DO NOTHING. reward_basis is the CUMULATIVE owed total
36
+ -- at the moment of the row, so it is monotonic and identical for a racing pair. A bare
37
+ -- delta would be wrong: two genuine increments that happened to be equal would
38
+ -- collide and one payment would be silently lost.
39
+ --
40
+ -- Additive and non-destructive. No historical row changes value.
41
+
42
+ -- (a) The cumulative cost-plus total this session was owed when the row was written.
43
+ -- NOT NULL DEFAULT 0 (rather than nullable) so the unique index below has clean
44
+ -- semantics: Postgres treats NULLs as DISTINCT in a unique index, so a nullable
45
+ -- basis would let two legacy rows share a session_id and would WEAKEN the very
46
+ -- backstop 091 added.
47
+ --
48
+ -- Existing rows therefore land on reward_basis = 0, which is the marker for
49
+ -- "booked before top-ups existed". The application refuses to top those up: doing
50
+ -- so would be an uncontrolled retroactive payout across every historical session.
51
+ -- Reconstructing what past sessions are owed is a deliberate, owner-signed-off
52
+ -- backfill, tracked separately.
53
+ ALTER TABLE credit_log
54
+ ADD COLUMN IF NOT EXISTS reward_basis integer NOT NULL DEFAULT 0;
55
+
56
+ -- (b) Swap the uniqueness key. CREATE the replacement BEFORE dropping the original so
57
+ -- the double-pay backstop is never absent, not even inside this transaction.
58
+ --
59
+ -- For legacy rows (all at basis 0) the new index is exactly as strict as the old
60
+ -- one — (session_id, 0) is unique per session — so nothing already paid can be
61
+ -- paid twice.
62
+ CREATE UNIQUE INDEX IF NOT EXISTS uniq_credit_session_token_reward_basis
63
+ ON credit_log (session_id, reward_basis)
64
+ WHERE reason = 'session.token_reward';
65
+
66
+ DROP INDEX IF EXISTS uniq_credit_session_token_reward;
67
+
68
+ -- Counter hygiene: as a core migration this consumes core number 196 (next free after
69
+ -- core_195), so the core allocator floor advances with it.
@@ -8,10 +8,12 @@
8
8
  // contributes.pollers:["routine-timers"]; the loader (src/module-loader/loader.js
9
9
  // startModulePollers) requires THIS file and calls start(deps) at boot, stop() at
10
10
  // shutdown — the module-agnostic successor to the hardcoded `routineTimers.start()`
11
- // block server.js used to carry. server.js (the game/droplet entrypoint) is the
12
- // ONLY caller of startModulePollers, never src/platform-server.js, so a future
13
- // second always-on instance on the same DB can't double-run (and the FOR UPDATE
14
- // row lock in tallyPeerVotes is the hard guard if one ever does).
11
+ // block server.js used to carry. src/platform-server.js is now the ONLY caller of
12
+ // startModulePollers (task 1002419) — server.js, the retired game/droplet
13
+ // entrypoint, was the previous one, and while it was gone NO module poller ran at
14
+ // all. It runs as a single process (docker-entrypoint.sh `exec`s it, no cluster),
15
+ // so a second always-on instance on the same DB still can't double-run — and the
16
+ // FOR UPDATE row lock in tallyPeerVotes is the hard guard if one ever does.
15
17
  //
16
18
  // WHY THIS EXISTS
17
19
  // ADR 0078 demoted peer-vote-tally to `mode: deterministic` and built an EXTERNAL
@@ -100,10 +102,9 @@ async function runPeerVoteTally(deps = {}) {
100
102
 
101
103
  // Start the timer(s). Idempotent. Kill-switched by ROUTINE_TIMERS_DISABLED=1.
102
104
  // Unref'd so it never holds the process open on a systemd restart. Started by the
103
- // loader's startModulePollers ONLY on the game/droplet entrypoint (server.js),
104
- // never in src/platform-server.js, so a future second always-on instance on the
105
- // same DB can't double-run (and the FOR UPDATE row lock in tallyPeerVotes is the
106
- // hard guard if one ever does).
105
+ // loader's startModulePollers from src/platform-server.js — the single always-on
106
+ // entrypoint (task 1002419) — so a second instance on the same DB can't double-run
107
+ // (and the FOR UPDATE row lock in tallyPeerVotes is the hard guard if one ever does).
107
108
  function start(deps = {}) {
108
109
  if (process.env.ROUTINE_TIMERS_DISABLED === '1') {
109
110
  console.log('[routine-timers] disabled (ROUTINE_TIMERS_DISABLED=1)');
@@ -18,13 +18,14 @@
18
18
  // blob; future behavior knobs add their own reserved keys. A missing key falls
19
19
  // back to the rank-derived default below, so the column stays sparse.
20
20
  //
21
- // Resolution = default-by-rank, capped-by-rank, server-enforced.
21
+ // Resolution = default-by-role, capped-by-role, server-enforced (see the ROLE
22
+ // METADATA block below for why a ceiling is metadata and not a permission).
22
23
  // - DEFAULT level rides the trust ladder (Xenos→Locked … Archon→Exploratory).
23
- // - MAX (ceiling) per rank: Xenos and Thetes are capped at their default (they
24
- // may dial DOWN for more focus, never UP); Metic and Archon may set any
25
- // level (their rank is a starting default, not a ceiling). Owner decision
24
+ // - CEILING per role: Xenos and Thetes are capped at their default (they may
25
+ // dial DOWN for more focus, never UP); Metic and Archon may set any level
26
+ // (their rank is a starting default, not a ceiling). Owner decision
26
27
  // 2026-06-19: "Thetes is gated; Metic is just a starting default."
27
- // - effectiveLevel = clamp(override ?? default, 0, maxForRank). So a Xenos who
28
+ // - effectiveLevel = clamp(override ?? default, 0, ceiling). So a Xenos who
28
29
  // stores Exploratory is still resolved to Locked — opening the setting up
29
30
  // for self-service later stays safe because the clamp is the enforcement,
30
31
  // not the UI.
@@ -98,15 +99,54 @@ const LEVEL_CONTRACT = Object.freeze({
98
99
  + 'investigations worth the user\'s attention as you go, while still landing the claimed task.',
99
100
  });
100
101
 
101
- // Default level per rank (the array index in LEVELS). Rides the live rank
102
- // ladder (xenos<thetes<metic<archon — db.RANKS / boxes.RANK_ORDER). A rank not
103
- // listed (e.g. a reserved/penalty rank, or an unknown string) falls back to the
104
- // safest floor: Locked.
105
- const RANK_DEFAULT = Object.freeze({ xenos: 0, thetes: 1, metic: 2, archon: 3 });
102
+ // ---- the wandering ceiling as ROLE METADATA (BV1.R107, ADR 0151) ----
103
+ //
104
+ // The governance model makes a permission a BOOLEAN atom and a rank a seeded
105
+ // ROLE. A wandering ceiling is neither: it is a NUMBER in 0..3. Spelling it as
106
+ // permissions would need one catalog key per level (`me.wandering.level.0..3`) —
107
+ // four keys no route consumes individually, against the catalog's first rule
108
+ // ("a permission no route consumes does not exist") and multiplying the key count
109
+ // to encode a quantity that was never a yes/no capability.
110
+ //
111
+ // So the ceiling is ROLE METADATA: a property carried BY the role, alongside —
112
+ // not inside — the permission that authorizes the write. The two compose:
113
+ // • the PERMISSION `me.wandering.set` (catalog: floor xenos, scope 'own',
114
+ // resource 'builder_prefs') answers MAY this builder set their OWN level. It
115
+ // is ownership-scoped, so it is answered by resolver.authorizeOwned(), never
116
+ // by an unconditional "do they hold it" — that is the route gate's business.
117
+ // • the ROLE METADATA below answers HOW HIGH they may set it. Holding the
118
+ // permission is never sufficient on its own: effectiveLevel() clamps to the
119
+ // role's ceiling server-side, so the ceiling is enforcement, not UI copy.
120
+ //
121
+ // Keyed by ROLE KEY. The four seeded rank-roles are named for the ranks they seed
122
+ // (governance_002), so a role key and a rank string coincide — which is why the
123
+ // rank-keyed helpers below still resolve correctly, and why BV1.R108 can fold this
124
+ // table onto the one live ladder without changing a value. A role this table does
125
+ // not name carries NO wandering metadata and resolves to the safest floor
126
+ // (Locked), the same fail-closed posture the resolver takes for a rank with no
127
+ // seeded role.
128
+ const ROLE_WANDERING = Object.freeze({
129
+ xenos: Object.freeze({ default: 0, ceiling: 0 }),
130
+ thetes: Object.freeze({ default: 1, ceiling: 1 }),
131
+ metic: Object.freeze({ default: 2, ceiling: 3 }),
132
+ archon: Object.freeze({ default: 3, ceiling: 3 }),
133
+ });
134
+ const NO_ROLE_WANDERING = Object.freeze({ default: 0, ceiling: 0 });
135
+
136
+ function roleWandering(roleKey) {
137
+ return Object.prototype.hasOwnProperty.call(ROLE_WANDERING, roleKey)
138
+ ? ROLE_WANDERING[roleKey]
139
+ : NO_ROLE_WANDERING;
140
+ }
106
141
 
107
- // Ceiling per rank. Below Metic the default IS the ceiling (dial down only);
108
- // Metic+ may pick any level. Unknown rank → floor (0).
109
- const RANK_MAX = Object.freeze({ xenos: 0, thetes: 1, metic: 3, archon: 3 });
142
+ // DERIVED projections of the metadata above, kept because scripts/gds/session-eval.js
143
+ // and the /me payload read them by rank. Deriving them means the two views cannot drift.
144
+ const RANK_DEFAULT = Object.freeze(
145
+ Object.fromEntries(Object.entries(ROLE_WANDERING).map(([role, meta]) => [role, meta.default])),
146
+ );
147
+ const RANK_MAX = Object.freeze(
148
+ Object.fromEntries(Object.entries(ROLE_WANDERING).map(([role, meta]) => [role, meta.ceiling])),
149
+ );
110
150
 
111
151
  const TOP_LEVEL = LEVELS.length - 1; // 3
112
152
  const RESERVED_KEY = 'wandering';
@@ -120,9 +160,10 @@ for (const key of LEVELS) {
120
160
  for (let i = 0; i < LEVELS.length; i++) {
121
161
  if (LEVEL_META[LEVELS[i]].n !== i) throw new Error(`wandering-prefs: LEVEL_META.${LEVELS[i]}.n must equal its index ${i}`);
122
162
  }
123
- for (const rank of Object.keys(RANK_DEFAULT)) {
124
- if (!(rank in RANK_MAX)) throw new Error(`wandering-prefs: RANK_DEFAULT has '${rank}' but RANK_MAX does not`);
125
- if (RANK_DEFAULT[rank] > RANK_MAX[rank]) throw new Error(`wandering-prefs: default > max for rank '${rank}'`);
163
+ for (const [role, meta] of Object.entries(ROLE_WANDERING)) {
164
+ if (!isLevelInt(meta.default)) throw new Error(`wandering-prefs: role '${role}' has a non-level default`);
165
+ if (!isLevelInt(meta.ceiling)) throw new Error(`wandering-prefs: role '${role}' has a non-level ceiling`);
166
+ if (meta.default > meta.ceiling) throw new Error(`wandering-prefs: default > ceiling for role '${role}'`);
126
167
  }
127
168
 
128
169
  // ---- pure helpers ----
@@ -141,11 +182,11 @@ function levelIndex(key) {
141
182
  }
142
183
 
143
184
  function defaultLevelForRank(rank) {
144
- return Object.prototype.hasOwnProperty.call(RANK_DEFAULT, rank) ? RANK_DEFAULT[rank] : 0;
185
+ return roleWandering(rank).default;
145
186
  }
146
187
 
147
188
  function maxLevelForRank(rank) {
148
- return Object.prototype.hasOwnProperty.call(RANK_MAX, rank) ? RANK_MAX[rank] : 0;
189
+ return roleWandering(rank).ceiling;
149
190
  }
150
191
 
151
192
  // The override a builder has stored (the raw 'wandering' key off behavior_prefs),
@@ -262,6 +303,8 @@ module.exports = {
262
303
  LEVELS,
263
304
  LEVEL_META,
264
305
  LEVEL_CONTRACT,
306
+ ROLE_WANDERING,
307
+ roleWandering,
265
308
  RANK_DEFAULT,
266
309
  RANK_MAX,
267
310
  TOP_LEVEL,
@@ -0,0 +1,73 @@
1
+ # OTB Dev Box — the desktop switch for a builder's hosted box
2
+
3
+ A menu-bar (macOS) / system-tray (Windows) app that turns a builder's hosted
4
+ dev box on and off with one switch. Built for the newcomer who should never
5
+ see a terminal: sign in with browser clicks (app pairing, ADR 0045), flip the
6
+ switch, and connect from Claude Desktop or the browser terminal.
7
+
8
+ What it does beyond the switch — each fixing a failure observed in the
9
+ 2026-06-10 onboarding sessions that killed the old `curl | bash` flow:
10
+
11
+ - **Pairing sign-in** — opens the builders' hall, one Approve click, token
12
+ delivered over the app's own secret channel (`/api/gds/auth/app-pair/*`).
13
+ The session is written to `~/.config/otb/gds-session.json`, the same file
14
+ the `/builder-*` CLI uses — pairing the app signs in the CLI too.
15
+ - **SSH key done right** — generates `~/.ssh/otb_builder` (ed25519) with real
16
+ 600 perms (icacls ACL on Windows) and registers it via `POST /box/ssh-key`.
17
+ - **known_hosts kept fresh** — Claude Desktop's embedded ssh2 reads ONLY
18
+ `~/.ssh/known_hosts`, and host keys change on every re-provision; the app
19
+ re-scans and replaces entries each time the box turns active.
20
+ - **Claude Desktop connection file** — installs the managed-settings file to
21
+ the system path with a native elevation prompt (osascript / UAC), the step
22
+ the agent-driven flow could never finish.
23
+ - **Native notifications** — box ready, rested while idle.
24
+ - **No heartbeats** — the app never sends `claude_active`; the droplet's idle
25
+ sweep stays authoritative so an unused box still reclaims itself (~10 min).
26
+
27
+ ## Layout
28
+
29
+ - `src/main.js` — Electron main: tray, window, IPC, wiring.
30
+ - `src/brand.js` — every brand string (ADR 0004: the civ name lives here only).
31
+ - `src/{pairing,box-controller,gds-client,session-store,ssh-setup,updater}.js` — the
32
+ logic, Electron-free and unit-tested in `test/unit.mjs`.
33
+ - `src/updater.js` — in-app update check (#924): semver compare + platform→target
34
+ resolution. `main.js` polls `GET /downloads/devbox/version` on open + every 12h
35
+ and, when the published release is strictly newer, shows the Update banner; the
36
+ Update button downloads the installer and hands it to the OS (browser-download
37
+ fallback on any error). **Bumping `config/devbox-app.json` version is what makes
38
+ the banner appear in the field** — see [`docs/recipes/devbox-app-release.md`](../docs/recipes/devbox-app-release.md).
39
+ - `renderer/` — the panel UI (sandboxed, contextIsolated; preload exposes
40
+ named verbs only).
41
+ - `scripts/gen-icons.js` — regenerates all icons from pixel grids (`npm run
42
+ icons`; borrows `sharp` from the repo root's node_modules).
43
+
44
+ ## Develop
45
+
46
+ ```
47
+ cd devbox-app
48
+ npm install
49
+ npm test # hermetic unit tests, no Electron needed
50
+ npm start # run against prod (or OTB_DEVBOX_API_BASE=http://… npm start)
51
+ ```
52
+
53
+ Dev/test env hooks: `OTB_DEVBOX_API_BASE` (point at a stub/staging GDS),
54
+ `OTB_DEVBOX_NO_ELEVATE=1` (skip the managed-settings elevation prompt),
55
+ `OTB_DEVBOX_SCREENSHOT_DIR` (write a PNG of the panel on every state change —
56
+ how CI/verification captures proof).
57
+
58
+ ## Build & release
59
+
60
+ ```
61
+ npm run dist:mac # dmg, arm64 + x64 (unsigned → ad-hoc)
62
+ npm run dist:win # NSIS one-click installer, x64 + arm64
63
+ ```
64
+
65
+ CI builds both on every push that touches `devbox-app/**`
66
+ (`.github/workflows/build-devbox-app.yml`) and uploads them as artifacts.
67
+ Publishing = attach the artifacts to a release on the PUBLIC releases repo
68
+ named in `config/devbox-app.json`, then bump that config and ship — the
69
+ branded `/downloads/devbox/*` redirects follow the config. Full recipe:
70
+ `docs/recipes/devbox-app-release.md`.
71
+
72
+ This subproject is self-contained on purpose: its `package.json` never feeds
73
+ the droplet's `npm ci`, and nothing under `devbox-app/` runs server-side.
@@ -0,0 +1,95 @@
1
+ # TEMPLATE — rendered to electron-builder.yml by scripts/gen-brand.js from
2
+ # config/branding.json (@@TOKENS@@ are substituted; ${version}/${arch}/${ext}
3
+ # are electron-builder's own and stay literal). Edit THIS file, never the
4
+ # generated electron-builder.yml.
5
+ #
6
+ # Brand strings (appId, productName, copyright, artifact slug) come from the
7
+ # instance branding so one source tree builds any instance's app (ADR 0004,
8
+ # generalised). Signing rides the instance's own CI (main-ref-gated Apple
9
+ # Developer ID; ADR 0072) — an unarmed build ad-hoc-signs the mac app, which
10
+ # arm64 requires to launch at all.
11
+ #
12
+ # Artifact names are pinned (no spaces, no world name inside — ADR 0004) and
13
+ # MUST match config/devbox-app.json's assets map; tests/devbox_downloads.mjs
14
+ # cross-checks the config side.
15
+
16
+ appId: @@APP_ID@@
17
+ productName: @@PRODUCT_NAME@@
18
+ copyright: @@COPYRIGHT@@
19
+
20
+ # buildResources is named build-resources (not electron-builder's default
21
+ # `build`) because the repo root .gitignore ignores build/ everywhere and the
22
+ # app icon in here is COMMITTED source, not output.
23
+ directories:
24
+ buildResources: build-resources
25
+ output: dist
26
+
27
+ files:
28
+ - src/**/*
29
+ - renderer/**/*
30
+ - assets/**/*
31
+ - package.json
32
+
33
+ asar: true
34
+
35
+ mac:
36
+ category: public.app-category.developer-tools
37
+ icon: build-resources/icon.png
38
+ target:
39
+ - target: dmg
40
+ arch: [arm64, x64]
41
+ artifactName: @@PRODUCT_SLUG@@-${version}-${arch}.${ext}
42
+
43
+ dmg:
44
+ artifactName: @@PRODUCT_SLUG@@-${version}-${arch}.${ext}
45
+ # On-brand install window (task 912). The backdrop
46
+ # (build-resources/dmg-background.png + @2x, drawn by
47
+ # scripts/gen-dmg-backdrop.py) guides the drag from the container (the app)
48
+ # into Applications. iconSize + window + the contents coords MUST match
49
+ # the icon-well centers the backdrop draws (LEFT 180,198 / RIGHT 480,198) — if
50
+ # you move one, re-run the generator. The volume icon is the app icon (the
51
+ # container). electron-builder auto-picks dmg-background@2x.png on Retina.
52
+ #
53
+ # task 1235: this electron-builder DMG is only an INTERMEDIATE. Its window
54
+ # backdrop does NOT render on macOS >= Sonoma — electron-builder writes a
55
+ # `pBBk` bookmark Finder can't resolve, so the install window shows up white.
56
+ # The CI step "Apply install-window backdrop (dmgbuild)" re-packages the
57
+ # signed+notarized .app out of this DMG into the FINAL DMG via dmgbuild
58
+ # (scripts/apply-dmg-backdrop.sh + scripts/dmgbuild-settings.py), which writes
59
+ # an alias-only .DS_Store that DOES render. Keep the coords below in sync with
60
+ # dmgbuild-settings.py (currently identical: 180,198 / 480,198, 660x400, size 100).
61
+ icon: build-resources/icon.png
62
+ background: build-resources/dmg-background.png
63
+ iconSize: 100
64
+ window:
65
+ width: 660
66
+ height: 400
67
+ contents:
68
+ - x: 180
69
+ y: 198
70
+ type: file
71
+ - x: 480
72
+ y: 198
73
+ type: link
74
+ path: /Applications
75
+
76
+ win:
77
+ icon: build-resources/icon.png
78
+ target:
79
+ - target: nsis
80
+ # x64 covers Intel/AMD PCs; arm64 is native for ARM Windows (Surface,
81
+ # Snapdragon laptops, Parallels VMs on Apple-silicon Macs). x64 also
82
+ # RUNS on arm64 via Windows emulation, so x64 stays the safe default
83
+ # link; arm64 is the first-class build for those machines (#908).
84
+ arch: [x64, arm64]
85
+
86
+ nsis:
87
+ oneClick: true
88
+ perMachine: false
89
+ runAfterFinish: true
90
+ deleteAppDataOnUninstall: false
91
+ # ${arch} suffix added in #908 when arm64 joined — two arches can't share
92
+ # one filename. Must stay in lockstep with config/devbox-app.json assets.
93
+ artifactName: @@PRODUCT_SLUG@@-Setup-${version}-${arch}.${ext}
94
+
95
+ publish: null
@@ -0,0 +1,95 @@
1
+ # TEMPLATE — rendered to electron-builder.yml by scripts/gen-brand.js from
2
+ # config/branding.json (@@TOKENS@@ are substituted; ${version}/${arch}/${ext}
3
+ # are electron-builder's own and stay literal). Edit THIS file, never the
4
+ # generated electron-builder.yml.
5
+ #
6
+ # Brand strings (appId, productName, copyright, artifact slug) come from the
7
+ # instance branding so one source tree builds any instance's app (ADR 0004,
8
+ # generalised). Signing rides the instance's own CI (main-ref-gated Apple
9
+ # Developer ID; ADR 0072) — an unarmed build ad-hoc-signs the mac app, which
10
+ # arm64 requires to launch at all.
11
+ #
12
+ # Artifact names are pinned (no spaces, no world name inside — ADR 0004) and
13
+ # MUST match config/devbox-app.json's assets map; tests/devbox_downloads.mjs
14
+ # cross-checks the config side.
15
+
16
+ appId: com.cloudbongos.devbox
17
+ productName: Cloud Bongos Dev Box
18
+ copyright: © Cloud Bongos
19
+
20
+ # buildResources is named build-resources (not electron-builder's default
21
+ # `build`) because the repo root .gitignore ignores build/ everywhere and the
22
+ # app icon in here is COMMITTED source, not output.
23
+ directories:
24
+ buildResources: build-resources
25
+ output: dist
26
+
27
+ files:
28
+ - src/**/*
29
+ - renderer/**/*
30
+ - assets/**/*
31
+ - package.json
32
+
33
+ asar: true
34
+
35
+ mac:
36
+ category: public.app-category.developer-tools
37
+ icon: build-resources/icon.png
38
+ target:
39
+ - target: dmg
40
+ arch: [arm64, x64]
41
+ artifactName: Cloud-Bongos-Dev-Box-${version}-${arch}.${ext}
42
+
43
+ dmg:
44
+ artifactName: Cloud-Bongos-Dev-Box-${version}-${arch}.${ext}
45
+ # On-brand install window (task 912). The backdrop
46
+ # (build-resources/dmg-background.png + @2x, drawn by
47
+ # scripts/gen-dmg-backdrop.py) guides the drag from the container (the app)
48
+ # into Applications. iconSize + window + the contents coords MUST match
49
+ # the icon-well centers the backdrop draws (LEFT 180,198 / RIGHT 480,198) — if
50
+ # you move one, re-run the generator. The volume icon is the app icon (the
51
+ # container). electron-builder auto-picks dmg-background@2x.png on Retina.
52
+ #
53
+ # task 1235: this electron-builder DMG is only an INTERMEDIATE. Its window
54
+ # backdrop does NOT render on macOS >= Sonoma — electron-builder writes a
55
+ # `pBBk` bookmark Finder can't resolve, so the install window shows up white.
56
+ # The CI step "Apply install-window backdrop (dmgbuild)" re-packages the
57
+ # signed+notarized .app out of this DMG into the FINAL DMG via dmgbuild
58
+ # (scripts/apply-dmg-backdrop.sh + scripts/dmgbuild-settings.py), which writes
59
+ # an alias-only .DS_Store that DOES render. Keep the coords below in sync with
60
+ # dmgbuild-settings.py (currently identical: 180,198 / 480,198, 660x400, size 100).
61
+ icon: build-resources/icon.png
62
+ background: build-resources/dmg-background.png
63
+ iconSize: 100
64
+ window:
65
+ width: 660
66
+ height: 400
67
+ contents:
68
+ - x: 180
69
+ y: 198
70
+ type: file
71
+ - x: 480
72
+ y: 198
73
+ type: link
74
+ path: /Applications
75
+
76
+ win:
77
+ icon: build-resources/icon.png
78
+ target:
79
+ - target: nsis
80
+ # x64 covers Intel/AMD PCs; arm64 is native for ARM Windows (Surface,
81
+ # Snapdragon laptops, Parallels VMs on Apple-silicon Macs). x64 also
82
+ # RUNS on arm64 via Windows emulation, so x64 stays the safe default
83
+ # link; arm64 is the first-class build for those machines (#908).
84
+ arch: [x64, arm64]
85
+
86
+ nsis:
87
+ oneClick: true
88
+ perMachine: false
89
+ runAfterFinish: true
90
+ deleteAppDataOnUninstall: false
91
+ # ${arch} suffix added in #908 when arm64 joined — two arches can't share
92
+ # one filename. Must stay in lockstep with config/devbox-app.json assets.
93
+ artifactName: Cloud-Bongos-Dev-Box-Setup-${version}-${arch}.${ext}
94
+
95
+ publish: null