@bongos/core 1.18.3 → 1.18.19

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 (471) hide show
  1. package/.bongos-core.json +1576 -286
  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 +4 -2
  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 +6 -1
  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/0144-devbox-rehome-onto-cloudbongos-plane.md +41 -0
  27. package/docs/adr/0144-federated-single-logout-backchannel.md +42 -0
  28. package/docs/adr/0145-devbox-app-branding-driven-module.md +44 -0
  29. package/docs/adr/0145-free-hosted-project-tier-isolation-and-domain-separation.md +62 -0
  30. package/docs/adr/0146-configurable-reward-mode-cost-plus-only.md +53 -0
  31. package/docs/adr/0147-standalone-live-docs-regen.md +39 -0
  32. package/docs/adr/0148-task-scoped-box-source-access.md +48 -0
  33. package/docs/adr/0149-bongos-dev-local-launcher.md +57 -0
  34. package/docs/adr/0150-box-first-boot-bringup-vendored-instances.md +68 -0
  35. package/docs/adr/0151-governance-permissions-as-atom-ranks-as-roles.md +229 -0
  36. package/docs/adr/0152-landing-gate-served-by-the-core-app.md +92 -0
  37. package/docs/adr/0152-metic-task-abandonment.md +67 -0
  38. package/docs/adr/0153-sequential-chain-runner.md +51 -0
  39. package/docs/adr/0154-goal-owner-criterion-authoring.md +86 -0
  40. package/docs/adr/README.md +10 -0
  41. package/docs/api/openapi.json +843 -17
  42. package/docs/api-reference.md +23 -10
  43. package/docs/architecture.md +4 -2
  44. package/docs/branding-contract.md +3 -1
  45. package/docs/canonical-permissions.md +21 -8
  46. package/docs/file-map.md +8 -0
  47. package/docs/onboarding/diagrams/01-task-lifecycle.mmd +2 -2
  48. package/docs/onboarding/diagrams/02-rank-ladder.mmd +3 -1
  49. package/docs/onboarding/diagrams/05-versioning.mmd +1 -3
  50. package/docs/onboarding/diagrams/assertions.json +4 -7
  51. package/docs/onboarding/drachmae-and-karma.md +3 -3
  52. package/docs/onboarding/primer.md +17 -16
  53. package/docs/onboarding/slash-commands.md +124 -0
  54. package/docs/recipes/builder-box-lifecycle.md +23 -0
  55. package/docs/recipes/devbox-app-release.md +14 -4
  56. package/docs/recipes/instance-first-run.md +16 -0
  57. package/docs/recipes/local-dev.md +81 -0
  58. package/docs/recipes/ops-gotchas.md +19 -1
  59. package/docs/recipes/overnight-builder.md +1 -1
  60. package/docs/recipes/parallel-goal-waves.md +174 -0
  61. package/docs/recipes/ship-and-regrade.md +2 -2
  62. package/docs/recipes/standalone-live-docs.md +48 -0
  63. package/docs/recipes/work-on-a-task-from-your-dev-box.md +108 -0
  64. package/migrations/003_pms.sql +6 -7
  65. package/migrations/006_pms_v2.sql +3 -31
  66. package/migrations/core_193_task_needs_rebase.sql +28 -0
  67. package/migrations/core_194_confirmed_backlog_clawback.sql +63 -0
  68. package/migrations/core_195_idea_triage_provenance.sql +57 -0
  69. package/migrations/core_196_session_reward_topup.sql +69 -0
  70. package/modules/autonomy/pollers/routine-timers.js +9 -8
  71. package/modules/builder-settings/wandering-prefs.js +61 -18
  72. package/modules/dev-box/app/README.md +73 -0
  73. package/modules/dev-box/app/assets/tray/mac-alertTemplate.png +0 -0
  74. package/modules/dev-box/app/assets/tray/mac-alertTemplate@2x.png +0 -0
  75. package/modules/dev-box/app/assets/tray/mac-busyTemplate.png +0 -0
  76. package/modules/dev-box/app/assets/tray/mac-busyTemplate@2x.png +0 -0
  77. package/modules/dev-box/app/assets/tray/mac-offTemplate.png +0 -0
  78. package/modules/dev-box/app/assets/tray/mac-offTemplate@2x.png +0 -0
  79. package/modules/dev-box/app/assets/tray/mac-onTemplate.png +0 -0
  80. package/modules/dev-box/app/assets/tray/mac-onTemplate@2x.png +0 -0
  81. package/modules/dev-box/app/assets/tray/win-alert.png +0 -0
  82. package/modules/dev-box/app/assets/tray/win-busy.png +0 -0
  83. package/modules/dev-box/app/assets/tray/win-off.png +0 -0
  84. package/modules/dev-box/app/assets/tray/win-on.png +0 -0
  85. package/modules/dev-box/app/build-resources/dmg-background.png +0 -0
  86. package/modules/dev-box/app/build-resources/dmg-background@2x.png +0 -0
  87. package/modules/dev-box/app/build-resources/icon.png +0 -0
  88. package/modules/dev-box/app/electron-builder.tmpl.yml +95 -0
  89. package/modules/dev-box/app/electron-builder.yml +95 -0
  90. package/modules/dev-box/app/package-lock.json +5291 -0
  91. package/modules/dev-box/app/package.json +30 -0
  92. package/modules/dev-box/app/renderer/app.js +319 -0
  93. package/modules/dev-box/app/renderer/index.html +45 -0
  94. package/modules/dev-box/app/renderer/styles.css +246 -0
  95. package/modules/dev-box/app/renderer/theme.generated.css +16 -0
  96. package/modules/dev-box/app/scripts/apply-dmg-backdrop.sh +82 -0
  97. package/modules/dev-box/app/scripts/dmgbuild-settings.py +39 -0
  98. package/modules/dev-box/app/scripts/gen-brand.js +138 -0
  99. package/modules/dev-box/app/scripts/gen-dmg-backdrop.py +323 -0
  100. package/modules/dev-box/app/scripts/gen-icons.js +286 -0
  101. package/modules/dev-box/app/scripts/vendor-client.js +23 -0
  102. package/modules/dev-box/app/src/box-controller.js +287 -0
  103. package/modules/dev-box/app/src/brand.generated.json +13 -0
  104. package/modules/dev-box/app/src/brand.js +16 -0
  105. package/modules/dev-box/app/src/config.js +19 -0
  106. package/modules/dev-box/app/src/gds-client.js +90 -0
  107. package/modules/dev-box/app/src/main.js +610 -0
  108. package/modules/dev-box/app/src/pairing.js +57 -0
  109. package/modules/dev-box/app/src/preload.js +29 -0
  110. package/modules/dev-box/app/src/session-store.js +58 -0
  111. package/modules/dev-box/app/src/ssh-setup.js +569 -0
  112. package/modules/dev-box/app/src/task-picker.js +70 -0
  113. package/modules/dev-box/app/src/tray.js +99 -0
  114. package/modules/dev-box/app/src/updater.js +55 -0
  115. package/modules/dev-box/app/src/vendor/bongos-client.cjs +698 -0
  116. package/modules/dev-box/app/test/unit.mjs +526 -0
  117. package/modules/dev-box/box-access.js +121 -4
  118. package/modules/dev-box/boxes.js +20 -0
  119. package/modules/dev-box/module.json +1 -1
  120. package/modules/dev-box/routes/box.js +87 -3
  121. package/modules/economy/credits.js +116 -25
  122. package/modules/economy/reward.js +8 -0
  123. package/modules/economy/streak.js +2 -1
  124. package/modules/governance/CLAUDE.md +15 -0
  125. package/modules/governance/catalog.js +419 -0
  126. package/modules/governance/config.js +174 -0
  127. package/modules/governance/db.js +125 -0
  128. package/modules/governance/migrations/governance_001_tables.sql +81 -0
  129. package/modules/governance/migrations/governance_002_seed_and_backfill.sql +103 -0
  130. package/modules/governance/migrations/governance_003_task_abandon_metic.sql +28 -0
  131. package/modules/governance/migrations/governance_004_criterion_create.sql +39 -0
  132. package/modules/governance/migrations/governance_005_resync_builder_rank_roles.sql +56 -0
  133. package/modules/governance/module.json +10 -0
  134. package/modules/governance/protected-surfaces.js +88 -0
  135. package/modules/governance/protected-surfaces.json +80 -0
  136. package/modules/governance/rank-roles.js +83 -0
  137. package/modules/governance/resolver.js +221 -0
  138. package/modules/governance/routes/governance.js +89 -0
  139. package/modules/governance/routes/role-sync.js +49 -0
  140. package/modules/governance/session-scopes.js +127 -0
  141. package/modules/governance/session-scopes.json +38 -0
  142. package/modules/hall-ui/public/atlas.css +117 -0
  143. package/modules/hall-ui/public/atlas.html +3 -114
  144. package/modules/hall-ui/public/brand-apply.js +34 -6
  145. package/modules/hall-ui/public/builders.js +187 -219
  146. package/modules/hall-ui/public/diagrams.html +2 -0
  147. package/modules/hall-ui/public/dom-utils.js +27 -0
  148. package/modules/hall-ui/public/drachmae.html +2 -0
  149. package/modules/hall-ui/public/drachmae.js +54 -1
  150. package/modules/hall-ui/public/gate.css +98 -0
  151. package/modules/hall-ui/public/gate.html +3 -95
  152. package/modules/hall-ui/public/goal-inbox.js +4 -1
  153. package/modules/hall-ui/public/goals-page.js +626 -114
  154. package/modules/hall-ui/public/goals.css +79 -1
  155. package/modules/hall-ui/public/goals.html +38 -11
  156. package/modules/hall-ui/public/hall-widgets.js +62 -52
  157. package/modules/hall-ui/public/harbor.css +123 -0
  158. package/modules/hall-ui/public/harbor.html +3 -120
  159. package/modules/hall-ui/public/index.html +11 -4
  160. package/modules/hall-ui/public/instances-panel.js +4 -1
  161. package/modules/hall-ui/public/modules.css +63 -0
  162. package/modules/hall-ui/public/modules.html +3 -60
  163. package/modules/hall-ui/public/new-project-wizard.js +7 -3
  164. package/modules/hall-ui/public/not-ready.html +2 -0
  165. package/modules/hall-ui/public/pair.css +73 -0
  166. package/modules/hall-ui/public/pair.html +3 -70
  167. package/modules/hall-ui/public/palette.js +302 -0
  168. package/modules/hall-ui/public/primer.css +13 -0
  169. package/modules/hall-ui/public/primer.html +2 -0
  170. package/modules/hall-ui/public/profile.html +2 -0
  171. package/modules/hall-ui/public/ranks.html +2 -0
  172. package/modules/hall-ui/public/roadmap.css +179 -0
  173. package/modules/hall-ui/public/roadmap.html +9 -172
  174. package/modules/hall-ui/public/roadmap.js +329 -316
  175. package/modules/hall-ui/public/sessions.css +16 -0
  176. package/modules/hall-ui/public/sessions.html +3 -13
  177. package/modules/hall-ui/public/settings.html +2 -0
  178. package/modules/hall-ui/public/settings.js +4 -4
  179. package/modules/hall-ui/public/shell.js +143 -18
  180. package/modules/hall-ui/public/style.css +231 -39
  181. package/modules/hall-ui/public/task-detail.js +347 -26
  182. package/modules/hall-ui/public/task.html +6 -1
  183. package/modules/hall-ui/public/watch.html +2 -0
  184. package/modules/hall-ui/public/work.html +8 -6
  185. package/modules/hall-ui/public/work.js +1 -1
  186. package/modules/ideas/inbox.js +61 -11
  187. package/modules/ideas/routes/inbox.js +23 -0
  188. package/modules/lifecycle/claim-eligibility.js +72 -0
  189. package/modules/lifecycle/db.js +519 -139
  190. package/modules/lifecycle/done-when.js +18 -3
  191. package/modules/lifecycle/github-push.js +19 -2
  192. package/modules/lifecycle/goal-advisory.js +114 -0
  193. package/modules/lifecycle/goal-authz.js +62 -0
  194. package/modules/lifecycle/lifecycle.js +9 -0
  195. package/modules/lifecycle/publish-reconciler.js +109 -6
  196. package/modules/lifecycle/routes/claims.js +68 -23
  197. package/modules/lifecycle/routes/done-when.js +110 -15
  198. package/modules/lifecycle/routes/goals.js +8 -38
  199. package/modules/lifecycle/routes/tasks.js +38 -6
  200. package/modules/lifecycle/ship-card.js +6 -0
  201. package/modules/onboarding/onboarding-state.js +1 -1
  202. package/modules/platform-identity/CLAUDE.md +15 -0
  203. package/modules/platform-identity/hub-device.js +147 -0
  204. package/modules/platform-identity/hub-keys.js +151 -0
  205. package/modules/platform-identity/migrations/platform_identity_001_tables.sql +114 -0
  206. package/modules/platform-identity/migrations/platform_identity_002_client_origin_index.sql +18 -0
  207. package/modules/platform-identity/migrations/platform_identity_003_project_memberships.sql +42 -0
  208. package/modules/platform-identity/migrations/platform_identity_004_projects.sql +44 -0
  209. package/modules/platform-identity/migrations/platform_identity_005_builder_activity.sql +42 -0
  210. package/modules/platform-identity/module.json +18 -0
  211. package/modules/platform-identity/platform-identity.js +531 -0
  212. package/modules/platform-identity/pollers/code-sweep.js +43 -0
  213. package/modules/platform-identity/routes/my-projects.js +140 -0
  214. package/modules/platform-identity/routes/projects.js +107 -0
  215. package/modules/platform-identity/routes/scouting.js +35 -0
  216. package/modules/platform-identity/routes/sso.js +402 -0
  217. package/modules/platform-identity/tests/platform-identity.mjs +388 -0
  218. package/modules/platform-identity/ui/.gitkeep +0 -0
  219. package/modules/security/routes/reports.js +10 -0
  220. package/modules/sessions/db.js +166 -21
  221. package/modules/sessions/module.json +1 -1
  222. package/modules/sessions/pollers/session-reward-reconcile.js +78 -0
  223. package/modules/sessions/routes/sessions.js +33 -0
  224. package/package-lock.json +2 -2
  225. package/package.json +1 -1
  226. package/public-landing/assets/anim/idle/frame_00.webp +0 -0
  227. package/public-landing/assets/anim/idle/frame_01.webp +0 -0
  228. package/public-landing/assets/anim/idle/frame_02.webp +0 -0
  229. package/public-landing/assets/anim/idle/frame_03.webp +0 -0
  230. package/public-landing/assets/anim/idle/frame_04.webp +0 -0
  231. package/public-landing/assets/anim/idle/frame_05.webp +0 -0
  232. package/public-landing/assets/anim/idle/frame_06.webp +0 -0
  233. package/public-landing/assets/anim/idle/frame_07.webp +0 -0
  234. package/public-landing/assets/anim/idle/frame_08.webp +0 -0
  235. package/public-landing/assets/anim/idle/frame_09.webp +0 -0
  236. package/public-landing/assets/anim/idle/frame_10.webp +0 -0
  237. package/public-landing/assets/anim/idle/frame_11.webp +0 -0
  238. package/public-landing/assets/anim/idle/frame_12.webp +0 -0
  239. package/public-landing/assets/anim/idle/frame_13.webp +0 -0
  240. package/public-landing/assets/anim/idle/frame_14.webp +0 -0
  241. package/public-landing/assets/anim/idle/frame_15.webp +0 -0
  242. package/public-landing/assets/anim/idle/frame_16.webp +0 -0
  243. package/public-landing/assets/anim/idle/frame_17.webp +0 -0
  244. package/public-landing/assets/anim/idle/frame_18.webp +0 -0
  245. package/public-landing/assets/anim/idle/frame_19.webp +0 -0
  246. package/public-landing/assets/anim/idle/frame_20.webp +0 -0
  247. package/public-landing/assets/anim/idle/frame_21.webp +0 -0
  248. package/public-landing/assets/anim/idle/frame_22.webp +0 -0
  249. package/public-landing/assets/anim/idle/frame_23.webp +0 -0
  250. package/public-landing/assets/anim/idle/frame_24.webp +0 -0
  251. package/public-landing/assets/anim/idle/frame_25.webp +0 -0
  252. package/public-landing/assets/anim/idle/frame_26.webp +0 -0
  253. package/public-landing/assets/anim/idle/frame_27.webp +0 -0
  254. package/public-landing/assets/anim/idle/frame_28.webp +0 -0
  255. package/public-landing/assets/anim/idle/frame_29.webp +0 -0
  256. package/public-landing/assets/anim/idle/frame_30.webp +0 -0
  257. package/public-landing/assets/anim/idle/frame_31.webp +0 -0
  258. package/public-landing/assets/anim/idle/frame_32.webp +0 -0
  259. package/public-landing/assets/anim/idle/frame_33.webp +0 -0
  260. package/public-landing/assets/anim/idle/frame_34.webp +0 -0
  261. package/public-landing/assets/anim/idle/frame_35.webp +0 -0
  262. package/public-landing/assets/anim/idle/frame_36.webp +0 -0
  263. package/public-landing/assets/anim/idle/frame_37.webp +0 -0
  264. package/public-landing/assets/anim/idle/frame_38.webp +0 -0
  265. package/public-landing/assets/anim/idle/frame_39.webp +0 -0
  266. package/public-landing/assets/anim/idle/frame_40.webp +0 -0
  267. package/public-landing/assets/anim/idle/frame_41.webp +0 -0
  268. package/public-landing/assets/anim/idle/frame_42.webp +0 -0
  269. package/public-landing/assets/anim/idle/frame_43.webp +0 -0
  270. package/public-landing/create.html +51 -58
  271. package/public-landing/gate/README.md +137 -0
  272. package/public-landing/gate/dispmaps/bar.png +0 -0
  273. package/public-landing/gate/dispmaps/flat.png +0 -0
  274. package/public-landing/gate/dispmaps/key.png +0 -0
  275. package/public-landing/gate/index.html +587 -0
  276. package/public-landing/gate/previews/cloud-bongos.jpg +0 -0
  277. package/public-landing/gate/previews/off-the-boats.jpg +0 -0
  278. package/public-landing/gate/space.css +799 -0
  279. package/public-landing/gate/space.js +1826 -0
  280. package/public-landing/gate/sprites/enter.png +0 -0
  281. package/public-landing/gate/sprites/search.png +0 -0
  282. package/public-landing/gate/sprites/terminal-blank.png +0 -0
  283. package/public-landing/gate/sprites/tile.png +0 -0
  284. package/public-landing/world/assets/clouds/cloud-island-large.png +0 -0
  285. package/public-landing/world/assets/clouds/cloud-puff.png +0 -0
  286. package/public-landing/world/assets/creatures/creature-whale-cargo.png +0 -0
  287. package/public-landing/world/assets/creatures/creature-whale-glide.png +0 -0
  288. package/public-landing/world/assets/plates/plate-clouds-near.png +0 -0
  289. package/public-landing/world/assets/plates/scene-establishing.png +0 -0
  290. package/scripts/gds/abandon.js +6 -4
  291. package/scripts/gds/api.js +10 -5
  292. package/scripts/gds/api.ps1 +26 -0
  293. package/scripts/gds/api.sh +6 -0
  294. package/scripts/gds/audit-ships.js +7 -1
  295. package/scripts/gds/backfill-sessions.js +78 -18
  296. package/scripts/gds/backup.js +23 -1
  297. package/scripts/gds/box-connect-lib.js +124 -31
  298. package/scripts/gds/box-sync.js +66 -0
  299. package/scripts/gds/box.js +225 -53
  300. package/scripts/gds/cf-tunnel.js +145 -6
  301. package/scripts/gds/claim.js +34 -3
  302. package/scripts/gds/cli-lib.js +161 -27
  303. package/scripts/gds/code.js +6 -2
  304. package/scripts/gds/deploy-divergence.js +209 -0
  305. package/scripts/gds/dev-lib.js +309 -0
  306. package/scripts/gds/dev.js +322 -0
  307. package/scripts/gds/doctor.js +90 -61
  308. package/scripts/gds/fetch-art-key.js +1 -1
  309. package/scripts/gds/fetch-sound-prefs.js +2 -2
  310. package/scripts/gds/fitness.js +228 -12
  311. package/scripts/gds/gds-db-backup.service +16 -1
  312. package/scripts/gds/gds-db-offsite.service +9 -1
  313. package/scripts/gds/gen-api-client.js +1 -1
  314. package/scripts/gds/gen-api-docs.js +36 -14
  315. package/scripts/gds/gen-diagrams.js +45 -28
  316. package/scripts/gds/gen-file-map.js +2 -1
  317. package/scripts/gds/gen-freshness.js +32 -0
  318. package/scripts/gds/gen-repo-map.js +3 -2
  319. package/scripts/gds/gen-session-index.js +14 -5
  320. package/scripts/gds/hooks/pre-push.js +2 -2
  321. package/scripts/gds/init.js +60 -3
  322. package/scripts/gds/install-git-hooks.js +1 -1
  323. package/scripts/gds/key-card.js +15 -6
  324. package/scripts/gds/key-status.js +1 -1
  325. package/scripts/gds/linkify-refs.js +35 -9
  326. package/scripts/gds/login.js +16 -1
  327. package/scripts/gds/paste-token.js +7 -1
  328. package/scripts/gds/preflight.js +126 -0
  329. package/scripts/gds/provision.js +123 -12
  330. package/scripts/gds/publish-manifest.js +7 -1
  331. package/scripts/gds/reauth.js +7 -3
  332. package/scripts/gds/regen-instance-docs.js +47 -0
  333. package/scripts/gds/run-unit-tests.js +4 -2
  334. package/scripts/gds/seed-bongos-v1-seo-tasks.js +104 -0
  335. package/scripts/gds/seed-bv1-profiles-tasks.js +126 -0
  336. package/scripts/gds/sequence.js +425 -0
  337. package/scripts/gds/session-cost.js +10 -1
  338. package/scripts/gds/setup.js +31 -5
  339. package/scripts/gds/shell.js +5 -3
  340. package/scripts/gds/ship.js +319 -29
  341. package/scripts/gds/smoke-gds.js +139 -8
  342. package/scripts/gds/start.js +171 -22
  343. package/scripts/gds/status.js +1 -1
  344. package/scripts/gds/triage.js +10 -9
  345. package/scripts/gds/upgrade.js +124 -2
  346. package/scripts/migrate.sh +99 -14
  347. package/scripts/status-mirror-sync.js +114 -84
  348. package/src/bongos/api-errors.js +1 -0
  349. package/src/bongos/auth.js +447 -102
  350. package/src/bongos/db-kernel.js +27 -0
  351. package/src/bongos/db.js +39 -0
  352. package/src/bongos/module-scope-map.js +27 -1
  353. package/src/bongos/permission-path-check.js +136 -102
  354. package/src/bongos/repo-info.js +47 -11
  355. package/src/bongos/route-rank-check.js +429 -46
  356. package/src/bongos/routes/_helpers.js +12 -1
  357. package/src/bongos/routes/auth.js +219 -2
  358. package/src/bongos/routes/backup.js +58 -2
  359. package/src/bongos/routes/me.js +35 -1
  360. package/src/bongos/serve-internal.js +84 -15
  361. package/src/branding.js +32 -0
  362. package/src/instance-config.js +30 -0
  363. package/src/module-api.js +124 -1
  364. package/src/platform-server.js +86 -9
  365. package/tests/abandon_task.mjs +59 -3
  366. package/tests/api_cli_flags.mjs +53 -0
  367. package/tests/auth_page_gate.mjs +13 -0
  368. package/tests/backfill_sessions.mjs +127 -1
  369. package/tests/backup_cli.mjs +54 -0
  370. package/tests/bongos_box_connect.mjs +77 -0
  371. package/tests/box_access.mjs +130 -1
  372. package/tests/box_credential.mjs +125 -0
  373. package/tests/box_host_keys.mjs +24 -0
  374. package/tests/box_infra_resolve.mjs +149 -0
  375. package/tests/box_scope_session.mjs +6 -2
  376. package/tests/box_sync.mjs +55 -0
  377. package/tests/box_task_scope.mjs +99 -0
  378. package/tests/branding.mjs +12 -0
  379. package/tests/cf_ruleset_preflight.mjs +142 -0
  380. package/tests/cf_tunnel.mjs +37 -0
  381. package/tests/claim_gate_rebase.mjs +98 -0
  382. package/tests/clawback_confirmed_backlog.mjs +103 -0
  383. package/tests/cli_lib_formatters.mjs +20 -0
  384. package/tests/confirm_attribution.mjs +30 -46
  385. package/tests/credit_grant.mjs +198 -10
  386. package/tests/criterion_authoring_gate.mjs +272 -0
  387. package/tests/criterion_progress.mjs +33 -1
  388. package/tests/dev_lib.mjs +253 -0
  389. package/tests/devbox_artifact_gate.mjs +75 -0
  390. package/tests/devbox_dmg_backdrop.mjs +137 -0
  391. package/tests/devbox_icons.mjs +304 -0
  392. package/tests/devbox_installer_brand.mjs +232 -0
  393. package/tests/devbox_key_register.mjs +234 -0
  394. package/tests/docs_openapi_route.mjs +110 -0
  395. package/tests/doctor_hooks.mjs +141 -0
  396. package/tests/extract_json.mjs +140 -0
  397. package/tests/fitness.mjs +89 -0
  398. package/tests/gen_freshness.mjs +96 -0
  399. package/tests/goal_advisory.mjs +251 -0
  400. package/tests/goal_criterion_confirm_ui.mjs +269 -0
  401. package/tests/goal_routes.mjs +59 -4
  402. package/tests/goal_tier.mjs +12 -3
  403. package/tests/governance_catalog.mjs +129 -0
  404. package/tests/governance_claim_eligibility.mjs +262 -0
  405. package/tests/governance_config.mjs +187 -0
  406. package/tests/governance_ownership_scope.mjs +410 -0
  407. package/tests/governance_page_gates.mjs +324 -0
  408. package/tests/governance_parity.mjs +336 -0
  409. package/tests/governance_principals.mjs +232 -0
  410. package/tests/governance_protected_surfaces.mjs +208 -0
  411. package/tests/governance_require_permission.mjs +223 -0
  412. package/tests/governance_resolver.mjs +84 -0
  413. package/tests/governance_role_sync.mjs +231 -0
  414. package/tests/governance_seed.mjs +127 -0
  415. package/tests/governance_session_scope.mjs +434 -0
  416. package/tests/governance_vanilla_boot.mjs +203 -0
  417. package/tests/grade_attribution.mjs +7 -7
  418. package/tests/hall_audit.mjs +223 -0
  419. package/tests/hall_nav.mjs +191 -0
  420. package/tests/hall_palette.mjs +206 -0
  421. package/tests/hall_signout.mjs +124 -0
  422. package/tests/hook_user_agents.mjs +10 -0
  423. package/tests/idea_triage.mjs +162 -0
  424. package/tests/idp_device_exchange.mjs +93 -0
  425. package/tests/init.mjs +51 -0
  426. package/tests/instance_config.mjs +65 -0
  427. package/tests/instance_mismatch_guard.mjs +23 -6
  428. package/tests/instance_release_config.mjs +104 -0
  429. package/tests/linkify_refs.mjs +15 -2
  430. package/tests/migrate_root_resolution.mjs +124 -1
  431. package/tests/module_contributions.mjs +55 -0
  432. package/tests/module_loader.mjs +10 -5
  433. package/tests/module_scope_active_claims.mjs +94 -0
  434. package/tests/needs_rebase.mjs +92 -0
  435. package/tests/one_active_claim_per_session.mjs +76 -0
  436. package/tests/override_attribution.mjs +10 -8
  437. package/tests/override_request_ownership.mjs +8 -4
  438. package/tests/parse_cookie_decode.mjs +42 -0
  439. package/tests/pay_on_land.mjs +129 -0
  440. package/tests/platform_boot.mjs +100 -1
  441. package/tests/post_login_landing.mjs +42 -0
  442. package/tests/preflight_checks.mjs +72 -0
  443. package/tests/provision.mjs +132 -7
  444. package/tests/publish_branch_route.mjs +53 -0
  445. package/tests/publish_reconciler.mjs +187 -2
  446. package/tests/repo_info_binding.mjs +248 -0
  447. package/tests/reward_gate.mjs +11 -2
  448. package/tests/reward_mode_cost_plus_only.mjs +116 -0
  449. package/tests/reward_mode_surfacing.mjs +168 -0
  450. package/tests/route_permission_check.mjs +301 -0
  451. package/tests/route_rank_check.mjs +254 -12
  452. package/tests/route_validation.mjs +30 -0
  453. package/tests/sequence.mjs +267 -0
  454. package/tests/session_digest.mjs +44 -0
  455. package/tests/session_index.mjs +18 -6
  456. package/tests/session_records.mjs +39 -1
  457. package/tests/session_reward_on_land.mjs +381 -0
  458. package/tests/session_reward_reconcile.mjs +229 -0
  459. package/tests/ship_api_client_pathspec.mjs +100 -0
  460. package/tests/ship_base_flag.mjs +93 -0
  461. package/tests/ship_ci_deploy.mjs +80 -3
  462. package/tests/ship_upstream_deletion.mjs +57 -0
  463. package/tests/smoke_target.mjs +233 -0
  464. package/tests/start_env_guard.mjs +87 -0
  465. package/tests/start_rebase_warning.mjs +103 -0
  466. package/tests/status_mirror_sync.mjs +122 -0
  467. package/tests/stop_refcheck.mjs +19 -1
  468. package/tests/task_goal_link.mjs +15 -6
  469. package/tests/upgrade.mjs +139 -0
  470. package/tests/web_admission_status.mjs +162 -0
  471. package/tests/write_validation_gaps.mjs +110 -0
@@ -0,0 +1,209 @@
1
+ #!/usr/bin/env node
2
+ // Compare what a live instance is SERVING against what is on main.
3
+ //
4
+ // Merging to main deploys nothing (ADR 0108/0137 — publishing is manual and
5
+ // owner-gated). That is by design, but every layer around it still reports
6
+ // success: /builder-ship prints success, the publish-reconciler flips
7
+ // confirmed → shipped, and credits pay out on "land". Nothing compared live
8
+ // against main, so between 2026-07-24 and 2026-08-03 a hundred and seventy-one
9
+ // commits — including a whole governance permission system — sat on main while
10
+ // cloudbongos.com served 1.18.17, and nobody noticed for ten days (blocker
11
+ // 1000097 / task 1002521). This script is the missing comparison.
12
+ //
13
+ // Two independent gaps, both reported:
14
+ // 1. UNRELEASED — commits on main since the last CORE_VERSION bump. No
15
+ // instance can take these at any pin, because no version carries them.
16
+ // 2. UNDEPLOYED — the live instance's coreVersion is behind the CORE_VERSION
17
+ // on main. A release exists; this instance has not moved its pin to it.
18
+ //
19
+ // Usage:
20
+ // node scripts/gds/deploy-divergence.js # check the instance this checkout talks to
21
+ // node scripts/gds/deploy-divergence.js --url https://example.com
22
+ // node scripts/gds/deploy-divergence.js --quiet # only print on divergence
23
+ // node scripts/gds/deploy-divergence.js --warn-only # always exit 0 (for a non-blocking ship warning)
24
+ //
25
+ // Exit codes: 0 aligned (or --warn-only), 1 diverged, 2 could not determine.
26
+
27
+ const fs = require('node:fs');
28
+ const path = require('node:path');
29
+ const { execSync } = require('node:child_process');
30
+
31
+ const { API_BASE, arg, hasFlag } = require('./cli-lib');
32
+
33
+ const REPO_ROOT = path.resolve(__dirname, '..', '..');
34
+ const MODULE_API = path.join(REPO_ROOT, 'src', 'module-api.js');
35
+
36
+ // Read CORE_VERSION by regex rather than require() — this runs in checkouts
37
+ // (and CI) where loading the doorway would pull config/branding that may not
38
+ // resolve, and we only ever want the literal.
39
+ function coreVersionAt(ref) {
40
+ let src;
41
+ try {
42
+ src = ref
43
+ ? execSync(`git show ${ref}:src/module-api.js`, { cwd: REPO_ROOT, encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'], maxBuffer: 64 * 1024 * 1024 })
44
+ : fs.readFileSync(MODULE_API, 'utf8');
45
+ } catch {
46
+ return null;
47
+ }
48
+ const m = src.match(/^const CORE_VERSION = '([^']+)'/m);
49
+ return m ? m[1] : null;
50
+ }
51
+
52
+ function git(cmd, fallback = null) {
53
+ try {
54
+ return execSync(`git ${cmd}`, { cwd: REPO_ROOT, encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'], maxBuffer: 64 * 1024 * 1024 }).trim();
55
+ } catch {
56
+ return fallback;
57
+ }
58
+ }
59
+
60
+ // The commit that last changed the CORE_VERSION literal, and how many commits
61
+ // sit on top of it. Must be `-G` (any diff line matching the regex), NOT `-S`:
62
+ // `-S` fires only when the number of occurrences changes, and a bump rewrites
63
+ // the single existing line — so `-S` silently walks back to the commit that
64
+ // first introduced the constant and reports every commit since as unreleased.
65
+ // `--no-merges` is load-bearing, not a nicety: a bump lands on main VIA a merge,
66
+ // so counting merges means the moment any release lands there is always ≥1
67
+ // "unreleased" commit — the merge that landed the bump. This check exists
68
+ // because a real gap hid for ten days behind layers that all reported success;
69
+ // one that cries wolf after every single release earns exactly the same
70
+ // scrolling-past it was added to prevent. Merge commits carry no content of
71
+ // their own, so they are not unreleased work. (task 1002558)
72
+ function unreleasedSince(mainRef) {
73
+ const bump = git(`log ${mainRef} -1 --format=%H -G"^const CORE_VERSION = " -- src/module-api.js`);
74
+ if (!bump) return null;
75
+ const count = git(`rev-list --count --no-merges ${bump}..${mainRef}`);
76
+ const withMerges = git(`rev-list --count ${bump}..${mainRef}`);
77
+ return {
78
+ bump,
79
+ bumpShort: bump.slice(0, 8),
80
+ bumpSubject: git(`log -1 --format=%s ${bump}`, ''),
81
+ bumpDate: git(`log -1 --format=%cs ${bump}`, ''),
82
+ count: count === null ? null : Number(count),
83
+ countWithMerges: withMerges === null ? null : Number(withMerges),
84
+ };
85
+ }
86
+
87
+ function cmpSemver(a, b) {
88
+ const pa = String(a).split('.').map(Number);
89
+ const pb = String(b).split('.').map(Number);
90
+ for (let i = 0; i < 3; i += 1) {
91
+ const d = (pa[i] || 0) - (pb[i] || 0);
92
+ if (d) return d < 0 ? -1 : 1;
93
+ }
94
+ return 0;
95
+ }
96
+
97
+ async function fetchLive(base) {
98
+ const url = `${String(base).replace(/\/+$/, '')}/version`;
99
+ const ctl = new AbortController();
100
+ const timer = setTimeout(() => ctl.abort(), 15000);
101
+ try {
102
+ const res = await fetch(url, { signal: ctl.signal, headers: { accept: 'application/json' } });
103
+ if (!res.ok) return { url, error: `HTTP ${res.status}` };
104
+ const body = await res.json();
105
+ if (!body || !body.coreVersion) return { url, error: 'no coreVersion field in /version' };
106
+ return { url, coreVersion: body.coreVersion, commitShort: body.commitShort || null };
107
+ } catch (e) {
108
+ return { url, error: e.name === 'AbortError' ? 'timed out after 15s' : (e.message || String(e)) };
109
+ } finally {
110
+ clearTimeout(timer);
111
+ }
112
+ }
113
+
114
+ async function check({ base } = {}) {
115
+ const target = base || arg('--url') || API_BASE;
116
+ const local = coreVersionAt(null);
117
+ if (!local) return { ok: false, undetermined: true, reason: 'could not read CORE_VERSION from src/module-api.js', target };
118
+
119
+ // Prefer origin/main — the released trunk — but fall back to HEAD in a
120
+ // checkout with no remote (a box, a tarball) so the check still says
121
+ // something useful instead of bailing.
122
+ const mainRef = git('rev-parse --verify --quiet origin/main') ? 'origin/main' : 'HEAD';
123
+ const onMain = coreVersionAt(mainRef) || local;
124
+ const unreleased = unreleasedSince(mainRef);
125
+ const live = await fetchLive(target);
126
+
127
+ if (live.error) return { ok: false, undetermined: true, reason: `could not read ${live.url}: ${live.error}`, target, local, onMain, unreleased };
128
+
129
+ const behind = cmpSemver(live.coreVersion, onMain) < 0;
130
+ const ahead = cmpSemver(live.coreVersion, onMain) > 0;
131
+ const hasUnreleased = !!(unreleased && unreleased.count > 0);
132
+
133
+ return {
134
+ ok: !behind && !hasUnreleased,
135
+ target, local, onMain, mainRef, unreleased, live, behind, ahead, hasUnreleased,
136
+ };
137
+ }
138
+
139
+ function report(r, { quiet = false } = {}) {
140
+ const say = (...a) => console.log(...a);
141
+
142
+ if (r.undetermined) {
143
+ console.error(`deploy-divergence: undetermined — ${r.reason}`);
144
+ return;
145
+ }
146
+ if (r.ok && quiet) return;
147
+
148
+ const host = String(r.target).replace(/^https?:\/\//, '');
149
+ say('');
150
+ say(` Deploy divergence · ${host}`);
151
+ say(' ──────────────────────────────────────────────────────────');
152
+ say(` live (serving) ${r.live.coreVersion}${r.live.commitShort ? ` (instance commit ${r.live.commitShort})` : ''}`);
153
+ say(` ${r.mainRef.padEnd(18)} ${r.onMain}`);
154
+ // Only worth a line when a bump is in flight in this tree but not yet landed.
155
+ if (r.local !== r.onMain) say(` working tree ${r.local} (not yet on ${r.mainRef})`);
156
+ if (r.unreleased) {
157
+ say(` last bump ${r.unreleased.bumpShort} ${r.unreleased.bumpDate} — ${r.unreleased.bumpSubject}`);
158
+ }
159
+ say('');
160
+
161
+ if (r.ok) {
162
+ say(' ✓ aligned — live matches main, and main carries no unreleased commits.');
163
+ say('');
164
+ return;
165
+ }
166
+
167
+ if (r.hasUnreleased) {
168
+ // Say which number this is: work commits and with-merges differ a lot on a
169
+ // big window (93 vs 171 for 1.18.18), and an unlabelled figure invites the
170
+ // "~100 vs 171" disagreement that muddied blocker 1000097.
171
+ const u = r.unreleased;
172
+ const merges = u.countWithMerges != null && u.countWithMerges !== u.count
173
+ ? ` (${u.countWithMerges} counting merges)` : '';
174
+ say(` ⚠ UNRELEASED — ${u.count} work commit${u.count === 1 ? '' : 's'}${merges} on ${r.mainRef} since the last CORE_VERSION bump.`);
175
+ say(' No instance can take these at ANY pin: no published version contains them.');
176
+ say(` Fix: bump CORE_VERSION in src/module-api.js (+ a changelog line) and land it.`);
177
+ }
178
+ if (r.behind) {
179
+ say(` ⚠ UNDEPLOYED — live is ${r.live.coreVersion}, main declares ${r.onMain}.`);
180
+ say(' A release exists that this instance has not moved its pin to.');
181
+ say(' Fix: package-core, then move the instance pin (see docs/recipes/upgrading-the-core.md).');
182
+ }
183
+ if (r.ahead) {
184
+ say(` note: live (${r.live.coreVersion}) is AHEAD of ${r.mainRef} (${r.onMain}) — this checkout is stale; fetch.`);
185
+ }
186
+ say('');
187
+ say(' Merging to main deploys nothing by design. "shipped" means "merged" —');
188
+ say(' it does not mean users can see it.');
189
+ say('');
190
+ }
191
+
192
+ async function main() {
193
+ const quiet = hasFlag('--quiet');
194
+ const warnOnly = hasFlag('--warn-only');
195
+ const r = await check();
196
+ report(r, { quiet });
197
+ if (warnOnly) return process.exit(0);
198
+ if (r.undetermined) return process.exit(2);
199
+ return process.exit(r.ok ? 0 : 1);
200
+ }
201
+
202
+ if (require.main === module) {
203
+ main().catch((e) => {
204
+ console.error('deploy-divergence: failed —', e.message || e);
205
+ process.exit(2);
206
+ });
207
+ }
208
+
209
+ module.exports = { check, report, coreVersionAt, unreleasedSince, cmpSemver };
@@ -0,0 +1,309 @@
1
+ 'use strict';
2
+ // scripts/gds/dev-lib.js — pure + injectable core of `bongos dev`, the one-command
3
+ // LOCAL launcher for a standalone instance repo (ADR 0149).
4
+ //
5
+ // WHY. A project scaffolded by the creation flow ships a repo whose
6
+ // config/branding.json points at http://localhost:<port> — it is CONFIGURED to run
7
+ // on the owner's own machine — but the platform shipped no command that actually
8
+ // STARTS it. Standing it up by hand is a six-part assembly (install+start Postgres,
9
+ // createdb, migrate, set PORT/PGDATABASE/PGHOST/OAuth env, launch the server) with
10
+ // three silent mismatches: the server reads PORT from env and NOT from branding
11
+ // (platform-server.js:44); the server DB name collapses to the OS user while
12
+ // migrate.sh defaults to `amazonprimea` (pool.js:47 vs migrate.sh:52) unless
13
+ // PGDATABASE is set for BOTH; and node-pg defaults to the Linux peer socket
14
+ // /var/run/postgresql (pool.js:46), which does not exist under Homebrew Postgres on
15
+ // a Mac. `bongos dev` collapses all of that into one idempotent command.
16
+ //
17
+ // Everything here is a PURE transform or an INJECTABLE helper (deps.fs / deps.spawn
18
+ // / deps.kill / deps.fetchImpl / deps.env / deps.userInfo / deps.*Path), so
19
+ // tests/dev_lib.mjs exercises every decision with NO real spawn, DB, or network.
20
+ // The side-effecting orchestration (start Postgres, createdb, run migrate, hand off
21
+ // to login) lives in dev.js.
22
+
23
+ const { spawn } = require('node:child_process');
24
+ const fs = require('node:fs');
25
+ const os = require('node:os');
26
+ const path = require('node:path');
27
+ const ic = require('../../src/instance-config');
28
+
29
+ // The server binds loopback (platform-server.js HOST default). We probe the IP
30
+ // literal, never "localhost", to dodge any IPv6/hosts-file quirk while it binds
31
+ // 127.0.0.1 — the same choice local-preview-lib.js made.
32
+ const HOST = '127.0.0.1';
33
+ const DEFAULT_PORT = 3000;
34
+
35
+ // scripts/gds/ -> the core package root (holds src/platform-server.js + scripts/migrate.sh).
36
+ const CORE_ROOT = path.resolve(__dirname, '..', '..');
37
+
38
+ function readJsonSafe(p, f = fs) {
39
+ try { return JSON.parse(f.readFileSync(p, 'utf8')); }
40
+ catch { return null; }
41
+ }
42
+
43
+ // Read the instance-identity fields the launcher needs, INSTANCE pack first
44
+ // (config/branding.json — host content, resolveInstanceRoot) then the CORE neutral
45
+ // starter (config/branding.neutral.json — ships with the package). Decoupled from
46
+ // the full branding() resolver on purpose (mirrors pool.js:instanceDbName): a
47
+ // branding validation error must never stop a local dev boot.
48
+ function loadBrandingFacts({ instanceRoot, coreRoot = CORE_ROOT, f = fs } = {}) {
49
+ const inst = readJsonSafe(path.join(instanceRoot, 'config', 'branding.json'), f) || {};
50
+ const neutral = readJsonSafe(path.join(coreRoot, 'config', 'branding.neutral.json'), f) || {};
51
+ const pick = (k, subk) => {
52
+ const from = (o) => (subk ? (o[k] && o[k][subk]) : o[k]);
53
+ const v = from(inst);
54
+ return (v === undefined || v === null || v === '') ? from(neutral) : v;
55
+ };
56
+ return {
57
+ publicOrigin: pick('domains', 'publicOrigin') || `http://localhost:${DEFAULT_PORT}`,
58
+ firstAdmin: pick('firstAdmin') || '',
59
+ // db.database is null in the neutral starter and usually absent in a fresh
60
+ // instance pack — deriveDbName() falls back to the repo dir name.
61
+ brandingDbName: (inst.db && inst.db.database) || (neutral.db && neutral.db.database) || null,
62
+ envPrefix: pick('envPrefix') || ic.FALLBACK_ENV_PREFIX,
63
+ configDir: pick('configDir') || ic.FALLBACK_DIR,
64
+ gameEnabled: false, // overridden by resolveRunParams from config/modules.json
65
+ };
66
+ }
67
+
68
+ // Port the instance is configured for. The server reads process.env.PORT and
69
+ // ignores branding, so `bongos dev` MUST set PORT to this or the server would come
70
+ // up on 3000 while branding (and every printed link) says <port>.
71
+ function parsePort(origin, fallback = DEFAULT_PORT) {
72
+ try {
73
+ const p = new URL(String(origin)).port;
74
+ const n = Number(p);
75
+ return Number.isInteger(n) && n > 0 && n < 65536 ? n : fallback;
76
+ } catch { return fallback; }
77
+ }
78
+
79
+ // Coerce an arbitrary string into a legal, safe Postgres database identifier:
80
+ // lowercased, non [a-z0-9_] collapsed to '_', must begin with a letter or '_'
81
+ // (a leading digit is illegal unquoted), max 63 bytes. Returns null if nothing
82
+ // usable survives, so deriveDbName can fall through to its last-resort default.
83
+ function sanitizeDbName(s) {
84
+ if (!s) return null;
85
+ let out = String(s).toLowerCase().replace(/[^a-z0-9_]+/g, '_').replace(/^_+|_+$/g, '');
86
+ if (!out) return null;
87
+ if (/^[0-9]/.test(out)) out = `db_${out}`;
88
+ return out.slice(0, 63);
89
+ }
90
+
91
+ // The DB name BOTH migrate.sh and the server pool will use. Precedence:
92
+ // explicit --db / override -> branding db.database -> the repo dir name ->
93
+ // 'cloudbongos_dev'. Whatever we pick is exported as PGDATABASE for both sides,
94
+ // which is the fix for the pool.js(OS-user) vs migrate.sh(amazonprimea) split.
95
+ function deriveDbName({ brandingDbName, instanceRoot, override } = {}) {
96
+ return (
97
+ sanitizeDbName(override) ||
98
+ sanitizeDbName(brandingDbName) ||
99
+ sanitizeDbName(instanceRoot && path.basename(instanceRoot)) ||
100
+ 'cloudbongos_dev'
101
+ );
102
+ }
103
+
104
+ // Resolve every parameter a local run needs, from branding + modules + env + argv.
105
+ // Pure: all IO comes through injected deps (f/env/userInfo/argv). PGHOST defaults to
106
+ // TCP 'localhost' (NOT the Linux peer socket the pool defaults to) so a Homebrew Mac
107
+ // Postgres is reachable; an explicit PGHOST/PGUSER in the environment still wins.
108
+ function resolveRunParams({
109
+ instanceRoot,
110
+ coreRoot = CORE_ROOT,
111
+ f = fs,
112
+ env = process.env,
113
+ userInfo = os.userInfo,
114
+ argv = [],
115
+ } = {}) {
116
+ const facts = loadBrandingFacts({ instanceRoot, coreRoot, f });
117
+ const modules = readJsonSafe(path.join(instanceRoot, 'config', 'modules.json'), f) || {};
118
+ const gameEnabled = !!(modules.modules && modules.modules.game);
119
+
120
+ const portOverride = argFrom(argv, '--port');
121
+ const dbOverride = argFrom(argv, '--db');
122
+
123
+ const port = portOverride ? parsePort(`http://x:${portOverride}`, parsePort(facts.publicOrigin)) : parsePort(facts.publicOrigin);
124
+ const dbName = deriveDbName({ brandingDbName: facts.brandingDbName, instanceRoot, override: dbOverride });
125
+
126
+ let pgUser = env.PGUSER;
127
+ if (!pgUser) { try { pgUser = userInfo().username; } catch { pgUser = undefined; } }
128
+
129
+ return {
130
+ instanceRoot,
131
+ coreRoot,
132
+ port,
133
+ host: HOST,
134
+ dbName,
135
+ pgHost: env.PGHOST || 'localhost',
136
+ pgUser,
137
+ pgPassword: env.PGPASSWORD || undefined,
138
+ publicOrigin: facts.publicOrigin.replace(/\/+$/, ''),
139
+ firstAdmin: facts.firstAdmin,
140
+ envPrefix: facts.envPrefix,
141
+ configDir: facts.configDir,
142
+ // A local dev instance never wants OTB's instance-seed migrations (they FK-crash
143
+ // a non-owning DB — migrate.sh:56). Game-less instances skip them via
144
+ // MEDUSA_GDS_ONLY=1; when the game module IS enabled we still skip instance
145
+ // seeds (default-off) but do not force GDS-only, so game schema still applies.
146
+ gdsOnly: !gameEnabled,
147
+ };
148
+ }
149
+
150
+ // The env a child process (migrate or server) runs with. Starts from baseEnv (so
151
+ // the child inherits any GitHub OAuth creds already in the shell) and overlays the
152
+ // resolved run parameters. role='server' adds PORT/HOST; both roles set the PG*
153
+ // trio + the instance-root pointer + the seed policy.
154
+ function buildEnv(params, { baseEnv = process.env, role = 'server' } = {}) {
155
+ const env = { ...baseEnv };
156
+ env.PGHOST = params.pgHost;
157
+ if (params.pgUser) env.PGUSER = params.pgUser;
158
+ if (params.pgPassword) env.PGPASSWORD = params.pgPassword;
159
+ env.PGDATABASE = params.dbName;
160
+ if (params.gdsOnly) env.MEDUSA_GDS_ONLY = '1';
161
+ // Point migrate + the server at the instance explicitly, so resolution never
162
+ // depends on a stray cwd (migrate.sh:26 gotcha).
163
+ env[`${params.envPrefix}_INSTANCE_ROOT`] = params.instanceRoot;
164
+ if (role === 'server') {
165
+ env.PORT = String(params.port);
166
+ env.HOST = params.host;
167
+ }
168
+ return env;
169
+ }
170
+
171
+ // Does the machine have GitHub OAuth creds this instance's server will read?
172
+ // Mirrors auth.js:loadGithubCreds EXACTLY (env <PREFIX>_GITHUB_CLIENT_ID/SECRET
173
+ // with legacy fallbacks, else ~/.config/<configDir>/gds-github.json | pms-github.json)
174
+ // so `bongos dev` predicts sign-in correctly instead of guessing.
175
+ function authWillWork({ resolveEnv = ic.resolveEnv, readConfigFileSync = ic.readConfigFileSync } = {}) {
176
+ if (resolveEnv('GITHUB_CLIENT_ID') && resolveEnv('GITHUB_CLIENT_SECRET')) return true;
177
+ for (const name of ['gds-github.json', 'pms-github.json']) {
178
+ try {
179
+ const raw = readConfigFileSync(name);
180
+ if (raw) {
181
+ const c = JSON.parse(raw);
182
+ if (c && c.client_id && c.client_secret) return true;
183
+ }
184
+ } catch { /* try next */ }
185
+ }
186
+ return false;
187
+ }
188
+
189
+ // --- managed background server (the local analogue of local-preview-lib) --------
190
+ // Keyed per (dbName, port) so two instances on one machine never collide on pid/log.
191
+
192
+ function slug(params) { return `${params.dbName}-${params.port}`.replace(/[^a-z0-9_.-]+/gi, '_'); }
193
+ function pidPath(params, deps = {}) {
194
+ return deps.pidPath || path.join((deps.tmpdir || os.tmpdir)(), `bongos-dev-${slug(params)}.pid`);
195
+ }
196
+ function logPath(params, deps = {}) {
197
+ return deps.logPath || path.join((deps.tmpdir || os.tmpdir)(), `bongos-dev-${slug(params)}.log`);
198
+ }
199
+
200
+ function readPid(params, deps = {}) {
201
+ const f = deps.fs || fs;
202
+ try {
203
+ const pid = Number(String(f.readFileSync(pidPath(params, deps), 'utf8')).trim());
204
+ return Number.isInteger(pid) && pid > 0 ? pid : null;
205
+ } catch { return null; }
206
+ }
207
+
208
+ // signal-0 existence probe: EPERM = alive but unsignalable, ESRCH = gone.
209
+ function pidAlive(pid, deps = {}) {
210
+ if (!pid) return false;
211
+ const kill = deps.kill || process.kill;
212
+ try { kill(pid, 0); return true; } catch (e) { return e.code === 'EPERM'; }
213
+ }
214
+
215
+ function isRunning(params, deps = {}) { return pidAlive(readPid(params, deps), deps); }
216
+
217
+ function serverEntry(params) { return path.join(params.coreRoot, 'src', 'platform-server.js'); }
218
+
219
+ // Start the platform server DETACHED (background), idempotent. Returns
220
+ // { ok, already, pid, url, log }. The parent polls health, prints the managed-server
221
+ // notice + the `bongos dev stop` command, and exits — the server keeps running.
222
+ function startDetached(params, deps = {}) {
223
+ const f = deps.fs || fs;
224
+ const spawnImpl = deps.spawn || spawn;
225
+ if (isRunning(params, deps)) {
226
+ return { ok: true, already: true, pid: readPid(params, deps), url: params.publicOrigin, log: logPath(params, deps) };
227
+ }
228
+ const lp = logPath(params, deps);
229
+ let out = 'ignore';
230
+ try { out = f.openSync(lp, 'w'); } catch { /* fall back to ignore */ }
231
+ const child = spawnImpl(deps.nodePath || process.execPath, [serverEntry(params)], {
232
+ cwd: params.instanceRoot,
233
+ env: buildEnv(params, { baseEnv: deps.env || process.env, role: 'server' }),
234
+ detached: true,
235
+ stdio: ['ignore', out, out],
236
+ windowsHide: true,
237
+ });
238
+ child.unref();
239
+ if (child.pid) f.writeFileSync(pidPath(params, deps), String(child.pid) + '\n');
240
+ return { ok: true, already: false, pid: child.pid, url: params.publicOrigin, log: lp };
241
+ }
242
+
243
+ function stop(params, deps = {}) {
244
+ const f = deps.fs || fs;
245
+ const kill = deps.kill || process.kill;
246
+ const pid = readPid(params, deps);
247
+ if (!pid || !pidAlive(pid, deps)) {
248
+ try { f.rmSync(pidPath(params, deps), { force: true }); } catch { /* best-effort */ }
249
+ return { ok: true, wasRunning: false };
250
+ }
251
+ try { kill(pid); } catch (e) { return { ok: false, reason: e.message, pid }; }
252
+ try { f.rmSync(pidPath(params, deps), { force: true }); } catch { /* best-effort */ }
253
+ return { ok: true, wasRunning: true, pid };
254
+ }
255
+
256
+ // Probe the running server. Returns { up, authConfigured }. `up` = the plain
257
+ // /healthz (needs no DB); authConfigured comes from /api/gds/healthz.
258
+ async function health(params, deps = {}) {
259
+ const fetchImpl = deps.fetchImpl || fetch;
260
+ const base = `http://${HOST}:${params.port}`;
261
+ const timeout = deps.healthTimeoutMs || 4000;
262
+ let up = false;
263
+ try {
264
+ const r = await fetchImpl(`${base}/healthz`, { signal: AbortSignal.timeout(timeout) });
265
+ up = r.status < 500;
266
+ } catch { return { up: false, authConfigured: false }; }
267
+ let authConfigured = false;
268
+ try {
269
+ const r = await fetchImpl(`${base}/api/gds/healthz`, { signal: AbortSignal.timeout(timeout) });
270
+ const j = await r.json();
271
+ authConfigured = j && j.auth_configured === true;
272
+ } catch { /* leave false */ }
273
+ return { up, authConfigured };
274
+ }
275
+
276
+ // argv helper local to dev-lib (dev.js uses cli-lib's arg() for the real process
277
+ // argv; this variant is pure so resolveRunParams stays unit-testable). Accepts
278
+ // `--flag value` and `--flag=value`.
279
+ function argFrom(args, name) {
280
+ const eq = args.find((a) => a.startsWith(`${name}=`));
281
+ if (eq != null) return eq.slice(name.length + 1);
282
+ const i = args.indexOf(name);
283
+ return i >= 0 ? args[i + 1] : null;
284
+ }
285
+
286
+ // depsInstallPlan (task 1002405) — decide whether `bongos dev` must install node
287
+ // deps before it can do anything useful, and with which command. A freshly-cloned
288
+ // instance has no node_modules, so migrate.sh (pg) and the server (pg/express)
289
+ // would crash; install FIRST — this makes `bongos dev` a true one-command bring-up
290
+ // even from a bare checkout (the case where a global/npx bongos, or `node
291
+ // <core>/scripts/gds/dev.js`, is reachable but deps are not). Prefer `npm ci` when
292
+ // a lockfile is present (reproducible + faster), else `npm install`. Pure:
293
+ // injectable fs.
294
+ function depsInstallPlan({ instanceRoot, f = fs } = {}) {
295
+ const present = f.existsSync(path.join(instanceRoot, 'node_modules'));
296
+ if (present) return { needed: false, cmd: null, args: [] };
297
+ const hasLock = f.existsSync(path.join(instanceRoot, 'package-lock.json'));
298
+ return hasLock
299
+ ? { needed: true, cmd: 'ci', args: ['ci'] }
300
+ : { needed: true, cmd: 'install', args: ['install'] };
301
+ }
302
+
303
+ module.exports = {
304
+ HOST, DEFAULT_PORT, CORE_ROOT,
305
+ readJsonSafe, loadBrandingFacts, parsePort, sanitizeDbName, deriveDbName,
306
+ resolveRunParams, buildEnv, authWillWork, depsInstallPlan,
307
+ slug, pidPath, logPath, readPid, pidAlive, isRunning, serverEntry,
308
+ startDetached, stop, health, argFrom,
309
+ };