@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,82 @@
1
+ #!/usr/bin/env bash
2
+ # Re-package electron-builder's mac DMGs so the install-window backdrop renders
3
+ # (task 1235). See scripts/dmgbuild-settings.py for the root cause: Finder on
4
+ # macOS >= Sonoma can't resolve the `pBBk` bookmark electron-builder writes, so
5
+ # its DMG shows a white window; dmgbuild's alias-only .DS_Store resolves and the
6
+ # backdrop (task 912) draws.
7
+ #
8
+ # For each dist/<ProductSlug>-<ver>-<arch>.dmg this:
9
+ # 1. mounts it read-only and copies the .app out with `ditto` (preserves the
10
+ # Developer ID signature + the stapled notarization ticket verbatim),
11
+ # 2. rebuilds the DMG with dmgbuild + the on-brand backdrop,
12
+ # 3. overwrites the original in place.
13
+ # The .app is byte-identical, so it stays notarized; the DMG is an unsigned
14
+ # carrier either way (verified: the published DMGs were never DMG-signed, only
15
+ # the app inside is). dmgbuild needs no GUI/Finder, so this is CI-safe (headless).
16
+ #
17
+ # Run from the devbox-app/ directory: bash scripts/apply-dmg-backdrop.sh [distDir]
18
+ set -euo pipefail
19
+
20
+ DIST="${1:-dist}"
21
+ RES="build-resources"
22
+ WORK="$(mktemp -d)"
23
+ trap 'rm -rf "$WORK"' EXIT
24
+
25
+ if [ ! -f "$RES/dmg-background.png" ] || [ ! -f "$RES/dmg-background@2x.png" ]; then
26
+ echo "ERROR: backdrop art missing in $RES (run scripts/gen-dmg-backdrop.py)" >&2
27
+ exit 1
28
+ fi
29
+
30
+ # Crisp on Retina: fold the 1x + 2x PNGs into a single multi-rep hidpi TIFF.
31
+ tiffutil -cathidpicheck "$RES/dmg-background.png" "$RES/dmg-background@2x.png" \
32
+ -out "$WORK/backdrop.tiff" >/dev/null
33
+
34
+ # Isolated, headless dmgbuild (writes .DS_Store directly; no Finder dependency).
35
+ python3 -m venv "$WORK/venv"
36
+ "$WORK/venv/bin/pip" install --quiet --disable-pip-version-check dmgbuild
37
+
38
+ # Brand comes from the generated brand (ADR 0145) so this re-packages whatever
39
+ # the branding-driven build produced — Cloud-Bongos-Dev-Box-*.dmg here, any
40
+ # instance's slug elsewhere — never a hardcoded product name.
41
+ SLUG="$(python3 -c 'import json;print(json.load(open("src/brand.generated.json"))["PRODUCT_SLUG"])')"
42
+ PNAME="$(python3 -c 'import json;print(json.load(open("src/brand.generated.json"))["PRODUCT_NAME"])')"
43
+
44
+ shopt -s nullglob
45
+ count=0
46
+ for DMG in "$DIST"/"$SLUG"-*.dmg; do
47
+ base="$(basename "$DMG" .dmg)" # e.g. Cloud-Bongos-Dev-Box-1.0.10-arm64
48
+ suffix="${base#"$SLUG"-}" # e.g. 1.0.10-arm64
49
+ volname="$PNAME $suffix" # e.g. Cloud Bongos Dev Box 1.0.10-arm64
50
+
51
+ mnt="$WORK/mnt-$suffix"
52
+ mkdir -p "$mnt"
53
+ hdiutil attach "$DMG" -nobrowse -readonly -mountpoint "$mnt" -owners off >/dev/null
54
+
55
+ app="$(/bin/ls -d "$mnt"/*.app 2>/dev/null | head -1)"
56
+ if [ -z "$app" ]; then
57
+ echo "ERROR: no .app inside $DMG" >&2
58
+ hdiutil detach "$mnt" >/dev/null 2>&1 || true
59
+ exit 1
60
+ fi
61
+ appname="$(basename "$app")"
62
+ ditto "$app" "$WORK/$appname"
63
+ hdiutil detach "$mnt" >/dev/null
64
+
65
+ out="$WORK/out-$suffix.dmg"
66
+ "$WORK/venv/bin/dmgbuild" \
67
+ -s scripts/dmgbuild-settings.py \
68
+ -D app="$WORK/$appname" \
69
+ -D bg="$WORK/backdrop.tiff" \
70
+ "$volname" "$out" >/dev/null
71
+
72
+ mv -f "$out" "$DMG"
73
+ rm -rf "$WORK/$appname"
74
+ echo " backdrop applied: $(basename "$DMG") (volume \"$volname\")"
75
+ count=$((count + 1))
76
+ done
77
+
78
+ if [ "$count" -eq 0 ]; then
79
+ echo "ERROR: no ${SLUG}-*.dmg found in $DIST" >&2
80
+ exit 1
81
+ fi
82
+ echo "Re-packaged $count DMG(s) with the install-window backdrop."
@@ -0,0 +1,39 @@
1
+ # dmgbuild settings for the OTB Dev Box install window (task 1235).
2
+ #
3
+ # WHY dmgbuild and not electron-builder's own DMG styling:
4
+ # electron-builder writes a .DS_Store whose background reference includes a
5
+ # modern security-scoped bookmark (`pBBk`). Finder on macOS >= Sonoma/Sequoia
6
+ # (verified white on 15.7.4) cannot resolve that bookmark, so it falls back to a
7
+ # solid white window — the embedded Greek-coast backdrop (task 912) is present
8
+ # but never drawn. dmgbuild writes an alias-only `.DS_Store` (no `pBBk`) that
9
+ # Finder DOES resolve, so the backdrop renders. The OS is not the problem; the
10
+ # bookmark electron-builder emits is. scripts/apply-dmg-backdrop.sh re-packages
11
+ # electron-builder's signed+notarized .app into a dmgbuild DMG using this file.
12
+ #
13
+ # Coordinates MUST match the icon-well centers drawn by gen-dmg-backdrop.py
14
+ # (LEFT 180,198 / RIGHT 480,198) and the 660x400 art — change one, change all.
15
+ # `app` and `bg` are passed in via dmgbuild -D defines.
16
+
17
+ import os.path
18
+
19
+ # -D app=<path to OTB Dev Box.app> -D bg=<path to the hidpi backdrop tiff>
20
+ application = defines["app"]
21
+ backdrop = defines["bg"]
22
+ appname = os.path.basename(application) # "OTB Dev Box.app"
23
+
24
+ # Contents of the disk image.
25
+ files = [application]
26
+ symlinks = {"Applications": "/Applications"}
27
+
28
+ # Window + icon layout — matched to the backdrop art's two marble wells.
29
+ background = backdrop
30
+ window_rect = ((150, 150), (660, 400)) # ((x, y), (w, h)); w/h match the 660x400 art
31
+ icon_size = 100
32
+ text_size = 12
33
+ icon_locations = {
34
+ appname: (180, 198), # the container (app) — left well
35
+ "Applications": (480, 198), # Applications — right well
36
+ }
37
+
38
+ # Compressed, read-only final image (same distribution format as before).
39
+ format = "UDZO"
@@ -0,0 +1,138 @@
1
+ // modules/dev-box/app/scripts/gen-brand.js — make the Dev Box app branding-driven.
2
+ //
3
+ // The app is a self-contained Electron subproject (ADR 0045) built separately
4
+ // from the server, so it can't read the instance branding at runtime. Instead
5
+ // this reads the instance's config/branding.json (the SAME contract the server
6
+ // resolves — docs/branding-contract.md) at BUILD time and emits three generated
7
+ // artifacts the app + electron-builder consume:
8
+ //
9
+ // src/brand.generated.json — every user-visible string + appId/slug/colors
10
+ // renderer/theme.generated.css — the :root design tokens (palette + fonts)
11
+ // electron-builder.yml — rendered from electron-builder.tmpl.yml
12
+ //
13
+ // So ONE source tree builds a "Cloud Bongos Dev Box" here and an "Amazonprimea
14
+ // Dev Box" on the OTB instance — the brand is a build input, not baked into code
15
+ // (the ADR 0004 posture, generalised: no instance's name is hardcoded anywhere).
16
+ //
17
+ // Run: npm run gen-brand (also invoked before start / test / dist:*).
18
+ // Resolution: BONGOS_BRANDING_FILE env → <repoRoot>/config/branding.json →
19
+ // <repoRoot>/config/branding.neutral.json (the Cloud Bongos pack).
20
+
21
+ const fs = require('node:fs');
22
+ const path = require('node:path');
23
+
24
+ const APP_DIR = path.resolve(__dirname, '..'); // modules/dev-box/app
25
+ const REPO_ROOT = path.resolve(APP_DIR, '..', '..', '..'); // repo root (…/app → …/dev-box → …/modules → root)
26
+
27
+ function readBranding() {
28
+ const candidates = [
29
+ process.env.BONGOS_BRANDING_FILE,
30
+ path.join(REPO_ROOT, 'config', 'branding.json'),
31
+ path.join(REPO_ROOT, 'config', 'branding.neutral.json'),
32
+ ].filter(Boolean);
33
+ for (const f of candidates) {
34
+ try {
35
+ const b = JSON.parse(fs.readFileSync(f, 'utf8'));
36
+ return { file: f, branding: b };
37
+ } catch { /* try next */ }
38
+ }
39
+ throw new Error('gen-brand: no branding file found (looked for config/branding.json, config/branding.neutral.json)');
40
+ }
41
+
42
+ // "Cloud Bongos" → "cloudbongos" (appId segment); "Cloud Bongos Dev Box" →
43
+ // "Cloud-Bongos-Dev-Box" (artifact filename — no spaces, ADR 0004).
44
+ const idSlug = (s) => String(s || '').toLowerCase().replace(/[^a-z0-9]+/g, '') || 'instance';
45
+ const fileSlug = (s) => String(s || '').trim().replace(/[^A-Za-z0-9]+/g, '-').replace(/^-+|-+$/g, '') || 'Dev-Box';
46
+
47
+ function buildBrand(branding) {
48
+ const id = branding.identity || {};
49
+ const dom = branding.domains || {};
50
+ const ui = (branding.theme && branding.theme.ui) || {};
51
+ const colors = ui.colors || {};
52
+ const world = id.worldName || id.productName || 'Cloud Bongos';
53
+ const company = (id.company && id.company.name) || world;
54
+ const productName = `${world} Dev Box`;
55
+ return {
56
+ // Rendered-at-runtime display strings (brand.js re-exports these).
57
+ CIV_NAME: world,
58
+ PRODUCT_NAME: productName,
59
+ DESKTOP_ENV_NAME: productName,
60
+ // Origins the built app pairs against. A release BUILD overrides these via
61
+ // env (DEVBOX_PUBLIC_ORIGIN/_BUILDERS_ORIGIN — set from CI repo variables)
62
+ // so the neutral core stays neutral (localhost) yet a cloudbongos.com build
63
+ // bakes cloudbongos.com — without committing a host origin to the core.
64
+ PUBLIC_ORIGIN: process.env.DEVBOX_PUBLIC_ORIGIN || dom.publicOrigin || 'http://localhost:3000',
65
+ BUILDERS_ORIGIN: process.env.DEVBOX_BUILDERS_ORIGIN || dom.buildersOrigin || dom.publicOrigin || 'http://localhost:3000',
66
+ // Build + packaging inputs.
67
+ PRODUCT_SLUG: fileSlug(productName),
68
+ APP_ID: `com.${idSlug(world)}.devbox`,
69
+ COPYRIGHT: `© ${company}`,
70
+ // Local integration: match the instance's CLI config dir so pairing the app
71
+ // also signs in the /builder-* CLI on this machine (session-store.js).
72
+ CONFIG_DIR: branding.configDir || 'cloudbongos',
73
+ // Panel header epigraph (branding copy.tagline).
74
+ TAGLINE: (branding.copy && branding.copy.tagline) || '',
75
+ // Pre-CSS window background (main.js) — the theme bg so first paint is on-brand.
76
+ BG_COLOR: colors.bg || '#ffffff',
77
+ };
78
+ }
79
+
80
+ // The renderer :root tokens — mirror renderer/styles.css var names.
81
+ function buildThemeCss(branding) {
82
+ const ui = (branding.theme && branding.theme.ui) || {};
83
+ const c = ui.colors || {};
84
+ const fonts = ui.fonts || {};
85
+ const v = (x, fb) => x || fb;
86
+ return `/* GENERATED by scripts/gen-brand.js from config/branding.json — do not edit by hand. */
87
+ :root {
88
+ --bg: ${v(c.bg, '#fff7ed')};
89
+ --bg-card: ${v(c.bgCard, '#ffffff')};
90
+ --bg-deep: ${v(c.bgDeep, '#ffedd5')};
91
+ --ink: ${v(c.ink, '#2a1f17')};
92
+ --ink-soft: ${v(c.inkSoft, '#6b5d52')};
93
+ --ink-faint: ${v(c.inkFaint, '#857567')};
94
+ --rule: ${v(c.rule, '#fdba74')};
95
+ --rule-soft: ${v(c.ruleSoft, '#fed7aa')};
96
+ --accent: ${v(c.accent, '#f97316')};
97
+ --accent-deep: ${v(c.accentDeep, '#ea580c')};
98
+ --terracotta: ${v(c.terracotta, '#fb7185')};
99
+ --font-display: ${v(fonts.display, '"Baloo 2", "Quicksand", system-ui, sans-serif')};
100
+ --font-body: ${v(fonts.body, '"Quicksand", system-ui, -apple-system, "Segoe UI", sans-serif')};
101
+ }
102
+ `;
103
+ }
104
+
105
+ function renderElectronBuilder(brand) {
106
+ const tmplPath = path.join(APP_DIR, 'electron-builder.tmpl.yml');
107
+ const tmpl = fs.readFileSync(tmplPath, 'utf8');
108
+ return tmpl
109
+ .replace(/@@APP_ID@@/g, brand.APP_ID)
110
+ .replace(/@@PRODUCT_NAME@@/g, brand.PRODUCT_NAME)
111
+ .replace(/@@COPYRIGHT@@/g, brand.COPYRIGHT)
112
+ .replace(/@@PRODUCT_SLUG@@/g, brand.PRODUCT_SLUG);
113
+ }
114
+
115
+ function main() {
116
+ const { file, branding } = readBranding();
117
+ const brand = buildBrand(branding);
118
+
119
+ const brandOut = path.join(APP_DIR, 'src', 'brand.generated.json');
120
+ fs.writeFileSync(brandOut, `${JSON.stringify(brand, null, 2)}\n`);
121
+
122
+ const themeOut = path.join(APP_DIR, 'renderer', 'theme.generated.css');
123
+ fs.writeFileSync(themeOut, buildThemeCss(branding));
124
+
125
+ const ebOut = path.join(APP_DIR, 'electron-builder.yml');
126
+ fs.writeFileSync(ebOut, renderElectronBuilder(brand));
127
+
128
+ const rel = (p) => path.relative(REPO_ROOT, p);
129
+ console.log(`gen-brand: ${brand.PRODUCT_NAME} (${brand.APP_ID}) ← ${rel(file)}`);
130
+ console.log(` wrote ${rel(brandOut)}, ${rel(themeOut)}, ${rel(ebOut)}`);
131
+ }
132
+
133
+ if (require.main === module) main();
134
+
135
+ // Exported so a test can call the PURE builders (tests/devbox_installer_brand.mjs
136
+ // gates the committed artifacts against them). Importing must never write —
137
+ // hence the require.main guard above.
138
+ module.exports = { readBranding, buildBrand, buildThemeCss, renderElectronBuilder, main };
@@ -0,0 +1,323 @@
1
+ #!/usr/bin/env python3
2
+ """Draw the DMG install-window backdrop from the INSTANCE BRANDING (task 912, 1002372).
3
+
4
+ The Mac .dmg install window shows two icons — the app on the left and an
5
+ Applications-folder alias on the right — and the user drags one onto the other.
6
+ This draws the Finder-window BACKDROP behind that gesture.
7
+
8
+ Branding is a BUILD INPUT, not baked in — the same posture gen-brand.js already
9
+ holds for the app's strings (ADR 0145). The palette comes from the pack's
10
+ `theme.ui.colors`, the headline names the product the build actually ships, the
11
+ subhead is the pack's tagline, and the type is the pack's own font stack. So ONE
12
+ source tree renders the vanilla Cloud Bongos sunrise here and a host instance's
13
+ palette + voice there: no hex, motif, or sentence belongs to a single brand.
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
+ Palette, type, product name and tagline all come from that file, so a render can
18
+ never mix one pack's colours with another's words.
19
+
20
+ A pack that wants different words states them outright as `copy.dmgHeadline` /
21
+ `copy.dmgSubhead`; otherwise both lines are derived, so a new instance needs no
22
+ extra config to get a correct install screen.
23
+
24
+ Draws DIRECTLY with Pillow (no SVG renderer needed in CI/headless) and
25
+ supersamples 2x then LANCZOS-downscales so text + edges read crisply at Retina.
26
+ Outputs:
27
+ build-resources/dmg-background.png 660x400 (@1x)
28
+ build-resources/dmg-background@2x.png 1320x800 (@2x; electron-builder auto-picks)
29
+
30
+ The icon SLOTS are left clear — electron-builder / dmgbuild place the real app +
31
+ Applications icons over them at the coords mirrored in scripts/dmgbuild-settings.py;
32
+ this art only draws the guidance AROUND them (wells, the beat line, copy).
33
+
34
+ Pillow is an ad-hoc dependency: the repo declares no Python manifest (the sibling
35
+ scripts/apply-dmg-backdrop.sh installs its own `dmgbuild` the same way), so run
36
+ this from a throwaway venv with `pip install Pillow`.
37
+
38
+ Run: python3 modules/dev-box/app/scripts/gen-dmg-backdrop.py
39
+ Re-run after any branding/layout/copy change; commit the two PNGs. Text renders in
40
+ the first face of the pack's stack that is INSTALLED on the rendering machine, so
41
+ a re-render on a different OS may substitute a different fallback face.
42
+ """
43
+ from __future__ import annotations # `X | None` hints must not run on macOS's system python 3.9
44
+
45
+ import json
46
+ import os
47
+ from dataclasses import dataclass
48
+ from typing import Union
49
+
50
+ from PIL import Image, ImageDraw, ImageFont
51
+
52
+ RGB = tuple[int, int, int]
53
+ Face = Union[ImageFont.FreeTypeFont, ImageFont.ImageFont] # a truetype face, or Pillow's built-in
54
+
55
+ APP_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) # modules/dev-box/app
56
+ REPO_ROOT = os.path.abspath(os.path.join(APP_DIR, "..", "..", ".."))
57
+
58
+ W, H = 660, 400 # the DMG window size (must match electron-builder.yml dmg.window)
59
+ SS = 2 # supersample factor for crisp anti-aliased output
60
+ LEFT = (180, 198) # app-icon slot center (matches dmg.contents[0])
61
+ RIGHT = (480, 198) # Applications slot center (matches dmg.contents[1])
62
+ ICON = 100 # dmg.iconSize
63
+
64
+ # theme.ui.colors keys → the roles this art draws, with the neutral pack's values
65
+ # as fallbacks so a partial instance pack still renders something coherent.
66
+ # Every role here MUST be one the app's own brand surface emits (gen-brand.js's
67
+ # buildThemeCss) — a role outside it has nothing prompting a host pack to define
68
+ # it, so its "fallback" would paint a Cloud Bongos colour into a fully-specified
69
+ # host build. That is exactly what `sea` did to the Applications-well rim.
70
+ COLOR_ROLES = {
71
+ "bg": "#fff7ed", "bgDeep": "#ffedd5", "bgCard": "#ffffff",
72
+ "ink": "#2a1f17", "inkSoft": "#6b5d52", "ruleSoft": "#fed7aa",
73
+ "accent": "#f97316", "accentDeep": "#ea580c", "terracotta": "#fb7185",
74
+ }
75
+
76
+ FONT_DIRS = (
77
+ "/System/Library/Fonts/Supplemental", "/Library/Fonts", "/System/Library/Fonts",
78
+ os.path.join(os.environ.get("WINDIR", "C:/Windows"), "Fonts"),
79
+ "/usr/share/fonts/truetype/dejavu",
80
+ )
81
+ # Tried only when none of the pack's own families is installed — a web-font stack
82
+ # ("Baloo 2", "Quicksand") usually isn't present on the machine doing the render.
83
+ GENERIC_BOLD = ("Arial Bold.ttf", "Helvetica.ttc", "segoeuib.ttf", "arialbd.ttf", "DejaVuSans-Bold.ttf")
84
+ GENERIC_REGULAR = ("Arial.ttf", "Helvetica.ttc", "segoeui.ttf", "arial.ttf", "DejaVuSans.ttf")
85
+ # CSS keywords name no file, so they fall through to the generic faces above.
86
+ CSS_GENERIC_FAMILIES = {"system-ui", "-apple-system", "sans-serif", "serif", "monospace", "ui-sans-serif"}
87
+
88
+
89
+ @dataclass(frozen=True)
90
+ class Art:
91
+ """Everything the render pulls from the instance pack — resolved once, then drawn."""
92
+ palette: dict[str, RGB]
93
+ headline: str
94
+ subhead: str
95
+ display_stack: str
96
+ body_stack: str
97
+
98
+
99
+ def read_json(path: str) -> dict | None:
100
+ try:
101
+ with open(path, encoding="utf-8") as fh:
102
+ return json.load(fh)
103
+ except (OSError, ValueError):
104
+ return None
105
+
106
+
107
+ def read_branding() -> dict:
108
+ """The pack, resolved exactly as gen-brand.js resolves it (env → instance → neutral)."""
109
+ for path in (os.environ.get("BONGOS_BRANDING_FILE"),
110
+ os.path.join(REPO_ROOT, "config", "branding.json"),
111
+ os.path.join(REPO_ROOT, "config", "branding.neutral.json")):
112
+ pack = read_json(path) if path else None
113
+ if pack is not None:
114
+ return pack
115
+ raise SystemExit("gen-dmg-backdrop: no branding pack found (is config/branding.neutral.json missing?)")
116
+
117
+
118
+ def brand_strings(branding: dict) -> dict:
119
+ """Product name + tagline, derived from the resolved pack exactly as gen-brand.js's
120
+ buildBrand derives them (`<worldName> Dev Box`, `copy.tagline`).
121
+
122
+ Deliberately NOT read out of gen-brand.js's src/brand.generated.json, which carries
123
+ the same two fields: that artifact is COMMITTED holding the neutral values and
124
+ nothing on this path regenerates it, so preferring it would let a stale checked-in
125
+ file outrank the host's own pack — the render would draw the host palette and type
126
+ under the neutral product's name. The pack decides, every time."""
127
+ identity = branding.get("identity") or {}
128
+ world = identity.get("worldName") or identity.get("productName") or "Cloud Bongos"
129
+ return {
130
+ "product": f"{world} Dev Box",
131
+ "tagline": (branding.get("copy") or {}).get("tagline") or "",
132
+ }
133
+
134
+
135
+ def copy_lines(branding: dict, brand: dict) -> tuple[str, str]:
136
+ """Headline + subhead. Derived from the instance's own name and tagline; a pack
137
+ whose voice needs different words states them as copy.dmgHeadline/dmgSubhead."""
138
+ copy = branding.get("copy") or {}
139
+ headline = copy.get("dmgHeadline") or f"Drag {brand['product']} to Applications"
140
+ subhead = copy.get("dmgSubhead")
141
+ return headline, brand["tagline"] if subhead is None else subhead
142
+
143
+
144
+ def hexrgb(value: str | None, fallback: str) -> RGB:
145
+ """A pack colour, or the neutral fallback when the pack omits it or writes junk —
146
+ one malformed hex must not abort a build over a decorative backdrop."""
147
+ for candidate in (value, fallback):
148
+ raw = str(candidate or "").lstrip("#")
149
+ if len(raw) == 3:
150
+ raw = "".join(c * 2 for c in raw)
151
+ if len(raw) == 6:
152
+ try:
153
+ return (int(raw[0:2], 16), int(raw[2:4], 16), int(raw[4:6], 16))
154
+ except ValueError:
155
+ continue
156
+ return (0, 0, 0)
157
+
158
+
159
+ def palette(branding: dict) -> dict[str, RGB]:
160
+ ui = (branding.get("theme") or {}).get("ui") or {}
161
+ colors = ui.get("colors") or {}
162
+ return {role: hexrgb(colors.get(role), fallback) for role, fallback in COLOR_ROLES.items()}
163
+
164
+
165
+ def font_files(stack: str, bold: bool) -> list[str]:
166
+ """A CSS family stack → font FILENAMES to try, in the pack's own preference order.
167
+ Covers both the macOS ("Georgia Bold.ttf") and Windows ("georgiab.ttf") naming."""
168
+ names: list[str] = []
169
+ for raw in str(stack or "").split(","):
170
+ family = raw.strip().strip("\"'")
171
+ if not family or family in CSS_GENERIC_FAMILIES:
172
+ continue
173
+ flat = family.replace(" ", "").lower()
174
+ if bold:
175
+ names += [f"{family} Bold.ttf", f"{flat}b.ttf", f"{flat}bd.ttf"]
176
+ names += [f"{family}.ttf", f"{flat}.ttf"]
177
+ return names + list(GENERIC_BOLD if bold else GENERIC_REGULAR)
178
+
179
+
180
+ def font(stack: str, size: int, bold: bool = False) -> Face:
181
+ for name in font_files(stack, bold):
182
+ for directory in FONT_DIRS:
183
+ path = os.path.join(directory, name)
184
+ if os.path.exists(path):
185
+ return ImageFont.truetype(path, size)
186
+ return ImageFont.load_default(size)
187
+
188
+
189
+ def lerp(a: RGB, b: RGB, t: float) -> RGB:
190
+ return tuple(round(a[i] + (b[i] - a[i]) * t) for i in range(3))
191
+
192
+
193
+ def vgradient(draw: ImageDraw.ImageDraw, x0: int, y0: int, x1: int, y1: int, c_top: RGB, c_bot: RGB) -> None:
194
+ h = max(1, y1 - y0)
195
+ for y in range(y0, y1):
196
+ draw.line([(x0, y), (x1, y)], fill=lerp(c_top, c_bot, (y - y0) / h))
197
+
198
+
199
+ def text_width(draw: ImageDraw.ImageDraw, text: str, fnt: Face) -> float:
200
+ left, _, right, _ = draw.textbbox((0, 0), text, font=fnt)
201
+ return right - left
202
+
203
+
204
+ def centered(draw: ImageDraw.ImageDraw, cx: float, y: float, text: str,
205
+ fnt: Face, fill: RGB) -> None:
206
+ draw.text((cx - text_width(draw, text, fnt) / 2, y), text, font=fnt, fill=fill)
207
+
208
+
209
+ def fitted_font(draw: ImageDraw.ImageDraw, text: str, stack: str, size: int, max_w: float) -> Face:
210
+ """Shrink the headline until it fits — an instance's product name is arbitrary
211
+ length, and a name that runs off the 660px art would ship a broken installer."""
212
+ fnt = font(stack, size, bold=True)
213
+ while size > 12 and text_width(draw, text, fnt) > max_w:
214
+ size -= 1
215
+ fnt = font(stack, size, bold=True)
216
+ return fnt
217
+
218
+
219
+ def cloud_band(draw: ImageDraw.ImageDraw, base_y: float, scale: int, fill: RGB) -> None:
220
+ # A soft cloud shelf across the foot: a flat fill topped by overlapping discs,
221
+ # so the band reads as one puffy silhouette and grounds the two icon wells.
222
+ y = base_y * scale
223
+ draw.rectangle([0, y, W * scale, H * scale], fill=fill)
224
+ x = -12.0 * scale
225
+ i = 0
226
+ while x < (W + 40) * scale:
227
+ r = (11 + 5 * ((i * 3) % 4)) * scale
228
+ draw.ellipse([x - r, y - r, x + r, y + r], fill=fill)
229
+ x += r * 1.15
230
+ i += 1
231
+
232
+
233
+ def bongo_mark(draw: ImageDraw.ImageDraw, center: tuple[float, float], scale: int,
234
+ big: RGB, small: RGB, head: RGB) -> None:
235
+ # The mark: a drum pair seen from above, mirroring the neutral pack's favicon
236
+ # geometry but coloured FROM the resolved pack — so it recolours per instance
237
+ # rather than stamping one brand's emblem into every build.
238
+ cx, cy = center[0] * scale, center[1] * scale
239
+ for dx, radius, body in ((-8.0, 13.0, big), (9.0, 10.0, small)):
240
+ x = cx + dx * scale
241
+ r = radius * scale
242
+ draw.ellipse([x - r, cy - r, x + r, cy + r], fill=body)
243
+ inner = r * 0.44
244
+ draw.ellipse([x - inner, cy - inner, x + inner, cy + inner], fill=head)
245
+
246
+
247
+ def well(draw: ImageDraw.ImageDraw, center: tuple[int, int], scale: int, face: RGB, rim: RGB) -> None:
248
+ # A soft raised disc the icon sits on — a filled card with concentric rings
249
+ # fading into it, so the placed icon reads as resting ON the surface.
250
+ cx, cy = center[0] * scale, center[1] * scale
251
+ rad = (ICON / 2 + 18) * scale
252
+ draw.ellipse([cx - rad, cy - rad, cx + rad, cy + rad], fill=face)
253
+ for i in range(6):
254
+ r = rad - i * 1.6 * scale
255
+ draw.ellipse([cx - r, cy - r, cx + r, cy + r], outline=lerp(rim, face, i / 5),
256
+ width=max(1, int(1.5 * scale)))
257
+
258
+
259
+ def beat_line(draw: ImageDraw.ImageDraw, p0: tuple[float, float], p1: tuple[float, float],
260
+ scale: int, dot: RGB, tip: RGB) -> None:
261
+ # The drag guidance: beats swelling toward a chevron. Direction without a
262
+ # world-specific motif — it reads the same whatever the instance is building.
263
+ x0, y0 = p0[0] * scale, p0[1] * scale
264
+ x1, y1 = p1[0] * scale, p1[1] * scale
265
+ beats = 6
266
+ for i in range(beats):
267
+ t = i / (beats - 1)
268
+ x, y = x0 + (x1 - x0) * t, y0 + (y1 - y0) * t
269
+ r = (2.0 + 2.4 * t) * scale
270
+ draw.ellipse([x - r, y - r, x + r, y + r], fill=dot)
271
+ a = 12 * scale
272
+ draw.polygon([(x1 + a * 1.5, y1), (x1 + a * 0.2, y1 - a * 0.8), (x1 + a * 0.2, y1 + a * 0.8)], fill=tip)
273
+
274
+
275
+ def render(scale: int, art: Art) -> Image.Image:
276
+ p = art.palette
277
+ img = Image.new("RGB", (W * scale, H * scale), p["bg"])
278
+ d = ImageDraw.Draw(img)
279
+ # the pack's field: its deep tint at the top easing into its base tone
280
+ vgradient(d, 0, 0, W * scale, H * scale, p["bgDeep"], p["bg"])
281
+ cloud_band(d, 344, scale, p["bgCard"])
282
+ bongo_mark(d, (W / 2, 34), scale, p["accent"], p["terracotta"], p["bg"])
283
+ centered(d, W * scale / 2, int(56 * scale), art.headline,
284
+ fitted_font(d, art.headline, art.display_stack, int(25 * scale), (W - 72) * scale), p["ink"])
285
+ if art.subhead:
286
+ centered(d, W * scale / 2, int(94 * scale), art.subhead,
287
+ font(art.body_stack, int(13 * scale)), p["inkSoft"])
288
+ # Wells + guidance. The destination rim takes the pack's accent — the same tone
289
+ # the beats arrive in — so the drop target is distinguishable from the source's
290
+ # soft rule at a glance.
291
+ well(d, LEFT, scale, p["bgCard"], p["ruleSoft"])
292
+ well(d, RIGHT, scale, p["bgCard"], p["accent"])
293
+ beat_line(d, (LEFT[0] + ICON / 2 + 24, LEFT[1]), (RIGHT[0] - ICON / 2 - 36, RIGHT[1]),
294
+ scale, p["accent"], p["accentDeep"])
295
+ # NB: no under-slot labels — Finder renders each icon's own name beneath it,
296
+ # so drawing our own would double them.
297
+ return img
298
+
299
+
300
+ def main() -> None:
301
+ branding = read_branding()
302
+ brand = brand_strings(branding)
303
+ headline, subhead = copy_lines(branding, brand)
304
+ fonts = ((branding.get("theme") or {}).get("ui") or {}).get("fonts") or {}
305
+ art = Art(
306
+ palette=palette(branding),
307
+ headline=headline,
308
+ subhead=subhead,
309
+ display_stack=fonts.get("display") or "",
310
+ body_stack=fonts.get("body") or "",
311
+ )
312
+
313
+ out_dir = os.path.join(APP_DIR, "build-resources")
314
+ # @1x: render at SS then downscale for anti-aliasing.
315
+ render(SS, art).resize((W, H), Image.LANCZOS).save(os.path.join(out_dir, "dmg-background.png"))
316
+ # @2x: native double-size canvas (so it is genuinely 2x detail), supersampled.
317
+ render(SS * 2, art).resize((W * 2, H * 2), Image.LANCZOS).save(os.path.join(out_dir, "dmg-background@2x.png"))
318
+ print(f'gen-dmg-backdrop: "{headline}" / "{subhead}"')
319
+ print(f" wrote {out_dir}/dmg-background.png ({W}x{H}) + dmg-background@2x.png ({W * 2}x{H * 2})")
320
+
321
+
322
+ if __name__ == "__main__":
323
+ main()