@bongos/core 1.18.0 → 1.18.18

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (553) hide show
  1. package/.bongos-core.json +1740 -640
  2. package/.claude/skills/builder-box/SKILL.md +6 -0
  3. package/.claude/skills/builder-sequence/SKILL.md +97 -0
  4. package/.claude/skills/builder-ship/SKILL.md +8 -2
  5. package/.claude/skills/builder-start/SKILL.md +11 -0
  6. package/.claude/skills/builder-sync/SKILL.md +41 -0
  7. package/.claude/skills/fix-task/SKILL.md +64 -0
  8. package/.claude/skills/goal-create/SKILL.md +158 -0
  9. package/.claude/skills/goal-review/SKILL.md +3 -1
  10. package/.claude/skills/idea-triage/SKILL.md +14 -8
  11. package/.claude/skills/merge-mode/SKILL.md +2 -2
  12. package/.claude/skills/new-project/SKILL.md +1 -0
  13. package/.claude/skills/planning-session/SKILL.md +45 -18
  14. package/.env.local.example +4 -3
  15. package/README.md +5 -3
  16. package/bin/bongos.js +1 -0
  17. package/clients/bongos-client/README.md +2 -2
  18. package/clients/bongos-client/bongos-client.global.js +82 -18
  19. package/clients/bongos-client/index.cjs +82 -18
  20. package/clients/bongos-client/index.d.ts +131 -29
  21. package/clients/bongos-client/index.mjs +82 -18
  22. package/config/branding.neutral.json +11 -2
  23. package/config/governance.neutral.json +21 -0
  24. package/docs/adr/0043-git-ssh-trust-boundary-and-rank-floor-on-permission-paths.md +24 -0
  25. package/docs/adr/0090-metic-task-authoring.md +1 -1
  26. package/docs/adr/0142-project-context-is-prose-only.md +41 -0
  27. package/docs/adr/0143-onboarding-repo-picker-oauth-scope.md +35 -0
  28. package/docs/adr/0144-devbox-rehome-onto-cloudbongos-plane.md +41 -0
  29. package/docs/adr/0144-federated-single-logout-backchannel.md +42 -0
  30. package/docs/adr/0145-devbox-app-branding-driven-module.md +44 -0
  31. package/docs/adr/0145-free-hosted-project-tier-isolation-and-domain-separation.md +62 -0
  32. package/docs/adr/0146-configurable-reward-mode-cost-plus-only.md +53 -0
  33. package/docs/adr/0147-standalone-live-docs-regen.md +39 -0
  34. package/docs/adr/0148-task-scoped-box-source-access.md +48 -0
  35. package/docs/adr/0149-bongos-dev-local-launcher.md +57 -0
  36. package/docs/adr/0150-box-first-boot-bringup-vendored-instances.md +68 -0
  37. package/docs/adr/0151-governance-permissions-as-atom-ranks-as-roles.md +229 -0
  38. package/docs/adr/0152-landing-gate-served-by-the-core-app.md +92 -0
  39. package/docs/adr/0152-metic-task-abandonment.md +67 -0
  40. package/docs/adr/0153-sequential-chain-runner.md +51 -0
  41. package/docs/adr/0154-goal-owner-criterion-authoring.md +86 -0
  42. package/docs/adr/README.md +15 -2
  43. package/docs/api/openapi.json +2648 -483
  44. package/docs/api-reference.md +73 -13
  45. package/docs/architecture.md +4 -2
  46. package/docs/branding-contract.md +3 -1
  47. package/docs/canonical-permissions.md +21 -8
  48. package/docs/file-map.md +15 -7
  49. package/docs/onboarding/diagrams/01-task-lifecycle.mmd +2 -2
  50. package/docs/onboarding/diagrams/02-rank-ladder.mmd +3 -1
  51. package/docs/onboarding/diagrams/05-versioning.mmd +1 -3
  52. package/docs/onboarding/diagrams/assertions.json +4 -7
  53. package/docs/onboarding/drachmae-and-karma.md +3 -3
  54. package/docs/onboarding/primer.md +17 -16
  55. package/docs/onboarding/slash-commands.md +124 -0
  56. package/docs/recipes/builder-box-lifecycle.md +23 -0
  57. package/docs/recipes/devbox-app-release.md +14 -4
  58. package/docs/recipes/instance-first-run.md +16 -0
  59. package/docs/recipes/local-dev.md +81 -0
  60. package/docs/recipes/ops-gotchas.md +19 -1
  61. package/docs/recipes/overnight-builder.md +1 -1
  62. package/docs/recipes/packaging-the-core.md +13 -0
  63. package/docs/recipes/parallel-goal-waves.md +174 -0
  64. package/docs/recipes/ship-and-regrade.md +2 -2
  65. package/docs/recipes/standalone-live-docs.md +48 -0
  66. package/docs/recipes/upgrading-the-core.md +5 -2
  67. package/docs/recipes/work-on-a-task-from-your-dev-box.md +108 -0
  68. package/migrations/003_pms.sql +6 -7
  69. package/migrations/006_pms_v2.sql +3 -31
  70. package/migrations/039_newcomer_friendly_column.sql +5 -0
  71. package/migrations/102_remove_box_first_provision_approval.sql +4 -0
  72. package/migrations/core_192_github_oauth_tokens.sql +33 -0
  73. package/migrations/core_193_task_needs_rebase.sql +28 -0
  74. package/migrations/core_194_confirmed_backlog_clawback.sql +63 -0
  75. package/migrations/core_195_idea_triage_provenance.sql +57 -0
  76. package/migrations/core_196_session_reward_topup.sql +69 -0
  77. package/modules/autonomy/pollers/routine-timers.js +9 -8
  78. package/modules/builder-settings/wandering-prefs.js +61 -18
  79. package/modules/dev-box/app/README.md +73 -0
  80. package/modules/dev-box/app/assets/tray/mac-alertTemplate.png +0 -0
  81. package/modules/dev-box/app/assets/tray/mac-alertTemplate@2x.png +0 -0
  82. package/modules/dev-box/app/assets/tray/mac-busyTemplate.png +0 -0
  83. package/modules/dev-box/app/assets/tray/mac-busyTemplate@2x.png +0 -0
  84. package/modules/dev-box/app/assets/tray/mac-offTemplate.png +0 -0
  85. package/modules/dev-box/app/assets/tray/mac-offTemplate@2x.png +0 -0
  86. package/modules/dev-box/app/assets/tray/mac-onTemplate.png +0 -0
  87. package/modules/dev-box/app/assets/tray/mac-onTemplate@2x.png +0 -0
  88. package/modules/dev-box/app/assets/tray/win-alert.png +0 -0
  89. package/modules/dev-box/app/assets/tray/win-busy.png +0 -0
  90. package/modules/dev-box/app/assets/tray/win-off.png +0 -0
  91. package/modules/dev-box/app/assets/tray/win-on.png +0 -0
  92. package/modules/dev-box/app/build-resources/dmg-background.png +0 -0
  93. package/modules/dev-box/app/build-resources/dmg-background@2x.png +0 -0
  94. package/modules/dev-box/app/build-resources/icon.png +0 -0
  95. package/modules/dev-box/app/electron-builder.tmpl.yml +95 -0
  96. package/modules/dev-box/app/electron-builder.yml +95 -0
  97. package/modules/dev-box/app/package-lock.json +5291 -0
  98. package/modules/dev-box/app/package.json +30 -0
  99. package/modules/dev-box/app/renderer/app.js +319 -0
  100. package/modules/dev-box/app/renderer/index.html +45 -0
  101. package/modules/dev-box/app/renderer/styles.css +246 -0
  102. package/modules/dev-box/app/renderer/theme.generated.css +16 -0
  103. package/modules/dev-box/app/scripts/apply-dmg-backdrop.sh +82 -0
  104. package/modules/dev-box/app/scripts/dmgbuild-settings.py +39 -0
  105. package/modules/dev-box/app/scripts/gen-brand.js +138 -0
  106. package/modules/dev-box/app/scripts/gen-dmg-backdrop.py +323 -0
  107. package/modules/dev-box/app/scripts/gen-icons.js +286 -0
  108. package/modules/dev-box/app/scripts/vendor-client.js +23 -0
  109. package/modules/dev-box/app/src/box-controller.js +287 -0
  110. package/modules/dev-box/app/src/brand.generated.json +13 -0
  111. package/modules/dev-box/app/src/brand.js +16 -0
  112. package/modules/dev-box/app/src/config.js +19 -0
  113. package/modules/dev-box/app/src/gds-client.js +90 -0
  114. package/modules/dev-box/app/src/main.js +610 -0
  115. package/modules/dev-box/app/src/pairing.js +57 -0
  116. package/modules/dev-box/app/src/preload.js +29 -0
  117. package/modules/dev-box/app/src/session-store.js +58 -0
  118. package/modules/dev-box/app/src/ssh-setup.js +569 -0
  119. package/modules/dev-box/app/src/task-picker.js +70 -0
  120. package/modules/dev-box/app/src/tray.js +99 -0
  121. package/modules/dev-box/app/src/updater.js +55 -0
  122. package/modules/dev-box/app/src/vendor/bongos-client.cjs +698 -0
  123. package/modules/dev-box/app/test/unit.mjs +526 -0
  124. package/modules/dev-box/box-access.js +121 -4
  125. package/modules/dev-box/boxes.js +20 -0
  126. package/modules/dev-box/module.json +1 -1
  127. package/modules/dev-box/routes/box.js +87 -3
  128. package/modules/economy/credits.js +116 -25
  129. package/modules/economy/reward.js +8 -0
  130. package/modules/economy/streak.js +2 -1
  131. package/modules/governance/CLAUDE.md +15 -0
  132. package/modules/governance/catalog.js +419 -0
  133. package/modules/governance/config.js +174 -0
  134. package/modules/governance/db.js +125 -0
  135. package/modules/governance/migrations/governance_001_tables.sql +81 -0
  136. package/modules/governance/migrations/governance_002_seed_and_backfill.sql +103 -0
  137. package/modules/governance/migrations/governance_003_task_abandon_metic.sql +28 -0
  138. package/modules/governance/migrations/governance_004_criterion_create.sql +39 -0
  139. package/modules/governance/migrations/governance_005_resync_builder_rank_roles.sql +56 -0
  140. package/modules/governance/module.json +10 -0
  141. package/modules/governance/protected-surfaces.js +88 -0
  142. package/modules/governance/protected-surfaces.json +80 -0
  143. package/modules/governance/rank-roles.js +83 -0
  144. package/modules/governance/resolver.js +221 -0
  145. package/modules/governance/routes/governance.js +89 -0
  146. package/modules/governance/routes/role-sync.js +49 -0
  147. package/modules/governance/session-scopes.js +127 -0
  148. package/modules/governance/session-scopes.json +38 -0
  149. package/modules/hall-ui/public/atlas.css +117 -0
  150. package/modules/hall-ui/public/atlas.html +3 -114
  151. package/modules/hall-ui/public/brand-apply.js +34 -6
  152. package/modules/hall-ui/public/builders.js +187 -219
  153. package/modules/hall-ui/public/diagrams.html +2 -0
  154. package/modules/hall-ui/public/dom-utils.js +27 -0
  155. package/modules/hall-ui/public/drachmae.html +2 -0
  156. package/modules/hall-ui/public/drachmae.js +54 -1
  157. package/modules/hall-ui/public/gate.css +98 -0
  158. package/modules/hall-ui/public/gate.html +3 -95
  159. package/modules/hall-ui/public/goal-inbox.js +4 -1
  160. package/modules/hall-ui/public/goals-page.js +626 -114
  161. package/modules/hall-ui/public/goals.css +79 -1
  162. package/modules/hall-ui/public/goals.html +38 -11
  163. package/modules/hall-ui/public/hall-widgets.js +62 -52
  164. package/modules/hall-ui/public/harbor.css +123 -0
  165. package/modules/hall-ui/public/harbor.html +3 -120
  166. package/modules/hall-ui/public/index.html +11 -4
  167. package/modules/hall-ui/public/instances-panel.js +4 -1
  168. package/modules/hall-ui/public/modules.css +63 -0
  169. package/modules/hall-ui/public/modules.html +3 -60
  170. package/modules/hall-ui/public/my-projects.html +78 -0
  171. package/modules/hall-ui/public/my-projects.js +142 -0
  172. package/modules/hall-ui/public/new-project-wizard.js +7 -3
  173. package/modules/hall-ui/public/not-ready.html +2 -0
  174. package/modules/hall-ui/public/pair.css +73 -0
  175. package/modules/hall-ui/public/pair.html +3 -70
  176. package/modules/hall-ui/public/palette.js +302 -0
  177. package/modules/hall-ui/public/primer.css +13 -0
  178. package/modules/hall-ui/public/primer.html +2 -0
  179. package/modules/hall-ui/public/profile.html +2 -0
  180. package/modules/hall-ui/public/ranks.html +2 -0
  181. package/modules/hall-ui/public/roadmap.css +179 -0
  182. package/modules/hall-ui/public/roadmap.html +9 -172
  183. package/modules/hall-ui/public/roadmap.js +329 -316
  184. package/modules/hall-ui/public/scouting.html +45 -0
  185. package/modules/hall-ui/public/scouting.js +73 -0
  186. package/modules/hall-ui/public/sessions.css +16 -0
  187. package/modules/hall-ui/public/sessions.html +3 -13
  188. package/modules/hall-ui/public/settings.html +2 -0
  189. package/modules/hall-ui/public/settings.js +4 -4
  190. package/modules/hall-ui/public/shell.js +143 -18
  191. package/modules/hall-ui/public/style.css +231 -39
  192. package/modules/hall-ui/public/task-detail.js +347 -26
  193. package/modules/hall-ui/public/task.html +6 -1
  194. package/modules/hall-ui/public/watch.html +2 -0
  195. package/modules/hall-ui/public/work.html +8 -6
  196. package/modules/hall-ui/public/work.js +1 -1
  197. package/modules/ideas/inbox.js +61 -11
  198. package/modules/ideas/routes/inbox.js +23 -0
  199. package/modules/lifecycle/claim-eligibility.js +72 -0
  200. package/modules/lifecycle/db.js +519 -139
  201. package/modules/lifecycle/done-when.js +18 -3
  202. package/modules/lifecycle/github-push.js +19 -2
  203. package/modules/lifecycle/goal-advisory.js +114 -0
  204. package/modules/lifecycle/goal-authz.js +62 -0
  205. package/modules/lifecycle/lifecycle.js +9 -0
  206. package/modules/lifecycle/publish-reconciler.js +109 -6
  207. package/modules/lifecycle/routes/claims.js +68 -23
  208. package/modules/lifecycle/routes/done-when.js +110 -15
  209. package/modules/lifecycle/routes/goals.js +8 -38
  210. package/modules/lifecycle/routes/tasks.js +38 -6
  211. package/modules/lifecycle/ship-card.js +6 -0
  212. package/modules/onboarding/onboarding-state.js +1 -1
  213. package/modules/platform-identity/CLAUDE.md +15 -0
  214. package/modules/platform-identity/hub-device.js +147 -0
  215. package/modules/platform-identity/hub-keys.js +151 -0
  216. package/modules/platform-identity/migrations/platform_identity_001_tables.sql +114 -0
  217. package/modules/platform-identity/migrations/platform_identity_002_client_origin_index.sql +18 -0
  218. package/modules/platform-identity/migrations/platform_identity_003_project_memberships.sql +42 -0
  219. package/modules/platform-identity/migrations/platform_identity_004_projects.sql +44 -0
  220. package/modules/platform-identity/migrations/platform_identity_005_builder_activity.sql +42 -0
  221. package/modules/platform-identity/module.json +18 -0
  222. package/modules/platform-identity/platform-identity.js +531 -0
  223. package/modules/platform-identity/pollers/code-sweep.js +43 -0
  224. package/modules/platform-identity/routes/my-projects.js +140 -0
  225. package/modules/platform-identity/routes/projects.js +107 -0
  226. package/modules/platform-identity/routes/scouting.js +35 -0
  227. package/modules/platform-identity/routes/sso.js +402 -0
  228. package/modules/platform-identity/tests/platform-identity.mjs +388 -0
  229. package/modules/platform-identity/ui/.gitkeep +0 -0
  230. package/modules/security/routes/reports.js +10 -0
  231. package/modules/sessions/db.js +166 -21
  232. package/modules/sessions/module.json +1 -1
  233. package/modules/sessions/pollers/session-reward-reconcile.js +78 -0
  234. package/modules/sessions/routes/sessions.js +33 -0
  235. package/modules/status-ui/public/explore.html +54 -0
  236. package/modules/status-ui/public/explore.js +67 -0
  237. package/package-lock.json +2 -2
  238. package/package.json +1 -1
  239. package/public-landing/assets/anim/idle/frame_00.webp +0 -0
  240. package/public-landing/assets/anim/idle/frame_01.webp +0 -0
  241. package/public-landing/assets/anim/idle/frame_02.webp +0 -0
  242. package/public-landing/assets/anim/idle/frame_03.webp +0 -0
  243. package/public-landing/assets/anim/idle/frame_04.webp +0 -0
  244. package/public-landing/assets/anim/idle/frame_05.webp +0 -0
  245. package/public-landing/assets/anim/idle/frame_06.webp +0 -0
  246. package/public-landing/assets/anim/idle/frame_07.webp +0 -0
  247. package/public-landing/assets/anim/idle/frame_08.webp +0 -0
  248. package/public-landing/assets/anim/idle/frame_09.webp +0 -0
  249. package/public-landing/assets/anim/idle/frame_10.webp +0 -0
  250. package/public-landing/assets/anim/idle/frame_11.webp +0 -0
  251. package/public-landing/assets/anim/idle/frame_12.webp +0 -0
  252. package/public-landing/assets/anim/idle/frame_13.webp +0 -0
  253. package/public-landing/assets/anim/idle/frame_14.webp +0 -0
  254. package/public-landing/assets/anim/idle/frame_15.webp +0 -0
  255. package/public-landing/assets/anim/idle/frame_16.webp +0 -0
  256. package/public-landing/assets/anim/idle/frame_17.webp +0 -0
  257. package/public-landing/assets/anim/idle/frame_18.webp +0 -0
  258. package/public-landing/assets/anim/idle/frame_19.webp +0 -0
  259. package/public-landing/assets/anim/idle/frame_20.webp +0 -0
  260. package/public-landing/assets/anim/idle/frame_21.webp +0 -0
  261. package/public-landing/assets/anim/idle/frame_22.webp +0 -0
  262. package/public-landing/assets/anim/idle/frame_23.webp +0 -0
  263. package/public-landing/assets/anim/idle/frame_24.webp +0 -0
  264. package/public-landing/assets/anim/idle/frame_25.webp +0 -0
  265. package/public-landing/assets/anim/idle/frame_26.webp +0 -0
  266. package/public-landing/assets/anim/idle/frame_27.webp +0 -0
  267. package/public-landing/assets/anim/idle/frame_28.webp +0 -0
  268. package/public-landing/assets/anim/idle/frame_29.webp +0 -0
  269. package/public-landing/assets/anim/idle/frame_30.webp +0 -0
  270. package/public-landing/assets/anim/idle/frame_31.webp +0 -0
  271. package/public-landing/assets/anim/idle/frame_32.webp +0 -0
  272. package/public-landing/assets/anim/idle/frame_33.webp +0 -0
  273. package/public-landing/assets/anim/idle/frame_34.webp +0 -0
  274. package/public-landing/assets/anim/idle/frame_35.webp +0 -0
  275. package/public-landing/assets/anim/idle/frame_36.webp +0 -0
  276. package/public-landing/assets/anim/idle/frame_37.webp +0 -0
  277. package/public-landing/assets/anim/idle/frame_38.webp +0 -0
  278. package/public-landing/assets/anim/idle/frame_39.webp +0 -0
  279. package/public-landing/assets/anim/idle/frame_40.webp +0 -0
  280. package/public-landing/assets/anim/idle/frame_41.webp +0 -0
  281. package/public-landing/assets/anim/idle/frame_42.webp +0 -0
  282. package/public-landing/assets/anim/idle/frame_43.webp +0 -0
  283. package/public-landing/create.html +252 -62
  284. package/public-landing/gate/README.md +137 -0
  285. package/public-landing/gate/dispmaps/bar.png +0 -0
  286. package/public-landing/gate/dispmaps/flat.png +0 -0
  287. package/public-landing/gate/dispmaps/key.png +0 -0
  288. package/public-landing/gate/index.html +587 -0
  289. package/public-landing/gate/previews/cloud-bongos.jpg +0 -0
  290. package/public-landing/gate/previews/off-the-boats.jpg +0 -0
  291. package/public-landing/gate/space.css +799 -0
  292. package/public-landing/gate/space.js +1826 -0
  293. package/public-landing/gate/sprites/enter.png +0 -0
  294. package/public-landing/gate/sprites/search.png +0 -0
  295. package/public-landing/gate/sprites/terminal-blank.png +0 -0
  296. package/public-landing/gate/sprites/tile.png +0 -0
  297. package/public-landing/world/assets/clouds/cloud-island-large.png +0 -0
  298. package/public-landing/world/assets/clouds/cloud-puff.png +0 -0
  299. package/public-landing/world/assets/creatures/creature-whale-cargo.png +0 -0
  300. package/public-landing/world/assets/creatures/creature-whale-glide.png +0 -0
  301. package/public-landing/world/assets/plates/plate-clouds-near.png +0 -0
  302. package/public-landing/world/assets/plates/scene-establishing.png +0 -0
  303. package/scripts/gds/abandon.js +6 -4
  304. package/scripts/gds/api.js +10 -5
  305. package/scripts/gds/api.ps1 +26 -0
  306. package/scripts/gds/api.sh +6 -0
  307. package/scripts/gds/audit-ships.js +7 -1
  308. package/scripts/gds/backfill-sessions.js +78 -18
  309. package/scripts/gds/backup.js +23 -1
  310. package/scripts/gds/box-connect-lib.js +124 -31
  311. package/scripts/gds/box-sync.js +66 -0
  312. package/scripts/gds/box.js +225 -53
  313. package/scripts/gds/cf-tunnel.js +145 -6
  314. package/scripts/gds/claim.js +34 -3
  315. package/scripts/gds/cli-lib.js +161 -21
  316. package/scripts/gds/code.js +6 -2
  317. package/scripts/gds/deploy-divergence.js +209 -0
  318. package/scripts/gds/dev-lib.js +309 -0
  319. package/scripts/gds/dev.js +322 -0
  320. package/scripts/gds/doctor.js +90 -61
  321. package/scripts/gds/fetch-art-key.js +1 -1
  322. package/scripts/gds/fetch-sound-prefs.js +2 -2
  323. package/scripts/gds/fitness.js +228 -12
  324. package/scripts/gds/gds-db-backup.service +16 -1
  325. package/scripts/gds/gds-db-offsite.service +9 -1
  326. package/scripts/gds/gen-api-client.js +1 -1
  327. package/scripts/gds/gen-api-docs.js +36 -14
  328. package/scripts/gds/gen-diagrams.js +45 -28
  329. package/scripts/gds/gen-file-map.js +2 -1
  330. package/scripts/gds/gen-freshness.js +32 -0
  331. package/scripts/gds/gen-repo-map.js +3 -2
  332. package/scripts/gds/gen-session-index.js +14 -5
  333. package/scripts/gds/hooks/pre-push.js +2 -2
  334. package/scripts/gds/init.js +60 -3
  335. package/scripts/gds/install-git-hooks.js +1 -1
  336. package/scripts/gds/key-card.js +15 -6
  337. package/scripts/gds/key-status.js +1 -1
  338. package/scripts/gds/linkify-refs.js +35 -9
  339. package/scripts/gds/lint-migrations.js +102 -0
  340. package/scripts/gds/login.js +58 -11
  341. package/scripts/gds/mirror-redact.js +5 -0
  342. package/scripts/gds/package-core.js +263 -4
  343. package/scripts/gds/paste-token.js +7 -1
  344. package/scripts/gds/preflight.js +126 -0
  345. package/scripts/gds/provision.js +268 -19
  346. package/scripts/gds/publish-manifest.js +22 -1
  347. package/scripts/gds/reauth.js +7 -3
  348. package/scripts/gds/regen-instance-docs.js +47 -0
  349. package/scripts/gds/run-unit-tests.js +42 -16
  350. package/scripts/gds/seed-bongos-v1-seo-tasks.js +104 -0
  351. package/scripts/gds/seed-bv1-profiles-tasks.js +126 -0
  352. package/scripts/gds/sequence.js +425 -0
  353. package/scripts/gds/session-cost.js +10 -1
  354. package/scripts/gds/setup.js +72 -5
  355. package/scripts/gds/shell.js +5 -3
  356. package/scripts/gds/ship.js +433 -59
  357. package/scripts/gds/smoke-gds.js +139 -8
  358. package/scripts/gds/start.js +171 -22
  359. package/scripts/gds/status.js +1 -1
  360. package/scripts/gds/triage.js +10 -9
  361. package/scripts/gds/upgrade.js +271 -7
  362. package/scripts/migrate.sh +10 -0
  363. package/scripts/status-mirror-sync.js +114 -84
  364. package/src/bongos/api-errors.js +1 -0
  365. package/src/bongos/auth.js +809 -107
  366. package/src/bongos/db-kernel.js +27 -0
  367. package/src/bongos/db.js +105 -0
  368. package/src/bongos/github-repos.js +93 -0
  369. package/src/bongos/module-scope-map.js +27 -1
  370. package/src/bongos/permission-path-check.js +136 -102
  371. package/src/bongos/repo-info.js +47 -11
  372. package/src/bongos/route-rank-check.js +429 -46
  373. package/src/bongos/routes/_helpers.js +12 -1
  374. package/src/bongos/routes/auth.js +312 -13
  375. package/src/bongos/routes/backup.js +58 -2
  376. package/src/bongos/routes/github.js +90 -0
  377. package/src/bongos/routes/instance.js +15 -3
  378. package/src/bongos/routes/me.js +35 -1
  379. package/src/bongos/routes.js +2 -0
  380. package/src/bongos/serve-internal.js +84 -15
  381. package/src/branding.js +49 -2
  382. package/src/instance-config.js +46 -0
  383. package/src/module-api.js +140 -1
  384. package/src/platform-server.js +86 -9
  385. package/tests/abandon_task.mjs +59 -3
  386. package/tests/api_cli_flags.mjs +53 -0
  387. package/tests/auth_page_gate.mjs +13 -0
  388. package/tests/backfill_sessions.mjs +127 -1
  389. package/tests/backup_cli.mjs +54 -0
  390. package/tests/bongos_box_connect.mjs +77 -0
  391. package/tests/box_access.mjs +130 -1
  392. package/tests/box_credential.mjs +125 -0
  393. package/tests/box_host_keys.mjs +24 -0
  394. package/tests/box_infra_resolve.mjs +149 -0
  395. package/tests/box_scope_session.mjs +6 -2
  396. package/tests/box_sync.mjs +55 -0
  397. package/tests/box_task_scope.mjs +99 -0
  398. package/tests/branding.mjs +41 -23
  399. package/tests/cf_ruleset_preflight.mjs +142 -0
  400. package/tests/cf_tunnel.mjs +37 -0
  401. package/tests/claim_gate_rebase.mjs +98 -0
  402. package/tests/clawback_confirmed_backlog.mjs +103 -0
  403. package/tests/cli_lib_formatters.mjs +20 -0
  404. package/tests/confirm_attribution.mjs +30 -46
  405. package/tests/credit_grant.mjs +198 -10
  406. package/tests/criterion_authoring_gate.mjs +272 -0
  407. package/tests/criterion_progress.mjs +33 -1
  408. package/tests/dev_lib.mjs +253 -0
  409. package/tests/devbox_artifact_gate.mjs +75 -0
  410. package/tests/devbox_dmg_backdrop.mjs +137 -0
  411. package/tests/devbox_icons.mjs +304 -0
  412. package/tests/devbox_installer_brand.mjs +232 -0
  413. package/tests/devbox_key_register.mjs +234 -0
  414. package/tests/docs_openapi_route.mjs +110 -0
  415. package/tests/doctor_hooks.mjs +141 -0
  416. package/tests/extract_json.mjs +140 -0
  417. package/tests/fitness.mjs +89 -0
  418. package/tests/gen_freshness.mjs +96 -0
  419. package/tests/github_repos_helpers.mjs +91 -0
  420. package/tests/github_token_store.mjs +121 -0
  421. package/tests/goal_advisory.mjs +251 -0
  422. package/tests/goal_criterion_confirm_ui.mjs +269 -0
  423. package/tests/goal_routes.mjs +59 -4
  424. package/tests/goal_tier.mjs +12 -3
  425. package/tests/governance_catalog.mjs +129 -0
  426. package/tests/governance_claim_eligibility.mjs +262 -0
  427. package/tests/governance_config.mjs +187 -0
  428. package/tests/governance_ownership_scope.mjs +410 -0
  429. package/tests/governance_page_gates.mjs +324 -0
  430. package/tests/governance_parity.mjs +336 -0
  431. package/tests/governance_principals.mjs +232 -0
  432. package/tests/governance_protected_surfaces.mjs +208 -0
  433. package/tests/governance_require_permission.mjs +223 -0
  434. package/tests/governance_resolver.mjs +84 -0
  435. package/tests/governance_role_sync.mjs +231 -0
  436. package/tests/governance_seed.mjs +127 -0
  437. package/tests/governance_session_scope.mjs +434 -0
  438. package/tests/governance_vanilla_boot.mjs +203 -0
  439. package/tests/grade_attribution.mjs +7 -7
  440. package/tests/hall_audit.mjs +223 -0
  441. package/tests/hall_nav.mjs +191 -0
  442. package/tests/hall_palette.mjs +206 -0
  443. package/tests/hall_signout.mjs +124 -0
  444. package/tests/hook_user_agents.mjs +10 -0
  445. package/tests/idea_triage.mjs +162 -0
  446. package/tests/idp_device_exchange.mjs +198 -0
  447. package/tests/idp_federation.mjs +100 -0
  448. package/tests/init.mjs +51 -0
  449. package/tests/instance_config.mjs +65 -0
  450. package/tests/instance_mismatch_guard.mjs +23 -6
  451. package/tests/instance_release_config.mjs +104 -0
  452. package/tests/linkify_refs.mjs +15 -2
  453. package/tests/migrations_forward_safe.mjs +86 -0
  454. package/tests/module_api.mjs +1 -1
  455. package/tests/module_contributions.mjs +55 -0
  456. package/tests/module_loader.mjs +10 -5
  457. package/tests/module_scope_active_claims.mjs +94 -0
  458. package/tests/needs_rebase.mjs +92 -0
  459. package/tests/one_active_claim_per_session.mjs +76 -0
  460. package/tests/override_attribution.mjs +10 -8
  461. package/tests/override_request_ownership.mjs +8 -4
  462. package/tests/package_core.mjs +246 -1
  463. package/tests/parse_cookie_decode.mjs +42 -0
  464. package/tests/pay_on_land.mjs +129 -0
  465. package/tests/platform_boot.mjs +100 -1
  466. package/tests/post_login_landing.mjs +42 -0
  467. package/tests/preflight_checks.mjs +72 -0
  468. package/tests/provision.mjs +289 -7
  469. package/tests/pruned_lockfile_integrity.mjs +79 -0
  470. package/tests/publish_branch_route.mjs +53 -0
  471. package/tests/publish_reconciler.mjs +187 -2
  472. package/tests/repo_info_binding.mjs +248 -0
  473. package/tests/reward_gate.mjs +11 -2
  474. package/tests/reward_mode_cost_plus_only.mjs +116 -0
  475. package/tests/reward_mode_surfacing.mjs +168 -0
  476. package/tests/route_permission_check.mjs +301 -0
  477. package/tests/route_rank_check.mjs +254 -12
  478. package/tests/route_validation.mjs +30 -0
  479. package/tests/sequence.mjs +267 -0
  480. package/tests/session_digest.mjs +44 -0
  481. package/tests/session_index.mjs +18 -6
  482. package/tests/session_records.mjs +39 -1
  483. package/tests/session_reward_on_land.mjs +381 -0
  484. package/tests/session_reward_reconcile.mjs +229 -0
  485. package/tests/ship_api_client_pathspec.mjs +100 -0
  486. package/tests/ship_base_flag.mjs +93 -0
  487. package/tests/ship_ci_deploy.mjs +140 -3
  488. package/tests/ship_upstream_deletion.mjs +57 -0
  489. package/tests/smoke_target.mjs +233 -0
  490. package/tests/start_env_guard.mjs +87 -0
  491. package/tests/start_rebase_warning.mjs +103 -0
  492. package/tests/status_mirror_sync.mjs +122 -0
  493. package/tests/stop_refcheck.mjs +19 -1
  494. package/tests/task_goal_link.mjs +15 -6
  495. package/tests/upgrade.mjs +393 -5
  496. package/tests/web_admission_status.mjs +162 -0
  497. package/tests/write_validation_gaps.mjs +110 -0
  498. package/.gitignore +0 -91
  499. package/docs/adr/0139-platform-history-migration-to-cloudbongos.md +0 -94
  500. package/docs/adr/0140-cloudbongos-standalone-reprovision.md +0 -43
  501. package/scripts/hall-preview/fixtures/access-requests.json +0 -20
  502. package/scripts/hall-preview/fixtures/achievements.json +0 -76
  503. package/scripts/hall-preview/fixtures/analytics__builder.json +0 -251
  504. package/scripts/hall-preview/fixtures/audit-log.json +0 -68
  505. package/scripts/hall-preview/fixtures/blockers__detail.json +0 -12
  506. package/scripts/hall-preview/fixtures/box__me.json +0 -5
  507. package/scripts/hall-preview/fixtures/boxes__cost-ledger.json +0 -69
  508. package/scripts/hall-preview/fixtures/builders__active-claims.json +0 -1
  509. package/scripts/hall-preview/fixtures/builders__directory.json +0 -81
  510. package/scripts/hall-preview/fixtures/builders__roster.json +0 -48
  511. package/scripts/hall-preview/fixtures/goals.json +0 -502
  512. package/scripts/hall-preview/fixtures/goals__25.json +0 -96
  513. package/scripts/hall-preview/fixtures/goals__26.json +0 -67
  514. package/scripts/hall-preview/fixtures/goals__3.json +0 -103
  515. package/scripts/hall-preview/fixtures/goals__35.json +0 -49
  516. package/scripts/hall-preview/fixtures/goals__conflicts.json +0 -154
  517. package/scripts/hall-preview/fixtures/goals__detail.json +0 -96
  518. package/scripts/hall-preview/fixtures/goals__inbox.json +0 -5
  519. package/scripts/hall-preview/fixtures/goals__requests.json +0 -3
  520. package/scripts/hall-preview/fixtures/grades__by-builder.json +0 -32
  521. package/scripts/hall-preview/fixtures/inbox.json +0 -330
  522. package/scripts/hall-preview/fixtures/inbox__detail.json +0 -15
  523. package/scripts/hall-preview/fixtures/instance.json +0 -124
  524. package/scripts/hall-preview/fixtures/leaderboard.json +0 -184
  525. package/scripts/hall-preview/fixtures/me.json +0 -476
  526. package/scripts/hall-preview/fixtures/me__art-key__own.json +0 -6
  527. package/scripts/hall-preview/fixtures/me__event-sound-prefs.json +0 -209
  528. package/scripts/hall-preview/fixtures/me__recent-ships.json +0 -304
  529. package/scripts/hall-preview/fixtures/me__render.json +0 -5
  530. package/scripts/hall-preview/fixtures/me__skill-prefs.json +0 -143
  531. package/scripts/hall-preview/fixtures/me__sound-prefs.json +0 -67
  532. package/scripts/hall-preview/fixtures/me__wandering.json +0 -43
  533. package/scripts/hall-preview/fixtures/override-requests.json +0 -15
  534. package/scripts/hall-preview/fixtures/provisioning__fleet.json +0 -1
  535. package/scripts/hall-preview/fixtures/provisioning__fleet__cost-ledger.json +0 -1
  536. package/scripts/hall-preview/fixtures/provisioning__instances.json +0 -1
  537. package/scripts/hall-preview/fixtures/public__analytics__ecosystem.json +0 -516
  538. package/scripts/hall-preview/fixtures/public__bounty-table.json +0 -37
  539. package/scripts/hall-preview/fixtures/public__cost-summary.json +0 -742
  540. package/scripts/hall-preview/fixtures/public__estimation-drift.json +0 -41
  541. package/scripts/hall-preview/fixtures/public__grades.json +0 -50
  542. package/scripts/hall-preview/fixtures/public__progress.json +0 -124
  543. package/scripts/hall-preview/fixtures/public__recent-shipped.json +0 -426
  544. package/scripts/hall-preview/fixtures/public__repo-health.json +0 -160
  545. package/scripts/hall-preview/fixtures/public__repo-info.json +0 -5
  546. package/scripts/hall-preview/fixtures/security__docs.json +0 -10
  547. package/scripts/hall-preview/fixtures/security__reports.json +0 -31
  548. package/scripts/hall-preview/fixtures/sessions__search.json +0 -9
  549. package/scripts/hall-preview/fixtures/tasks.json +0 -2420
  550. package/scripts/hall-preview/fixtures/tasks__claimable.json +0 -2555
  551. package/scripts/hall-preview/fixtures/tasks__detail.json +0 -111
  552. package/scripts/hall-preview/fixtures/tasks__peer-votes__feed.json +0 -330
  553. package/scripts/hall-preview/fixtures/versions.json +0 -89
@@ -0,0 +1,102 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+ // scripts/gds/lint-migrations.js — core-migration FORWARD-SAFETY guard (task 1002223 / audit H10,
4
+ // finding F6).
5
+ //
6
+ // `bongos upgrade` auto-rollback reverts the CODE pin but DELIBERATELY NOT the database
7
+ // (upgrade.js: "the DB is deliberately NOT rolled back … core migrations are additive + forward-
8
+ // safe"). That safety rested on a convention nothing checked. A core migration is FORWARD-SAFE when
9
+ // the PREVIOUS code version still runs against the post-migration schema — so a code-only rollback
10
+ // never strands old code on a schema it can't read. This lints core migrations for operations that
11
+ // BREAK that property and fails unless the migration explicitly acknowledges the exception with a
12
+ // -- forward-unsafe: <reason>
13
+ // pragma (a deliberate, documented decision — e.g. a historical migration, or one paired with a
14
+ // two-step deploy).
15
+ //
16
+ // SAFE (never flagged): CREATE TABLE/INDEX/…, ADD COLUMN, INSERT/UPDATE, and DROP-then-CREATE of an
17
+ // INDEX / TRIGGER / VIEW / FUNCTION / POLICY (the recreate-in-place idempotency pattern) — none of
18
+ // these remove state the old code depends on.
19
+ // UNSAFE (flagged): DROP TABLE, DROP COLUMN, RENAME COLUMN, ALTER TABLE … RENAME TO, ALTER COLUMN …
20
+ // TYPE, SET NOT NULL — each removes / renames / retypes existing state old code reads.
21
+ //
22
+ // Usage:
23
+ // node scripts/gds/lint-migrations.js # STRICT: exit 1 on any unacknowledged unsafe migration
24
+ // node scripts/gds/lint-migrations.js --warn # report only, always exit 0 (migrate.sh uses this)
25
+ // node scripts/gds/lint-migrations.js --dir <path> # scan a specific migrations dir (default: <repo>/migrations)
26
+
27
+ const fs = require('node:fs');
28
+ const path = require('node:path');
29
+
30
+ const REPO_ROOT = path.resolve(__dirname, '..', '..');
31
+
32
+ // The acknowledgement pragma. Lives in a SQL comment; matched on the raw file text.
33
+ const ACK_RE = /^\s*--\s*forward-unsafe\s*:\s*(.+)$/im;
34
+
35
+ // Genuinely forward-unsafe statement shapes. Ordered; a file matching ANY is unsafe.
36
+ const UNSAFE = [
37
+ ['drop-table', /\bdrop\s+table\b/i],
38
+ ['drop-column', /\bdrop\s+column\b/i],
39
+ ['rename-column', /\brename\s+column\b/i],
40
+ ['rename-table', /\balter\s+table\s+[^\s;]+\s+rename\s+to\b/i],
41
+ ['alter-column-type', /\balter\s+column\b[^;]*\btype\b/i],
42
+ ['set-not-null', /\bset\s+not\s+null\b/i],
43
+ ];
44
+
45
+ // Strip block + line comments so a pattern that appears only in prose/comments never counts as an
46
+ // operation. (The ACK pragma is detected separately, on the raw text, since it IS a comment.)
47
+ function stripSqlComments(sql) {
48
+ return String(sql).replace(/\/\*[\s\S]*?\*\//g, ' ').replace(/--[^\n]*/g, ' ');
49
+ }
50
+
51
+ // Classify one migration's SQL: { unsafe: [names], acknowledged: bool, ackReason }.
52
+ function scanMigrationSql(sql) {
53
+ const code = stripSqlComments(sql);
54
+ const unsafe = [];
55
+ for (const [name, re] of UNSAFE) if (re.test(code)) unsafe.push(name);
56
+ const ack = String(sql).match(ACK_RE);
57
+ return { unsafe, acknowledged: !!ack, ackReason: ack ? ack[1].trim() : null };
58
+ }
59
+
60
+ // Scan a migrations directory. Returns { violations, acknowledged } — each entry { file, unsafe, ackReason }.
61
+ // `violations` = unsafe WITHOUT the pragma (fail-closed); `acknowledged` = unsafe WITH it (documented).
62
+ function scanMigrationsDir(dir, fsImpl = fs) {
63
+ let files;
64
+ try { files = fsImpl.readdirSync(dir).filter((f) => f.endsWith('.sql')); }
65
+ catch { return { violations: [], acknowledged: [], scanned: 0 }; }
66
+ const violations = [];
67
+ const acknowledged = [];
68
+ for (const f of files.sort()) {
69
+ const r = scanMigrationSql(fsImpl.readFileSync(path.join(dir, f), 'utf8'));
70
+ if (r.unsafe.length === 0) continue;
71
+ (r.acknowledged ? acknowledged : violations).push({ file: f, unsafe: r.unsafe, ackReason: r.ackReason });
72
+ }
73
+ return { violations, acknowledged, scanned: files.length };
74
+ }
75
+
76
+ function arg(name, fallback = null) {
77
+ const i = process.argv.indexOf(name);
78
+ return i >= 0 && i + 1 < process.argv.length ? process.argv[i + 1] : fallback;
79
+ }
80
+
81
+ function main() {
82
+ const warnOnly = process.argv.includes('--warn');
83
+ const dir = path.resolve(arg('--dir', path.join(REPO_ROOT, 'migrations')));
84
+ const { violations, acknowledged, scanned } = scanMigrationsDir(dir);
85
+
86
+ for (const a of acknowledged) {
87
+ console.error(`[migrations] forward-unsafe (acknowledged): ${a.file} [${a.unsafe.join(', ')}] — ${a.ackReason}`);
88
+ }
89
+ if (violations.length === 0) {
90
+ console.error(`[migrations] forward-safety: OK — ${scanned} core migration(s) scanned, ${acknowledged.length} acknowledged exception(s), 0 unacknowledged.`);
91
+ return 0;
92
+ }
93
+ console.error(`[migrations] forward-safety: ${warnOnly ? 'WARNING' : 'FAILED'} — ${violations.length} core migration(s) contain a forward-UNSAFE operation with no acknowledgement:`);
94
+ for (const v of violations) console.error(` ${v.file} [${v.unsafe.join(', ')}]`);
95
+ console.error(' A code-only `bongos upgrade` rollback does NOT revert the DB, so old code would run on this schema.');
96
+ console.error(' If intentional, add a top-of-file SQL comment: -- forward-unsafe: <why it is safe here / the two-step plan>');
97
+ return warnOnly ? 0 : 1;
98
+ }
99
+
100
+ module.exports = { scanMigrationSql, scanMigrationsDir, stripSqlComments, UNSAFE, ACK_RE };
101
+
102
+ if (require.main === module) process.exit(main());
@@ -23,6 +23,7 @@
23
23
  const readline = require('node:readline');
24
24
  const { spawnSync } = require('node:child_process');
25
25
  const { saveSession } = require('./cli-lib');
26
+ const { branding } = require('../../src/branding');
26
27
 
27
28
  // --- pure helpers (unit-tested) ---------------------------------------------
28
29
 
@@ -80,12 +81,26 @@ function prompt(q) {
80
81
 
81
82
  const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
82
83
 
83
- // Run the GitHub device flow against `base`. Returns one of:
84
+ // Run the device flow and return one of:
84
85
  // { state: 'ok', token, builder } | { state: 'not_approved' } | { state: 'expired' }
85
- async function runDeviceFlow(base, { log }) {
86
- const start = await fetchJson(`${base}/api/gds/auth/device/start`, { method: 'POST' });
87
- if (start.status === 503 || (start.data && start.data.error === 'auth_not_configured')) {
88
- throw new Error('that instance has no GitHub sign-in configured yet — ask its Archon to set it up.');
86
+ //
87
+ // Two modes (ADR 0141 §1). Direct: run GitHub device flow against the instance's
88
+ // own app. Federated (idp = { origin, client_id } from the instance manifest): run
89
+ // the device flow against the HUB, then exchange the hub-signed assertion at the
90
+ // instance's /auth/device/exchange — a hosted instance has no GitHub app of its own.
91
+ // The instance's OWN admission gate runs at poll (direct) or exchange (federated),
92
+ // so the stranger path is identical for both.
93
+ async function runDeviceFlow(base, { log, idp = null }) {
94
+ const startUrl = idp ? `${idp.origin}/api/gds/sso/device/start` : `${base}/api/gds/auth/device/start`;
95
+ const start = await fetchJson(startUrl, {
96
+ method: 'POST',
97
+ // Federated: the hub resolves + binds the client from the USER-TYPED origin
98
+ // (`base`), never a client_id we supply — so this CLI can only ever mint an
99
+ // assertion scoped to the instance the user actually typed (ADR 0141 §1).
100
+ ...(idp ? { body: JSON.stringify({ origin: base }) } : {}),
101
+ });
102
+ if (start.status === 503 || (start.data && (start.data.error === 'auth_not_configured' || (start.data.error && start.data.error.code === 'idp_signing_unconfigured')))) {
103
+ throw new Error('that instance has no sign-in configured yet — ask its Archon to set it up.');
89
104
  }
90
105
  if (!start.ok || !start.data || !start.data.device_code) {
91
106
  throw new Error(`could not start sign-in (HTTP ${start.status}).`);
@@ -103,15 +118,29 @@ async function runDeviceFlow(base, { log }) {
103
118
  openBrowser(verification_uri);
104
119
  log(' Waiting for you to approve in the browser…');
105
120
 
121
+ const pollUrl = idp ? `${idp.origin}/api/gds/sso/device/poll` : `${base}/api/gds/auth/device/poll`;
106
122
  const deadline = Date.now() + expires * 1000;
107
123
  while (Date.now() < deadline) {
108
124
  await sleep(interval * 1000);
109
- const poll = await fetchJson(`${base}/api/gds/auth/device/poll`, {
125
+ const poll = await fetchJson(pollUrl, {
110
126
  method: 'POST',
127
+ // The hub uses the client BOUND to device_code at start; the CLI names no
128
+ // audience at poll either (ADR 0141 §1).
111
129
  body: JSON.stringify({ device_code }),
112
130
  });
113
131
  const st = poll.data && poll.data.state;
114
- if (st === 'ok') return { state: 'ok', token: poll.data.token, builder: poll.data.builder };
132
+ if (st === 'ok') {
133
+ if (!idp) return { state: 'ok', token: poll.data.token, builder: poll.data.builder };
134
+ // Federated: exchange the hub assertion at the instance for a real session.
135
+ const ex = await fetchJson(`${base}/api/gds/auth/device/exchange`, {
136
+ method: 'POST',
137
+ body: JSON.stringify({ assertion: poll.data.assertion }),
138
+ });
139
+ const exState = ex.data && ex.data.state;
140
+ if (exState === 'not_approved' || (ex.status === 403 && exState !== 'pending')) return { state: 'not_approved' };
141
+ if (!ex.ok || !ex.data || !ex.data.token) throw new Error(`sign-in exchange failed (HTTP ${ex.status}).`);
142
+ return { state: 'ok', token: ex.data.token, builder: ex.data.builder };
143
+ }
115
144
  if (st === 'not_approved' || (poll.status === 403 && st !== 'pending')) return { state: 'not_approved' };
116
145
  if (st === 'denied') throw new Error('sign-in was denied in the browser.');
117
146
  if (st === 'expired') return { state: 'expired' };
@@ -121,6 +150,20 @@ async function runDeviceFlow(base, { log }) {
121
150
  return { state: 'expired' };
122
151
  }
123
152
 
153
+ // A helpful, instance-agnostic example origin for the sign-in prompt. login is
154
+ // deliberately instance-agnostic (point it at ANY instance), so the example is
155
+ // derived from THIS checkout's branding — a branded checkout shows its own
156
+ // instance; the neutral core shows a neutral placeholder. It never hardcodes
157
+ // another instance's real domain (hardcoding one steered a new builder to the
158
+ // wrong backend — task 1002400, onboarding-audit area 3).
159
+ function exampleInstanceOrigin() {
160
+ try {
161
+ const origin = branding().domains && branding().domains.publicOrigin;
162
+ if (origin && !/localhost|127\.0\.0\.1/.test(origin)) return origin;
163
+ } catch (_) { /* branding unavailable — fall through to the neutral placeholder */ }
164
+ return 'https://your-instance.example.com';
165
+ }
166
+
124
167
  // --- main --------------------------------------------------------------------
125
168
 
126
169
  async function main() {
@@ -130,7 +173,7 @@ async function main() {
130
173
 
131
174
  let base;
132
175
  try {
133
- base = normalizeInstanceBase(urlArg || (await prompt('Instance URL (e.g. https://amazonprimea.com): ')));
176
+ base = normalizeInstanceBase(urlArg || (await prompt(`Instance URL (e.g. ${exampleInstanceOrigin()}): `)));
134
177
  } catch {
135
178
  console.error('✖ usage: bongos login <instance-url>');
136
179
  process.exit(2);
@@ -146,10 +189,14 @@ async function main() {
146
189
  }
147
190
  const instanceName = man.data.name || base;
148
191
  const admission = (man.data.admission && man.data.admission.policy) || 'invite-only';
149
- log(` Signing in to ${instanceName}${admission === 'invite-only' ? ' (invite-only)' : ''}`);
192
+ // Federation (ADR 0141 §1): a hosted instance advertises auth.idp = { origin,
193
+ // client_id } — sign-in then runs through the Cloud Bongos hub, not a local
194
+ // GitHub app. null → the direct-GitHub path, unchanged.
195
+ const idp = (man.data.auth && man.data.auth.idp) || null;
196
+ log(` Signing in to ${instanceName}${admission === 'invite-only' ? ' (invite-only)' : ''}${idp ? ' — via Cloud Bongos platform sign-in' : ''}`);
150
197
 
151
198
  // 2. Device flow.
152
- let flow = await runDeviceFlow(base, { log });
199
+ let flow = await runDeviceFlow(base, { log, idp });
153
200
 
154
201
  // 3. Stranger path: not admitted → file the request, poll until an Archon 👍s,
155
202
  // then finish sign-in with a fresh device flow (the first code is spent).
@@ -193,7 +240,7 @@ async function main() {
193
240
  }
194
241
  process.stdout.write('.');
195
242
  }
196
- flow = await runDeviceFlow(base, { log });
243
+ flow = await runDeviceFlow(base, { log, idp });
197
244
  }
198
245
 
199
246
  if (flow.state !== 'ok') {
@@ -185,6 +185,11 @@ function deriveInstanceLiterals(branding = {}, extra = {}) {
185
185
  add(tokens, company.name);
186
186
  { const label = registrableLabel(company.site); if (label) add(tokens, label); }
187
187
 
188
+ // `extra` also carries a caller-supplied FIXED FLOOR: the package-core neutral-floor gate
189
+ // (task 1002217 / audit H4) injects prior-instance tokens/owners here so published DOCS de-brand
190
+ // against them and the fail-closed gate fires EVEN WITH NO branding.json. Those floor literals
191
+ // live with the CALLER (package-core), never hardcoded in this file — so this redactor's self-leak
192
+ // invariant (tests/mirror_redact.mjs) keeps holding while the floor is still enforced.
188
193
  for (const t of extra.tokens || []) add(tokens, t);
189
194
  for (const o of extra.owners || []) add(owners, o);
190
195
 
@@ -52,8 +52,15 @@
52
52
  //
53
53
  // USAGE:
54
54
  // node scripts/gds/package-core.js [--ref <git-ref>] [--out-dir dist]
55
- // [--version <v>] [--droplet-ip <ip>] [--publish [--live]]
55
+ // [--version <v>] [--droplet-ip <ip>] [--self-verify [--self-verify-boot]] [--publish [--live]]
56
56
  // Defaults: --ref HEAD, --out-dir dist, --version = platform CORE_VERSION.
57
+ // --self-verify after building, prove the artifact reproduces its pin (unpack the .tgz,
58
+ // recompute tree_sha256 == manifest + verify tarball.sha256). Fatal on mismatch.
59
+ // (task 1002218 / audit H3) — deterministic + network-free.
60
+ // --self-verify-boot ALSO `npm install <tgz>` into a throwaway consumer + boot /healthz
61
+ // (reuses tests/consumer_layout_boot.mjs; needs npm/registry access).
62
+ // --leak-scan-strict make the post-build leak scan FATAL on any prior-instance literal
63
+ // (default: the scan runs + WARNS; --no-leak-scan skips it). task 1002220 / H6.
57
64
  // Outputs (nothing is committed — dist/ is gitignored):
58
65
  // dist/bongos-core-<version>/ the redacted core tree (inspectable)
59
66
  // dist/bongos-core-<version>.tgz the installable unit
@@ -64,6 +71,7 @@
64
71
  // npm org on a paid plan + `npm login`; this script holds no token.
65
72
 
66
73
  const fs = require('node:fs');
74
+ const os = require('node:os');
67
75
  const path = require('node:path');
68
76
  const zlib = require('node:zlib');
69
77
  const crypto = require('node:crypto');
@@ -207,6 +215,33 @@ function processCoreFile({ pathRel, raw, rules, sensitiveLiterals, envSecrets })
207
215
  };
208
216
  }
209
217
 
218
+ // ---------------------------------------------------------------------------
219
+ // npm-STRIPPED special files (idea 1000543) — the file-SELECTION gate that keeps
220
+ // the pin HONEST about what a consumer actually installs.
221
+ //
222
+ // `npm install <tgz>` never materializes certain special files into the installed
223
+ // package — most notably `.gitignore` (confirmed live: installing the 1.18.1 core
224
+ // .tgz drops exactly one file, ./.gitignore), plus `.npmignore` and `.npmrc`. But
225
+ // package-core enumerates the GIT tree, so isPublishable() happily selects a tracked
226
+ // `.gitignore`. If it lands in the manifest files[] + tree_sha256 pin, the tree a
227
+ // consumer recomputes over node_modules/@bongos/core is MISSING it → verifyInstalledPin
228
+ // (upgrade.js) reports `missing installed file: .gitignore` → hash mismatch → the bump
229
+ // auto-rolls-back. That breaks EVERY vendored `bongos upgrade` with pin-verify ON, and
230
+ // the unattended auto-upgrade subscription (task 2150 / ADR 0136) has no way to pass
231
+ // --skip-pin-verify — so it would roll back every patch (the fix reported in idea 1000543).
232
+ //
233
+ // The manifest must describe exactly what npm installs, so these files are dropped BEFORE
234
+ // they enter the tarball, filesMeta, or the pin (main(), below). Matched by BASENAME at any
235
+ // depth — npm strips nested ones too. The asymmetry is deliberately safe: excluding a file
236
+ // npm WOULD have kept only slightly weakens the pin (verifyInstalledPin iterates the manifest
237
+ // files[], so an un-pinned file is simply not checked); FAILING to exclude one npm strips is
238
+ // FATAL. And these files are useless inside a vendored dependency anyway.
239
+ const NPM_STRIPPED_BASENAMES = new Set(['.gitignore', '.npmignore', '.npmrc']);
240
+ function isNpmStripped(pathRel) {
241
+ const base = String(pathRel || '').split('/').pop();
242
+ return NPM_STRIPPED_BASENAMES.has(base);
243
+ }
244
+
210
245
  // ---------------------------------------------------------------------------
211
246
  // The CORE's own package.json + package-lock.json (task 1882, ADR 0108 §2).
212
247
  //
@@ -545,6 +580,164 @@ function publishTarball({ tgzPath, live }) {
545
580
  console.error(`[package-core] npm publish ${live ? 'succeeded — @bongos/core is live (private).' : 'dry-run OK — the tarball is publishable.'}`);
546
581
  }
547
582
 
583
+ // The neutral FLOOR — customer-zero identity tokens a portable core must never carry (task 1002220
584
+ // / H6 + task 1002217 / H4, finding F2). When packaging from the NEUTRAL trunk there is no
585
+ // config/branding.json, so the branding-DERIVED redaction + gate see ZERO literals and report
586
+ // "clean" while stripping nothing. This fixed floor closes that gap two ways, both keyed off ONE
587
+ // definition:
588
+ // • DOC path (H4, fail-closed): injected as `extra` {tokens, owners} into buildRedactionRules +
589
+ // collectSensitiveLiterals, so the existing doc-redaction STRIPS these from published docs
590
+ // (tokens→NEUTRAL_TOKEN, owners→NEUTRAL_OWNER) and the fail-closed no-leak gate ABORTS the build
591
+ // if one SURVIVES — even with no branding.json. The floor lives HERE and is injected at call
592
+ // time, so mirror-redact.js stays literal-free (its self-leak invariant holds); functional files
593
+ // still ship VERBATIM (the R88 fix) and are covered by the H6 advisory below, not this gate.
594
+ // • SCAN path (H6, advisory): scanArtifactForLeaks measures the flat list across the WHOLE artifact.
595
+ // findLeaks' public-IP floor separately catches the old droplet IP.
596
+ const PRIOR_INSTANCE_FLOOR = { tokens: ['amazonprimea', 'off the boats'], owners: ['lars589'] };
597
+ const PRIOR_INSTANCE_LITERALS = [...PRIOR_INSTANCE_FLOOR.tokens, ...PRIOR_INSTANCE_FLOOR.owners];
598
+
599
+ // High-signal committed-credential shapes for the secret WARNING (task 1002220 / audit H6, finding
600
+ // F3). Deliberately NARROW — well-known provider token prefixes + PEM private-key headers + an
601
+ // explicit secret=<literal> assignment — rather than the generic entropy catch-all, which fires on
602
+ // ordinary long identifiers / base64 data and buries a real hit (an early cut warned on 339 files,
603
+ // ~all benign). A match is a WARNING to eyeball; the HARD gate against live env-secret VALUES is
604
+ // functionalSecretLeaks in the build loop. Scans functional files (docs are redacted + gated already).
605
+ const CREDENTIAL_SHAPE_PATTERNS = [
606
+ ['anthropic-key', /\bsk-ant-[A-Za-z0-9_-]{16,}/],
607
+ ['openai-key', /\bsk-[A-Za-z0-9]{24,}/],
608
+ ['github-token', /\b(?:ghp|gho|ghu|ghs|ghr)_[A-Za-z0-9]{20,}\b|\bgithub_pat_[A-Za-z0-9_]{20,}/],
609
+ ['aws-access-key-id', /\b(?:AKIA|ASIA)[A-Z0-9]{16}\b/],
610
+ ['google-api-key', /\bAIza[A-Za-z0-9_-]{30,}/],
611
+ ['slack-token', /\bxox[baprs]-[A-Za-z0-9-]{10,}/],
612
+ ['private-key-pem', /-----BEGIN (?:RSA |EC |OPENSSH |DSA |PGP )?PRIVATE KEY-----/],
613
+ ['secret-assignment', /\b(?:api[_-]?key|secret|password|passwd|access[_-]?token|auth[_-]?token)\b\s*[:=]\s*['"][^'"\s]{16,}['"]/i],
614
+ ];
615
+ function credentialShapeHits(text) {
616
+ const names = [];
617
+ for (const [name, re] of CREDENTIAL_SHAPE_PATTERNS) if (re.test(text)) names.push(name);
618
+ return names;
619
+ }
620
+
621
+ // classify a shipped path for the scan — mirrors processCoreFile's doc/stub/functional split.
622
+ function scanKindOf(pathRel) {
623
+ if (isAgentDoc(pathRel)) return 'stub';
624
+ if (isDocPath(pathRel)) return 'doc';
625
+ return 'functional';
626
+ }
627
+
628
+ // Independent post-build leak scan over the built core file set (task 1002220 / audit H6). Two layers:
629
+ // • INSTANCE LITERALS — findLeaks() over PRIOR_INSTANCE_LITERALS (+ the public-IP floor) across
630
+ // EVERY shipped file: brand/owner/history that must not ship in a portable core. This is the
631
+ // "prove no leak" measurement the neutral trunk was missing (F2).
632
+ // • CREDENTIAL SHAPES — credentialShapeHits() over FUNCTIONAL files only (docs are redacted+gated
633
+ // already): a high-signal WARNING that a committed credential shape shipped.
634
+ // `files` = [{ path, content }]. Pure — returns findings; the caller decides warn vs fatal.
635
+ function scanArtifactForLeaks(files, { literals = PRIOR_INSTANCE_LITERALS } = {}) {
636
+ const literalHits = []; // { path, label, sample }
637
+ const secretWarnings = []; // { path, shapes }
638
+ for (const f of files || []) {
639
+ const text = Buffer.isBuffer(f.content) ? f.content.toString('utf8') : String(f.content == null ? '' : f.content);
640
+ for (const lk of findLeaks(text, literals)) literalHits.push({ path: f.path, label: lk.label, sample: lk.sample });
641
+ if (scanKindOf(f.path) === 'functional') {
642
+ const shapes = credentialShapeHits(text);
643
+ if (shapes.length) secretWarnings.push({ path: f.path, shapes });
644
+ }
645
+ }
646
+ return { literalHits, secretWarnings };
647
+ }
648
+
649
+ // Self-verify the built artifact (task 1002218 / audit H3, findings F1+F5). The packager
650
+ // claims a consumer "verifies an install by recomputing [the pin] from the unpacked tree" —
651
+ // this proves the packager's OWN output actually does, at build time, on the produce side:
652
+ // 1. REPRODUCE THE PIN (always; deterministic, network-free): unpack the built .tgz and
653
+ // recompute tree_sha256 over the unpacked tree, asserting it equals the manifest — plus
654
+ // the on-disk .tgz bytes vs manifest.tarball.sha256. Catches a tar-writer bug, a corrupt
655
+ // write, or pin drift BEFORE the artifact is trusted or published (the R88 failure class).
656
+ // 2. BARE-INSTALL BOOT (opt-in --self-verify-boot; needs npm/registry): `npm install <tgz>`
657
+ // into a throwaway consumer and boot /healthz, by reusing the proven consumer-layout
658
+ // harness (tests/consumer_layout_boot.mjs) with CORE_TGZ set — no duplication. The deeper
659
+ // standing CI gate is task 1002221; this is the packager's own quick smoke.
660
+ // Fatal (exit 1) on a reproduce mismatch or a boot failure — a self-verify that finds a
661
+ // problem must stop the build, never warn-and-ship.
662
+ // Pure recompute — read each pinned file from an unpacked tree, rebuild the canonical tree
663
+ // hash (using the manifest's already-canonical modes), and compare to the manifest, plus the
664
+ // on-disk .tgz bytes vs manifest.tarball.sha256. No process.exit / no spawn → unit-testable.
665
+ // Returns { ok, problems, fileCount }.
666
+ function reproducePinFromTree({ treeRoot, sidecar, tgzPath }, fsImpl = fs) {
667
+ const problems = [];
668
+ const recomputed = [];
669
+ for (const f of sidecar.files || []) {
670
+ const abs = path.join(treeRoot, ...String(f.path).split('/'));
671
+ let buf;
672
+ try { buf = fsImpl.readFileSync(abs); } catch { problems.push(`missing in artifact: ${f.path}`); continue; }
673
+ const sha = sha256hex(buf);
674
+ if (sha !== f.sha256) problems.push(`content mismatch: ${f.path}`);
675
+ recomputed.push({ path: f.path, mode: f.mode, sha256: sha });
676
+ }
677
+ const treeHash = canonicalTreeHash(recomputed);
678
+ if (problems.length === 0 && treeHash !== sidecar.tree_sha256) {
679
+ problems.push(`recomputed tree_sha256 ${treeHash.slice(0, 12)}… ≠ manifest ${String(sidecar.tree_sha256).slice(0, 12)}…`);
680
+ }
681
+ if (tgzPath && sidecar.tarball && sidecar.tarball.sha256) {
682
+ const tgzSha = sha256hex(fsImpl.readFileSync(tgzPath));
683
+ if (tgzSha !== sidecar.tarball.sha256) {
684
+ problems.push(`on-disk .tgz sha256 ${tgzSha.slice(0, 12)}… ≠ manifest.tarball.sha256 ${String(sidecar.tarball.sha256).slice(0, 12)}…`);
685
+ }
686
+ }
687
+ return { ok: problems.length === 0, problems, fileCount: (sidecar.files || []).length };
688
+ }
689
+
690
+ function selfVerify({ tgzPath, sidecar, outDir, boot }) {
691
+ console.error('');
692
+ console.error('[self-verify] verifying the built artifact reproduces its pin …');
693
+
694
+ // 1. reproduce the pin from the built .tgz.
695
+ const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'bongos-selfverify-'));
696
+ try {
697
+ let treeRoot;
698
+ const x = spawnSync('tar', ['-xzf', tgzPath, '-C', tmp], { encoding: 'utf8' });
699
+ if (!x.error && x.status === 0) {
700
+ treeRoot = path.join(tmp, TAR_ROOT.replace(/\/$/, '')); // the `package/` root inside the tarball
701
+ } else {
702
+ // tar unavailable — fall back to the on-disk stage dir (the SAME staged buffers written
703
+ // to the .tgz), so the pin is still recomputed from a materialized tree. Less faithful
704
+ // (won't catch a tar-writer bug), so it's a loud warning, not silent.
705
+ treeRoot = path.join(outDir, stageDirName(sidecar.core_version));
706
+ console.error(`[self-verify] ! tar unavailable (${x.error ? x.error.message : 'exit ' + x.status}); recomputing over the stage dir ${path.relative(REPO_ROOT, treeRoot)} instead of the unpacked .tgz.`);
707
+ if (!fs.existsSync(treeRoot)) { console.error('[self-verify] FAILED — neither `tar` nor the stage dir is available to recompute the pin.'); process.exit(1); }
708
+ }
709
+ const rep = reproducePinFromTree({ treeRoot, sidecar, tgzPath });
710
+ if (!rep.ok) {
711
+ console.error(`[self-verify] FAILED — the built artifact does NOT reproduce its pin (${rep.problems.length} problem(s)):`);
712
+ for (const p of rep.problems.slice(0, 20)) console.error(' ' + p);
713
+ if (rep.problems.length > 20) console.error(` … +${rep.problems.length - 20} more`);
714
+ process.exit(1);
715
+ }
716
+ console.error(`[self-verify] ✓ pin reproduced — tree_sha256 + tarball.sha256 match the manifest (${rep.fileCount} files).`);
717
+ } finally {
718
+ try { fs.rmSync(tmp, { recursive: true, force: true }); } catch { /* best-effort */ }
719
+ }
720
+
721
+ // 2. bare-install boot (opt-in) — reuse the proven consumer-layout harness with CORE_TGZ.
722
+ if (boot) {
723
+ const harness = path.join(REPO_ROOT, 'tests', 'consumer_layout_boot.mjs');
724
+ if (!fs.existsSync(harness)) {
725
+ console.error('[self-verify] • bare-install boot skipped — tests/consumer_layout_boot.mjs not present (packaging from an installed core, where tests/ is not shipped).');
726
+ return;
727
+ }
728
+ console.error('[self-verify] bare-install boot: `npm install <tgz>` + boot /healthz (via consumer_layout_boot, CORE_TGZ set) …');
729
+ const r = spawnSync(process.execPath, [harness], {
730
+ cwd: REPO_ROOT, encoding: 'utf8', maxBuffer: 256 * 1024 * 1024,
731
+ env: { ...process.env, CORE_TGZ: tgzPath }, stdio: ['ignore', process.stderr, process.stderr],
732
+ });
733
+ if (r.error || r.status !== 0) {
734
+ console.error(`[self-verify] FAILED — the packaged core did not install + boot (${r.error ? r.error.message : 'exit ' + r.status}).`);
735
+ process.exit(1);
736
+ }
737
+ console.error('[self-verify] ✓ bare-install boot OK — the packaged core installs and serves /healthz.');
738
+ }
739
+ }
740
+
548
741
  function main() {
549
742
  const ref = arg('--ref', 'HEAD');
550
743
  const outDir = path.resolve(REPO_ROOT, arg('--out-dir', 'dist'));
@@ -564,8 +757,17 @@ function main() {
564
757
  // dependency set, not the game's. Pulled out of the generic per-file loop below.
565
758
  const SYNTHESIZED_PATHS = new Set(['package.json', 'package-lock.json']);
566
759
  const entries = parseLsTree(git(['ls-tree', '-r', sha])).filter((e) => e.type === 'blob');
567
- const publishable = entries.filter((e) => isPublishable(e.path) && !SYNTHESIZED_PATHS.has(e.path));
760
+ const selected = entries.filter((e) => isPublishable(e.path) && !SYNTHESIZED_PATHS.has(e.path));
761
+ // Drop npm-never-installed files (.gitignore, .npmignore, .npmrc) so the manifest +
762
+ // tree_sha256 pin describe exactly what `npm install` materializes — otherwise the
763
+ // consumer's verifyInstalledPin fails on the missing file and the vendored upgrade
764
+ // auto-rolls-back (idea 1000543). Done here, so they never reach the tarball or the pin.
765
+ const npmStripped = selected.filter((e) => isNpmStripped(e.path));
766
+ const publishable = selected.filter((e) => !isNpmStripped(e.path));
568
767
  console.error(`[package-core] ${ref} = ${sha.slice(0, 12)} — ${entries.length} files, ${publishable.length} in the core (+2 synthesized), ${entries.length - publishable.length - 2} excluded.`);
768
+ if (npmStripped.length) {
769
+ console.error(`[package-core] dropped ${npmStripped.length} npm-never-installed file(s) from the pin (npm strips these on install; idea 1000543): ${npmStripped.map((e) => e.path).join(', ')}`);
770
+ }
569
771
  if (publishable.length === 0) {
570
772
  console.error('refusing: the core set is EMPTY — almost certainly a manifest/ref mismatch, not intentional. Aborting.');
571
773
  process.exit(1);
@@ -595,11 +797,19 @@ function main() {
595
797
  try { branding = JSON.parse(fs.readFileSync(path.join(REPO_ROOT, 'config/branding.json'), 'utf8')); }
596
798
  catch { /* vanilla instance: derive from the public-IP floor only */ }
597
799
  const ownerAliases = String(process.env.OWNER_ALIASES || '').split(',').map((s) => s.trim()).filter(Boolean);
598
- const extra = { owners: ownerAliases, literals: dropletIp ? [dropletIp] : [] };
800
+ // The NEUTRAL FLOOR (task 1002217 / audit H4): always inject the fixed prior-instance tokens so
801
+ // docs de-brand + the no-leak gate fires against them EVEN WITH NO branding.json (the F2 gap).
802
+ // Merged on top of this instance's own branding — for a branded instance the floor is harmless
803
+ // extra; for the neutral trunk it is the only thing standing between a doc and customer-zero's name.
804
+ const extra = {
805
+ owners: [...ownerAliases, ...PRIOR_INSTANCE_FLOOR.owners],
806
+ tokens: [...PRIOR_INSTANCE_FLOOR.tokens],
807
+ literals: dropletIp ? [dropletIp] : [],
808
+ };
599
809
  const rules = buildRedactionRules(branding, extra);
600
810
  const sensitiveLiterals = collectSensitiveLiterals(branding, extra);
601
811
  const envSecrets = collectEnvSecrets();
602
- console.error(`[package-core] ${rules.length} redaction rule(s), ${sensitiveLiterals.length} gate literal(s) + public-IP floor + ${envSecrets.length} env-secret literal(s).`);
812
+ console.error(`[package-core] ${rules.length} redaction rule(s), ${sensitiveLiterals.length} gate literal(s) + public-IP floor + ${envSecrets.length} env-secret literal(s) (incl. the neutral floor).`);
603
813
 
604
814
  // 4. Process every file IN MEMORY (processCoreFile: docs redacted, functional verbatim)
605
815
  // + the fail-closed no-leak gate. Nothing touches dist/ until the ENTIRE set is proven
@@ -710,6 +920,47 @@ function main() {
710
920
  console.error(` manifest ${path.relative(REPO_ROOT, manifestPath)} ← the PIN a downstream repo records`);
711
921
  console.error(` tree ${path.relative(REPO_ROOT, stageDir)}/`);
712
922
 
923
+ // Optional self-verify leg (task 1002218 / audit H3): prove the built artifact reproduces
924
+ // its pin (+ optionally installs & boots) BEFORE it can be trusted or published. Runs after
925
+ // the artifact is written; a failure exits non-zero so an unverified/broken artifact never
926
+ // reaches the publish leg below or a downstream consumer.
927
+ if (process.argv.includes('--self-verify')) {
928
+ selfVerify({ tgzPath, sidecar, outDir, boot: process.argv.includes('--self-verify-boot') });
929
+ }
930
+
931
+ // Post-build leak scan (task 1002220 / audit H6): independent verification that the BUILT
932
+ // artifact carries no prior-instance identity, + a warn-level secret-shape scan over functional
933
+ // files. Runs on every build (skip with --no-leak-scan). Prior-instance literals are a WARNING
934
+ // by default and a hard FAILURE under --leak-scan-strict (the trunk still carries residual
935
+ // history until task 1002219 sweeps it + task 1002217 adds the fail-closed floor gate). A
936
+ // secret-shape hit is ALWAYS a non-fatal warning — the R88 functional-verbatim fix is preserved.
937
+ if (!process.argv.includes('--no-leak-scan')) {
938
+ const scanFiles = staged.map((s) => ({ path: s.name.startsWith(TAR_ROOT) ? s.name.slice(TAR_ROOT.length) : s.name, content: s.buf }));
939
+ const scan = scanArtifactForLeaks(scanFiles);
940
+ const strict = process.argv.includes('--leak-scan-strict');
941
+ if (scan.literalHits.length) {
942
+ const byLit = {};
943
+ for (const h of scan.literalHits) byLit[h.sample] = (byLit[h.sample] || 0) + 1;
944
+ const summary = Object.entries(byLit).sort((a, b) => b[1] - a[1]).map(([k, n]) => `${k}×${n}`).join(', ');
945
+ const files = [...new Set(scan.literalHits.map((h) => h.path))];
946
+ console.error('');
947
+ console.error(`[leak-scan] ${strict ? 'FAILED' : 'WARNING'} — ${scan.literalHits.length} prior-instance literal hit(s) across ${files.length} shipped file(s): ${summary}`);
948
+ for (const p of files.slice(0, 30)) console.error(` ${p}`);
949
+ if (files.length > 30) console.error(` … +${files.length - 30} more file(s)`);
950
+ if (strict) { console.error('[leak-scan] --leak-scan-strict: refusing to certify a leaky artifact.'); process.exit(1); }
951
+ console.error('[leak-scan] non-fatal for now — becomes a hard gate once the trunk sweep (task 1002219) + the fail-closed floor gate (task 1002217) land. Run --leak-scan-strict to enforce today.');
952
+ } else {
953
+ console.error('[leak-scan] ✓ no prior-instance literals in the built artifact.');
954
+ }
955
+ if (scan.secretWarnings.length) {
956
+ console.error(`[leak-scan] ! ${scan.secretWarnings.length} functional file(s) carry a credential SHAPE (WARNING, not fatal — confirm none is a real credential; a real one must be removed at SOURCE):`);
957
+ for (const w of scan.secretWarnings.slice(0, 20)) console.error(` ${w.path} [${w.shapes.join(', ')}]`);
958
+ if (scan.secretWarnings.length > 20) console.error(` … +${scan.secretWarnings.length - 20} more`);
959
+ } else {
960
+ console.error('[leak-scan] ✓ no committed-credential shapes in functional files.');
961
+ }
962
+ }
963
+
713
964
  // Optional publish leg (task 2089): --publish runs `npm publish` on the built
714
965
  // tarball. DRY-RUN by default; the real, permanent publish needs --publish --live
715
966
  // (+ npm auth). Kept AFTER the artifact is written so a publish failure never
@@ -730,12 +981,20 @@ module.exports = {
730
981
  octal, tarChecksum, ustarHeader, splitUstarName, buildTar,
731
982
  // task 1824 — the consumer-core file-classification layer (pure; unit-tested)
732
983
  isDocPath, functionalSecretLeaks, processCoreFile,
984
+ // idea 1000543 — npm-never-installed files dropped from the pin so vendored `bongos upgrade` pin-verify matches
985
+ isNpmStripped, NPM_STRIPPED_BASENAMES,
733
986
  // task 2121 — agent-doc (CLAUDE.md/AGENTS.md) stub so a consumer's node_modules can't auto-load core memory
734
987
  isAgentDoc, CORE_AGENT_DOC_STUB,
735
988
  // task 1882 (ADR 0108 §2) — the real installable npm package
736
989
  CORE_DEP_NAMES, buildPackageJson, resolveLockDep, lockfileClosure, prunePackageLock,
737
990
  // task 2089 (ADR 0134) — private-by-default npm publish
738
991
  publishArgs,
992
+ // task 1002218 (audit H3) — build-time self-verify (reproduce pin + optional bare-install boot)
993
+ selfVerify, reproducePinFromTree,
994
+ // task 1002220 (audit H6) — post-build leak scan (prior-instance literals + functional secret shapes)
995
+ scanArtifactForLeaks, PRIOR_INSTANCE_LITERALS,
996
+ // task 1002217 (audit H4) — the fixed neutral floor injected into the doc-redaction + fail-closed gate
997
+ PRIOR_INSTANCE_FLOOR,
739
998
  };
740
999
 
741
1000
  if (require.main === module) main();
@@ -28,7 +28,13 @@ const ic = require('../../src/instance-config');
28
28
  // WRITE the session to the configured instance dir (OTB → ~/.config/otb,
29
29
  // unchanged; a fresh instance → its neutral dir). R61 / task 1200.
30
30
  const SESSION_PATH = ic.configPath('gds-session.json');
31
- const API_BASE = ic.resolveEnv('API_BASE') || 'https://amazonprimea.com';
31
+ // env → the EXISTING session's api_base → this instance's branding default.
32
+ // On a RE-auth a session file already exists, and its api_base is the best signal
33
+ // of which instance the builder is on — this file used to hardcode the founder
34
+ // host here (a straggler the task 1002201 sweep missed), so a token minted by
35
+ // another instance was verified against amazonprimea.com and rejected 401 with a
36
+ // perfectly valid token (task 1002427).
37
+ const API_BASE = ic.resolveApiBase();
32
38
 
33
39
  function usage(extra) {
34
40
  if (extra) console.error(extra);