@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
@@ -0,0 +1,125 @@
1
+ 'use strict';
2
+
3
+ // modules/governance/db.js — the governance db slice (ADR 0151, BV1.R95).
4
+ //
5
+ // Reads the role/grant/assignment rows governance_001_tables.sql defines. The
6
+ // per-domain db-slice pattern (ADR 0091 §2): the pool comes through the published
7
+ // doorway (src/module-api.js), never `../../src/bongos/db`.
8
+ //
9
+ // NO CACHING, deliberately. Every read here runs against the live DB on the
10
+ // request that needs it, so a revoked role takes effect on the builder's very
11
+ // next request — the ADR 0016 trust-boundary rule the resolver inherits.
12
+
13
+ const { pool } = require('../../src/module-api');
14
+ const { planRankRoleSync } = require('./rank-roles');
15
+
16
+ // Every permission_key granted to any role the builder holds. Duplicates across
17
+ // roles are collapsed by the DISTINCT — the resolver's UNION semantics. Returns
18
+ // a flat array of raw key strings (validated against the catalog by resolver.js);
19
+ // [] for a builder with no roles, which fails closed to zero authority.
20
+ async function getPermissionKeysForBuilder(builderId) {
21
+ const { rows } = await pool.query(
22
+ `SELECT DISTINCT rp.permission_key
23
+ FROM governance_builder_roles br
24
+ JOIN governance_role_permissions rp ON rp.role_key = br.role_key
25
+ WHERE br.builder_id = $1`,
26
+ [builderId],
27
+ );
28
+ return rows.map((r) => r.permission_key);
29
+ }
30
+
31
+ // The role keys a builder holds, for introspection surfaces (the Archon tab,
32
+ // R110/R111) and for explaining a 403. Not part of the resolve path.
33
+ async function getRoleKeysForBuilder(builderId) {
34
+ const { rows } = await pool.query(
35
+ `SELECT role_key FROM governance_builder_roles WHERE builder_id = $1 ORDER BY role_key`,
36
+ [builderId],
37
+ );
38
+ return rows.map((r) => r.role_key);
39
+ }
40
+
41
+ // --- the rank→role WRITE half (BV1.R95b) ------------------------------------
42
+ //
43
+ // Everything above this line only READS. Until R95b the only write to
44
+ // governance_builder_roles in the whole repo was governance_002's run-once
45
+ // backfill, so a builder created after it had NO role (locked out of every
46
+ // permission gate) and a builder whose rank CHANGED kept their OLD rank-role
47
+ // (a demoted Archon still resolving Archon permissions — the ADR 0016 defect).
48
+ //
49
+ // The writer below is a TRANSACTION PARTICIPANT: every caller hands it the client
50
+ // its own BEGIN/COMMIT is running on, so the role rotation commits — or rolls back
51
+ // — atomically with the rank write it accompanies. Core reaches it through the
52
+ // `governance.roleSync` kernel port (routes/role-sync.js); it never imports this
53
+ // module (ADR 0083).
54
+
55
+ // The seeded rank-roles as the DB actually defines them. Read per call rather than
56
+ // cached: it is the same authority data the resolver refuses to memoise, and this
57
+ // runs on rank writes + sign-in, never in a hot loop.
58
+ async function getSeededRankRoles(q) {
59
+ const { rows } = await q.query(
60
+ `SELECT role_key, seeded_rank, is_system
61
+ FROM governance_roles
62
+ WHERE is_system = true AND seeded_rank IS NOT NULL`,
63
+ );
64
+ return rows;
65
+ }
66
+
67
+ // A builder's assignments joined to their role definitions, so the plan can tell a
68
+ // seeded rank-role from an Archon-assigned custom one.
69
+ async function getRoleAssignmentsForBuilder(q, builderId) {
70
+ const { rows } = await q.query(
71
+ `SELECT br.role_key, r.is_system, r.seeded_rank
72
+ FROM governance_builder_roles br
73
+ JOIN governance_roles r ON r.role_key = br.role_key
74
+ WHERE br.builder_id = $1`,
75
+ [builderId],
76
+ );
77
+ return rows;
78
+ }
79
+
80
+ // syncRankRole(client, { builderId, rank }) — make the builder's seeded rank-role
81
+ // match `rank`: add the matching one, DELETE every other seeded one, leave custom
82
+ // roles alone. `client` is the caller's open transaction client; pass null/undefined
83
+ // to run on the pool (the sign-in path, which has no surrounding transaction).
84
+ //
85
+ // Idempotent — a builder whose role already matches costs two SELECTs and no write,
86
+ // so calling it on every sign-in quietly repairs drift instead of accumulating it.
87
+ // The DELETE is keyed on the plan's `remove` list, which is built only from rows the
88
+ // join proved are is_system seeded roles, so a custom role can never be caught by it.
89
+ // assigned_by is left NULL: a rank-derived assignment is the system's, not a person's
90
+ // (governance_002's backfill uses the same convention; the human decision is audited
91
+ // in credit_log's rank_change row).
92
+ async function syncRankRole(client, { builderId, rank } = {}) {
93
+ if (builderId === null || builderId === undefined || builderId === '') {
94
+ return { target: null, added: [], removed: [] };
95
+ }
96
+ const q = client || pool;
97
+ const seededRoles = await getSeededRankRoles(q);
98
+ const currentRoles = await getRoleAssignmentsForBuilder(q, builderId);
99
+ const plan = planRankRoleSync({ rank, currentRoles, seededRoles });
100
+
101
+ if (plan.remove.length > 0) {
102
+ await q.query(
103
+ `DELETE FROM governance_builder_roles
104
+ WHERE builder_id = $1 AND role_key = ANY($2::text[])`,
105
+ [builderId, [...plan.remove]],
106
+ );
107
+ }
108
+ if (plan.add.length > 0) {
109
+ await q.query(
110
+ `INSERT INTO governance_builder_roles (builder_id, role_key)
111
+ SELECT $1, unnest($2::text[])
112
+ ON CONFLICT (builder_id, role_key) DO NOTHING`,
113
+ [builderId, [...plan.add]],
114
+ );
115
+ }
116
+ return { target: plan.target, added: [...plan.add], removed: [...plan.remove] };
117
+ }
118
+
119
+ module.exports = {
120
+ getPermissionKeysForBuilder,
121
+ getRoleKeysForBuilder,
122
+ getSeededRankRoles,
123
+ getRoleAssignmentsForBuilder,
124
+ syncRankRole,
125
+ };
@@ -0,0 +1,81 @@
1
+ -- governance_001_tables.sql — the `governance` module's DB slice (ADR 0151, BV1.R93).
2
+ --
3
+ -- The live home for configurable roles/permissions: assignable roles, the role→
4
+ -- permission grants, and builder→role assignments. This is the DB half of the
5
+ -- permissions-as-atom model; the permission CATALOG itself is CODE
6
+ -- (modules/governance/catalog.js — a permission maps to a real route, so it is not
7
+ -- UI-invented data), so `permission_key` here is a string validated app-side against
8
+ -- the catalog, deliberately NOT a foreign key to a permissions table.
9
+ --
10
+ -- Preserves ADR 0016: authority is still resolved server-side, per request, uncached
11
+ -- (R95). These tables only hold the role definitions + assignments the resolver unions;
12
+ -- they grant nothing on their own.
13
+ --
14
+ -- Module migrations are ADDITIVE and NAMESPACED (ADR 0083 §Decision #5): every object
15
+ -- is governance_-prefixed, there is no down-migration, and the filename is
16
+ -- governance_-prefixed (ADR 0108 §5) so its stem never collides across repos.
17
+ -- `scripts/migrate.sh` discovers + applies this when the governance module is enabled
18
+ -- (default:true). Idempotent: safe to re-run.
19
+
20
+ BEGIN;
21
+
22
+ -- ---------------------------------------------------------------------------
23
+ -- governance_roles — an assignable role. Four are seeded as the compat rank-roles
24
+ -- (is_system=true, seeded_rank set); the rest are Archon-authored custom roles.
25
+ -- role_key is the stable natural key the other two tables reference.
26
+ -- ---------------------------------------------------------------------------
27
+ CREATE TABLE IF NOT EXISTS governance_roles (
28
+ id bigserial PRIMARY KEY,
29
+ -- stable slug: 'xenos'/'thetes'/'metic'/'archon' for the seeded rank-roles, or a
30
+ -- kebab-case slug for an Archon-created custom role.
31
+ role_key text NOT NULL UNIQUE,
32
+ label text NOT NULL,
33
+ description text,
34
+ -- for a seeded rank-role: the rank it reproduces (xenos/thetes/metic/archon);
35
+ -- NULL for a custom role. Kept so the resolver + tab can recognise the four
36
+ -- protected rank-roles (they cannot be deleted/renamed via the tab).
37
+ seeded_rank text,
38
+ -- true for the four seeded rank-roles — a system role the tab renders but cannot
39
+ -- delete/rename. Custom roles are false.
40
+ is_system boolean NOT NULL DEFAULT false,
41
+ -- who authored a custom role (NULL for the seeded rank-roles).
42
+ created_by bigint REFERENCES builders(id) ON DELETE SET NULL,
43
+ created_at timestamptz NOT NULL DEFAULT now(),
44
+ updated_at timestamptz NOT NULL DEFAULT now()
45
+ );
46
+
47
+ -- ---------------------------------------------------------------------------
48
+ -- governance_role_permissions — the grants: which permission KEYS a role holds.
49
+ -- permission_key is a catalog key (modules/governance/catalog.js), validated
50
+ -- app-side (NOT a FK — the catalog is code, not a table). The governance API (R110)
51
+ -- refuses to insert a `system` permission for a custom role; the seeded rank-roles
52
+ -- legitimately hold their archon-floored system perms.
53
+ -- ---------------------------------------------------------------------------
54
+ CREATE TABLE IF NOT EXISTS governance_role_permissions (
55
+ role_key text NOT NULL REFERENCES governance_roles(role_key) ON DELETE CASCADE,
56
+ permission_key text NOT NULL,
57
+ granted_at timestamptz NOT NULL DEFAULT now(),
58
+ PRIMARY KEY (role_key, permission_key)
59
+ );
60
+
61
+ CREATE INDEX IF NOT EXISTS governance_role_permissions_perm_idx
62
+ ON governance_role_permissions (permission_key);
63
+
64
+ -- ---------------------------------------------------------------------------
65
+ -- governance_builder_roles — builder→role assignments. The resolver (R95) unions
66
+ -- the permissions of every role a builder holds. The compat seed backfills exactly
67
+ -- one role per builder (their rank-role) so day-one behaviour is identical.
68
+ -- ---------------------------------------------------------------------------
69
+ CREATE TABLE IF NOT EXISTS governance_builder_roles (
70
+ builder_id bigint NOT NULL REFERENCES builders(id) ON DELETE CASCADE,
71
+ role_key text NOT NULL REFERENCES governance_roles(role_key) ON DELETE CASCADE,
72
+ -- who assigned this role (NULL for the compat-seed backfill).
73
+ assigned_by bigint REFERENCES builders(id) ON DELETE SET NULL,
74
+ assigned_at timestamptz NOT NULL DEFAULT now(),
75
+ PRIMARY KEY (builder_id, role_key)
76
+ );
77
+
78
+ CREATE INDEX IF NOT EXISTS governance_builder_roles_role_idx
79
+ ON governance_builder_roles (role_key);
80
+
81
+ COMMIT;
@@ -0,0 +1,103 @@
1
+ -- governance_002_seed_and_backfill.sql — the compatibility seed (ADR 0151, BV1.R93).
2
+ --
3
+ -- Seeds the four rank-roles (xenos/thetes/metic/archon) with the grants that
4
+ -- reproduce today's rank thresholds EXACTLY, then backfills every existing builder
5
+ -- the role matching their current rank. This is the "zero day-one behavior change"
6
+ -- guarantee: after this runs, resolveBuilderPermissions() (R95) returns, for every
7
+ -- builder, precisely the powers requireRank granted them before.
8
+ --
9
+ -- The role→permission grants below are MATERIALISED from the code catalog's
10
+ -- RANK_ROLE_SEED (modules/governance/catalog.js), which derives them from each
11
+ -- permission's floor (cumulative nested supersets). tests/governance_seed.mjs parses
12
+ -- this file and asserts the four arrays equal RANK_ROLE_SEED, so this snapshot can
13
+ -- never silently drift from the catalog (the "no second list that drifts" discipline,
14
+ -- SEC #863 / R108). A NEW permission granted to a seeded role is a NEW migration, not
15
+ -- an edit here (migrations are run-once).
16
+ --
17
+ -- Idempotent (ON CONFLICT DO NOTHING throughout): safe to re-run.
18
+
19
+ BEGIN;
20
+
21
+ -- ── the four seeded rank-roles ────────────────────────────────────────────────
22
+ -- Labels are the canonical rank names (portable core); an instance may surface its
23
+ -- own display labels via config/governance.json (R96) without changing these keys.
24
+ INSERT INTO governance_roles (role_key, label, description, seeded_rank, is_system) VALUES
25
+ ('xenos', 'Xenos', 'Seeded rank-role — the sandboxed entry rank. Own-work + public read.', 'xenos', true),
26
+ ('thetes', 'Thetes', 'Seeded rank-role — graduated newcomer. Adds the general claim queue.', 'thetes', true),
27
+ ('metic', 'Metic', 'Seeded rank-role — the trusted working rank. Adds review/triage/authoring.', 'metic', true),
28
+ ('archon', 'Archon', 'Seeded rank-role — top authority. Adds the admin + system (non-grantable) powers.', 'archon', true)
29
+ ON CONFLICT (role_key) DO NOTHING;
30
+
31
+ -- ── the grants (materialised from RANK_ROLE_SEED; guarded by tests/governance_seed.mjs) ──
32
+
33
+ -- xenos (Xenos) — 12 permissions
34
+ INSERT INTO governance_role_permissions (role_key, permission_key)
35
+ SELECT 'xenos', unnest(ARRAY[
36
+ 'task.claim.newcomer', 'task.ship', 'task.act.own', 'box.manage.own',
37
+ 'memory.manage.own', 'credential.manage.own', 'me.wandering.set', 'cost.log',
38
+ 'learning.create', 'blocker.file', 'idea.file', 'page.view.public'
39
+ ])
40
+ ON CONFLICT (role_key, permission_key) DO NOTHING;
41
+
42
+ -- thetes (Thetes) — 14 permissions
43
+ INSERT INTO governance_role_permissions (role_key, permission_key)
44
+ SELECT 'thetes', unnest(ARRAY[
45
+ 'task.claim.any', 'page.view.builder', 'task.claim.newcomer', 'task.ship',
46
+ 'task.act.own', 'box.manage.own', 'memory.manage.own', 'credential.manage.own',
47
+ 'me.wandering.set', 'cost.log', 'learning.create', 'blocker.file',
48
+ 'idea.file', 'page.view.public'
49
+ ])
50
+ ON CONFLICT (role_key, permission_key) DO NOTHING;
51
+
52
+ -- metic (Metic) — 38 permissions
53
+ INSERT INTO governance_role_permissions (role_key, permission_key)
54
+ SELECT 'metic', unnest(ARRAY[
55
+ 'idea.triage', 'blocker.review', 'task.create', 'task.edit',
56
+ 'task.promote', 'task.demote', 'task.vote', 'task.dependency.manage',
57
+ 'task.criterion.manage', 'criterion.satisfy', 'criterion.review', 'planning.run',
58
+ 'priority.run', 'goal.create', 'goal.archive', 'goal.scope.widen',
59
+ 'module.enable', 'module.submit', 'security.report.file', 'llm_cache.use',
60
+ 'backup.manage', 'source.clone.local', 'box.source.full', 'task.claim.any',
61
+ 'page.view.builder', 'goal.manage.own', 'task.claim.newcomer', 'task.ship',
62
+ 'task.act.own', 'box.manage.own', 'memory.manage.own', 'credential.manage.own',
63
+ 'me.wandering.set', 'cost.log', 'learning.create', 'blocker.file',
64
+ 'idea.file', 'page.view.public'
65
+ ])
66
+ ON CONFLICT (role_key, permission_key) DO NOTHING;
67
+
68
+ -- archon (Archon) — 71 permissions
69
+ INSERT INTO governance_role_permissions (role_key, permission_key)
70
+ SELECT 'archon', unnest(ARRAY[
71
+ 'builder.rank.set', 'version.close', 'task.confirm.grade_bypass', 'dependency.cross_tier.manage',
72
+ 'goal.scope.manage', 'governance.manage', 'gate_approval.decide', 'deploy.push_main',
73
+ 'path.protected.modify', 'infra.ssh', 'builder.status.set', 'builder.budget.set',
74
+ 'task.abandon', 'criterion.unsatisfy', 'auth.session.revoke', 'access_request.review',
75
+ 'security.report.adjudicate', 'provisioning.fleet.manage', 'box.fleet.manage', 'autonomy.precheck',
76
+ 'discord.channels.manage', 'project.curate', 'override_request.decide', 'claim.release.any',
77
+ 'idea.ratify', 'task.newcomer_restock', 'task.peer_votes.tally', 'goal.reorder',
78
+ 'builder.roster.read', 'session.search', 'memory.read.any', 'audit_log.read',
79
+ 'idea.triage', 'blocker.review', 'task.create', 'task.edit',
80
+ 'task.promote', 'task.demote', 'task.vote', 'task.dependency.manage',
81
+ 'task.criterion.manage', 'criterion.satisfy', 'criterion.review', 'planning.run',
82
+ 'priority.run', 'goal.create', 'goal.archive', 'goal.scope.widen',
83
+ 'module.enable', 'module.submit', 'security.report.file', 'llm_cache.use',
84
+ 'backup.manage', 'source.clone.local', 'box.source.full', 'task.claim.any',
85
+ 'page.view.builder', 'goal.manage.own', 'task.claim.newcomer', 'task.ship',
86
+ 'task.act.own', 'box.manage.own', 'memory.manage.own', 'credential.manage.own',
87
+ 'me.wandering.set', 'cost.log', 'learning.create', 'blocker.file',
88
+ 'idea.file', 'page.view.public', 'page.view.governance'
89
+ ])
90
+ ON CONFLICT (role_key, permission_key) DO NOTHING;
91
+
92
+ -- ── backfill: every builder gets the role matching their current rank ─────────
93
+ -- The JOIN admits only builders whose rank IS one of the four seeded rank-roles.
94
+ -- A builder on a reserved/unwired rank (none exist today — verified) gets NO row and
95
+ -- resolves to zero permissions: the deliberate fail-closed divergence of ADR 0151
96
+ -- (stricter than today's latent threshold-admit of an unwired rank).
97
+ INSERT INTO governance_builder_roles (builder_id, role_key)
98
+ SELECT b.id, b.rank
99
+ FROM builders b
100
+ JOIN governance_roles r ON r.role_key = b.rank AND r.is_system = true
101
+ ON CONFLICT (builder_id, role_key) DO NOTHING;
102
+
103
+ COMMIT;
@@ -0,0 +1,28 @@
1
+ -- governance_003_task_abandon_metic.sql — grant `task.abandon` to the Metic rank-role
2
+ -- (ADR 0152; task 1002476).
3
+ --
4
+ -- The abandon route was re-ranked Archon→Metic+ in the code gate, and the catalog's
5
+ -- floor for `task.abandon` moved archon→metic to match. The compat seed
6
+ -- (governance_002) is RUN-ONCE, so a floor change cannot be made by editing it —
7
+ -- every already-migrated database would keep the old grant. This is the follow-on
8
+ -- grant migration the 002 header calls for ("a NEW permission granted to a seeded
9
+ -- role is a NEW migration, not an edit here").
10
+ --
11
+ -- Additive only: `archon` already holds `task.abandon` (the seeded roles are
12
+ -- cumulative supersets), so lowering the floor adds exactly one grant — metic —
13
+ -- and revokes nothing. Idempotent (ON CONFLICT DO NOTHING); forward-safe (INSERT
14
+ -- only, no dropped or retyped state).
15
+ --
16
+ -- The drift guard (tests/governance_seed.mjs) reads the UNION of the seed and every
17
+ -- later governance_* grant migration, so this file is what keeps the SQL snapshot
18
+ -- equal to catalog.js's floor-derived RANK_ROLE_SEED.
19
+
20
+ BEGIN;
21
+
22
+ INSERT INTO governance_role_permissions (role_key, permission_key)
23
+ SELECT 'metic', unnest(ARRAY[
24
+ 'task.abandon'
25
+ ])
26
+ ON CONFLICT (role_key, permission_key) DO NOTHING;
27
+
28
+ COMMIT;
@@ -0,0 +1,39 @@
1
+ -- governance_004_criterion_create.sql — grant the new `criterion.create` atom to the
2
+ -- Metic and Archon rank-roles (ADR 0154; task 1002538).
3
+ --
4
+ -- `POST /versions/:id/done-when` was re-gated Archon→Metic+ (walled by goal
5
+ -- ownership: a non-Archon must name a goal they own or manage, on a building
6
+ -- version). In catalog.js the route moved OUT of the bundled `criterion.review`
7
+ -- row — where it had been mis-filed while it was still Archon-only (ADR 0154 §5)
8
+ -- — and into its own atom, `criterion.create`, floor `metic`.
9
+ --
10
+ -- Unlike governance_003 (a floor MOVE, where archon already held the key), this is
11
+ -- a BRAND-NEW key: no role holds it yet, so both rungs the floor implies are
12
+ -- granted here. RANK_ROLE_SEED is a cumulative superset — floor `metic` means
13
+ -- {metic, archon} — and the drift guard (tests/governance_seed.mjs) compares the
14
+ -- UNION of the run-once seed (governance_002) plus every later grant migration
15
+ -- against that floor-derived seed, per role.
16
+ --
17
+ -- Additive only: one new permission key granted to two roles; nothing is revoked.
18
+ -- (`criterion.review` keeps its key and its floor — only its human `guards` note
19
+ -- lost the route.) Idempotent (ON CONFLICT DO NOTHING); forward-safe (INSERT only).
20
+ --
21
+ -- One block PER ROLE, in the governance_003 shape: the drift guard scans for
22
+ -- `SELECT '<role>', unnest(ARRAY[…])` literally, so a combined
23
+ -- `unnest(ARRAY['metic','archon']), 'key'` form parses as zero grants and reds it.
24
+
25
+ BEGIN;
26
+
27
+ INSERT INTO governance_role_permissions (role_key, permission_key)
28
+ SELECT 'metic', unnest(ARRAY[
29
+ 'criterion.create'
30
+ ])
31
+ ON CONFLICT (role_key, permission_key) DO NOTHING;
32
+
33
+ INSERT INTO governance_role_permissions (role_key, permission_key)
34
+ SELECT 'archon', unnest(ARRAY[
35
+ 'criterion.create'
36
+ ])
37
+ ON CONFLICT (role_key, permission_key) DO NOTHING;
38
+
39
+ COMMIT;
@@ -0,0 +1,56 @@
1
+ -- governance_005_resync_builder_rank_roles.sql — repair drifted builder→role
2
+ -- assignments (ADR 0151, BV1.R95b).
3
+ --
4
+ -- governance_002 backfilled every builder the seeded role matching their rank, but
5
+ -- it is run-once and nothing kept it true afterwards: the only write to
6
+ -- governance_builder_roles in the repo WAS that backfill. So by now the table can
7
+ -- hold two kinds of wrong row:
8
+ -- • MISSING — a builder admitted after 002 ran has no role at all, and
9
+ -- resolveBuilderPermissions() returns the empty set (denied everywhere).
10
+ -- • STALE — a builder whose rank CHANGED still holds their OLD rank-role. A
11
+ -- demoted Archon keeps resolving Archon permissions, which defeats ADR 0016's
12
+ -- "a demotion takes effect on the very next request" promise one layer beneath
13
+ -- the (correctly uncached) resolver. This is the security half.
14
+ --
15
+ -- BV1.R95b closes the hole going forward (the two _applyRankChangeTx twins rotate
16
+ -- the role inside the rank transaction, and sign-in assigns it at registration);
17
+ -- this migration repairs the rows already written.
18
+ --
19
+ -- IDEMPOTENT: the DELETE only matches rows that disagree with builders.rank and the
20
+ -- INSERT is ON CONFLICT DO NOTHING, so a second run changes nothing.
21
+ --
22
+ -- ADDITIVE per ADR 0083 §Decision #5: no DDL at all, no down path, every object it
23
+ -- names is governance_-prefixed or the core `builders` table it reads. It touches
24
+ -- governance_builder_roles (ASSIGNMENTS) and NOT governance_role_permissions
25
+ -- (GRANTS) — so it revokes no grant and the union model tests/governance_seed.mjs
26
+ -- builds over the grant migrations is untouched.
27
+
28
+ BEGIN;
29
+
30
+ -- 1. Drop every SEEDED rank-role a builder holds that no longer matches their rank.
31
+ -- Keyed on is_system + seeded_rank, so an Archon-assigned CUSTOM role
32
+ -- (is_system=false) is never caught by it — only the rank-derived role rotates.
33
+ -- This clause is the security fix: an additive-only repair would leave the
34
+ -- demoted Archon's `archon` row in place, preserving exactly the privilege
35
+ -- retention this migration exists to end.
36
+ DELETE FROM governance_builder_roles br
37
+ USING governance_roles r, builders b
38
+ WHERE r.role_key = br.role_key
39
+ AND b.id = br.builder_id
40
+ AND r.is_system = true
41
+ AND r.seeded_rank IS NOT NULL
42
+ AND r.seeded_rank IS DISTINCT FROM b.rank;
43
+
44
+ -- 2. (Re)assign the seeded rank-role matching each builder's CURRENT rank.
45
+ -- The INNER JOIN is governance_002's, deliberately: a builder sitting on one of
46
+ -- the twelve legacy ranks migration 023's CHECK still admits matches no seeded
47
+ -- role, so they get NO row and resolve to zero permissions. That is ADR 0151's
48
+ -- intentional fail-closed divergence (stricter than the old latent
49
+ -- threshold-admit of an unwired rank) — preserve it; do not invent a grant here.
50
+ INSERT INTO governance_builder_roles (builder_id, role_key)
51
+ SELECT b.id, r.role_key
52
+ FROM builders b
53
+ JOIN governance_roles r ON r.seeded_rank = b.rank AND r.is_system = true
54
+ ON CONFLICT (builder_id, role_key) DO NOTHING;
55
+
56
+ COMMIT;
@@ -0,0 +1,10 @@
1
+ {
2
+ "key": "governance",
3
+ "title": "Governance (configurable roles & permissions)",
4
+ "description": "Discord-style configurable roles and permissions (ADR 0151, BONGOS-V1 goal 1000030). Permissions are the atomic authority unit and the four ranks (xenos/thetes/metic/archon) become seeded roles; this module owns the code-defined permission catalog (each key flagged `system` = trust-boundary, non-grantable, or operational = delegable) plus the compatibility seed that reproduces today's rank thresholds exactly. A core-domain module, default-on: a Cloud Bongos instance ships configurable roles the way it ships its status page. This first slice (BV1.R94) is the catalog + seed as code constants — the single source the resolver (R95), the requirePermission gate (R97), and the Archon tab (R110/R111) all read. Preserves ADR 0016 (authority resolved server-side, per request, uncached).",
5
+ "coreVersion": "^1.18.0",
6
+ "default": true,
7
+ "contributes": { "migrations": true, "routes": ["governance", "role-sync"] },
8
+ "provides": ["governance", "governance.roleSync"],
9
+ "consumes": []
10
+ }
@@ -0,0 +1,88 @@
1
+ 'use strict';
2
+
3
+ // modules/governance/protected-surfaces.js — the protected-surface registry's
4
+ // GOVERNANCE-side API (ADR 0151 §4, BV1.R101).
5
+ //
6
+ // POLICY OWNS / SUBSTRATE ENFORCES — the whole point of this slice.
7
+ //
8
+ // GOVERNANCE OWNS THE DATA. protected-surfaces.json (next to this file) says
9
+ // WHICH repo paths are protected, at WHAT rank floor, mapped to WHICH
10
+ // `system: true` catalog permission. That file is the source of truth; the
11
+ // Archon governance tab (R110/R111) edits it as policy. Before R101 the same
12
+ // information was a hardcoded PROTECTED_GLOBS constant inside the kernel.
13
+ //
14
+ // THE OUT-OF-BAND SUBSTRATE STAYS THE ENFORCER. Nothing here enforces
15
+ // anything. The enforcers are the git pre-push hook, the grader pre-pass, the
16
+ // post-push main audit, gate-review.js and the goal-scope check — they read
17
+ // the registry with no DB, no server and no HTTP, and they are what actually
18
+ // blocks a sub-floor change (ADR 0043). Re-mapping a floor here changes what
19
+ // the substrate protects; it can never make the request path the authority.
20
+ // Moving enforcement into the app is the regression R101 exists to prevent
21
+ // (ADR 0151 §"Alternatives considered").
22
+ //
23
+ // NO DB DEPENDENCY. A DB-backed override is a governance-tab concern layered
24
+ // on top and must never become a dependency of the hook — the hook runs on a
25
+ // laptop with no database and no network. This module is therefore
26
+ // synchronous, DB-free and requires nothing but the catalog and the doorway.
27
+ //
28
+ // DEPENDENCY DIRECTION (ADR 0083's one-way rule). Code flows exactly one way:
29
+ // this module → src/module-api.js (the published doorway) → the kernel matcher
30
+ // in src/bongos/permission-path-check.js. Core never requires this module. The
31
+ // kernel loads the JSON data file directly (a static data read, the same way the
32
+ // sibling trust primitive route-rank-check.js reads modules/*/routes/*.js off
33
+ // disk to rank-gate them), and we read the parsed result back through the
34
+ // doorway so there is exactly ONE loader and ONE matcher — no second list to
35
+ // drift (the SEC #863 failure mode).
36
+
37
+ const api = require('../../src/module-api');
38
+ const catalog = require('./catalog');
39
+
40
+ const ppc = api.permissionPathCheck;
41
+
42
+ // The registry, as parsed by the single kernel loader.
43
+ const SURFACES = ppc.PROTECTED_SURFACES;
44
+
45
+ // ── invariants asserted at module load (fail loud on an authoring typo) ───────
46
+ // The governance module is where the registry meets the permission catalog, so
47
+ // this is where "every protected surface maps to a real, non-grantable
48
+ // permission" is checked. A surface mapping to an unknown key, or to a
49
+ // `system: false` (grantable) key, would mean an Archon could hand a custom role
50
+ // the power the substrate is supposed to reserve — exactly the wall ADR 0151 §4
51
+ // builds. Fail loud rather than silently protecting nothing.
52
+ for (const s of SURFACES) {
53
+ const perm = catalog.byKey(s.permission);
54
+ if (!perm) {
55
+ throw new Error(`protected-surface registry: "${s.glob}" maps to "${s.permission}", which the permission catalog does not define`);
56
+ }
57
+ if (perm.system !== true) {
58
+ throw new Error(`protected-surface registry: "${s.glob}" maps to "${s.permission}", which is not system:true — a protected surface must map to a NON-GRANTABLE permission (ADR 0151 §4)`);
59
+ }
60
+ if (!catalog.RANK_ORDER.includes(s.floor)) {
61
+ throw new Error(`protected-surface registry: "${s.glob}" declares floor "${s.floor}", which is not a rank`);
62
+ }
63
+ }
64
+
65
+ // The lowest floor any surface declares — 'metic' today (ADR 0043's Metic floor,
66
+ // now DERIVED from registry data rather than hardcoded).
67
+ const FLOOR = catalog.RANK_ORDER[Math.min(...SURFACES.map((s) => catalog.RANK_ORDER.indexOf(s.floor)))];
68
+
69
+ // The distinct system permissions the registry maps onto.
70
+ const SYSTEM_PERMISSIONS = Object.freeze([...new Set(SURFACES.map((s) => s.permission))].sort());
71
+
72
+ // Per-path lookups — the single matcher, reached through the doorway.
73
+ const surfaceFor = (file) => ppc.surfaceFor(file);
74
+ const floorFor = (file) => ppc.floorFor(file);
75
+ const permissionFor = (file) => ppc.permissionFor(file);
76
+ const isProtected = (file) => ppc.surfaceFor(file) !== null;
77
+
78
+ module.exports = {
79
+ SURFACES,
80
+ PROTECTED_GLOBS: ppc.PROTECTED_GLOBS,
81
+ REGISTRY_PATH: ppc.REGISTRY_PATH,
82
+ FLOOR,
83
+ SYSTEM_PERMISSIONS,
84
+ surfaceFor,
85
+ floorFor,
86
+ permissionFor,
87
+ isProtected,
88
+ };
@@ -0,0 +1,80 @@
1
+ {
2
+ "_readme": [
3
+ "The PROTECTED-SURFACE REGISTRY (ADR 0151 §4, BV1.R101). GOVERNANCE OWNS THIS DATA:",
4
+ "which repo paths are protected, at what rank floor, mapped to which system:true catalog",
5
+ "permission. It replaces the PROTECTED_GLOBS constant that used to be hardcoded in",
6
+ "src/bongos/permission-path-check.js.",
7
+ "",
8
+ "POLICY OWNS / SUBSTRATE ENFORCES. Editing this file re-maps POLICY. It does NOT move",
9
+ "enforcement into the app: the pre-push git hook, the grader pre-pass, main-audit.js,",
10
+ "gate-review.js and the goal-scope check all read this registry OUT OF BAND (no DB, no",
11
+ "server, no HTTP) and remain the enforcers (ADR 0043). A governance-tab edit can widen or",
12
+ "narrow the protected set and re-map a floor; it can never make the request path the",
13
+ "authority.",
14
+ "",
15
+ "WHY JSON IN THE REPO AND NOT A DB TABLE: the enforcers run where there is no database and",
16
+ "no server (a git pre-push hook on a laptop, a CI job). The registry's canonical form must",
17
+ "therefore be synchronously loadable from the checkout. A DB-backed override is a",
18
+ "governance-tab concern layered on top — never a dependency of the hook.",
19
+ "",
20
+ "SCHEMA: floor/permission at the top level are the defaults; a surface may override either.",
21
+ "'glob' follows the permission-path-check matcher: an exact path, a trailing '/' subtree,",
22
+ "'*' within one segment, '**' across segments."
23
+ ],
24
+ "version": 1,
25
+ "floor": "metic",
26
+ "permission": "path.protected.modify",
27
+ "surfaces": [
28
+ { "glob": "src/bongos/auth.js", "group": "authz", "why": "requireBuilder / requireRank definitions" },
29
+ { "glob": "src/bongos/db.js", "group": "authz", "why": "rank tiers, REQUIRES_RANK_TIER — the CASE edited in the SEC #863 incident" },
30
+ { "glob": "src/bongos/route-rank-check.js", "group": "authz", "why": "the route-gate auditor" },
31
+ { "glob": "src/bongos/permission-path-check.js", "group": "authz", "why": "the matcher that reads this registry (self-protect)" },
32
+ { "glob": "src/bongos/middleware/", "group": "authz", "why": "audit + any auth middleware" },
33
+ { "glob": "src/bongos/routes/", "group": "authz", "why": "routes carry the requireRank gates" },
34
+ { "glob": "modules/lifecycle/routes/", "group": "authz", "why": "BV1.R86: the lifecycle routes carved out of src/bongos/routes/ — the gates follow the routes" },
35
+ { "glob": "src/bongos/routes.js", "group": "authz", "why": "the route MOUNT table (a new ungated route lands here)" },
36
+ { "glob": "server.js", "group": "authz", "why": "the Express entrypoint — wires every router + host routing" },
37
+ { "glob": "modules/governance/", "group": "authz", "why": "the governance module (ADR 0151) — its resolver, catalog, gates AND this registry ARE the authz core; the glob is the prospective carve so CREATION is gated as strictly as edits (ADR 0086 §4)" },
38
+ { "glob": "modules/dev-box/boxes.js", "group": "box-access", "why": "boxScopeForRank / provision floor — decides who gets a full source clone" },
39
+ { "glob": "modules/dev-box/box-access.js", "group": "box-access", "why": "decideSourceAccess (rank → starter|full source)" },
40
+ { "glob": "modules/dev-box/box-credential.js", "group": "box-access", "why": "mints the box source credential" },
41
+ { "glob": "modules/grading/grader.js", "group": "pipeline", "why": "BV1.R73: grading carved to modules/grading/ — the glob follows the file" },
42
+ { "glob": "modules/grading/grader-workers/", "group": "pipeline", "why": "the adversarial panel (Narc/Hacker/Quality/Efficiency) — blunting these blunts the gate" },
43
+ { "glob": "modules/grading/grader-rubric.json", "group": "pipeline", "why": "the grader's scoring rubric (ADR 0058)" },
44
+ { "glob": "scripts/gds/ship.js", "group": "pipeline", "why": "the ship lifecycle driver" },
45
+ { "glob": "scripts/gds/confirm.js", "group": "pipeline", "why": "the confirm step" },
46
+ { "glob": "scripts/gds/merge-mode.js", "group": "pipeline", "why": "the manual land fallback" },
47
+ { "glob": "scripts/gds/ci-grade.js", "group": "pipeline", "why": "CI grade-gate driver — steers the grade check (ADR 0058)" },
48
+ { "glob": "scripts/gds/grade-smoke.js", "group": "pipeline", "why": "grade-smoke driver (ADR 0058)" },
49
+ { "glob": "scripts/gds/conductor-dispatch.js", "group": "pipeline", "why": "mid-flight grader dispatch (ADR 0058)" },
50
+ { "glob": "scripts/gds/main-audit.js", "group": "pipeline", "why": "the post-push audit — the authoritative substrate layer (self-protect)" },
51
+ { "glob": "scripts/gds/push-main.js", "group": "pipeline", "why": "the sanctioned main-push (carries the override)" },
52
+ { "glob": "scripts/gds/run-unit-tests.js", "group": "pipeline", "why": "the unit-gate test SELECTOR — muting tests here blunts the CI gate (ADR 0058)" },
53
+ { "glob": "scripts/gds/gate-review.js", "group": "pipeline", "why": "the automatic gatekeeper itself (self-protect, ADR 0058)" },
54
+ { "glob": "scripts/gds/setup.js", "group": "pipeline", "why": "/builder-setup — installs the pre-push hook" },
55
+ { "glob": "scripts/gds/install-git-hooks.js", "group": "pipeline", "why": "the hook installer (self-protect)" },
56
+ { "glob": "scripts/gds/hooks/", "group": "pipeline", "why": "the git hooks themselves (self-protect)" },
57
+ { "glob": "scripts/gds/cli-lib.js", "group": "pipeline", "why": "session/token + apiCall the gate relies on" },
58
+ { "glob": "scripts/migrate.sh", "group": "pipeline", "why": "applies migrations on deploy" },
59
+ { "glob": "scripts/deploy/", "group": "pipeline", "why": "the deploy scripts run on the droplet at deploy (ADR 0058)" },
60
+ { "glob": "modules/lifecycle/merge-lock.js", "group": "pipeline", "why": "BV1.R86: serializes a ship" },
61
+ { "glob": "modules/lifecycle/ship-preflight.js", "group": "pipeline", "why": "BV1.R86: preflights a ship" },
62
+ { "glob": "modules/lifecycle/github-push.js", "group": "pipeline", "why": "BV1.R86: the server-mediated branch publish (ADR 0055)" },
63
+ { "glob": "modules/lifecycle/conflict-resolve.js", "group": "pipeline", "why": "BV1.R86: self-heals generated-file conflicts at land time" },
64
+ { "glob": "modules/lifecycle/publish-reconciler.js", "group": "pipeline", "why": "BV1.R86: lands confirmed merges" },
65
+ { "glob": "migrations/", "group": "schema", "why": "schema migrations are never newcomer work" },
66
+ { "glob": "infra/", "group": "substrate", "why": "the deploy substrate" },
67
+ { "glob": ".github/", "group": "substrate", "why": "CI workflows (deploy-prod.yml, grade gate, secrets-scan) are the pipeline" },
68
+ { "glob": "CODEOWNERS", "group": "substrate", "why": "the code-owner gate list itself (ADR 0058)" },
69
+ { "glob": "config/deploy.json", "group": "substrate", "why": "the laptop/ci/bypass deploy switch (ADR 0058)" },
70
+ { "glob": "package.json", "group": "substrate", "why": "npm scripts + prepare/lifecycle hooks run during install/CI" },
71
+ { "glob": "package-lock.json", "group": "substrate", "why": "the resolved supply chain npm ci installs on the droplet (ADR 0058)" },
72
+ { "glob": ".husky/", "group": "substrate", "why": "pre-push (this gate) + pre-commit (secrets guard) — the hooks the gate is DELIVERED through (self-protect Layer 1)" },
73
+ { "glob": "docs/canonical-permissions.md", "group": "docs", "why": "the trust-boundary documentation" },
74
+ { "glob": "docs/adr/0016-trust-boundary-server-enforced-permissions.md", "group": "docs", "why": "the trust-boundary decision of record" },
75
+ { "glob": "docs/adr/0043-git-ssh-trust-boundary-and-rank-floor-on-permission-paths.md", "group": "docs", "why": "this gate's own ADR" },
76
+ { "glob": "CLAUDE.md", "group": "docs", "why": "the methodology core" },
77
+ { "glob": ".claude/hooks/", "group": "automation", "why": "local automation that could weaken a gate" },
78
+ { "glob": ".claude/settings.json", "group": "automation", "why": "local automation that could weaken a gate" }
79
+ ]
80
+ }