@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
@@ -183,6 +183,32 @@ async function hasInvitedAccessRequest(login) {
183
183
  return rows.length > 0;
184
184
  }
185
185
 
186
+ // accessStateByLogin — the three admission-relevant facts about a github login,
187
+ // in ONE round trip: is there already a builder row (member), has an Archon
188
+ // approved them (invited), are they waiting on one (pending). Case-insensitive
189
+ // throughout, matching hasInvitedAccessRequest above.
190
+ //
191
+ // This exists for the "confirming access" waiting page (task 1002097): the page
192
+ // polls a probe that must distinguish "admission is imminent" from "genuine
193
+ // stranger", and doing that with three separate helpers would mean three
194
+ // queries per poll per visitor. It reports FACTS ONLY — the admission DECISION
195
+ // stays in auth.firstTimeAdmissionBlocked, which is the single gate both flows
196
+ // share (SR-19). Nothing here grants access.
197
+ async function accessStateByLogin(login) {
198
+ const { rows } = await pool.query(
199
+ `SELECT
200
+ EXISTS (SELECT 1 FROM builders
201
+ WHERE lower(github_login) = lower($1)) AS member,
202
+ EXISTS (SELECT 1 FROM access_requests
203
+ WHERE lower(github_login) = lower($1) AND status = 'invited') AS invited,
204
+ EXISTS (SELECT 1 FROM access_requests
205
+ WHERE lower(github_login) = lower($1) AND status = 'pending') AS pending`,
206
+ [login]
207
+ );
208
+ const r = rows[0] || {};
209
+ return { member: !!r.member, invited: !!r.invited, pending: !!r.pending };
210
+ }
211
+
186
212
  // --- Auth sessions (kernel: the builder_sessions token store auth.js drives) ----
187
213
 
188
214
  // SESSION_TTL_MS — session TTL on create/refresh. Centralized so the migration's
@@ -247,6 +273,7 @@ module.exports = {
247
273
  seatFirstAdminIfUnclaimed,
248
274
  getBuilderByGithubId,
249
275
  hasInvitedAccessRequest,
276
+ accessStateByLogin,
250
277
  createSession,
251
278
  lookupSession,
252
279
  };
package/src/bongos/db.js CHANGED
@@ -298,6 +298,15 @@ async function deleteGithubToken({ builderId, p = pool }) {
298
298
  // shipped." Pure decision helper is exported so tests/rank_gate.mjs can exercise
299
299
  // the threshold without a DB.
300
300
 
301
+ // REQUIRES_RANK_TIER — the canonical rank → tier (1-based) map: the rank "brain".
302
+ // MUST stay byte-identical to permission-path-check.js RANK_TIER and
303
+ // modules/lifecycle/db.js REQUIRES_RANK_TIER (tests/permission_path.mjs guards the
304
+ // drift; gate-review.js + diagram-facts.js parse this literal by regex, so keep it
305
+ // an inline object literal). Restores the binding the LIVE_RANK_LADDER carve
306
+ // (ADR 0102) left dangling — both singleRungPromotionError below and the
307
+ // db.REQUIRES_RANK_TIER export depend on it.
308
+ const REQUIRES_RANK_TIER = { xenos: 1, thetes: 2, metic: 3, archon: 4 };
309
+
301
310
  function singleRungPromotionError(oldRank, newRank) {
302
311
  const a = REQUIRES_RANK_TIER[oldRank];
303
312
  const b = REQUIRES_RANK_TIER[newRank];
@@ -330,6 +339,24 @@ function singleRungPromotionError(oldRank, newRank) {
330
339
  // has nothing to collide on, so it's returned unchanged. Pure + exported so
331
340
  // tests can exercise it without a DB; claimTask calls it inside the txn.
332
341
 
342
+ // syncGovernanceRankRole — keep governance_builder_roles in step with builders.rank
343
+ // (BV1.R95b, ADR 0151). Resolved through the `governance.roleSync` kernel port and
344
+ // run on the CALLER's transaction client, so the role rotation is atomic with the
345
+ // rank write beside it. Core never requires modules/governance/ (ADR 0083).
346
+ //
347
+ // A missing port means the governance module is disabled, and skipping is the
348
+ // fail-closed answer: the builder then holds no role and resolves to ZERO
349
+ // permissions. Admitting anything here would make disabling a module a privilege
350
+ // escalation — the same call requirePermission() makes on a missing port.
351
+ //
352
+ // TWINNED with modules/lifecycle/db.js (the boundary-forced _applyRankChangeTx
353
+ // duplication, ADR 0091 §2) — keep the two in sync.
354
+ async function syncGovernanceRankRole(client, builderId, rank) {
355
+ const roleSync = seams.resolveOptional('governance.roleSync');
356
+ if (!roleSync || typeof roleSync.syncRankRole !== 'function') return null;
357
+ return roleSync.syncRankRole(client, { builderId, rank });
358
+ }
359
+
333
360
  async function _applyRankChangeTx(client, { builderId, newRank, actorBuilderId, reasonNote = null, enforceSingleRungPromotion = false }) {
334
361
  const { rows } = await client.query(
335
362
  `SELECT id, rank, status FROM builders WHERE id = $1 FOR UPDATE`,
@@ -354,6 +381,17 @@ async function _applyRankChangeTx(client, { builderId, newRank, actorBuilderId,
354
381
  [builderId, newRank]
355
382
  );
356
383
 
384
+ // BV1.R95b — rotate the seeded rank-role IN THIS TRANSACTION. A DEMOTION removes
385
+ // the higher role rather than merely adding the lower one; without that, a demoted
386
+ // Archon keeps resolving Archon permissions (the ADR 0016 freshness promise broken
387
+ // one layer beneath the uncached resolver).
388
+ //
389
+ // Deliberately NOT wrapped in try/catch. If the rotation cannot be written the rank
390
+ // write must roll back with it: a committed rank change whose role rotation failed
391
+ // IS the privilege-retention bug, just with a smaller window. Refusing leaves the
392
+ // builder consistently at their OLD rank, which the actor can retry.
393
+ await syncGovernanceRankRole(client, builderId, newRank);
394
+
357
395
  // V3.R28 / #249 — ceremony delta. Promotion (moving UP the ladder = lower
358
396
  // index in RANKS) earns +5; demotion is audit-only (0).
359
397
  const oldIdx = RANKS.indexOf(oldRank);
@@ -960,6 +998,7 @@ module.exports = {
960
998
  SESSION_TTL_MS,
961
999
  LIVE_RANK_LADDER,
962
1000
  singleRungPromotionError,
1001
+ REQUIRES_RANK_TIER,
963
1002
  recentKarmaForBuilder,
964
1003
  karmaDelta30d,
965
1004
  insertAuditLog,
@@ -166,6 +166,18 @@ const MODULE_GLOBS = {
166
166
  // to the kernel key (02-module-map §5).
167
167
  'modules/security/',
168
168
  ],
169
+ // --- configurable roles & permissions: the permission resolver, catalog, and
170
+ // requirePermission gate (BV1 goal 1000030, ADR 0151). A core default:true
171
+ // module. `protected` DERIVES true — its 'modules/governance/' glob is in
172
+ // PROTECTED_GLOBS (the authz core) — so creating a goal scoped to governance is
173
+ // Archon-only (BV1.R57 / POST /goals). The config globs are the per-instance
174
+ // role templates (R96), listed here as goal territory; the module code lands in
175
+ // R93+ (the carve precedes the feature — ADR 0086 §4). Written as exact paths,
176
+ // not 'config/governance*.json' — the touches convention has no wildcards. ---
177
+ governance: [
178
+ 'modules/governance/',
179
+ 'config/governance.json', 'config/governance.neutral.json',
180
+ ],
169
181
  // --- the two web surfaces ---
170
182
  // BV1.R85: hall-UI graduated to modules/hall-ui/ — public-builders/ moved to
171
183
  // modules/hall-ui/public/ and is now a loader-discovered web-surface module
@@ -194,7 +206,21 @@ const MODULE_GLOBS = {
194
206
  // core/CLI-side under scripts/discord/.
195
207
  'scripts/discord/', 'modules/discord/',
196
208
  ],
197
- 'dev-box': ['modules/dev-box/', 'devbox-app/'],
209
+ // --- per-builder cloud dev boxes (DO droplets + Cloudflare tunnels), the sandbox
210
+ // preview, server-mediated branch publish. First module carved to modules/dev-box/
211
+ // (BV1.R43 — ADR 0083). Like the lifecycle/onboarding CLIs, the dev-box OPERATOR +
212
+ // connect commands travel with the domain but stay under scripts/gds/: box.js (the
213
+ // DO/CF provisioning control plane), box-auth-check, box-connect-lib, and the box
214
+ // smoke test. Listing them here so a `dev-box`-scoped goal's wall covers the box
215
+ // provisioning / bring-up work (BV1.R91, goal 1000051 C2), not just modules/dev-box/.
216
+ // The box BRING-UP scripts (infra/box-*.sh — box-source-fetch, heartbeat, terminal
217
+ // units) are INSTANCE-VENDORED (ADR 0144; not tracked in core), so they are not
218
+ // listed here yet — they come under this key when de-vendored (task 1002243).
219
+ 'dev-box': [
220
+ 'modules/dev-box/', 'devbox-app/',
221
+ 'scripts/gds/box.js', 'scripts/gds/box-auth-check.js', 'scripts/gds/box-connect-lib.js',
222
+ 'scripts/gds/smoke-box.sh',
223
+ ],
198
224
  };
199
225
 
200
226
  // A glob set is protected iff it overlaps the imported PROTECTED_GLOBS — the one
@@ -9,8 +9,8 @@
9
9
  // permission-sensitive path" a DETECTABLE, GATEABLE event — exactly the way
10
10
  // route-rank-check.js makes "an ungated write route" detectable.
11
11
  //
12
- // This module is PURE and dependency-free (no fs, no pg, no network) so it can
13
- // run in three places with identical logic:
12
+ // This module is PURE and dependency-free (no pg, no network, no require of any
13
+ // other repo module) so it can run in three places with identical logic:
14
14
  // 1. the LOCAL pre-push git hook (scripts/gds/hooks/pre-push.js) — fast,
15
15
  // cooperative, blocks the honest builder before the push leaves the box;
16
16
  // 2. the GRADER pre-pass (modules/grading/grader.js) — folds a finding into the panel
@@ -25,6 +25,9 @@
25
25
 
26
26
  'use strict';
27
27
 
28
+ const fs = require('node:fs');
29
+ const path = require('node:path');
30
+
28
31
  // Rank → tier. MUST mirror REQUIRES_RANK_TIER in src/bongos/db.js. We keep a LOCAL
29
32
  // copy (not an import) so this module stays dependency-free for the client-side
30
33
  // pre-push hook; tests/permission_path.mjs asserts the two never drift (the
@@ -32,87 +35,82 @@
32
35
  // reintroduce here).
33
36
  const RANK_TIER = { xenos: 1, thetes: 2, metic: 3, archon: 4 };
34
37
 
35
- // The floor: a builder must be at least this tier to touch a protected path.
36
- // metic(3) = the first "trusted working rank" (scope/trust grants begin here).
37
- // xenos(1) and thetes(2) are below it — sandbox + graduated-newcomer.
38
- const MIN_TIER = RANK_TIER.metic; // 3
39
-
40
- // Permission-sensitive paths. A diff from a sub-Metic builder that touches ANY
41
- // of these is a blocker. Glob rules (see globToRegExp): a trailing '/' matches
42
- // the whole subtree; '**' matches across '/'; '*' matches within one segment.
38
+ // ── the protected-surface REGISTRY (BV1.R101, ADR 0151 §4) ──────────────────
39
+ //
40
+ // GOVERNANCE OWNS THE DATA; THE SUBSTRATE STAYS THE ENFORCER. Which paths are
41
+ // protected, at what rank floor, mapped to which `system: true` catalog
42
+ // permission, is governance-owned data in
43
+ // modules/governance/protected-surfaces.json — no longer a constant baked into
44
+ // this file. Re-mapping a floor there is a POLICY edit. It cannot move
45
+ // enforcement into the app: the three layers above (git pre-push hook, grader
46
+ // pre-pass, post-push main audit) plus gate-review.js and the goal-scope check
47
+ // read the registry out of band — no DB, no server, no HTTP — and remain the
48
+ // authority (ADR 0043). Making the request path the enforcer is the exact
49
+ // regression this task exists to prevent.
50
+ //
51
+ // WHY A REPO FILE AND NOT A DB QUERY: the enforcers run where there is no
52
+ // database and no server (a git hook on a laptop, a CI job). The registry's
53
+ // canonical form must therefore be SYNCHRONOUSLY loadable from the checkout. A
54
+ // DB-backed override belongs to the governance tab, layered on top — never a
55
+ // dependency of the hook.
56
+ //
57
+ // WHY `fs` AND NOT `require`: ADR 0083's one-way rule forbids core from
58
+ // require-ing a MODULE's code (a module's behavior + enable/disable state must
59
+ // never be something the kernel depends on), and scripts/gds/fitness.js enforces
60
+ // that on `require` edges. This is not code — it is a static declarative data
61
+ // file that ships inside the core package next to this file (modules/ ships with
62
+ // @bongos/core, ADR 0108 §1). The sibling trust primitive route-rank-check.js
63
+ // already reads modules/*/routes/*.js off disk the same way, to rank-gate them.
64
+ // Code still flows ONE way only: modules/governance/protected-surfaces.js →
65
+ // src/module-api.js → this matcher. Nothing in core requires the module.
43
66
  //
44
- // Scope rationale: the rank/permission/ship-grade/deploy machinery, DB schema
45
- // migrations, the server route + middleware layer that carries the gates, and
46
- // the trust-boundary documentation. Game/art/UI/docs work — what newcomers
47
- // actually build — is deliberately NOT here. Metic+ is unaffected entirely.
48
- const PROTECTED_GLOBS = [
49
- // --- the rank ladder + authorization machinery ---
50
- 'src/bongos/auth.js', // requireBuilder / requireRank definitions
51
- 'src/bongos/db.js', // rank tiers, REQUIRES_RANK_TIER, the CASE niels edited
52
- 'src/bongos/route-rank-check.js', // the route-gate auditor
53
- 'src/bongos/permission-path-check.js',// THIS gate (self-protect)
54
- 'src/bongos/middleware/', // audit + any auth middleware
55
- 'src/bongos/routes/', // routes carry the requireRank gates
56
- 'modules/lifecycle/routes/', // BV1.R86: the lifecycle routes (tasks/claims/versions/goals/deps/done-when/gate-approvals) carved out of src/bongos/routes/ — the gates follow the routes
57
- 'src/bongos/routes.js', // #863-followup: the route MOUNT table (a new ungated route lands here)
58
- 'server.js', // #863-followup: the Express/Colyseus entrypoint — wires every router + host routing
59
- // --- rank-gated source/box access (the crown-jewels clone decision) ---
60
- // These moved into the dev-box module (BV1.R43, ADR 0083) but stay protected:
61
- // they decide who gets a full source clone + mint the credential, sensitive by
62
- // nature wherever they live. (routes/box.js's rank gates are covered separately
63
- // by the route-rank fitness check, which scans modules/*/routes/.)
64
- 'modules/dev-box/boxes.js', // #863-followup: boxScopeForRank / provision floor — decides who gets a full source clone
65
- 'modules/dev-box/box-access.js', // #863-followup: decideSourceAccess (rank → starter|full source)
66
- 'modules/dev-box/box-credential.js', // #863-followup: mints the box source credential
67
- // --- the ship / grade / deploy pipeline ---
68
- 'modules/grading/grader.js', // BV1.R73: grading carved to modules/grading/ — the protected glob follows the file
69
- 'modules/grading/grader-workers/', // #863-followup: the adversarial panel (Narc/Hacker/Quality/Efficiency) — blunting these blunts the gate
70
- 'modules/grading/grader-rubric.json', // task 1139 (ADR 0058): the grader's scoring rubric — blunting it blunts the grade gate
71
- 'scripts/gds/ship.js',
72
- 'scripts/gds/confirm.js',
73
- 'scripts/gds/merge-mode.js',
74
- 'scripts/gds/ci-grade.js', // task 1143 (ADR 0058): CI grade-gate driver — steers the grade check
75
- 'scripts/gds/grade-smoke.js', // task 1143 (ADR 0058): grade-smoke driver
76
- 'scripts/gds/conductor-dispatch.js',// task 1143 (ADR 0058): mid-flight grader dispatch
77
- 'scripts/gds/main-audit.js', // the post-push audit (self-protect)
78
- 'scripts/gds/push-main.js', // the sanctioned main-push (carries the override)
79
- 'scripts/gds/run-unit-tests.js', // task 1139 (ADR 0058): the unit-gate test SELECTOR — muting tests here blunts the CI gate
80
- 'scripts/gds/gate-review.js', // task 1139 (ADR 0058): the automatic gatekeeper itself (self-protect)
81
- 'scripts/gds/setup.js', // /builder-setup — installs the pre-push hook
82
- 'scripts/gds/install-git-hooks.js',// the hook installer (self-protect)
83
- 'scripts/gds/hooks/', // the git hooks themselves (self-protect)
84
- 'scripts/gds/cli-lib.js', // session/token + apiCall the gate relies on
85
- 'scripts/migrate.sh', // #863-followup: applies migrations on deploy
86
- 'scripts/deploy/', // task 1139 (ADR 0058): the deploy scripts run on the droplet at deploy
87
- // BV1.R86: the ship-pipeline libs carved to modules/lifecycle/ — the protected
88
- // globs follow the files (the grading-carve precedent above). github-push is the
89
- // server-mediated branch publish (ADR 0055); conflict-resolve/publish-reconciler
90
- // self-heal + land merges; merge-lock/ship-preflight serialize + preflight a ship.
91
- 'modules/lifecycle/merge-lock.js',
92
- 'modules/lifecycle/ship-preflight.js',
93
- 'modules/lifecycle/github-push.js',
94
- 'modules/lifecycle/conflict-resolve.js',
95
- 'modules/lifecycle/publish-reconciler.js',
96
- // --- schema: migrations are never newcomer work ---
97
- 'migrations/',
98
- // --- the deploy substrate ---
99
- 'infra/',
100
- '.github/', // #863-followup: CI workflows (deploy-prod.yml, grade gate, secrets-scan) are the pipeline
101
- 'CODEOWNERS', // task 1139 (ADR 0058): the code-owner gate list itself
102
- 'config/deploy.json', // task 1139 (ADR 0058): the laptop/ci/bypass deploy switch
103
- 'package.json', // #863-followup: npm scripts + prepare/lifecycle hooks run during install/CI
104
- 'package-lock.json', // task 1143 (ADR 0058): the resolved supply chain npm ci installs on the droplet
105
- // --- the hooks the gate is DELIVERED through (self-protect Layer 1) ---
106
- '.husky/', // #863-followup: pre-push (this gate) + pre-commit (secrets guard)
107
- // --- the trust-boundary documentation + methodology core ---
108
- 'docs/canonical-permissions.md',
109
- 'docs/adr/0016-trust-boundary-server-enforced-permissions.md',
110
- 'docs/adr/0043-git-ssh-trust-boundary-and-rank-floor-on-permission-paths.md', // #863-followup: this fix's own ADR
111
- 'CLAUDE.md',
112
- // --- local automation that could weaken a gate ---
113
- '.claude/hooks/',
114
- '.claude/settings.json',
115
- ];
67
+ // FAIL-CLOSED: a missing/empty/malformed registry THROWS. It never degrades to
68
+ // "nothing is protected" — an unreadable registry is a broken install, not an
69
+ // opted-out module.
70
+ //
71
+ // Resolved from __dirname (this file is <coreRoot>/src/bongos/) rather than via
72
+ // instance-config so the load keeps zero repo imports and works in a bare git
73
+ // hook.
74
+ const REGISTRY_PATH = path.join(__dirname, '..', '..', 'modules', 'governance', 'protected-surfaces.json');
75
+
76
+ function loadRegistry(file) {
77
+ let raw;
78
+ try {
79
+ raw = JSON.parse(fs.readFileSync(file, 'utf8'));
80
+ } catch (e) {
81
+ throw new Error(`protected-surface registry unreadable at ${file}: ${e.message}. Refusing to run with an empty protected set (ADR 0043).`);
82
+ }
83
+ const surfaces = Array.isArray(raw && raw.surfaces) ? raw.surfaces : null;
84
+ if (!surfaces || surfaces.length === 0) {
85
+ throw new Error(`protected-surface registry ${file} declares no surfaces. Refusing to run with an empty protected set (ADR 0043).`);
86
+ }
87
+ const defaultFloor = raw.floor;
88
+ const defaultPermission = raw.permission;
89
+ return Object.freeze(surfaces.map((s) => {
90
+ const glob = s && s.glob;
91
+ const floor = (s && s.floor) || defaultFloor;
92
+ const permission = (s && s.permission) || defaultPermission;
93
+ if (typeof glob !== 'string' || !glob) throw new Error(`protected-surface registry ${file}: a surface is missing 'glob'`);
94
+ if (!RANK_TIER[floor]) throw new Error(`protected-surface registry ${file}: surface "${glob}" has an unknown floor "${floor}"`);
95
+ if (typeof permission !== 'string' || !permission) throw new Error(`protected-surface registry ${file}: surface "${glob}" maps to no permission`);
96
+ return Object.freeze({ glob, floor, permission, group: (s && s.group) || null, why: (s && s.why) || null });
97
+ }));
98
+ }
99
+
100
+ // The registry as loaded: [{ glob, floor, permission, group, why }] in file order.
101
+ const PROTECTED_SURFACES = loadRegistry(REGISTRY_PATH);
102
+
103
+ // The glob list, DERIVED from the registry. Kept as an export because it is the
104
+ // long-standing single source every consumer already reads (module-scope-map,
105
+ // gate-review, publish-manifest, search-ingest-md, sequence) — those keep
106
+ // working unchanged and are now registry-backed by construction.
107
+ const PROTECTED_GLOBS = PROTECTED_SURFACES.map((s) => s.glob);
108
+
109
+ // The floor a builder must clear to touch a protected path, DERIVED from the
110
+ // registry (the lowest floor any surface declares — 'metic' today). Exported
111
+ // because main-audit.js and modules/lifecycle/routes/tasks.js gate on it; the
112
+ // per-surface floor below is what actually decides a hit.
113
+ const MIN_TIER = Math.min(...PROTECTED_SURFACES.map((s) => RANK_TIER[s.floor]));
116
114
 
117
115
  const SPECIALTY =
118
116
  'deterministic: a builder below Metic must not modify rank / permission / ship-grade / deploy / migration surfaces (ADR 0016, SEC #863)';
@@ -142,31 +140,54 @@ function escapeRe(s) {
142
140
  return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
143
141
  }
144
142
 
145
- const COMPILED = PROTECTED_GLOBS.map((g) => ({ glob: g, re: globToRegExp(g) }));
143
+ const COMPILED = PROTECTED_SURFACES.map((s) => ({ ...s, re: globToRegExp(s.glob) }));
146
144
 
147
145
  // Normalize a path the way git reports it (forward slashes, no leading ./).
148
146
  function normalize(p) {
149
147
  return String(p || '').replace(/\\/g, '/').replace(/^\.\//, '').trim();
150
148
  }
151
149
 
152
- // Which of `files` are permission-sensitive? Returns [{ file, glob }].
150
+ // Which of `files` are permission-sensitive? Returns
151
+ // [{ file, glob, floor, permission }] — floor + permission come straight from the
152
+ // registry entry that matched, so a governance re-map flows through every caller.
153
153
  function matchProtected(files) {
154
154
  const out = [];
155
155
  for (const raw of files || []) {
156
156
  const f = normalize(raw);
157
157
  if (!f) continue;
158
158
  const hit = COMPILED.find((c) => c.re.test(f));
159
- if (hit) out.push({ file: f, glob: hit.glob });
159
+ if (hit) out.push({ file: f, glob: hit.glob, floor: hit.floor, permission: hit.permission });
160
160
  }
161
161
  return out;
162
162
  }
163
163
 
164
+ // The registry entry governing `file`, or null when the path is unprotected.
165
+ function surfaceFor(file) {
166
+ const f = normalize(file);
167
+ const hit = f ? COMPILED.find((c) => c.re.test(f)) : null;
168
+ return hit ? { glob: hit.glob, floor: hit.floor, permission: hit.permission, group: hit.group, why: hit.why } : null;
169
+ }
170
+
171
+ // The rank floor governing `file` ('metic' today), or null when unprotected.
172
+ function floorFor(file) {
173
+ const s = surfaceFor(file);
174
+ return s ? s.floor : null;
175
+ }
176
+
177
+ // The system:true catalog permission `file` maps to, or null when unprotected.
178
+ function permissionFor(file) {
179
+ const s = surfaceFor(file);
180
+ return s ? s.permission : null;
181
+ }
182
+
164
183
  function tierOf(rank) {
165
184
  return RANK_TIER[rank] ?? null; // null = unknown rank
166
185
  }
167
186
 
168
- // Is this rank below the protected-path floor? Unknown ranks are treated as
169
- // NOT-below here (no finding) — see checkPermissionPaths for why unknown must
187
+ // Is this rank below the registry's LOWEST protected-path floor? A coarse
188
+ // "could this rank be blocked by anything at all" test — main-audit.js uses it
189
+ // after matchProtected has already established a hit. Unknown ranks are treated
190
+ // as NOT-below here (no finding) — see checkPermissionPaths for why unknown must
170
191
  // fail OPEN at the grader layer (the authoritative audit, layer 3, owns the
171
192
  // unknown/dishonest case and reads the real rank from the DB).
172
193
  function isBelowFloor(rank) {
@@ -184,21 +205,26 @@ function isBelowFloor(rank) {
184
205
  // the layer that fails-closed on the dishonest path.
185
206
  function checkPermissionPaths({ changedFiles, builderRank } = {}) {
186
207
  const protectedHits = matchProtected(changedFiles);
187
- const below = isBelowFloor(builderRank);
208
+ const tier = tierOf(builderRank);
188
209
  const findings = [];
189
- if (below && protectedHits.length > 0) {
190
- for (const h of protectedHits) {
191
- findings.push({
192
- severity: 'blocker',
193
- file: h.file,
194
- glob: h.glob,
195
- summary:
196
- `${h.file} is a permission-sensitive surface (matches \`${h.glob}\`). `
197
- + `Builder rank '${builderRank}' is below Metic and may not modify the rank / permission / `
198
- + `ship-grade / deploy / migration machinery (ADR 0016, SEC #863). `
199
- + `If this change is genuinely needed, an Archon must own it.`,
200
- });
201
- }
210
+ for (const h of protectedHits) {
211
+ // Decided against the hit's OWN registry floor, not a global constant — that
212
+ // is what makes "the governance tab can re-map the floor" real. Every surface
213
+ // floors at 'metic' today, so this is identical to the previous global test.
214
+ if (tier === null || tier >= RANK_TIER[h.floor]) continue;
215
+ const floorLabel = h.floor.charAt(0).toUpperCase() + h.floor.slice(1);
216
+ findings.push({
217
+ severity: 'blocker',
218
+ file: h.file,
219
+ glob: h.glob,
220
+ floor: h.floor,
221
+ permission: h.permission,
222
+ summary:
223
+ `${h.file} is a permission-sensitive surface (matches \`${h.glob}\`). `
224
+ + `Builder rank '${builderRank}' is below ${floorLabel} and may not modify the rank / permission / `
225
+ + `ship-grade / deploy / migration machinery (ADR 0016, SEC #863). `
226
+ + `If this change is genuinely needed, an Archon must own it.`,
227
+ });
202
228
  }
203
229
  return {
204
230
  ok: findings.length === 0,
@@ -215,8 +241,11 @@ function checkPermissionPaths({ changedFiles, builderRank } = {}) {
215
241
  function toWorkerResult(result) {
216
242
  const verdict = result.hardFail ? 'fail' : 'pass';
217
243
  const n = result.findings.length;
244
+ // Floor label comes from the findings (registry data), not a hardcoded 'Metic'.
245
+ const floor = n > 0 ? result.findings[0].floor : null;
246
+ const floorLabel = floor ? floor.charAt(0).toUpperCase() + floor.slice(1) : 'the floor';
218
247
  const header = verdict === 'fail'
219
- ? `Permission-Path — FAIL (rank '${result.builderRank}' below Metic touched ${n} protected path(s))`
248
+ ? `Permission-Path — FAIL (rank '${result.builderRank}' below ${floorLabel} touched ${n} protected path(s))`
220
249
  : `Permission-Path — PASS`;
221
250
  const body = result.findings.map((f) => ` [${f.severity}] ${f.file} — ${f.summary}`).join('\n');
222
251
  return {
@@ -238,9 +267,14 @@ module.exports = {
238
267
  RANK_TIER,
239
268
  MIN_TIER,
240
269
  PROTECTED_GLOBS,
270
+ PROTECTED_SURFACES,
271
+ REGISTRY_PATH,
241
272
  SPECIALTY,
242
273
  globToRegExp,
243
274
  matchProtected,
275
+ surfaceFor,
276
+ floorFor,
277
+ permissionFor,
244
278
  tierOf,
245
279
  isBelowFloor,
246
280
  checkPermissionPaths,
@@ -44,26 +44,61 @@ function loadFromEnv() {
44
44
  return null;
45
45
  }
46
46
 
47
- function loadFromGit() {
47
+ // The directory this file's package occupies: <root>/src/bongos -> <root>. In the core
48
+ // dev checkout that is the repo root; installed, it is node_modules/@bongos/core.
49
+ const PACKAGE_ROOT = path.resolve(__dirname, '..', '..');
50
+
51
+ // Same directory? Normalizes separator style (git prints POSIX separators even on
52
+ // Windows) and surrounding whitespace, and matches the platform's case rules.
53
+ function sameDir(a, b) {
54
+ if (!a || !b) return false;
55
+ const norm = (p) => {
56
+ const abs = path.resolve(String(p).trim());
57
+ return process.platform === 'win32' ? abs.toLowerCase() : abs;
58
+ };
59
+ return norm(a) === norm(b);
60
+ }
61
+
62
+ // Resolve the remote ONLY when this package IS the git repo, never a repo found by
63
+ // walking up out of it.
64
+ //
65
+ // git discovery searches ANCESTORS of cwd. In the core dev checkout PACKAGE_ROOT is the
66
+ // repo root, so `git remote get-url origin` answers correctly. But on a live instance
67
+ // (ADR 0140) the hub runs from node_modules/@bongos/core, which is not a repo — so git
68
+ // climbed out of the package, found the enclosing INSTANCE repo, and returned its origin.
69
+ // That silently bound a DIFFERENT repo than the one the core publishes to, so every
70
+ // publish call 404'd against a repo the builder had no access to.
71
+ //
72
+ // Anchor the answer: ask git for its toplevel first and require it to EQUAL the directory
73
+ // we probed. If it differs, the repo is someone else's — return null so loadRepoInfo falls
74
+ // through to the env pin (<PREFIX>_GITHUB_OWNER/_REPO) or to an explicit no_repo_info. An
75
+ // error is strictly better than a wrong answer here: a misbind produces confident 404s
76
+ // that are hard to trace, while the error is printed verbatim by the publish-reconciler's
77
+ // boot line and names its own cure.
78
+ function loadFromGit(deps = {}) {
79
+ const exec = deps.execSync || execSync;
80
+ const root = deps.packageRoot || PACKAGE_ROOT;
81
+ const opts = { cwd: root, encoding: 'utf8', timeout: 2000, stdio: ['ignore', 'pipe', 'pipe'] };
48
82
  try {
49
- const remote = execSync('git remote get-url origin', {
50
- cwd: path.resolve(__dirname, '..', '..'),
51
- encoding: 'utf8',
52
- timeout: 2000,
53
- stdio: ['ignore', 'pipe', 'pipe'],
54
- });
55
- return parseRemote(remote);
83
+ const toplevel = exec('git rev-parse --show-toplevel', opts);
84
+ if (!sameDir(toplevel, root)) return null;
85
+ return parseRemote(exec('git remote get-url origin', opts));
56
86
  } catch (_) {
57
87
  return null;
58
88
  }
59
89
  }
60
90
 
61
- function loadRepoInfo() {
91
+ // `deps` is the test seam (execSync / packageRoot); production calls this with no args.
92
+ function loadRepoInfo(deps = {}) {
62
93
  if (cached) return cached;
63
94
  const branch = ic.resolveEnv('GITHUB_BRANCH') || 'main';
64
- const parsed = loadFromEnv() || loadFromGit();
95
+ const parsed = loadFromEnv() || loadFromGit(deps);
65
96
  if (!parsed) {
66
- cached = { error: 'no_repo_info', detail: 'git remote origin not parseable and <PREFIX>_GITHUB_OWNER/REPO unset' };
97
+ cached = {
98
+ error: 'no_repo_info',
99
+ detail:
100
+ 'git remote origin not resolvable from this package (an installed core does not sit inside the repo it publishes to) and <PREFIX>_GITHUB_OWNER/REPO unset',
101
+ };
67
102
  return cached;
68
103
  }
69
104
  const base = `https://github.com/${parsed.owner}/${parsed.repo}`;
@@ -87,5 +122,6 @@ function _resetCacheForTests() {
87
122
  module.exports = {
88
123
  loadRepoInfo,
89
124
  parseRemote,
125
+ loadFromGit, // exported for unit tests — the anchor rule is the whole fix
90
126
  _resetCacheForTests,
91
127
  };