@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,78 @@
1
+ 'use strict';
2
+
3
+ // modules/sessions/pollers/session-reward-reconcile.js — the loader poller entry
4
+ // for the STATE-BASED session-token-reward safety net (ADR 0120).
5
+ //
6
+ // The deferred cost-plus session reward books via two LIVE triggers only: the
7
+ // upload-time gate and the in-process `task.shipped` listener. Both miss a task
8
+ // that reaches status='shipped' OUT OF BAND — a direct-SQL / hotpatch flip, or a
9
+ // ship in another process — leaving that session's drachmae stranded forever even
10
+ // though the code landed. This poller periodically calls
11
+ // db.reconcileUnbookedSessionRewards(), which books the reward for any already-
12
+ // shipped session that has no session.token_reward row yet. It is the sessions-
13
+ // domain twin of the publish-reconciler poller (the same loader inversion), and
14
+ // mirrors the task 1679 achievements reconcile (reconcile from durable STATE, not
15
+ // the event). Idempotent — a re-sweep never double-pays.
16
+ //
17
+ // Deliberately independent of any GitHub push credential (unlike the confirmed→
18
+ // shipped publish-reconciler): this sweep touches no git and its whole purpose is
19
+ // to run on a credential-less box and recover already-shipped-but-unbooked work.
20
+
21
+ const db = require('../db');
22
+
23
+ // Sweep cadence. A safety net, not a hot path — the live triggers do the real-time
24
+ // work; this only catches out-of-band ships, so a relaxed interval is fine.
25
+ const INTERVAL_MS = Number(process.env.SESSION_REWARD_RECONCILE_MS) || 10 * 60_000;
26
+ const FIRST_SWEEP_DELAY_MS = Number(process.env.SESSION_REWARD_RECONCILE_FIRST_MS) || 90_000;
27
+
28
+ let intervalTimer = null;
29
+ let firstTimer = null;
30
+
31
+ async function runSweep() {
32
+ const res = await db.reconcileUnbookedSessionRewards({ limit: 200 });
33
+ if (res && res.awarded > 0) {
34
+ console.log(
35
+ `[session-reward-reconcile] booked ${res.awarded} stranded session reward(s) (scanned ${res.scanned})`
36
+ );
37
+ }
38
+ return res;
39
+ }
40
+
41
+ // Start the periodic sweep. Idempotent (a second call is a no-op). Kill-switched by
42
+ // SESSION_REWARD_RECONCILE_DISABLED=1. Timers are unref'd so they never hold the
43
+ // process open on a systemd restart (the publish-reconciler / discord-bot precedent).
44
+ function start() {
45
+ if (process.env.SESSION_REWARD_RECONCILE_DISABLED === '1') {
46
+ console.log('[session-reward-reconcile] disabled (SESSION_REWARD_RECONCILE_DISABLED=1)');
47
+ return;
48
+ }
49
+ if (intervalTimer) return; // already started
50
+
51
+ const tick = () => {
52
+ runSweep().catch((e) =>
53
+ console.error(`[session-reward-reconcile] sweep crashed: ${e && e.message ? e.message : e}`)
54
+ );
55
+ };
56
+
57
+ firstTimer = setTimeout(tick, FIRST_SWEEP_DELAY_MS);
58
+ if (firstTimer.unref) firstTimer.unref();
59
+ intervalTimer = setInterval(tick, INTERVAL_MS);
60
+ if (intervalTimer.unref) intervalTimer.unref();
61
+
62
+ console.log(
63
+ `[session-reward-reconcile] started — recovering out-of-band-ship rewards every ${Math.round(INTERVAL_MS / 1000)}s`
64
+ );
65
+ }
66
+
67
+ function stop() {
68
+ if (firstTimer) {
69
+ clearTimeout(firstTimer);
70
+ firstTimer = null;
71
+ }
72
+ if (intervalTimer) {
73
+ clearInterval(intervalTimer);
74
+ intervalTimer = null;
75
+ }
76
+ }
77
+
78
+ module.exports = { start, stop, runSweep, INTERVAL_MS };
@@ -133,7 +133,40 @@ function recordSessionRead(req, { route, targetBuilderId, target }) {
133
133
  });
134
134
  }
135
135
 
136
+ // The module's kernel seam wiring, registered when this factory runs (the loader
137
+ // runs it at boot, already gated to enabled instances — the discord precedent).
138
+ // This is the LAND-side half of ADR 0120 "pay on land": core emits `task.shipped`
139
+ // on EVERY land path (publish-reconciler + routes/tasks) with the shipped task
140
+ // row; we react by booking the cost-plus SESSION token reward for any session
141
+ // that shipped that task and DEFERRED its reward at upload time (the ci/async
142
+ // order, where the digest upload precedes the land). The upload-side gate in
143
+ // db.upsertSessionRecordWithReward covers the sync/laptop order; both call the
144
+ // SAME idempotent award, so re-firing is a safe per-session_id no-op.
145
+ //
146
+ // sessions registers no PORT (provides: []) — this is an EVENT listener, not a
147
+ // port, so module.json is unchanged. off→on guarded so a test re-instantiating
148
+ // the router never stacks duplicate listeners (the discord listener precedent).
149
+ let _shipRewardListenerOff = null;
150
+ function registerSeams() {
151
+ if (_shipRewardListenerOff) _shipRewardListenerOff();
152
+ _shipRewardListenerOff = api.on('task.shipped', (task) => {
153
+ const id = task && task.id;
154
+ if (id == null) return;
155
+ // Best-effort: awardDeferredSessionRewardForTask is async, so a rejection
156
+ // (e.g. a transient DB error) surfaces as a rejected promise — .catch keeps
157
+ // it from escaping the listener. This matters because the dominant ci/async
158
+ // land path emits SYNCHRONOUSLY (publish-reconciler's emit()), which is
159
+ // fire-and-forget and would otherwise turn a throw here into an
160
+ // unhandledRejection. A missed reward is re-attempted idempotently on the
161
+ // next land/upload, so a best-effort miss never double-pays or loses money.
162
+ db.awardDeferredSessionRewardForTask(id).catch((e) =>
163
+ console.error('[sessions] deferred session-reward on task.shipped (non-blocking):',
164
+ e && e.message ? e.message : e));
165
+ });
166
+ }
167
+
136
168
  module.exports = function buildSessionsRouter() {
169
+ registerSeams();
137
170
  const router = express.Router();
138
171
  const uploadJson = express.json({ limit: UPLOAD_BODY_LIMIT });
139
172
 
package/package-lock.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@bongos/core",
3
- "version": "1.18.3",
3
+ "version": "1.18.19",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@bongos/core",
9
- "version": "1.18.3",
9
+ "version": "1.18.19",
10
10
  "license": "AGPL-3.0-or-later",
11
11
  "dependencies": {
12
12
  "express": "^4.21.2",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bongos/core",
3
- "version": "1.18.3",
3
+ "version": "1.18.19",
4
4
  "description": "Cloud Bongos — the AI-first build platform core (GDS + platform surfaces + module system), installed as a versioned dependency (ADR 0108).",
5
5
  "license": "AGPL-3.0-or-later",
6
6
  "main": "src/platform-server.js",
@@ -82,11 +82,6 @@ body{
82
82
  .exit-wizard kbd{font-family:var(--font-mono);font-size:10px;line-height:1;background:rgba(42,31,23,0.08);border-radius:4px;padding:2px 5px;color:var(--ink-faint);}
83
83
  .who{display:flex;align-items:center;gap:10px;font-size:14px;font-weight:600;color:var(--ink-soft);min-height:34px;}
84
84
  .who img{width:30px;height:30px;border-radius:50%;border:2px solid #fff;box-shadow:0 2px 8px rgba(42,31,23,0.12);}
85
- .who a{color:var(--accent-deep);text-decoration:none;font-weight:700;}
86
- .who a:hover{text-decoration:underline;}
87
- /* switch to a different GitHub account from inside the wizard (task 2098) */
88
- .who .switch-acct{font-size:12px;font-weight:600;color:var(--ink-faint);padding-left:10px;margin-left:2px;border-left:1px solid rgba(42,31,23,0.16);}
89
- .who .switch-acct:hover{color:var(--accent-deep);}
90
85
 
91
86
  /* ── layout ── */
92
87
  .wrap{
@@ -202,10 +197,6 @@ input[type=text].bad{border-color:var(--err);}
202
197
  .ghbtn:hover{background:#1b1f24;transform:translateY(-1px);}
203
198
  .ghbtn:active{transform:translateY(0) scale(.98);}
204
199
  .ghbtn .gh{width:18px;height:18px;fill:currentColor;}
205
- .ghcreate{display:none;margin-bottom:18px;}
206
- .ghcreate.on{display:block;}
207
- .ghcreate-note{font-size:13px;font-weight:500;color:var(--ink-faint);line-height:1.5;margin-top:9px;max-width:52ch;}
208
- .ghcreate-note strong{color:var(--ink-soft);}
209
200
  .steps .ghbtn{font-size:14px;padding:9px 16px;margin-top:10px;}
210
201
 
211
202
  /* buttons */
@@ -380,7 +371,11 @@ input[type=text].bad{border-color:var(--err);}
380
371
  <section class="panel" id="panel-gate" aria-labelledby="gate-h">
381
372
  <h1 id="gate-h">Create a project</h1>
382
373
  <p class="lede">Your own <strong data-brand-product-inline>Cloud Bongos</strong> project: its own repository, its own home on the web, and a hall of AI builders who do the building. You set the direction; agents lay down the code. Sign in with GitHub so the project belongs to <em>you</em> from the very first beat.</p>
383
- <a class="btn big" id="signin" href="/api/bongos/auth/web/start?return=/create">
374
+ <!-- The wizard's own sign-in opts into repo scope (read:user + public_repo) up
375
+ front (&repos=1) — anyone here intends to create a project, which needs repo
376
+ access, so we ask once at sign-in instead of a second "Connect" step at Home.
377
+ Hall + CLI sign-in stay identity-only (ADR 0143 least-privilege default). -->
378
+ <a class="btn big" id="signin" href="/api/bongos/auth/web/start?return=/create&amp;repos=1">
384
379
  <svg class="gh" viewBox="0 0 16 16" aria-hidden="true"><path d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27s1.36.09 2 .27c1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.01 8.01 0 0 0 16 8c0-4.42-3.58-8-8-8z"/></svg>
385
380
  Sign in with GitHub
386
381
  </a>
@@ -430,7 +425,7 @@ input[type=text].bad{border-color:var(--err);}
430
425
  to the manual owner/repo inputs below (#manualHome), which stay authoritative. -->
431
426
  <div id="ghConnect" class="reassure">
432
427
  <p id="ghConnectMsg">&nbsp;</p>
433
- <button type="button" class="btn" id="ghConnectBtn">Connect GitHub</button>
428
+ <button type="button" class="btn" id="ghConnectBtn">Allow repo access</button>
434
429
  <button type="button" class="linklike" id="ghManualToggle">or enter it manually</button>
435
430
  </div>
436
431
  <div id="ghBusy" class="hint" hidden>Reading your GitHub repositories…</div>
@@ -454,13 +449,6 @@ input[type=text].bad{border-color:var(--err);}
454
449
  <input type="text" id="f-repo" placeholder="mercury" autocomplete="off" maxlength="120">
455
450
  </label>
456
451
  </div>
457
- <div class="ghcreate" id="ghCreate">
458
- <a class="ghbtn" id="ghCreateBtn" href="https://github.com/new" target="_blank" rel="noopener">
459
- <svg class="gh" viewBox="0 0 16 16" aria-hidden="true"><path d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27s1.36.09 2 .27c1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.01 8.01 0 0 0 16 8c0-4.42-3.58-8-8-8z"/></svg>
460
- Create this repo on GitHub
461
- </a>
462
- <p class="ghcreate-note">Opens GitHub with the name already filled in — just click <strong>Create repository</strong> and leave it empty, then come back and press Next. Already made it? Just press Next.</p>
463
- </div>
464
452
  <div class="hint" id="h-home">&nbsp;</div>
465
453
  <div class="nav">
466
454
  <button type="button" class="btn ghost" data-back="1">← Back</button>
@@ -607,10 +595,6 @@ input[type=text].bad{border-color:var(--err);}
607
595
  if (owner) u.searchParams.set('owner', owner);
608
596
  return u.toString();
609
597
  }
610
- function updateGhCreateHref() {
611
- var a = $('ghCreateBtn');
612
- if (a) a.href = githubNewRepoUrl(state.owner, state.repo);
613
- }
614
598
  function esc(s) {
615
599
  return String(s == null ? '' : s).replace(/[&<>"']/g, function (c) {
616
600
  return { '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;' }[c];
@@ -664,7 +648,6 @@ input[type=text].bad{border-color:var(--err);}
664
648
  if (!state.repoTouched) {
665
649
  state.repo = deriveRepo(state.name);
666
650
  $('f-repo').value = state.repo;
667
- updateGhCreateHref();
668
651
  }
669
652
  var h = $('h-name');
670
653
  if (state.slug) { h.innerHTML = 'its handle on the platform: <code>' + esc(state.slug) + '</code>'; h.className = 'hint'; }
@@ -678,10 +661,6 @@ input[type=text].bad{border-color:var(--err);}
678
661
  $('c-green').setAttribute('aria-pressed', String(mode === 'greenfield'));
679
662
  $('c-adopt').setAttribute('aria-pressed', String(mode === 'adopt'));
680
663
  $('adoptNote').classList.toggle('on', mode === 'adopt');
681
- // The "create the repo on GitHub" button only makes sense when starting fresh —
682
- // an adopted repo already exists.
683
- $('ghCreate').classList.toggle('on', mode === 'greenfield');
684
- updateGhCreateHref();
685
664
  saveDraft();
686
665
  syncHome();
687
666
  }
@@ -692,7 +671,7 @@ input[type=text].bad{border-color:var(--err);}
692
671
  /* ── GitHub-integrated repo picker (adopt) + auto-create (fresh) — task 1002099 / ADR 0143.
693
672
  Progressive enhancement: probe GET /github/repos once we reach step 2.
694
673
  200 → we hold repo access → show the picker (adopt) / name field (fresh);
695
- 403 → offer "Connect GitHub" (a ?repos=1 re-sign-in);
674
+ 403 → offer to grant repo access (a ?repos=1 re-sign-in);
696
675
  503 (server can't store the token) or any error → the manual inputs.
697
676
  #manualHome (the classic owner/repo fields) is the always-safe fallback. ── */
698
677
  var repoAccess = null; // null=unknown/loading · true · 'need-connect' · 'unavailable' · 'manual'
@@ -722,10 +701,10 @@ input[type=text].bad{border-color:var(--err);}
722
701
  $('manualHome').hidden = !isManualHome();
723
702
  if (need) {
724
703
  $('ghConnectMsg').textContent = adopt
725
- ? 'Connect your GitHub account and pick the repository to adopt.'
726
- : 'Connect your GitHub account and we’ll create the empty repo for you.';
704
+ ? 'You’re signed in — creating your project needs one more GitHub permission. Allow repo access to pick the repository to adopt.'
705
+ : 'You’re signed in — creating your project needs one more GitHub permission. Allow repo access and we’ll create the empty repo for you.';
727
706
  }
728
- if (ok && adopt) renderRepoList();
707
+ if (ok && adopt) initAdoptPicker();
729
708
  if (ok && !adopt) syncFresh();
730
709
  }
731
710
 
@@ -749,25 +728,57 @@ input[type=text].bad{border-color:var(--err);}
749
728
  }
750
729
  $('ghNewName').addEventListener('input', function () { state.repo = this.value.trim(); updateFreshPreview(); saveDraft(); });
751
730
 
731
+ // The adopt picker behaves like a standard combobox (owner design review, task
732
+ // 1002247): the dropdown is open only while the visitor is choosing. Selecting a
733
+ // repo fills the search box with its full_name and collapses the list; focusing or
734
+ // typing reopens it.
735
+ function openRepoList() { $('ghList').hidden = false; renderRepoList(); }
736
+ function closeRepoList() { $('ghList').hidden = true; }
752
737
  function renderRepoList() {
753
738
  var q = ($('ghSearch').value || '').trim().toLowerCase();
754
- var rows = repoList.filter(function (r) { return !q || String(r.full_name).toLowerCase().indexOf(q) >= 0; }).slice(0, 100);
755
739
  var sel = state.owner && state.repo ? (state.owner + '/' + state.repo) : '';
740
+ // When the box just shows the current selection verbatim, list everything (so the
741
+ // visitor can switch) instead of filtering down to the one selected repo.
742
+ var effectiveQ = q === sel.toLowerCase() ? '' : q;
743
+ var rows = repoList.filter(function (r) { return !effectiveQ || String(r.full_name).toLowerCase().indexOf(effectiveQ) >= 0; }).slice(0, 100);
756
744
  $('ghList').innerHTML = rows.length
757
745
  ? rows.map(function (r) {
758
746
  return '<button type="button" class="ghrow" role="option" data-full="' + esc(r.full_name) + '" aria-selected="' + (r.full_name === sel) + '">' +
759
747
  '<span>' + esc(r.full_name) + '</span>' + (r.private ? '<span class="priv">Private</span>' : '') + '</button>';
760
748
  }).join('')
761
- : '<div class="hint" style="padding:10px 12px">No repositories match “' + esc(q) + '”.</div>';
749
+ : '<div class="hint" style="padding:10px 12px">No repositories match “' + esc(effectiveQ) + '”.</div>';
750
+ }
751
+ // Fill the search box with the current selection (if any) and collapse the list;
752
+ // with nothing chosen yet, open it so the visitor sees their repos right away.
753
+ function initAdoptPicker() {
754
+ var sel = state.owner && state.repo ? state.owner + '/' + state.repo : '';
755
+ // Only treat it as a confirmed pick if it's actually one of the visitor's repos.
756
+ // owner/repo are auto-derived from the project name, so a bare derived value must
757
+ // NOT masquerade as a chosen repo — that would collapse the list onto a repo that
758
+ // may not even exist.
759
+ var picked = sel && repoList.some(function (r) { return String(r.full_name).toLowerCase() === sel.toLowerCase(); });
760
+ var h = $('ghAdoptHint');
761
+ if (picked) {
762
+ $('ghSearch').value = sel;
763
+ h.className = 'hint ok'; h.innerHTML = 'Adopting <strong>' + esc(sel) + '</strong>.';
764
+ closeRepoList();
765
+ } else {
766
+ $('ghSearch').value = '';
767
+ h.className = 'hint'; h.innerHTML = '&nbsp;';
768
+ openRepoList();
769
+ }
762
770
  }
763
- $('ghSearch').addEventListener('input', renderRepoList);
771
+ $('ghSearch').addEventListener('focus', function () { this.select(); openRepoList(); });
772
+ $('ghSearch').addEventListener('input', openRepoList);
764
773
  $('ghList').addEventListener('click', function (ev) {
765
774
  var row = ev.target.closest('.ghrow'); if (!row) return;
766
775
  var full = row.getAttribute('data-full'); var slash = full.indexOf('/');
767
776
  state.owner = full.slice(0, slash); state.repo = full.slice(slash + 1);
777
+ state.ownerTouched = true; state.repoTouched = true; // a real pick — don't let name-derive clobber it
768
778
  saveDraft();
769
- Array.prototype.forEach.call($('ghList').querySelectorAll('.ghrow'), function (b) { b.setAttribute('aria-selected', String(b === row)); });
779
+ $('ghSearch').value = full; // pull the choice into the search box …
770
780
  var h = $('ghAdoptHint'); h.className = 'hint ok'; h.innerHTML = 'Adopting <strong>' + esc(full) + '</strong>.';
781
+ closeRepoList(); // … and collapse the dropdown (typical UX)
771
782
  });
772
783
 
773
784
  function ensureRepoAccess() {
@@ -785,8 +796,8 @@ input[type=text].bad{border-color:var(--err);}
785
796
  .then(syncHome);
786
797
  }
787
798
 
788
- $('f-owner').addEventListener('input', function () { state.owner = this.value.trim(); state.ownerTouched = true; updateGhCreateHref(); saveDraft(); });
789
- $('f-repo').addEventListener('input', function () { state.repo = this.value.trim(); state.repoTouched = true; updateGhCreateHref(); saveDraft(); });
799
+ $('f-owner').addEventListener('input', function () { state.owner = this.value.trim(); state.ownerTouched = true; saveDraft(); });
800
+ $('f-repo').addEventListener('input', function () { state.repo = this.value.trim(); state.repoTouched = true; saveDraft(); });
790
801
 
791
802
  $('f-slug').addEventListener('input', function () {
792
803
  state.slugTouched = true;
@@ -815,7 +826,7 @@ input[type=text].bad{border-color:var(--err);}
815
826
  }
816
827
  if (step === 2) {
817
828
  var homeErr = function (msg) { $('h-home').textContent = msg; $('h-home').className = 'hint err'; return false; };
818
- if (repoAccess === 'need-connect') return homeErr('Connect GitHub to pick or create your repository — or choose “enter it manually”.');
829
+ if (repoAccess === 'need-connect') return homeErr('Allow GitHub repo access to pick or create your repository — or choose “enter it manually”.');
819
830
  if (repoAccess === true && state.mode !== 'adopt') {
820
831
  // Fresh with access: just the new repo NAME (owner is the signed-in account).
821
832
  if (!validRepoName(state.repo)) return homeErr('Name the new repository (letters, numbers, and . _ -).');
@@ -1114,31 +1125,14 @@ input[type=text].bad{border-color:var(--err);}
1114
1125
  if (e.key === 'Escape') { e.preventDefault(); exitWizard(); }
1115
1126
  });
1116
1127
 
1117
- /* ── switch GitHub account from inside the wizard (task 2098) ── you are not
1118
- locked to the account you entered with: sign out of the Cloud Bongos session,
1119
- then restart GitHub sign-in so the wizard reloads under whichever account you
1120
- pick. (To land on a *different* GitHub account, switch accounts on GitHub's
1121
- side; this un-locks our session so the next sign-in is honored.) */
1122
- function switchAccount(e) {
1123
- if (e) e.preventDefault();
1124
- var back = API + '/auth/web/start?return=/create';
1125
- fetch(API + '/auth/logout', { method: 'POST', credentials: 'same-origin' })
1126
- .then(function () { window.location.assign(back); })
1127
- .catch(function () { window.location.assign(back); });
1128
- }
1129
-
1130
1128
  /* ── boot: who am I? ── (task 2094: the wizard shows the signed-in identity
1131
1129
  ONLY — never a link into cloud bongos's own builders hall. A project
1132
- creator enters a hall from their own project's landing, not from here.
1133
- task 2098: + a "switch account" action so you're not locked in.) ── */
1130
+ creator enters a hall from their own project's landing, not from here.) ── */
1134
1131
  function renderWho() {
1135
1132
  var w = $('who');
1136
1133
  if (me) {
1137
1134
  w.innerHTML = (me.avatar_url ? '<img src="' + esc(me.avatar_url) + '" alt="">' : '') +
1138
- '<span>' + esc(me.github_login || 'signed in') + '</span>' +
1139
- '<a href="#" class="switch-acct" id="switchAcct">switch account</a>';
1140
- var sw = $('switchAcct');
1141
- if (sw) sw.addEventListener('click', switchAccount);
1135
+ '<span>' + esc(me.github_login || 'signed in') + '</span>';
1142
1136
  } else {
1143
1137
  w.innerHTML = '';
1144
1138
  }
@@ -1154,7 +1148,6 @@ input[type=text].bad{border-color:var(--err);}
1154
1148
  if (!state.ownerTouched && !state.owner && me && me.github_login) {
1155
1149
  state.owner = me.github_login;
1156
1150
  $('f-owner').value = state.owner;
1157
- updateGhCreateHref();
1158
1151
  saveDraft();
1159
1152
  }
1160
1153
  // returning after a create? resume watching that instance instead of a blank wizard
@@ -0,0 +1,137 @@
1
+ # Cloud Bongos — landing gate (interactive)
2
+
3
+ `index.html` is the Cloud Bongos landing experience — a small state machine over one
4
+ self-contained page (`space.css` + `space.js` for the space side; the download scene + its
5
+ drag physics live in the inline `<script>`), built from **drawn ATLA-style sprites**
6
+ (`sprites/*.png` — a `> cloud bongos` terminal, an enter key, a search bar, and download tiles)
7
+ refracting the backdrop through SVG displacement maps.
8
+
9
+ ## The flow (tasks 1721, 1722)
10
+
11
+ Four body-class views: `view-entry` → `view-space` → `view-download` / `view-detail`.
12
+
13
+ 1. **Entry — type `bongos`, in space** (`#entry`, `view-entry`). Every visit lands here: the
14
+ `> cloud ▊` terminal floating in the **space sky** (deep-blue → sunrise gradient, drifting
15
+ clouds), *not* the temple world. Typing `bongos` is the ritual on every visit; the enter key
16
+ lights, and pressing enter flashes through into the circles. Driven by `space.js`
17
+ (`entryKey` / `enterSite`), reusing the terminal + enter CSS.
18
+ 2. **Space — the circles** (`#space` / `#space-canvas`, `view-space`). A pannable, zoomable sky
19
+ of project orbs, sized by number of builders, moving as a **living gravity cluster** — every
20
+ orb attracts every other by its mass (∝ size³), so the field swirls, bodies sling past one
21
+ another, and a small orb is sometimes briefly caught circling a big one like a moon (task
22
+ 1733; see "The gravity" below). Project data is **inlined** in `space.js` (`PROJECTS`) — Caddy
23
+ 404s `*.json` on staging — so swap in a real registry by changing `PROJECTS` + `layout()`.
24
+ - **Drag** empty sky to pan · **scroll** to zoom (toward the cursor).
25
+ - **Hover** an orb for an info card; **click** it to dive in.
26
+ - **Space bar** opens a search scoped to the orbs **currently in view** (client-side match
27
+ over name/tagline/tags now; the Claude-backed answer is a dormant seam, `aiAnswer()`, per
28
+ the "interaction now, AI wired next" decision).
29
+ - **Click empty sky** drops a new project circle.
30
+ 3. **Download — Cloud Bongos' install page** (`#land` in `.stage`, `view-download`). Clicking the
31
+ Cloud Bongos orb zooms **straight** into the search + download-tiles scene over the
32
+ temple-world backdrop — **no type-`bongos` gate in between** (that already happened at entry).
33
+ `space.js` fires `cb:show-download`; the inline script reveals `#land` and runs its drag.
34
+ 4. **Detail — other projects** (`#detail`, `view-detail`). Clicking any non-self orb opens a
35
+ themed "full story — coming soon" page with a link out to its live site.
36
+
37
+ **ESC** rises from the download or detail page back to the circles (`space.js` → `cb:hide-download`).
38
+
39
+ ## The download scene's elastic-tether drag (task 1534)
40
+
41
+ Every download sprite (search bar, tiles, wordmark) is draggable. Grab one and it resists like
42
+ it's anchored; pull past the threshold and it snaps free, hovers, then drifts slowly home and
43
+ re-docks, carrying a faint idle bob at rest. The physics is scoped to `.stage`, so it never
44
+ grabs the entry terminal (which `space.js` owns). **This scene is separate from the space
45
+ gravity below — the download interaction is unchanged by task 1733.**
46
+
47
+ ## The space gravity — a living n-body cluster (task 1733)
48
+
49
+ The project orbs on the **space** view (`view-space`) are a real gravitational simulation, all
50
+ in `space.js`. Each orb is a body with **mass ∝ size³** (more builders → bigger → heavier →
51
+ pulls harder), so the sky behaves like planets, suns and the occasional fleeting moon rather
52
+ than a fixed scatter. Under the hood:
53
+
54
+ - **Softened Newtonian gravity**, every pair, so a close pass *slings* instead of blowing up
55
+ (no singularities).
56
+ - A **fixed-timestep leapfrog integrator** (`physStep`) for accurate, energy-stable motion; the
57
+ frame loop (`tick` → `render`) interpolates between steps for smoothness.
58
+ - **Soft collisions** so discs bounce rather than overlap; a **soft containment well** plus a
59
+ **band thermostat** keep the swirl bounded and lively *forever* — energy is free to ebb and
60
+ flow inside a band, and only a runaway collapse or blow-up gets a gentle nudge back.
61
+ - Net drift is removed each step, so the cluster stays centred on screen (and stays put when a
62
+ visitor adds a new orb, which is flung into the swirl).
63
+
64
+ Verified stable over a 60-minute headless simulation (no NaN, never off-screen, never
65
+ interpenetrating). It replaced the earlier "drift around fixed anchors" motion.
66
+
67
+ ### Tuning the gravity
68
+
69
+ All knobs live in the `GRAV` block near the top of `space.js`:
70
+
71
+ | You want… | Edit (in `GRAV`) |
72
+ |--------------------------------------|---------------------------------------------------|
73
+ | Livelier / faster swirl | `SWIRL` ↑ (or `THERMO_LO` ↑) |
74
+ | Calmer, slower motion | `SWIRL` ↓ / `THERMO_LO` ↓ |
75
+ | Tighter cluster (less panning) | `CONTAIN_R` ↓ |
76
+ | Stronger pull between orbs | `G` ↑ |
77
+ | Fewer hard slingshots on near-misses | `SOFTEN` ↑ |
78
+ | Bouncier contacts (→ elastic) | `REST` → 1 |
79
+ | Sharper size→mass contrast | lower `R_REF`, or raise the `massFor` exponent |
80
+
81
+ Turn it off (freeze the orbs): comment out `initOrbits()` in `layout()` and the
82
+ `requestAnimationFrame(tick)` at the bottom of the IIFE — pan, zoom, hover, dive, search and the
83
+ table all keep working.
84
+
85
+ > **Live:** password-locked at **staging.cloudbongos.com** (the future landing page, parked
86
+ > for review). Setup + the password live in [`docs/recipes/cloudbongos-staging-gate.md`](../../../docs/recipes/cloudbongos-staging-gate.md).
87
+ > Branding (logo coin / wordmark / mascot / code-glyphs) is still being finished and is
88
+ > intentionally untouched here.
89
+
90
+ ## Preview it locally
91
+
92
+ The page links to art one folder up (`../world/assets/…`), so it must be served from the
93
+ **`brand/cloud-bongos/`** folder — not from `gate/` itself, or those paths break.
94
+
95
+ **Easiest (Claude Code preview):** start the `gate-preview` server in `.claude/launch.json`,
96
+ then open `http://127.0.0.1:8137/gate/index.html`.
97
+
98
+ **From a terminal** (repo root): `python3 -m http.server --directory brand/cloud-bongos 8137`,
99
+ then open <http://127.0.0.1:8137/gate/index.html>.
100
+
101
+ Use a Chromium browser (Chrome/Edge) — the live-glass refraction relies on `backdrop-filter`
102
+ with an SVG displacement map. Safari ignores the displacement and falls back to a plain
103
+ frost, which still looks fine.
104
+
105
+ ## Tuning the drag
106
+
107
+ All behaviour is in the single `<script>` at the bottom of `index.html`, which opens with a
108
+ commented **TUNING** block. Common tweaks:
109
+
110
+ | You want… | Edit (in the TUNING block) |
111
+ |------------------------------------------|-----------------------------------|
112
+ | Harder / easier to pull a pane free | `THRESHOLD` — ↑ harder, ↓ easier |
113
+ | How far a tap can move before it's a drag | `DEADZONE` — px |
114
+ | Freed panes wait longer before returning | `HOVER` — milliseconds |
115
+ | Slower, more drifty return | `DRIFT_MAXV` — ↓ slower |
116
+ | Wider, more wandering return path | `WMAG` ↑ and/or `TURN` ↓ |
117
+ | More / less idle "breathing" | `BOB_X`, `BOB_Y` (px) — `0` = off |
118
+
119
+ ### Which elements are draggable
120
+
121
+ The drag targets **`.mark`** (the wordmark) + every **`.el`** sprite pane (terminal, enter,
122
+ search, tiles). To add one, give it the `el` class (or push it into the `els` array); to
123
+ exclude one, filter it out of `els`.
124
+
125
+ ### Why it does not fight the other animations
126
+
127
+ The drag only ever applies `transform: translate(…)` to each pane, and it owns that property
128
+ exclusively — so the float/reveal animations were replaced by the drag's own idle bob, and
129
+ the enter key's reveal uses opacity + blur only (no transform). The **scene** transforms
130
+ (the dive's scale/translate) live on the parent `.gate`/`.land` containers, so they compose
131
+ with the per-pane drag instead of clobbering it. When a scene settles, `measure()` re-runs
132
+ so a freshly-revealed pane drags from the right home.
133
+
134
+ ### Turn the drag off
135
+
136
+ Delete the second `<script>` IIFE (the drag) — the type→dive interaction and all the art
137
+ keep working untouched.