@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,286 @@
1
+ // modules/dev-box/app/scripts/gen-icons.js — generate the app + tray icons from
2
+ // pixel grids (no binary sources to maintain; re-run after edits: npm run icons).
3
+ //
4
+ // The MOTIF is a bongo pair — a large drum and a small one, joined, the emblem
5
+ // the neutral pack already draws in its favicon and the DMG backdrop. It is
6
+ // brand-NEUTRAL geometry: it says "this is the Dev Box", not "this is one
7
+ // particular host's world". (It replaces a shipping-container + wave-footer motif
8
+ // that belonged to one instance's lore and shipped into every build.)
9
+ //
10
+ // The COLOURS are a build input, not baked in — the posture gen-brand.js holds for
11
+ // the app's strings and gen-dmg-backdrop.py holds for the install screen (ADR 0145).
12
+ // The palette comes from the resolved pack's `theme.ui.colors`, so ONE source tree
13
+ // renders the vanilla Cloud Bongos icons here and a host instance's own icons there.
14
+ //
15
+ // ONE source, resolved exactly as gen-brand.js resolves it (docs/branding-contract.md):
16
+ // BONGOS_BRANDING_FILE -> config/branding.json -> config/branding.neutral.json
17
+ //
18
+ // Uses sharp from the REPO ROOT's node_modules (the art pipeline already
19
+ // depends on it) so this subproject keeps zero runtime deps of its own.
20
+ //
21
+ // Outputs (committed):
22
+ // build-resources/icon.png 1024×1024 app icon (electron-builder
23
+ // derives .icns/.ico from it)
24
+ // assets/tray/mac-<state>Template.png 16px mono template (+@2x 32px)
25
+ // assets/tray/win-<state>.png 32px colored
26
+ // States: off | on | busy | alert
27
+
28
+ const path = require('node:path');
29
+ const fs = require('node:fs');
30
+
31
+ // sharp is a repo-root devDep (the art pipeline already needs it), not a dep
32
+ // of this subproject — so resolve it from a few candidate roots rather than a
33
+ // single brittle relative path: normal resolution (NODE_PATH / a local
34
+ // install) first, then the repo root walked up from here. Fail with an
35
+ // actionable message instead of a raw MODULE_NOT_FOUND.
36
+ function resolveSharp() {
37
+ const candidates = [
38
+ 'sharp',
39
+ path.join(__dirname, '..', 'node_modules', 'sharp'),
40
+ path.join(__dirname, '..', '..', 'node_modules', 'sharp'),
41
+ ];
42
+ for (const c of candidates) {
43
+ try { return require(c); } catch { /* try the next root */ }
44
+ }
45
+ throw new Error(
46
+ "gen-icons: cannot find 'sharp'. Run `npm install` at the repo root " +
47
+ '(sharp is a root devDep shared with the art pipeline), then retry.'
48
+ );
49
+ }
50
+ const sharp = resolveSharp();
51
+
52
+ const APP_DIR = path.resolve(__dirname, '..'); // modules/dev-box/app
53
+ const REPO_ROOT = path.resolve(APP_DIR, '..', '..', '..'); // repo root
54
+ const OUT_TRAY = path.join(APP_DIR, 'assets', 'tray');
55
+ const OUT_BUILD = path.join(APP_DIR, 'build-resources');
56
+
57
+ // theme.ui.colors key → the value used when a pack omits it. Every fallback here
58
+ // is the NEUTRAL pack's own colour, so a literal in this file is never a brand.
59
+ //
60
+ // Every role MUST be one gen-brand.js's buildThemeCss emits into the app's :root —
61
+ // that CSS block is the surface a host pack author actually fills. A role outside
62
+ // it has nothing prompting a host to define it, so its "fallback" would paint a
63
+ // Cloud Bongos colour into a fully-specified host build.
64
+ const COLOR_ROLES = {
65
+ bgCard: '#ffffff', bgDeep: '#ffedd5', ink: '#2a1f17',
66
+ rule: '#fdba74', ruleSoft: '#fed7aa',
67
+ accent: '#f97316', accentDeep: '#ea580c', terracotta: '#fb7185',
68
+ };
69
+
70
+ // The pack, resolved exactly as gen-brand.js resolves it (env → instance → neutral).
71
+ //
72
+ // Deliberately NOT read from src/brand.generated.json, which also carries colours:
73
+ // that artifact is TRACKED holding the neutral values and nothing on the icon path
74
+ // regenerates it, so preferring it would let a stale checked-in file outrank the
75
+ // host's own pack. The pack decides, every time.
76
+ function readBranding() {
77
+ const candidates = [
78
+ process.env.BONGOS_BRANDING_FILE,
79
+ path.join(REPO_ROOT, 'config', 'branding.json'),
80
+ path.join(REPO_ROOT, 'config', 'branding.neutral.json'),
81
+ ].filter(Boolean);
82
+ for (const f of candidates) {
83
+ try { return { file: f, branding: JSON.parse(fs.readFileSync(f, 'utf8')) }; } catch { /* try next */ }
84
+ }
85
+ throw new Error('gen-icons: no branding file found (looked for config/branding.json, config/branding.neutral.json)');
86
+ }
87
+
88
+ // A pack colour, or the neutral fallback when the pack omits it or writes junk —
89
+ // one malformed hex must not abort a build over an icon.
90
+ function palette(branding) {
91
+ const colors = (((branding.theme || {}).ui) || {}).colors || {};
92
+ const out = {};
93
+ for (const [role, fallback] of Object.entries(COLOR_ROLES)) {
94
+ const v = String(colors[role] || '').trim();
95
+ out[role] = /^#[0-9a-fA-F]{3}(?:[0-9a-fA-F]{3})?$/.test(v) ? v : fallback;
96
+ }
97
+ return out;
98
+ }
99
+
100
+ // ---------------------------------------------------------------------------
101
+ // 16×16 tray grids. Legend: '.' transparent · '#' rim/shell · 'h' head · 'f' shell
102
+ // fill · 'm' state mark
103
+ //
104
+ // The bongo pair, three-quarter view: a large drum left, the small one joined at
105
+ // its right with its head sitting two rows lower. Two things make it read as DRUMS
106
+ // at 16px rather than boxes: the head is an oval that OVERHANGS the narrower shell
107
+ // (a flat top of the same size reads as a crate), and the profile is SQUAT — a
108
+ // bongo is about as wide as it is tall, so a tall shell reads as a jar.
109
+ //
110
+ // Every state keeps that silhouette and differs in SHAPE as well as colour,
111
+ // because the macOS template icons are monochrome: hollow (off) · filled (on) ·
112
+ // struck heads + beats overhead (busy) · a corner badge (alert).
113
+ // ---------------------------------------------------------------------------
114
+ const GRIDS = {
115
+ off: [
116
+ '................',
117
+ '................',
118
+ '................',
119
+ '................',
120
+ '..#######.......',
121
+ '.#.......#......',
122
+ '..#######.......',
123
+ '...#...#.#####..',
124
+ '...#...##.....#.',
125
+ '...#...#.#####..',
126
+ '...#...#.#...#..',
127
+ '...#...#.#...#..',
128
+ '...#####.#####..',
129
+ '................',
130
+ '................',
131
+ '................',
132
+ ],
133
+ on: [
134
+ '................',
135
+ '................',
136
+ '................',
137
+ '................',
138
+ '..#######.......',
139
+ '.#hhhhhhh#......',
140
+ '..#######.......',
141
+ '...#fff#.#####..',
142
+ '...#fff##hhhhh#.',
143
+ '...#fff#.#####..',
144
+ '...#fff#.#fff#..',
145
+ '...#fff#.#fff#..',
146
+ '...#####.#####..',
147
+ '................',
148
+ '................',
149
+ '................',
150
+ ],
151
+ busy: [
152
+ '................',
153
+ '................',
154
+ '....m...m...m...',
155
+ '................',
156
+ '..#######.......',
157
+ '.#hhhhhhh#......',
158
+ '..#######.......',
159
+ '...#...#.#####..',
160
+ '...#...##hhhhh#.',
161
+ '...#...#.#####..',
162
+ '...#...#.#...#..',
163
+ '...#...#.#...#..',
164
+ '...#####.#####..',
165
+ '................',
166
+ '................',
167
+ '................',
168
+ ],
169
+ alert: [
170
+ '............mmm.',
171
+ '............mmm.',
172
+ '............mmm.',
173
+ '................',
174
+ '..#######.......',
175
+ '.#.......#......',
176
+ '..#######.......',
177
+ '...#...#.#####..',
178
+ '...#...##.....#.',
179
+ '...#...#.#####..',
180
+ '...#...#.#...#..',
181
+ '...#...#.#...#..',
182
+ '...#####.#####..',
183
+ '................',
184
+ '................',
185
+ '................',
186
+ ],
187
+ };
188
+
189
+ function gridToSvg(grid, pal, cell = 1) {
190
+ const h = grid.length;
191
+ const w = grid[0].length;
192
+ const rects = [];
193
+ for (let y = 0; y < h; y++) {
194
+ for (let x = 0; x < w; x++) {
195
+ const color = pal[grid[y][x]];
196
+ if (!color) continue;
197
+ rects.push(`<rect x="${x * cell}" y="${y * cell}" width="${cell}" height="${cell}" fill="${color}"/>`);
198
+ }
199
+ }
200
+ return `<svg xmlns="http://www.w3.org/2000/svg" width="${w * cell}" height="${h * cell}" shape-rendering="crispEdges">${rects.join('')}</svg>`;
201
+ }
202
+
203
+ async function renderPng(svg, size, file) {
204
+ await sharp(Buffer.from(svg)).resize(size, size, { kernel: 'nearest' }).png().toFile(file);
205
+ console.log('wrote', path.relative(process.cwd(), file));
206
+ }
207
+
208
+ // The app icon: the pack's field + border, the bongo pair, a beat line at the foot.
209
+ // Built as a 32×32 grid then nearest-upscaled to 1024 so it keeps the pixel look at
210
+ // every size electron-builder derives.
211
+ function buildAppGrid() {
212
+ const rows = [];
213
+ for (let y = 0; y < 32; y++) rows.push('.'.repeat(32).split(''));
214
+ const put = (x, y, c) => { if (rows[y] && rows[y][x] !== undefined) rows[y][x] = c; };
215
+ // Radii are nudged by half a cell so the boundary row/column lands ON the grid
216
+ // instead of being clipped away — otherwise a 3-row oval renders as 1.
217
+ const oval = (cx, cy, rx, ry, c) => {
218
+ for (let y = cy - ry; y <= cy + ry; y++) for (let x = cx - rx; x <= cx + rx; x++) {
219
+ const dx = (x - cx) / (rx + 0.5);
220
+ const dy = (y - cy) / (ry + 0.5);
221
+ if (dx * dx + dy * dy <= 1) put(x, y, c);
222
+ }
223
+ };
224
+
225
+ for (let y = 2; y < 30; y++) for (let x = 2; x < 30; x++) put(x, y, 'p');
226
+ for (let x = 2; x < 30; x++) { put(x, 2, 'b'); put(x, 29, 'b'); }
227
+ for (let y = 2; y < 30; y++) { put(2, y, 'b'); put(29, y, 'b'); }
228
+
229
+ // One drum: a shell hanging from the head's centre line, then the head oval
230
+ // drawn over it — so the head overhangs the shell, the cue that reads as a drum.
231
+ const drum = (cx, cy, rx, ry, shellRx, yBase, shell) => {
232
+ for (let y = cy; y <= yBase; y++) for (let x = cx - shellRx; x <= cx + shellRx; x++) put(x, y, 'k');
233
+ for (let y = cy; y < yBase; y++) for (let x = cx - shellRx + 1; x < cx + shellRx; x++) put(x, y, shell);
234
+ oval(cx, cy, rx, ry, 'k');
235
+ oval(cx, cy, rx - 1, ry - 1, 'h');
236
+ };
237
+ drum(11, 12, 7, 3, 5, 24, 't'); // the large drum
238
+ drum(22, 15, 5, 3, 3, 24, 's'); // the small one, drawn second so it sits in front
239
+
240
+ // The beat: four even strokes across the foot — rhythm without a world-specific
241
+ // motif, echoing the DMG backdrop's beat line. Spanning the full width (rather
242
+ // than sitting under each shell) keeps them reading as a strip, not as legs.
243
+ for (let x = 5; x <= 23; x += 6) for (let dx = 0; dx < 3; dx++) { put(x + dx, 26, 'd'); put(x + dx, 27, 'd'); }
244
+
245
+ return rows.map((r) => r.join(''));
246
+ }
247
+
248
+ async function main() {
249
+ fs.mkdirSync(OUT_TRAY, { recursive: true });
250
+ fs.mkdirSync(OUT_BUILD, { recursive: true });
251
+
252
+ const { file, branding } = readBranding();
253
+ const p = palette(branding);
254
+
255
+ // macOS template: pure black + alpha, for EVERY state. This is an OS
256
+ // requirement, not a brand choice — the system recolours a *Template.png itself
257
+ // (menu-bar light/dark, inverted when selected), and any colour in the source
258
+ // is discarded. Branding one of these would be a bug, not a feature.
259
+ const MAC = { '#': '#000000', f: '#000000', h: '#000000', m: '#000000' };
260
+ // Windows: state via colour on a taskbar we don't control — it may be light OR
261
+ // dark, so every state outlines in the pack's DEEP accent, the one tone that
262
+ // holds contrast against both. The pale tints only ever appear as fill inside
263
+ // that outline, where the shape is already legible.
264
+ const WIN = {
265
+ off: { '#': p.accentDeep },
266
+ on: { '#': p.accentDeep, f: p.accent, h: p.bgDeep },
267
+ busy: { '#': p.accentDeep, h: p.accent, m: p.accent },
268
+ alert: { '#': p.accentDeep, m: p.terracotta },
269
+ };
270
+
271
+ for (const state of Object.keys(GRIDS)) {
272
+ const macSvg = gridToSvg(GRIDS[state], MAC);
273
+ await renderPng(macSvg, 16, path.join(OUT_TRAY, `mac-${state}Template.png`));
274
+ await renderPng(macSvg, 32, path.join(OUT_TRAY, `mac-${state}Template@2x.png`));
275
+ await renderPng(gridToSvg(GRIDS[state], WIN[state]), 32, path.join(OUT_TRAY, `win-${state}.png`));
276
+ }
277
+
278
+ const APP_PALETTE = {
279
+ p: p.bgCard, b: p.rule, k: p.ink,
280
+ t: p.accent, s: p.terracotta, h: p.ruleSoft, d: p.accentDeep,
281
+ };
282
+ await renderPng(gridToSvg(buildAppGrid(), APP_PALETTE), 1024, path.join(OUT_BUILD, 'icon.png'));
283
+ console.log(`gen-icons: palette ← ${path.relative(REPO_ROOT, file)}`);
284
+ }
285
+
286
+ main().catch((e) => { console.error(e); process.exit(1); });
@@ -0,0 +1,23 @@
1
+ // devbox-app/scripts/vendor-client.js — vendor the generated CJS API client
2
+ // into src/vendor/ so it ships INSIDE the packaged asar.
3
+ //
4
+ // The devbox app is a self-contained, zero-dependency subproject (ADR 0045): it
5
+ // is never `npm install`ed and electron-builder packages `src/**` only, so it
6
+ // cannot require() the repo's clients/bongos-client/ at runtime. This copies the
7
+ // committed CJS build in verbatim (byte-for-byte) so gds-client.js can require
8
+ // './vendor/bongos-client.cjs'. Vendoring a generated FILE (not an npm dep) keeps
9
+ // the subproject dependency-free. The main repo's api_client test asserts this
10
+ // copy stays byte-identical to the canonical build, so drift fails CI.
11
+ //
12
+ // Run: npm run vendor (also invoked before `start`, `test`, and `dist:*`).
13
+
14
+ const fs = require('node:fs');
15
+ const path = require('node:path');
16
+
17
+ const SRC = path.resolve(__dirname, '../../../../clients/bongos-client/index.cjs');
18
+ const DEST = path.resolve(__dirname, '../src/vendor/bongos-client.cjs');
19
+
20
+ const bytes = fs.readFileSync(SRC);
21
+ fs.mkdirSync(path.dirname(DEST), { recursive: true });
22
+ fs.writeFileSync(DEST, bytes);
23
+ console.log(`vendor-client: ${path.relative(process.cwd(), DEST)} (${bytes.length} bytes) ← clients/bongos-client/index.cjs`);
@@ -0,0 +1,287 @@
1
+ // devbox-app/src/box-controller.js — the app's state machine.
2
+ //
3
+ // Owns: the polled view of GET /box/me (+ /box/terminal), the on/off actions,
4
+ // poll cadence, and transition events (for notifications + tray updates). The
5
+ // pure decision helpers are exported separately so test/unit.mjs can pin them
6
+ // without Electron.
7
+ //
8
+ // Status vocabulary the UI/tray consume (derived, NOT the raw box.state):
9
+ // signed_out | loading | none | provisioning | active
10
+ // | closing | error
11
+ //
12
+ // Cost posture (deliberate, with one narrow exception): the app NEVER sends
13
+ // claude_active heartbeats, so the droplet's idle sweep stays authoritative — a
14
+ // box nobody is using is reclaimed in ~10–15 min and the app just reflects that.
15
+ // The exception (leg 3): while the app is ACTIVELY pointed at the box (panel open,
16
+ // or within HEARTBEAT_WINDOW_MS of a connect action) it sends BODY-LESS heartbeats
17
+ // that bump last_activity_at only — never claude_active. That stops the box reaping
18
+ // out from under a builder who is mid-connect (the vicious cycle where a failing
19
+ // connect lets the box rebuild with fresh host keys) WITHOUT keeping a genuinely
20
+ // idle box (app closed, no recent connect) alive. See shouldHeartbeat().
21
+
22
+ // Derive the UI status from a GET /box/me payload.
23
+ function deriveStatus(payload) {
24
+ if (!payload || !payload.box) return 'none';
25
+ const state = payload.box.state || 'none';
26
+ const intent = payload.open_intent || null;
27
+ if (intent && intent.action === 'deprovision') return 'closing';
28
+ if (intent && (intent.action === 'provision' || intent.action === 'wake')) return 'provisioning';
29
+ if (state === 'active') return 'active';
30
+ if (state === 'provisioning' || state === 'waking') return 'provisioning';
31
+ if (state === 'parking') return 'closing';
32
+ // parked = off-but-resumable; surfaced as plain "off" — the switch wakes it.
33
+ if (state === 'parked' || state === 'none' || state === 'destroyed') return 'none';
34
+ if (state === 'error') return 'error';
35
+ return 'none';
36
+ }
37
+
38
+ // Poll cadence by status (ms). Transitions poll fast; settled states slow.
39
+ function pollDelayMs(status) {
40
+ switch (status) {
41
+ case 'provisioning':
42
+ case 'closing': return 5_000;
43
+ case 'active': return 60_000;
44
+ case 'error': return 60_000;
45
+ default: return 120_000; // none / signed_out idle tick
46
+ }
47
+ }
48
+
49
+ // Leg 3 — connect-window keep-alive. The heartbeat timer ticks at this cadence,
50
+ // well under the box-side cron (5 min) and the idle threshold (10 min), so even a
51
+ // short connect window defers a reap.
52
+ const HEARTBEAT_TICK_MS = 120_000; // 2 min
53
+ // Keep beating for this long after a deliberate connect action (box on / terminal
54
+ // open) so retries during a flaky connect stay covered even if the panel is hidden.
55
+ const HEARTBEAT_WINDOW_MS = 600_000; // 10 min
56
+
57
+ // PURE (unit-tested): should the app send a keep-alive heartbeat right now? Only
58
+ // when the box is active AND the app is actively pointed at it — panel visible, or
59
+ // within HEARTBEAT_WINDOW_MS of a connect action. An idle box (app closed, no
60
+ // recent connect) returns false, so it still reaps on schedule (cost posture).
61
+ function shouldHeartbeat({ status, panelVisible, lastConnectActionAt, now }) {
62
+ if (status !== 'active') return false;
63
+ if (panelVisible) return true;
64
+ return Boolean(lastConnectActionAt) && (now - lastConnectActionAt) < HEARTBEAT_WINDOW_MS;
65
+ }
66
+
67
+ // Map a status transition to a user-facing notification (or null).
68
+ // `ctx.envName` = the Claude Desktop environment name.
69
+ function transitionNotification(prev, next, ctx = {}) {
70
+ if (prev === next) return null;
71
+ if (next === 'active') {
72
+ return {
73
+ title: 'Your dev box is ready',
74
+ body: ctx.settingsInstalled
75
+ ? `Restart Claude Desktop, then Code → Environment → “${ctx.envName}”. The browser terminal works too.`
76
+ : 'Connect from the app panel — browser terminal or SSH.',
77
+ };
78
+ }
79
+ if (prev === 'active' && next === 'none') {
80
+ return {
81
+ title: 'Your dev box closed',
82
+ body: 'It shut down (boxes rest after ~10 idle minutes, or when closed). Flip the switch to raise it again.',
83
+ };
84
+ }
85
+ if (next === 'error') {
86
+ return {
87
+ title: 'Your dev box hit an error',
88
+ body: 'Flip the switch to try again, or ask in #support on Discord.',
89
+ };
90
+ }
91
+ return null;
92
+ }
93
+
94
+ // createBoxController({ client, signedIn, onChange, onNotify, sleep? })
95
+ // onChange(snapshot) fires on every refresh; onNotify({title, body}) on
96
+ // meaningful transitions. Snapshot shape is what ipc.js ships to the renderer.
97
+ function createBoxController({ client, signedIn, onChange, onNotify, envName }) {
98
+ let status = 'loading';
99
+ let box = null;
100
+ let terminal = null; // { url, credential } when active+ready
101
+ let lastError = null;
102
+ let settingsInstalled = false; // set by main.js after ssh-setup runs
103
+ let hostKeys = null; // { ok, reason?, attempt? } — #909 pinning status
104
+ let keyRegistered = null; // { ok, reason, fingerprint, attempts } — task 1002509
105
+ let busy = false; // an ensure/close request is in flight
106
+ let timer = null;
107
+ let stopped = false;
108
+ let panelVisible = false; // leg 3: main.js pushes BrowserWindow show/hide
109
+ let lastConnectActionAt = 0; // leg 3: stamped on box-on / terminal-open
110
+ let hbTimer = null; // leg 3: keep-alive timer (separate from the poll)
111
+
112
+ function snapshot() {
113
+ return {
114
+ status: signedIn() ? status : 'signed_out',
115
+ busy,
116
+ box: box && {
117
+ state: box.state,
118
+ hostname: box.hostname,
119
+ size: box.size_slug,
120
+ provisionedAt: box.provisioned_at,
121
+ // task 1187 / idea 235: the box's GDS-reported PUBLIC host keys
122
+ // (`<keytype> <base64>` per line). main.js pins known_hosts from these
123
+ // (authoritative — no keyscan/TOFU) when present, else falls back to keyscan.
124
+ hostKeysText: box.host_keys || null,
125
+ // When those keys were reported, and when the CURRENT droplet came up. A
126
+ // report older than the activation belongs to a box that no longer exists,
127
+ // so main.js keyscans instead of pinning a dead identity (task 1002509).
128
+ hostKeysAt: box.host_keys_at || null,
129
+ activeSince: box.active_since || null,
130
+ },
131
+ terminal,
132
+ lastError,
133
+ settingsInstalled,
134
+ hostKeys,
135
+ keyRegistered,
136
+ };
137
+ }
138
+
139
+ function emit() { onChange(snapshot()); }
140
+
141
+ function schedule(ms) {
142
+ if (stopped) return;
143
+ clearTimeout(timer);
144
+ timer = setTimeout(() => refresh().catch(() => {}), ms ?? pollDelayMs(status));
145
+ // Don't hold the process open for a poll timer.
146
+ if (timer.unref) timer.unref();
147
+ }
148
+
149
+ // Leg 3: body-less keep-alive while the app is actively pointed at the box (see
150
+ // the cost-posture note up top). Best-effort — a failed beat just retries next
151
+ // tick; it never blocks or surfaces an error to the user.
152
+ function heartbeatTick() {
153
+ if (stopped || !signedIn()) return;
154
+ if (!shouldHeartbeat({ status, panelVisible, lastConnectActionAt, now: Date.now() })) return;
155
+ Promise.resolve(client.boxHeartbeat()).catch(() => {});
156
+ }
157
+
158
+ async function refresh() {
159
+ if (stopped) return snapshot();
160
+ if (!signedIn()) {
161
+ status = 'signed_out';
162
+ box = null; terminal = null;
163
+ emit();
164
+ return snapshot();
165
+ }
166
+ try {
167
+ const payload = await client.boxMe();
168
+ const prev = status;
169
+ box = payload.box && payload.box.state !== 'none' ? payload.box : null;
170
+ status = deriveStatus(payload);
171
+ lastError = null;
172
+ // Terminal link only matters when active; warming_up → keep polling.
173
+ if (status === 'active') {
174
+ try {
175
+ const t = await client.boxTerminal();
176
+ terminal = t && t.url ? { url: t.url, credential: t.credential } : null;
177
+ } catch { terminal = null; }
178
+ } else {
179
+ terminal = null;
180
+ hostKeys = null; // re-pinned on next activation (#909)
181
+ }
182
+ const note = transitionNotification(prev, status, { envName, settingsInstalled });
183
+ if (note && prev !== 'loading') {
184
+ onNotify(note);
185
+ }
186
+ if (note && status === 'active') {
187
+ // 'ready' fires even from a cold start (prev === 'loading') if the
188
+ // box JUST became active from this app's own ensure — handled by main.
189
+ }
190
+ } catch (err) {
191
+ if (err && err.status === 401) {
192
+ status = 'signed_out';
193
+ } else {
194
+ lastError = err.message || 'unreachable';
195
+ if (status === 'loading') status = 'error';
196
+ }
197
+ }
198
+ emit();
199
+ schedule();
200
+ return snapshot();
201
+ }
202
+
203
+ return {
204
+ snapshot,
205
+ refresh,
206
+ setSettingsInstalled(v) { settingsInstalled = v; emit(); },
207
+ setHostKeys(v) { hostKeys = v; emit(); },
208
+ // Registration outcome from ssh-setup.registerKeyWithVerify. Unlike hostKeys it
209
+ // is NOT cleared when the box goes inactive: the key lives on the builder's
210
+ // account, so a failure stays visible (and retryable) until it verifies.
211
+ setKeyRegistered(v) { keyRegistered = v; emit(); },
212
+ // Leg 3: main.js pushes panel visibility; connect actions stamp a window.
213
+ setPanelVisible(v) { panelVisible = !!v; },
214
+ markConnectAttempt() { lastConnectActionAt = Date.now(); },
215
+
216
+ // Flip ON: POST /box/ensure, then poll fast. Returns the server message.
217
+ async turnOn() {
218
+ lastConnectActionAt = Date.now(); // leg 3: turning the box on is a connect action
219
+ busy = true; lastError = null; emit();
220
+ try {
221
+ return await client.boxEnsure();
222
+ } catch (err) {
223
+ lastError = err.body && err.body.error === 'BOX_PROVISION_DENIED'
224
+ ? `Not allowed: ${err.body.reason || 'provision denied'}`
225
+ : (err.message || 'request failed');
226
+ throw err;
227
+ } finally {
228
+ busy = false;
229
+ await refresh();
230
+ }
231
+ },
232
+
233
+ // Flip OFF: POST /box/close (active only — the API 409s otherwise).
234
+ async turnOff() {
235
+ busy = true; lastError = null; emit();
236
+ try {
237
+ return await client.boxClose();
238
+ } catch (err) {
239
+ lastError = err.body && err.body.error === 'box_not_active'
240
+ ? 'The box is mid-change — wait for it to settle, then try again.'
241
+ : (err.message || 'request failed');
242
+ throw err;
243
+ } finally {
244
+ busy = false;
245
+ await refresh();
246
+ }
247
+ },
248
+
249
+ start() {
250
+ stopped = false;
251
+ refresh().catch(() => {});
252
+ // leg 3: a separate, unref'd keep-alive timer (gated by shouldHeartbeat).
253
+ if (!hbTimer) { hbTimer = setInterval(heartbeatTick, HEARTBEAT_TICK_MS); if (hbTimer.unref) hbTimer.unref(); }
254
+ },
255
+ stop() {
256
+ stopped = true;
257
+ clearTimeout(timer);
258
+ if (hbTimer) { clearInterval(hbTimer); hbTimer = null; }
259
+ },
260
+ };
261
+ }
262
+
263
+ // ttyd's HTTP-basic username is the fixed `otb` user (box-terminal.service:
264
+ // --credential "otb:<pass>"); the password is the long hex `credential`.
265
+ const TERMINAL_USER = 'otb';
266
+
267
+ // Browser-terminal URL with the basic-auth userinfo embedded, so opening it
268
+ // lands signed-in (browsers still honor user:pass@host for top-level
269
+ // navigation; ttyd auth is plain HTTP basic). Falls back to the bare URL if
270
+ // there's no credential or the URL doesn't parse.
271
+ function terminalAuthUrl(terminal) {
272
+ if (!terminal || !terminal.url) return null;
273
+ if (!terminal.credential) return terminal.url;
274
+ try {
275
+ const u = new URL(terminal.url);
276
+ u.username = TERMINAL_USER;
277
+ u.password = terminal.credential;
278
+ return u.toString();
279
+ } catch {
280
+ return terminal.url;
281
+ }
282
+ }
283
+
284
+ module.exports = {
285
+ createBoxController, deriveStatus, pollDelayMs, transitionNotification,
286
+ terminalAuthUrl, TERMINAL_USER, shouldHeartbeat,
287
+ };
@@ -0,0 +1,13 @@
1
+ {
2
+ "CIV_NAME": "Cloud Bongos",
3
+ "PRODUCT_NAME": "Cloud Bongos Dev Box",
4
+ "DESKTOP_ENV_NAME": "Cloud Bongos Dev Box",
5
+ "PUBLIC_ORIGIN": "http://localhost:3000",
6
+ "BUILDERS_ORIGIN": "http://localhost:3000",
7
+ "PRODUCT_SLUG": "Cloud-Bongos-Dev-Box",
8
+ "APP_ID": "com.cloudbongos.devbox",
9
+ "COPYRIGHT": "© Cloud Bongos",
10
+ "CONFIG_DIR": "cloudbongos",
11
+ "TAGLINE": "Building software is laying down a beat.",
12
+ "BG_COLOR": "#fff7ed"
13
+ }
@@ -0,0 +1,16 @@
1
+ // modules/dev-box/app/src/brand.js — the app's brand strings, in ONE place.
2
+ //
3
+ // GENERATED, not hardcoded: scripts/gen-brand.js writes src/brand.generated.json
4
+ // from the instance's config/branding.json at build time (prestart / pretest /
5
+ // predist), so the SAME source tree renders a "Cloud Bongos Dev Box" here and an
6
+ // "Amazonprimea Dev Box" on the OTB instance — no instance name is baked into
7
+ // code (the ADR 0004 posture, generalised to every instance). The generated file
8
+ // is committed (the Cloud Bongos default) so a fresh checkout runs without a
9
+ // generate step; every build regenerates it from the local branding.
10
+ //
11
+ // Keys: CIV_NAME (world/instance name, runtime display only), PRODUCT_NAME,
12
+ // DESKTOP_ENV_NAME, PUBLIC_ORIGIN, BUILDERS_ORIGIN, PRODUCT_SLUG (artifact
13
+ // filenames), APP_ID, COPYRIGHT, CONFIG_DIR (matches the CLI session dir),
14
+ // BG_COLOR (pre-CSS window bg), TAGLINE (panel epigraph).
15
+
16
+ module.exports = require('./brand.generated.json');
@@ -0,0 +1,19 @@
1
+ // devbox-app/src/config.js — runtime configuration resolution.
2
+
3
+ const brand = require('./brand');
4
+
5
+ function apiBase(sessionApiBase) {
6
+ // Priority: explicit env (dev/staging) → the base recorded in the session
7
+ // file at sign-in → production default. Trailing slashes are stripped so
8
+ // path concatenation stays uniform.
9
+ const raw = process.env.OTB_DEVBOX_API_BASE || sessionApiBase || brand.PUBLIC_ORIGIN;
10
+ return String(raw).replace(/\/+$/, '');
11
+ }
12
+
13
+ function appVersion() {
14
+ // package.json is inside the asar in production; require() resolves it both
15
+ // packed and in dev.
16
+ return require('../package.json').version;
17
+ }
18
+
19
+ module.exports = { apiBase, appVersion };