@bongos/core 1.18.3 → 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 (469) hide show
  1. package/.bongos-core.json +1575 -285
  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/status-mirror-sync.js +114 -84
  347. package/src/bongos/api-errors.js +1 -0
  348. package/src/bongos/auth.js +447 -102
  349. package/src/bongos/db-kernel.js +27 -0
  350. package/src/bongos/db.js +39 -0
  351. package/src/bongos/module-scope-map.js +27 -1
  352. package/src/bongos/permission-path-check.js +136 -102
  353. package/src/bongos/repo-info.js +47 -11
  354. package/src/bongos/route-rank-check.js +429 -46
  355. package/src/bongos/routes/_helpers.js +12 -1
  356. package/src/bongos/routes/auth.js +219 -2
  357. package/src/bongos/routes/backup.js +58 -2
  358. package/src/bongos/routes/me.js +35 -1
  359. package/src/bongos/serve-internal.js +84 -15
  360. package/src/branding.js +32 -0
  361. package/src/instance-config.js +30 -0
  362. package/src/module-api.js +112 -1
  363. package/src/platform-server.js +86 -9
  364. package/tests/abandon_task.mjs +59 -3
  365. package/tests/api_cli_flags.mjs +53 -0
  366. package/tests/auth_page_gate.mjs +13 -0
  367. package/tests/backfill_sessions.mjs +127 -1
  368. package/tests/backup_cli.mjs +54 -0
  369. package/tests/bongos_box_connect.mjs +77 -0
  370. package/tests/box_access.mjs +130 -1
  371. package/tests/box_credential.mjs +125 -0
  372. package/tests/box_host_keys.mjs +24 -0
  373. package/tests/box_infra_resolve.mjs +149 -0
  374. package/tests/box_scope_session.mjs +6 -2
  375. package/tests/box_sync.mjs +55 -0
  376. package/tests/box_task_scope.mjs +99 -0
  377. package/tests/branding.mjs +12 -0
  378. package/tests/cf_ruleset_preflight.mjs +142 -0
  379. package/tests/cf_tunnel.mjs +37 -0
  380. package/tests/claim_gate_rebase.mjs +98 -0
  381. package/tests/clawback_confirmed_backlog.mjs +103 -0
  382. package/tests/cli_lib_formatters.mjs +20 -0
  383. package/tests/confirm_attribution.mjs +30 -46
  384. package/tests/credit_grant.mjs +198 -10
  385. package/tests/criterion_authoring_gate.mjs +272 -0
  386. package/tests/criterion_progress.mjs +33 -1
  387. package/tests/dev_lib.mjs +253 -0
  388. package/tests/devbox_artifact_gate.mjs +75 -0
  389. package/tests/devbox_dmg_backdrop.mjs +137 -0
  390. package/tests/devbox_icons.mjs +304 -0
  391. package/tests/devbox_installer_brand.mjs +232 -0
  392. package/tests/devbox_key_register.mjs +234 -0
  393. package/tests/docs_openapi_route.mjs +110 -0
  394. package/tests/doctor_hooks.mjs +141 -0
  395. package/tests/extract_json.mjs +140 -0
  396. package/tests/fitness.mjs +89 -0
  397. package/tests/gen_freshness.mjs +96 -0
  398. package/tests/goal_advisory.mjs +251 -0
  399. package/tests/goal_criterion_confirm_ui.mjs +269 -0
  400. package/tests/goal_routes.mjs +59 -4
  401. package/tests/goal_tier.mjs +12 -3
  402. package/tests/governance_catalog.mjs +129 -0
  403. package/tests/governance_claim_eligibility.mjs +262 -0
  404. package/tests/governance_config.mjs +187 -0
  405. package/tests/governance_ownership_scope.mjs +410 -0
  406. package/tests/governance_page_gates.mjs +324 -0
  407. package/tests/governance_parity.mjs +336 -0
  408. package/tests/governance_principals.mjs +232 -0
  409. package/tests/governance_protected_surfaces.mjs +208 -0
  410. package/tests/governance_require_permission.mjs +223 -0
  411. package/tests/governance_resolver.mjs +84 -0
  412. package/tests/governance_role_sync.mjs +231 -0
  413. package/tests/governance_seed.mjs +127 -0
  414. package/tests/governance_session_scope.mjs +434 -0
  415. package/tests/governance_vanilla_boot.mjs +203 -0
  416. package/tests/grade_attribution.mjs +7 -7
  417. package/tests/hall_audit.mjs +223 -0
  418. package/tests/hall_nav.mjs +191 -0
  419. package/tests/hall_palette.mjs +206 -0
  420. package/tests/hall_signout.mjs +124 -0
  421. package/tests/hook_user_agents.mjs +10 -0
  422. package/tests/idea_triage.mjs +162 -0
  423. package/tests/idp_device_exchange.mjs +93 -0
  424. package/tests/init.mjs +51 -0
  425. package/tests/instance_config.mjs +65 -0
  426. package/tests/instance_mismatch_guard.mjs +23 -6
  427. package/tests/instance_release_config.mjs +104 -0
  428. package/tests/linkify_refs.mjs +15 -2
  429. package/tests/module_contributions.mjs +55 -0
  430. package/tests/module_loader.mjs +10 -5
  431. package/tests/module_scope_active_claims.mjs +94 -0
  432. package/tests/needs_rebase.mjs +92 -0
  433. package/tests/one_active_claim_per_session.mjs +76 -0
  434. package/tests/override_attribution.mjs +10 -8
  435. package/tests/override_request_ownership.mjs +8 -4
  436. package/tests/parse_cookie_decode.mjs +42 -0
  437. package/tests/pay_on_land.mjs +129 -0
  438. package/tests/platform_boot.mjs +100 -1
  439. package/tests/post_login_landing.mjs +42 -0
  440. package/tests/preflight_checks.mjs +72 -0
  441. package/tests/provision.mjs +132 -7
  442. package/tests/publish_branch_route.mjs +53 -0
  443. package/tests/publish_reconciler.mjs +187 -2
  444. package/tests/repo_info_binding.mjs +248 -0
  445. package/tests/reward_gate.mjs +11 -2
  446. package/tests/reward_mode_cost_plus_only.mjs +116 -0
  447. package/tests/reward_mode_surfacing.mjs +168 -0
  448. package/tests/route_permission_check.mjs +301 -0
  449. package/tests/route_rank_check.mjs +254 -12
  450. package/tests/route_validation.mjs +30 -0
  451. package/tests/sequence.mjs +267 -0
  452. package/tests/session_digest.mjs +44 -0
  453. package/tests/session_index.mjs +18 -6
  454. package/tests/session_records.mjs +39 -1
  455. package/tests/session_reward_on_land.mjs +381 -0
  456. package/tests/session_reward_reconcile.mjs +229 -0
  457. package/tests/ship_api_client_pathspec.mjs +100 -0
  458. package/tests/ship_base_flag.mjs +93 -0
  459. package/tests/ship_ci_deploy.mjs +80 -3
  460. package/tests/ship_upstream_deletion.mjs +57 -0
  461. package/tests/smoke_target.mjs +233 -0
  462. package/tests/start_env_guard.mjs +87 -0
  463. package/tests/start_rebase_warning.mjs +103 -0
  464. package/tests/status_mirror_sync.mjs +122 -0
  465. package/tests/stop_refcheck.mjs +19 -1
  466. package/tests/task_goal_link.mjs +15 -6
  467. package/tests/upgrade.mjs +139 -0
  468. package/tests/web_admission_status.mjs +162 -0
  469. package/tests/write_validation_gaps.mjs +110 -0
@@ -939,8 +939,56 @@ const PROMPT_IDENTITY_LITERALS = [
939
939
  /amazonprimea/i,
940
940
  ];
941
941
  // Scan roots (recursive) + kernel-prompt CLIs that live outside them.
942
- const IDENTITY_SCAN_ROOTS = ['src/bongos', 'modules'];
942
+ // scripts/gds joined at task 1002427: the builder CLI was UNSCANNED, which is how
943
+ // paste-token.js kept a hardcoded 'https://amazonprimea.com' long after the task
944
+ // 1002201 sweep replaced that literal everywhere else. The straggler surfaced as a
945
+ // 401 that locked a builder out of re-authenticating — a valid token verified
946
+ // against the wrong host — and nothing in CI could see it (idea 1000564).
947
+ const IDENTITY_SCAN_ROOTS = ['src/bongos', 'modules', 'scripts/gds'];
943
948
  const IDENTITY_SCAN_EXTRA_FILES = ['scripts/gds/architect-audit.js'];
949
+ // PERMANENT, COUNTED carve-outs for the scripts/gds root — {file: allowed hits}.
950
+ //
951
+ // Widening the root (task 1002427) surfaced 23 files. Task 1002428 swept them:
952
+ // every one whose literal was a real runtime bug is FIXED (a wrong instance URL
953
+ // printed to a builder, an `amazonprimea-*` User-Agent that should come from
954
+ // branding().userAgent(), a hardcoded product name in user-facing copy). What is
955
+ // left below is NOT a backlog — each entry is a literal that is CORRECT as
956
+ // written, so this list is permanent rather than a ratchet-to-zero.
957
+ //
958
+ // The COUNT is still enforced, which is the point: an entry may not grow. A file
959
+ // here that gains a NEW hardcoded host fails the build exactly like an unlisted
960
+ // one, and any file NOT listed must have zero hits. Getting better never fails.
961
+ //
962
+ // Why each is legitimate:
963
+ // * box.js — REAL Cloudflare/DNS zone names for the operator plane
964
+ // (`dev.<host>`, the terminal zone). These are infrastructure identifiers,
965
+ // not an instance URL; routing them through branding would be wrong. (Its
966
+ // two genuine API-base defaults WERE fixed in 1002428.)
967
+ // * publish-manifest.js — the literal FILENAMES of two real committed docs
968
+ // (docs/recipes/amazonprimea-decouple-*.md). Renaming the strings without
969
+ // renaming the files would break the publish manifest.
970
+ // * backfill-sessions.js / version-close.js — one-time operator runbook text
971
+ // naming the actual prod DB + a historical local directory. Describing what
972
+ // an operator must literally type is the correct content.
973
+ // * seed-*.js — the hits are PROSE INSIDE seeded task descriptions: historical
974
+ // records of what those tasks said when they were filed. Rewriting them would
975
+ // falsify the record; deleting the files would destroy it. They are inert
976
+ // (idempotent on source_ref) and referenced only by generated manifests.
977
+ const IDENTITY_SCAN_ALLOWED = {
978
+ 'scripts/gds/backfill-sessions.js': 2,
979
+ 'scripts/gds/box.js': 3,
980
+ 'scripts/gds/publish-manifest.js': 2,
981
+ 'scripts/gds/seed-bongos-coreB-tranche1-tasks.js': 1,
982
+ 'scripts/gds/seed-bongos-coreB-tranche2-tasks.js': 1,
983
+ 'scripts/gds/seed-bongos-goal-hierarchy-tasks.js': 1,
984
+ 'scripts/gds/seed-bongos-tasks.js': 1,
985
+ 'scripts/gds/seed-gds-v4-tasks.js': 1,
986
+ 'scripts/gds/seed-medusa-tasks.js': 7,
987
+ 'scripts/gds/seed-module-upstreaming.js': 1,
988
+ 'scripts/gds/seed-productization-tasks.js': 6,
989
+ 'scripts/gds/seed-provisioning-tasks.js': 2,
990
+ 'scripts/gds/version-close.js': 1,
991
+ };
944
992
  const IDENTITY_SCAN_EXTS = new Set(['.js', '.html', '.css']);
945
993
  // Path-prefix carve-outs (see the header note). Kept SMALL and documented.
946
994
  const IDENTITY_SCAN_EXCLUDE = ['modules/game/'];
@@ -954,7 +1002,14 @@ function stripCommentsForIdentity(text, ext) {
954
1002
  let t = String(text == null ? '' : text);
955
1003
  if (ext === '.html') return t.replace(/<!--[\s\S]*?-->/g, '');
956
1004
  t = t.replace(/\/\*[\s\S]*?\*\//g, ''); // block comments (js + css)
957
- if (ext === '.js') t = t.split('\n').map((l) => l.replace(/(^|[^:])\/\/.*$/, '$1')).join('\n');
1005
+ // Split on /\r?\n/, NOT '\n' (task 1002428). On a CRLF checkout the old split
1006
+ // left a trailing \r on every line, and since `.` does not match \r the `.*$`
1007
+ // in the line-comment pattern could never reach end-of-string — so NO line
1008
+ // comment was stripped and every rationale comment naming the old brand was
1009
+ // scanned as if it were code. That silently broke this function's whole
1010
+ // contract ("a rationale comment naming the old brand never trips the scan")
1011
+ // on Windows, and made the check disagree with Linux CI on the same tree.
1012
+ if (ext === '.js') t = t.split(/\r?\n/).map((l) => l.replace(/(^|[^:])\/\/.*$/, '$1')).join('\n');
958
1013
  return t;
959
1014
  }
960
1015
 
@@ -1016,12 +1071,30 @@ function checkKernelPromptsNoHardcodedIdentity() {
1016
1071
  for (const extra of IDENTITY_SCAN_EXTRA_FILES) {
1017
1072
  if (!seen.has(extra)) { seen.add(extra); entries.push({ abs: path.join(CORE_ROOT, extra), rel: extra }); }
1018
1073
  }
1074
+ let ratcheted = 0;
1019
1075
  for (const { abs, rel: relFile } of entries) {
1020
1076
  let text;
1021
1077
  try { text = fs.readFileSync(abs, 'utf8'); }
1022
1078
  catch { continue; } // a file moved/carved away is not a failure of this gate
1023
1079
  scanned += 1;
1024
- violations.push(...scanPromptIdentity(relFile, text));
1080
+ const found = scanPromptIdentity(relFile, text);
1081
+ // Permanently allowed at a COUNT (task 1002428): the listed literals are
1082
+ // correct as written, but an entry may not GROW — a new hardcode in one of
1083
+ // these files fails exactly like one in an unlisted file.
1084
+ const allowed = IDENTITY_SCAN_ALLOWED[relFile];
1085
+ if (allowed !== undefined) {
1086
+ if (found.length > allowed) {
1087
+ violations.push(
1088
+ `${relFile} now hardcodes the host-brand identity ${found.length} time(s), above its allowed ${allowed} — ` +
1089
+ `the carve-out covers the EXISTING literals (see IDENTITY_SCAN_ALLOWED), not new ones. ` +
1090
+ `Read the host from branding()/window.__BRANDING__ instead (task 1002428).`
1091
+ );
1092
+ } else {
1093
+ ratcheted += 1;
1094
+ }
1095
+ continue;
1096
+ }
1097
+ violations.push(...found);
1025
1098
  }
1026
1099
  return {
1027
1100
  name: 'portable code carries no hardcoded host identity (BV1.R76 / task 1705)',
@@ -1029,7 +1102,43 @@ function checkKernelPromptsNoHardcodedIdentity() {
1029
1102
  hardFail: violations.length > 0,
1030
1103
  violations,
1031
1104
  warnings: [],
1032
- note: `${scanned} portable source file(s) scanned (src/bongos/** + modules/** + kernel-prompt CLIs, minus the instance-1 product + generated dirs) — none may hardcode the host brand; identity comes from branding()/window.__BRANDING__ (ADR 0062 §3/§5).`,
1105
+ note: `${scanned} portable source file(s) scanned (src/bongos/** + modules/** + scripts/gds/** + kernel-prompt CLIs, minus the instance-1 product + generated dirs) — none may hardcode the host brand; identity comes from branding()/window.__BRANDING__ (ADR 0062 §3/§5). ${ratcheted} scripts/gds file(s) hold documented permanent carve-outs (real DNS zones, real doc filenames, operator runbook text, historical seeded prose) capped at a count they may not exceed — task 1002428.`,
1106
+ };
1107
+ }
1108
+
1109
+ // ============================================================================
1110
+ // Check 15b — core-shipped NEUTRAL config packs carry no host identity (BV1.R102)
1111
+ // ============================================================================
1112
+ //
1113
+ // Sibling of Check 15 for the CONFIG class. IDENTITY_SCAN_EXTS is js/html/css, so
1114
+ // the scan above never opens a .json — yet config/*.neutral.json is precisely what
1115
+ // a vanilla instance boots from (branding R76, governance R96). A host name leaking
1116
+ // into a neutral pack ships one instance's brand to EVERY fresh instance, and until
1117
+ // this check nothing in CI could see it.
1118
+ //
1119
+ // Only the NEUTRAL packs are scanned, deliberately: an instance pack (config/
1120
+ // branding.json, config/governance.json) is host content that legitimately names
1121
+ // its host, and is boundary-excluded from the published core anyway.
1122
+ function checkNeutralConfigsNoHostIdentity() {
1123
+ const violations = [];
1124
+ const dir = path.join(CORE_ROOT, 'config');
1125
+ let names = [];
1126
+ try { names = fs.readdirSync(dir).filter((n) => n.endsWith('.neutral.json')).sort(); }
1127
+ catch { /* no config/ dir in this root — nothing to gate */ }
1128
+ for (const name of names) {
1129
+ const relFile = `config/${name}`;
1130
+ let text;
1131
+ try { text = fs.readFileSync(path.join(dir, name), 'utf8'); }
1132
+ catch { continue; } // a pack that moved away is not a failure of this gate
1133
+ violations.push(...scanPromptIdentity(relFile, text, '.json'));
1134
+ }
1135
+ return {
1136
+ name: 'neutral config packs carry no host identity (BV1.R102)',
1137
+ ok: violations.length === 0,
1138
+ hardFail: violations.length > 0,
1139
+ violations,
1140
+ warnings: [],
1141
+ note: `${names.length} core-shipped config/*.neutral.json pack(s) scanned — a vanilla instance boots from these alone, so none may name a host brand. Instance packs (config/<name>.json) are host content and are deliberately NOT scanned.`,
1033
1142
  };
1034
1143
  }
1035
1144
 
@@ -1076,7 +1185,77 @@ function checkDesignContract() {
1076
1185
  // reskin/module-toggle reaches every widget). Self-contained: a pure scan of
1077
1186
  // index.html's <main>, no module/registry state needed. Exported for the unit test.
1078
1187
  // (The hall surface itself carved to modules/hall-ui/public/ in BV1.R85.)
1188
+ //
1189
+ // task 2177 — the check also asserts the HOME ZONE BUDGETS. builders.js caps how
1190
+ // many widgets each Home zone mounts (ZONE_BUDGETS); past the cap a widget still
1191
+ // mounts, but into a hidden overflow behind a "More" link. That graceful degrade
1192
+ // is the runtime safety net — it is NOT a licence to keep adding cards, and
1193
+ // because nothing goes red, a Home that has quietly outgrown its budget looks
1194
+ // fine. So CI counts the registrations statically and fails on a breach: the
1195
+ // budget becomes a decision someone has to make deliberately (raise the number
1196
+ // in builders.js, with a reason) instead of one that erodes.
1197
+ //
1198
+ // NOT statically checkable, by design: the `home-banner` rule is "at most ONE
1199
+ // VISIBLE", not "at most one registered" — both banner widgets (welcome,
1200
+ // getting-started) are hiddenByDefault and reveal themselves from onboarding
1201
+ // state, so only runtime knows the count. It is documented as a runtime
1202
+ // invariant in modules/hall-ui/CLAUDE.md rather than faked with a static count
1203
+ // that would either fail today or assert nothing.
1079
1204
  const HALL_INDEX = path.join(moduleDir('hall-ui'), 'public', 'index.html'); // hall-ui is a module — resolve its root (ADR 0108)
1205
+ const HALL_PUBLIC = path.join(moduleDir('hall-ui'), 'public');
1206
+ const HALL_BUILDERS_JS = path.join(HALL_PUBLIC, 'builders.js');
1207
+
1208
+ // Parse builders.js's ZONE_BUDGETS object literal — the single source of truth
1209
+ // for the caps (this check must never carry its own copy of the numbers).
1210
+ function parseZoneBudgets(js) {
1211
+ const block = js.match(/const\s+ZONE_BUDGETS\s*=\s*\{([^}]*)\}/);
1212
+ if (!block) return null;
1213
+ const budgets = {};
1214
+ for (const [, zone, n] of block[1].matchAll(/['"]([\w-]+)['"]\s*:\s*(\d+)/g)) {
1215
+ budgets[zone] = Number(n);
1216
+ }
1217
+ return Object.keys(budgets).length ? budgets : null;
1218
+ }
1219
+
1220
+ // Count registrations per Home zone across every hall client script that calls
1221
+ // contributeHallWidget. The zones are static string literals in the registration
1222
+ // object, so a literal scan is exact for the code we have (and a computed zone
1223
+ // would be a placement smell in its own right — dom-utils' runtime validation
1224
+ // still rejects an unknown one). Requiring the contributeHallWidget mention is
1225
+ // what keeps an unrelated `zone:` key in some other object from eating budget.
1226
+ function scanHallZoneCounts(dir = HALL_PUBLIC, fsImpl = fs) {
1227
+ const counts = {};
1228
+ let files = [];
1229
+ try { files = fsImpl.readdirSync(dir).filter((f) => f.endsWith('.js')); } catch { return counts; }
1230
+ for (const file of files) {
1231
+ let src = '';
1232
+ try { src = fsImpl.readFileSync(path.join(dir, file), 'utf8'); } catch { continue; }
1233
+ if (!src.includes('contributeHallWidget')) continue;
1234
+ for (const [, zone] of src.matchAll(/\bzone:\s*['"]([\w-]+)['"]/g)) {
1235
+ counts[zone] = (counts[zone] || 0) + 1;
1236
+ }
1237
+ }
1238
+ return counts;
1239
+ }
1240
+
1241
+ function checkHallZoneBudgets(fsImpl = fs) {
1242
+ let js = '';
1243
+ try { js = fsImpl.readFileSync(HALL_BUILDERS_JS, 'utf8'); } catch { return []; }
1244
+ const budgets = parseZoneBudgets(js);
1245
+ if (!budgets) {
1246
+ return ['modules/hall-ui/public/builders.js no longer declares a parseable ZONE_BUDGETS literal — the Home zone budgets are unenforced (task 2177).'];
1247
+ }
1248
+ const counts = scanHallZoneCounts(HALL_PUBLIC, fsImpl);
1249
+ const violations = [];
1250
+ for (const [zone, budget] of Object.entries(budgets)) {
1251
+ const n = counts[zone] || 0;
1252
+ if (n > budget) {
1253
+ violations.push(
1254
+ `Home zone "${zone}" has ${n} registered hall widgets but its budget is ${budget} — the extras mount into the hidden overflow, so Home silently grew. Move it to its own page (see modules/hall-ui/CLAUDE.md §Placement), or raise ZONE_BUDGETS['${zone}'] in modules/hall-ui/public/builders.js deliberately (task 2177).`);
1255
+ }
1256
+ }
1257
+ return violations;
1258
+ }
1080
1259
 
1081
1260
  function hallMainSlice(html) {
1082
1261
  // Strip HTML comments first so a comment that merely MENTIONS <main> or a
@@ -1107,13 +1286,14 @@ function checkHallSectionRegistry() {
1107
1286
  const hardcoded = scanHallSectionRegistry(html);
1108
1287
  const violations = hardcoded.map((s) =>
1109
1288
  `modules/hall-ui/public/index.html <main> hardcodes a hall section "${s}" — register it via window.OTB.contributeHallWidget (modules/hall-ui/public/hall-widgets.js for core, a module's client script otherwise), don't inline the markup (BV1.R75 / ADR 0062 §4).`);
1289
+ violations.push(...checkHallZoneBudgets());
1110
1290
  return {
1111
- name: 'hall widgets are registry-owned (ADR 0062 §4)',
1291
+ name: 'hall widgets are registry-owned + within zone budget (ADR 0062 §4, task 2177)',
1112
1292
  ok: violations.length === 0,
1113
1293
  hardFail: violations.length > 0,
1114
1294
  violations,
1115
1295
  warnings: [],
1116
- note: 'modules/hall-ui/public/index.html <main> contains no hardcoded <section class="scroll"> — the hall-widget registry is the single source of truth.',
1296
+ note: 'modules/hall-ui/public/index.html <main> contains no hardcoded <section class="scroll">, and every Home zone is within the ZONE_BUDGETS cap builders.js declares — the registry is the single source of truth and Home cannot grow unnoticed.',
1117
1297
  };
1118
1298
  }
1119
1299
 
@@ -1126,8 +1306,9 @@ function checkHallSectionRegistry() {
1126
1306
  // config/modules.json, …) is boundary-EXCLUDED, so a bare `require('../../config/<excluded>.json')`
1127
1307
  // in core code throws MODULE_NOT_FOUND and CRASHES the boot on a fresh instance — the exact
1128
1308
  // class that shipped in R88 (two hard-requires in src/bongos/serve-internal.js; task 1824). The
1129
- // cure is to guard the read (tryRequireConfig / fs.existsSync → fail-to-vanilla), which also
1130
- // makes the require a non-literal so it no longer matches here. Check 1 (core↔host) scans
1309
+ // cure is to READ the config instead of requiring it, tolerating a missing file
1310
+ // (tryLoadInstanceConfig in src/bongos/serve-internal.js / fs.existsSync → fail-to-vanilla), which
1311
+ // also drops the require() literal so it no longer matches here. Check 1 (core↔host) scans
1131
1312
  // code→code IMPORTS and misses this code→config class. Only an EXCLUDED config is a violation;
1132
1313
  // a publishable neutral.*/schema config (which ships with the core) is fine.
1133
1314
  const CONFIG_REQUIRE_RE = /require\(\s*['"]([^'"]*config\/[^'"]*\.json)['"]\s*\)/g;
@@ -1155,7 +1336,7 @@ function checkCoreRequiresExcludedConfig() {
1155
1336
  while ((m = CONFIG_REQUIRE_RE.exec(raw))) {
1156
1337
  const relCfg = rel(path.resolve(path.dirname(abs), m[1]));
1157
1338
  if (!isPublishable(relCfg)) {
1158
- violations.push(`${relFile} → require('${m[1]}') hard-requires an instance-excluded config (${relCfg}) — guard the read (tryRequireConfig / fs.existsSync, fail-to-vanilla) so a fresh core boots (task 1824).`);
1339
+ violations.push(`${relFile} → require('${m[1]}') hard-requires an instance-excluded config (${relCfg}) — guard the read (tryLoadInstanceConfig in src/bongos/serve-internal.js / fs.existsSync, fail-to-vanilla) so a fresh core boots (task 1824).`);
1159
1340
  }
1160
1341
  }
1161
1342
  }
@@ -1362,15 +1543,45 @@ const WRITE_VALIDATION_EXEMPT = new Set([
1362
1543
  'PATCH /me/display-name', // { display_name } — displayName.validate
1363
1544
  ]);
1364
1545
 
1546
+ // KNOWN GAPS, NOT EXEMPTIONS (task 1002551 / BV1.R109b → tracked by task 1002552).
1547
+ //
1548
+ // These three are NOT validated-by-lib and do NOT belong in WRITE_VALIDATION_EXEMPT
1549
+ // above — they are genuine ADR 0118 gaps that must grow a validateOrRespond schema.
1550
+ // They surface here only because R109b taught route discovery to see MULTI-LINE
1551
+ // `router.<method>(` declarations: this check reuses gen-api-docs.extractModel(), so
1552
+ // the same regex hole that hid them from the rank auditor hid them from Check 18 too.
1553
+ // They are pre-existing (never validated), not a regression.
1554
+ //
1555
+ // They are recorded rather than fixed because modules/*/routes/*.js was being migrated
1556
+ // concurrently by BV1.R105, so editing those files would have collided. This is the
1557
+ // Check-17 ratchet posture: a NAMED, non-growing baseline. The list may only ever
1558
+ // SHRINK — a new unvalidated write route is not on it and still hard-fails the build,
1559
+ // and a stale entry (one that HAS been fixed) also fails, so the list cannot rot.
1560
+ // Delete this constant when task 1002552 lands and Check 18 returns to hard-zero.
1561
+ // EMPTY as of BV1.R109c (task 1002552) — all three original entries now validate.
1562
+ // Kept (rather than deleted) as the ratchet itself: an empty named baseline states
1563
+ // the invariant is hard-zero, and any future entry has to be added deliberately.
1564
+ const WRITE_VALIDATION_BASELINE = new Set([]);
1565
+
1365
1566
  function scanUnvalidatedWriteRoutes(model = genApiDocs.extractModel()) {
1366
1567
  const unvalidated = [];
1568
+ const baselineSeen = new Set();
1367
1569
  for (const r of model) {
1368
1570
  if (!['POST', 'PUT', 'PATCH', 'DELETE'].includes(r.method)) continue;
1369
1571
  // Only routes that actually read a body have an input surface to validate;
1370
1572
  // a write that ignores req.body has nothing to declare.
1371
1573
  if (!r.body || !r.body.readsBody || r.body.validated) continue;
1372
- if (WRITE_VALIDATION_EXEMPT.has(`${r.method} ${r.route}`)) continue;
1373
- unvalidated.push(`${r.method} ${r.route} [${r.file}]`);
1574
+ const key = `${r.method} ${r.route}`;
1575
+ if (WRITE_VALIDATION_EXEMPT.has(key)) continue;
1576
+ if (WRITE_VALIDATION_BASELINE.has(key)) { baselineSeen.add(key); continue; }
1577
+ unvalidated.push(`${key} [${r.file}]`);
1578
+ }
1579
+ // A baseline entry that no longer reports as unvalidated has been FIXED — it must be
1580
+ // removed, or the list would quietly license a future regression on that same route.
1581
+ for (const key of WRITE_VALIDATION_BASELINE) {
1582
+ if (!baselineSeen.has(key)) {
1583
+ unvalidated.push(`${key} [STALE BASELINE — this route now validates; remove it from WRITE_VALIDATION_BASELINE in scripts/gds/fitness.js]`);
1584
+ }
1374
1585
  }
1375
1586
  return unvalidated;
1376
1587
  }
@@ -1389,7 +1600,7 @@ function checkWriteRoutesValidated() {
1389
1600
  hardFail: violations.length > 0,
1390
1601
  violations,
1391
1602
  warnings: [],
1392
- note: `body-reading POST/PUT/PATCH/DELETE routes must call validateOrRespond/validate (${WRITE_VALIDATION_EXEMPT.size} dynamic-key prefs routes validate by lib); ${unvalidated.length} unvalidated.`,
1603
+ note: `body-reading POST/PUT/PATCH/DELETE routes must call validateOrRespond/validate (${WRITE_VALIDATION_EXEMPT.size} dynamic-key prefs routes validate by lib; ${WRITE_VALIDATION_BASELINE.size} known gaps tracked by task 1002552, list may only shrink); ${unvalidated.length} unvalidated.`,
1393
1604
  };
1394
1605
  }
1395
1606
 
@@ -1411,6 +1622,7 @@ const CHECKS = [
1411
1622
  checkMigrationNamespacing, // Check 11b — ADR 0108 §5 cross-repo migration filenames + stem collision
1412
1623
  checkDesignContract,
1413
1624
  checkKernelPromptsNoHardcodedIdentity, // BV1.R76 — kernel prompts read identity from branding()
1625
+ checkNeutralConfigsNoHostIdentity, // Check 15b — BV1.R102: .json is outside the scan above
1414
1626
  checkHallSectionRegistry,
1415
1627
  checkCoreRequiresExcludedConfig, // task 1824 — core→instance-config require guard
1416
1628
  checkCoreRequiresExcludedModule, // Check 16b — task 2135: core→excluded-module require guard (R89 split)
@@ -1463,9 +1675,13 @@ module.exports = {
1463
1675
  checkKernelPromptsNoHardcodedIdentity, // BV1.R76 / task 1705
1464
1676
  scanPromptIdentity, // BV1.R76 — negative-test hook
1465
1677
  stripCommentsForIdentity, // task 1705 — comment-strip helper (tested)
1678
+ checkNeutralConfigsNoHostIdentity, // Check 15b — BV1.R102 (tested)
1466
1679
  checkHallSectionRegistry,
1467
1680
  scanHallSectionRegistry,
1468
1681
  hallMainSlice,
1682
+ parseZoneBudgets, // task 2177 — hall Home zone budgets (tested)
1683
+ scanHallZoneCounts,
1684
+ checkHallZoneBudgets,
1469
1685
  checkErrorEnvelope, // Check 17 — BV1 R03 / task 1990 (ADR 0116)
1470
1686
  scanFlatErrorSites, // shared with the R04 sweep + its test
1471
1687
  countFlatErrors, // per-text detector (tested)
@@ -10,7 +10,22 @@ User=lars
10
10
  Group=lars
11
11
  # Run the canonical script from the deployed repo. Keep this path stable;
12
12
  # the recipe doc points at it for restore + manual-one-shot use cases.
13
- ExecStart=/home/lars/amazonprimea/scripts/gds/db-backup-nightly.sh
13
+ #
14
+ # Invoke through `bash` explicitly, NOT as a bare ExecStart path. Every .sh in
15
+ # this repo is committed mode 100644 (they are run as `bash script.sh` — the
16
+ # /backup/trigger route does exactly that via execFile('bash', …)), so a bare
17
+ # ExecStart fails `203/EXEC` the moment the file lands without an exec bit.
18
+ # That is not hypothetical: on cloudbongos this unit died silently for 13 days
19
+ # (2026-07-15 → 07-28) because a core upgrade re-extracted the script 0664, and
20
+ # a `chmod +x` fix does not survive the NEXT upgrade. Going through bash makes
21
+ # the exec bit irrelevant (task 1002479).
22
+ #
23
+ # EDIT THIS PATH when installing — it must point at the deployed core scripts
24
+ # dir for THIS instance, which differs by layout:
25
+ # vendored/standalone: /home/<user>/<instance>/node_modules/@bongos/core/scripts/gds/db-backup-nightly.sh
26
+ # full clone: /home/<user>/<repo>/scripts/gds/db-backup-nightly.sh
27
+ # See docs/recipes/gds-db-backup.md.
28
+ ExecStart=/usr/bin/env bash /home/lars/amazonprimea/scripts/gds/db-backup-nightly.sh
14
29
  # Modest hardening — the script only needs read on the socket + write on
15
30
  # /var/backups/gds. Anything else is gravy for forensics.
16
31
  NoNewPrivileges=true
@@ -10,7 +10,15 @@ User=lars
10
10
  Group=lars
11
11
  # Run the canonical script from the deployed repo. Keep this path stable;
12
12
  # the recipe doc points at it for restore + manual-one-shot use cases.
13
- ExecStart=/home/lars/amazonprimea/scripts/gds/db-backup-offsite.sh
13
+ #
14
+ # Invoke through `bash`, NOT as a bare ExecStart path — see the long note in
15
+ # gds-db-backup.service. Same trap, same fix: the .sh files here are committed
16
+ # mode 100644, so a bare ExecStart dies `203/EXEC` and no chmod survives a core
17
+ # upgrade. The nightly unit lost 13 days of backups to exactly this (1002479);
18
+ # this unit would have hit it on its first run.
19
+ #
20
+ # EDIT THIS PATH when installing (layout differs — vendored vs full clone).
21
+ ExecStart=/usr/bin/env bash /home/lars/amazonprimea/scripts/gds/db-backup-offsite.sh
14
22
  # S3 credentials for the Space live outside the repo (ADR 0022). systemd
15
23
  # injects them; the file is chmod 600, owned by lars. Missing-file is
16
24
  # tolerated here (the script fails loudly if the vars are unset) so a
@@ -439,7 +439,7 @@ function renderReadme(spec, ops) {
439
439
  `import { createClient, ApiError } from '@bongos/client';`,
440
440
  ``,
441
441
  `const api = createClient({`,
442
- ` baseUrl: 'https://amazonprimea.com${base}', // or omit for same-origin (browser)`,
442
+ ` baseUrl: 'https://example.com${base}', // your instance origin — or omit for same-origin (browser)`,
443
443
  ` token: process.env.BONGOS_TOKEN, // Authorization: Bearer <token>`,
444
444
  `});`,
445
445
  ``,
@@ -41,6 +41,7 @@ const { API_PREFIX, API_VERSION, VERSIONED_API_PREFIX } = require('../../src/bon
41
41
  // category taxonomy the spec advertises (ADR 0116). Keep them pure — the generator
42
42
  // must never execute a route file or open a connection.
43
43
  const { LIMITS } = require('../../src/bongos/routes/_helpers');
44
+ const { sameContent } = require('./gen-freshness'); // eol-insensitive freshness (CRLF checkouts)
44
45
  const apiErrors = require('../../src/bongos/api-errors');
45
46
 
46
47
  const CORE_ROOT = resolveCoreRoot();
@@ -451,6 +452,11 @@ function extractBody(lines, i, method, constants = {}) {
451
452
  function responseValueSchema(expr) {
452
453
  const v = String(expr).trim();
453
454
  if (v === 'true' || v === 'false') return { type: 'boolean' };
455
+ // `!!x` and `Boolean(x)` are boolean whatever x is — the two idioms routes use
456
+ // to coerce a truthy value before returning it (task 1002430). Without this a
457
+ // handler returning `{ ok: true }` in one branch and `{ ok: !!v }` in another
458
+ // documents `ok` as untyped, which is strictly worse than the truth.
459
+ if (/^!!/.test(v) || /^Boolean\s*\(/.test(v)) return { type: 'boolean' };
454
460
  if (/^pageMeta\s*\(/.test(v)) return { $ref: '#/components/schemas/PageInfo' };
455
461
  return {}; // a variable / call / ternary — the KEY is the win; leave the value open
456
462
  }
@@ -533,26 +539,38 @@ function tagFor(route) {
533
539
 
534
540
  // The full extracted model: [{ method, route, openapiPath, params, rank, tag,
535
541
  // description, body, file }], stable-sorted.
536
- function extractModel() {
542
+ // `routeFiles` is injectable so a test can drive the model over a fixture route file
543
+ // (the default is the live core + module route set).
544
+ function extractModel({ routeFiles = rrc.discoverRouteFiles() } = {}) {
537
545
  const routes = [];
538
546
  const globals = globalConstants();
539
- for (const { abs, rel } of rrc.discoverRouteFiles()) {
547
+ for (const { abs, rel } of routeFiles) {
540
548
  const text = fs.readFileSync(abs, 'utf8');
541
549
  const lines = text.split('\n');
542
550
  // Constants a validate() rule in THIS file may reference (LIMITS + file-local consts).
543
551
  const constants = Object.assign({}, globals, scanFileConstants(text));
544
- for (let i = 0; i < lines.length; i++) {
545
- const m = /^\s*router\.(get|post|patch|put|delete)\s*\(\s*['"]([^'"]+)['"]/.exec(lines[i]);
546
- if (!m) continue;
547
- const method = m[1].toUpperCase();
548
- const route = m[2];
552
+ // rrc.scanDeclarations, not a second regex: the spec must enumerate EXACTLY the
553
+ // routes the ship-time gate enforces. Its own regex missed multi-line declarations,
554
+ // so 10 real endpoints (6 writes) were absent from the published contract entirely
555
+ // (BV1.R109b). An unresolvable path is skipped here and reported by checkRouteRanks.
556
+ for (const decl of rrc.scanDeclarations(text)) {
557
+ if (decl.route === null) continue;
558
+ const i = decl.line;
559
+ const method = decl.method;
560
+ const route = decl.route;
549
561
  const { openapiPath, names } = pathParams(route);
562
+ // classifyGate, not classifyRank: a route migrated to requirePermission(...)
563
+ // (BV1.R104-R107) resolves through the governance catalog to its real rank
564
+ // floor, and names the authority atoms it gates on. Before BV1.R109 those
565
+ // routes documented as 'any-builder' — enforcement was correct, the LABEL lied.
566
+ const { rank, perms } = rrc.classifyGate(lines, i);
550
567
  routes.push({
551
568
  method,
552
569
  route,
553
570
  openapiPath,
554
571
  params: names,
555
- rank: rrc.classifyRank(lines, i),
572
+ rank,
573
+ perms,
556
574
  tag: tagFor(route),
557
575
  description: commentBlockAbove(lines, i),
558
576
  body: extractBody(lines, i, method, constants),
@@ -697,10 +715,14 @@ function buildOperation(r, stats, schemas) {
697
715
  tags: [r.tag],
698
716
  summary: `${r.method} ${r.route}`,
699
717
  description: (r.description ? r.description + '\n\n' : '')
700
- + `**Rank:** \`${r.rank}\` — ${RANK_BLURB[r.rank] || RANK_BLURB.unknown}`,
718
+ + `**Rank:** \`${r.rank}\` — ${RANK_BLURB[r.rank] || RANK_BLURB.unknown}`
719
+ + (r.perms && r.perms.length ? `\n\n**Permissions:** \`${r.perms.join('`, `')}\` (all required).` : ''),
701
720
  'x-rank': r.rank,
702
721
  'x-source': r.file,
703
722
  };
723
+ // The authority ATOMS a migrated route gates on (ADR 0151). Absent on a route still
724
+ // gated by rank, so today's spec is byte-identical until BV1.R104-R107 land.
725
+ if (r.perms && r.perms.length) op['x-permissions'] = r.perms;
704
726
  if (r.params.length) {
705
727
  op.parameters = r.params.map((name) => ({
706
728
  name, in: 'path', required: true, schema: { type: 'string' },
@@ -768,11 +790,11 @@ function buildOpenapi(model) {
768
790
  const spec = {
769
791
  openapi: '3.1.0',
770
792
  info: {
771
- title: `${productName()} — GDS API`,
793
+ title: `${productName()} — Bongos API`,
772
794
  version: specVersion(),
773
795
  license: { name: 'AGPL-3.0-or-later', url: 'https://www.gnu.org/licenses/agpl-3.0.html' },
774
796
  description: [
775
- `The Game Development System (GDS) API — the build-methodology backend`,
797
+ `The Bongos API — the build-methodology backend`,
776
798
  `(builders, tasks, claims, credits, grading, memory, dev boxes).`,
777
799
  ``,
778
800
  `**API version:** \`${API_VERSION}\` — served at \`${VERSIONED_API_PREFIX}\`. The`,
@@ -843,7 +865,7 @@ function buildReferenceMd(model, spec) {
843
865
  const L = [];
844
866
  L.push(MD_BANNER);
845
867
  L.push('');
846
- L.push('# GDS API reference');
868
+ L.push('# Bongos API reference');
847
869
  L.push('');
848
870
  L.push(`> **Generated from the live route files** — the route file is authoritative. ${model.length} endpoints across ${new Set(model.map((r) => r.file)).size} route files.`);
849
871
  L.push('> Machine-readable spec: [`docs/api/openapi.json`](api/openapi.json) (OpenAPI 3.1). Rendered docs site: **`/docs`** (e.g. `cloudbongos.com/docs`).');
@@ -986,8 +1008,8 @@ function main() {
986
1008
  if (checkOnly) {
987
1009
  const stale = files.filter((f) => {
988
1010
  let cur = null;
989
- try { cur = fs.readFileSync(f.full, 'utf8'); } catch { /* missing → stale */ }
990
- return cur !== f.content;
1011
+ try { cur = fs.readFileSync(f.full, 'utf8'); } catch { return true; /* missing → stale */ }
1012
+ return !sameContent(cur, f.content);
991
1013
  });
992
1014
  if (stale.length) {
993
1015
  console.error(`gen-api-docs --check: STALE — would update ${stale.map((f) => f.rel).join(', ')}. Run \`node scripts/gds/gen-api-docs.js\` and commit.`);