@bongos/core 1.17.0

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 (1142) hide show
  1. package/.bongos-core.json +5727 -0
  2. package/.claude/skills/blocker-review/SKILL.md +91 -0
  3. package/.claude/skills/blocker-solve/SKILL.md +96 -0
  4. package/.claude/skills/builder-backup/SKILL.md +54 -0
  5. package/.claude/skills/builder-box/SKILL.md +45 -0
  6. package/.claude/skills/builder-claim/SKILL.md +76 -0
  7. package/.claude/skills/builder-connect/SKILL.md +59 -0
  8. package/.claude/skills/builder-cost/SKILL.md +47 -0
  9. package/.claude/skills/builder-end/SKILL.md +81 -0
  10. package/.claude/skills/builder-exit/SKILL.md +88 -0
  11. package/.claude/skills/builder-key/SKILL.md +40 -0
  12. package/.claude/skills/builder-reauth/SKILL.md +50 -0
  13. package/.claude/skills/builder-redteam/SKILL.md +109 -0
  14. package/.claude/skills/builder-release/SKILL.md +41 -0
  15. package/.claude/skills/builder-setup/SKILL.md +62 -0
  16. package/.claude/skills/builder-ship/SKILL.md +79 -0
  17. package/.claude/skills/builder-stage/SKILL.md +51 -0
  18. package/.claude/skills/builder-start/SKILL.md +46 -0
  19. package/.claude/skills/close-box/SKILL.md +43 -0
  20. package/.claude/skills/design-sync/SKILL.md +69 -0
  21. package/.claude/skills/dev/SKILL.md +49 -0
  22. package/.claude/skills/feedback/SKILL.md +32 -0
  23. package/.claude/skills/figma-design-sync/SKILL.md +74 -0
  24. package/.claude/skills/goal-review/SKILL.md +89 -0
  25. package/.claude/skills/idea-triage/SKILL.md +144 -0
  26. package/.claude/skills/ideate/SKILL.md +82 -0
  27. package/.claude/skills/merge-mode/SKILL.md +169 -0
  28. package/.claude/skills/new-project/SKILL.md +108 -0
  29. package/.claude/skills/otb-character-review/SKILL.md +71 -0
  30. package/.claude/skills/otb-design-review/SKILL.md +47 -0
  31. package/.claude/skills/otb-feedback-capture/SKILL.md +45 -0
  32. package/.claude/skills/otb-figma-sync/SKILL.md +54 -0
  33. package/.claude/skills/otb-tile-generate/SKILL.md +45 -0
  34. package/.claude/skills/paint/SKILL.md +70 -0
  35. package/.claude/skills/planning-session/SKILL.md +299 -0
  36. package/.claude/skills/priority-session/SKILL.md +134 -0
  37. package/.claude/skills/read-session-export/SKILL.md +58 -0
  38. package/.claude/skills/recall/SKILL.md +52 -0
  39. package/.claude/skills/session-handoff/SKILL.md +46 -0
  40. package/.claude/skills/status/SKILL.md +33 -0
  41. package/.devcontainer/Dockerfile +69 -0
  42. package/.devcontainer/README.md +73 -0
  43. package/.devcontainer/devcontainer-lock.json +19 -0
  44. package/.devcontainer/devcontainer.json +93 -0
  45. package/.devcontainer/healthcheck.sh +84 -0
  46. package/.devcontainer/init-firewall.sh +198 -0
  47. package/.devcontainer/post-create.sh +153 -0
  48. package/.dockerignore +36 -0
  49. package/.env.local.example +32 -0
  50. package/.gitattributes +83 -0
  51. package/.gitignore +91 -0
  52. package/CLAUDE.md +242 -0
  53. package/CODE_OF_CONDUCT.md +46 -0
  54. package/CONTRIBUTING.md +51 -0
  55. package/Dockerfile +46 -0
  56. package/GOVERNANCE.md +44 -0
  57. package/LICENSE +661 -0
  58. package/README.md +88 -0
  59. package/bin/bongos.js +222 -0
  60. package/clients/bongos-client/README.md +48 -0
  61. package/clients/bongos-client/bongos-client.global.js +617 -0
  62. package/clients/bongos-client/examples/hello-world.mjs +62 -0
  63. package/clients/bongos-client/index.cjs +619 -0
  64. package/clients/bongos-client/index.d.ts +793 -0
  65. package/clients/bongos-client/index.mjs +614 -0
  66. package/clients/bongos-client/package.json +32 -0
  67. package/config/branding.neutral.json +60 -0
  68. package/config/design-tokens.neutral.json +51 -0
  69. package/config/design-tokens.schema.json +96 -0
  70. package/config/modules.neutral.json +9 -0
  71. package/docker-compose.yml +52 -0
  72. package/docker-entrypoint.sh +30 -0
  73. package/docs/adr/0001-stack-choice-single-vps.md +45 -0
  74. package/docs/adr/0002-digitalocean-over-hetzner.md +27 -0
  75. package/docs/adr/0003-cloudflare-origin-cert-over-letsencrypt.md +35 -0
  76. package/docs/adr/0004-amazonprimea-name-and-trademark-acceptance.md +45 -0
  77. package/docs/adr/0005-invisible-other-players-in-v1.md +37 -0
  78. package/docs/adr/0006-singleton-worldroom-plus-queueroom.md +30 -0
  79. package/docs/adr/0007-persistent-identity-via-localstorage-uuid.md +31 -0
  80. package/docs/adr/0008-family-based-tile-generation.md +86 -0
  81. package/docs/adr/0008-google-chat-oauth-user-auth.md +37 -0
  82. package/docs/adr/0009-autotile-architecture.md +181 -0
  83. package/docs/adr/0010-pms-architecture.md +70 -0
  84. package/docs/adr/0011-terrain-id-rendering-model.md +174 -0
  85. package/docs/adr/0012-ground-object-cell-split.md +208 -0
  86. package/docs/adr/0013-paths-as-object-overlays-with-autotile-dispatch.md +158 -0
  87. package/docs/adr/0014-families-canonical-over-vocabulary.md +103 -0
  88. package/docs/adr/0015-task-dependencies-and-auto-promotion.md +125 -0
  89. package/docs/adr/0016-trust-boundary-server-enforced-permissions.md +132 -0
  90. package/docs/adr/0017-code-simplification-and-repo-cleanliness.md +74 -0
  91. package/docs/adr/0018-three-rank-model-goes-live.md +97 -0
  92. package/docs/adr/0019-red-team-bounty-payout-table.md +52 -0
  93. package/docs/adr/0020-security-model-public-stub.md +70 -0
  94. package/docs/adr/0021-per-builder-skill-model-preferences.md +99 -0
  95. package/docs/adr/0022-secrets-policy.md +124 -0
  96. package/docs/adr/0023-kind-multipliers-and-idea-bonus.md +118 -0
  97. package/docs/adr/0024-cloneable-repo-local-first-memory.md +277 -0
  98. package/docs/adr/0024-multi-agent-system-architecture.md +125 -0
  99. package/docs/adr/0025-offsite-backup-vendor-digitalocean-spaces.md +59 -0
  100. package/docs/adr/0025-structured-criterion-task-link.md +44 -0
  101. package/docs/adr/0026-bfg-memory-hygiene-and-cross-builder-learning.md +117 -0
  102. package/docs/adr/0027-bfg-session-inefficiency-evaluator.md +170 -0
  103. package/docs/adr/0028-honest-llm-spend-accounting.md +109 -0
  104. package/docs/adr/0029-status-page-outage-resilience.md +59 -0
  105. package/docs/adr/0030-builders-hall-live-updates.md +59 -0
  106. package/docs/adr/0031-cloud-dev-environments-for-builders.md +222 -0
  107. package/docs/adr/0032-discord-mirror-of-gds.md +50 -0
  108. package/docs/adr/0033-discord-bot-service-principal.md +82 -0
  109. package/docs/adr/0034-thetes-graduated-newcomer-rank.md +53 -0
  110. package/docs/adr/0035-builder-onboarding-three-paths.md +128 -0
  111. package/docs/adr/0036-builders-subdomain-and-ui-reorg.md +40 -0
  112. package/docs/adr/0037-discord-channel-management.md +51 -0
  113. package/docs/adr/0038-chromebook-ttyd-cloudflare-tunnel.md +111 -0
  114. package/docs/adr/0039-setup-first-onboarding-ux.md +49 -0
  115. package/docs/adr/0040-remote-control-default-browser-onramp.md +47 -0
  116. package/docs/adr/0041-temporary-grader-bypass-killswitch.md +40 -0
  117. package/docs/adr/0042-builder-self-deploy-ci-auto-merge.md +82 -0
  118. package/docs/adr/0043-git-ssh-trust-boundary-and-rank-floor-on-permission-paths.md +74 -0
  119. package/docs/adr/0044-mediterranean-palette-replacement.md +73 -0
  120. package/docs/adr/0044-per-box-live-game-preview.md +57 -0
  121. package/docs/adr/0045-devbox-desktop-app.md +133 -0
  122. package/docs/adr/0046-sandbox-first-review-gate.md +72 -0
  123. package/docs/adr/0047-discord-bugs-intake-anti-abuse.md +36 -0
  124. package/docs/adr/0048-works-in-progress-ia-split.md +32 -0
  125. package/docs/adr/0049-split-parallel-safety-contract.md +46 -0
  126. package/docs/adr/0050-device-flow-admission-invite-gated-by-default.md +43 -0
  127. package/docs/adr/0051-full-session-transcript-corpus.md +57 -0
  128. package/docs/adr/0052-sandbox-for-everyone-game-only-preview.md +70 -0
  129. package/docs/adr/0053-scoped-dev-box-session.md +55 -0
  130. package/docs/adr/0054-session-token-reward-cost-plus.md +63 -0
  131. package/docs/adr/0055-server-mediated-branch-publish.md +47 -0
  132. package/docs/adr/0056-prod-deploy-script-mirror-and-healthcheck-retry.md +62 -0
  133. package/docs/adr/0057-container-cost-ledger.md +28 -0
  134. package/docs/adr/0057-discord-archon-approval-channels.md +64 -0
  135. package/docs/adr/0058-automatic-gate-surface-gatekeeper.md +64 -0
  136. package/docs/adr/0059-single-approval-remove-devbox-approval-gate.md +37 -0
  137. package/docs/adr/0060-gds-retrieval-layer.md +97 -0
  138. package/docs/adr/0061-context-layer-decomposition.md +61 -0
  139. package/docs/adr/0062-medusa-instance-model-decoupling.md +107 -0
  140. package/docs/adr/0063-generated-repo-map-symbol-skeleton.md +44 -0
  141. package/docs/adr/0064-rename-medusa-to-cloud-bongos.md +49 -0
  142. package/docs/adr/0065-agpl-nonprofit-ai-first-governance.md +69 -0
  143. package/docs/adr/0066-generated-file-map-sections.md +51 -0
  144. package/docs/adr/0067-session-start-fast-forward-pull.md +43 -0
  145. package/docs/adr/0068-shared-art-key-for-newcomers.md +38 -0
  146. package/docs/adr/0069-hall-live-channel-sse-decouple.md +35 -0
  147. package/docs/adr/0070-deterministic-session-card-delivery.md +44 -0
  148. package/docs/adr/0071-box-confirm-before-destroyed-and-drift-reconcile.md +59 -0
  149. package/docs/adr/0072-bongos-app-mac-signed-first-windows-deferred.md +35 -0
  150. package/docs/adr/0072-dev-box-code-staleness-visibility.md +39 -0
  151. package/docs/adr/0073-builder-needs-signal-and-byok-gemini-key.md +64 -0
  152. package/docs/adr/0073-secrets-scan-exclude-uri-detector.md +37 -0
  153. package/docs/adr/0074-memory-map-stays-light.md +32 -0
  154. package/docs/adr/0075-per-system-cost-attribution.md +37 -0
  155. package/docs/adr/0076-in-session-deterministic-compaction.md +58 -0
  156. package/docs/adr/0077-content-addressed-llm-cache.md +32 -0
  157. package/docs/adr/0078-deterministic-first-cron.md +48 -0
  158. package/docs/adr/0079-value-gated-autonomous-engagement.md +39 -0
  159. package/docs/adr/0080-llm-cache-api-transport-and-trust-model.md +46 -0
  160. package/docs/adr/0081-tool-agnostic-design-layer.md +62 -0
  161. package/docs/adr/0082-productized-key-storage-provisioning.md +32 -0
  162. package/docs/adr/0082-server-side-merge-conflict-auto-resolution.md +55 -0
  163. package/docs/adr/0083-modular-architecture-strangler-build-plan.md +143 -0
  164. package/docs/adr/0084-auto-derived-per-task-rank-floor.md +86 -0
  165. package/docs/adr/0085-remove-default-per-builder-spend-cap.md +39 -0
  166. package/docs/adr/0086-goal-scoped-work-hierarchy.md +123 -0
  167. package/docs/adr/0087-bongos-app-architecture-and-handoff-contract.md +49 -0
  168. package/docs/adr/0087-compete-on-governance-not-tooling-cursor.md +65 -0
  169. package/docs/adr/0088-consistent-character-generative-animation.md +169 -0
  170. package/docs/adr/0089-modular-multi-model-adversarial-grader.md +179 -0
  171. package/docs/adr/0090-metic-task-authoring.md +73 -0
  172. package/docs/adr/0091-bounding-the-kernel-and-db-carve.md +106 -0
  173. package/docs/adr/0092-hosting-module-and-on-prem-target.md +117 -0
  174. package/docs/adr/0093-tranche-2-core-carve-sequence.md +72 -0
  175. package/docs/adr/0094-repo-atlas-self-drawing-codebase-map.md +49 -0
  176. package/docs/adr/0095-borrowed-memory-and-retrieval-concepts.md +65 -0
  177. package/docs/adr/0095-cross-agent-context-management.md +58 -0
  178. package/docs/adr/0096-require-reward-before-workable.md +209 -0
  179. package/docs/adr/0097-one-active-claim-per-session-and-worktree-binding.md +46 -0
  180. package/docs/adr/0097-retroactive-reward-backfill.md +124 -0
  181. package/docs/adr/0098-public-mirror-publish-manifest.md +92 -0
  182. package/docs/adr/0099-delayed-redacted-mirror-export.md +55 -0
  183. package/docs/adr/0100-two-repo-instance-model.md +60 -0
  184. package/docs/adr/0101-goal-hierarchy-phase-5-judge-membership-reward.md +90 -0
  185. package/docs/adr/0102-finish-bounding-the-kernel.md +36 -0
  186. package/docs/adr/0103-core-first-extraction-cloud-bongos-trunk.md +70 -0
  187. package/docs/adr/0103-gdsv4-bongos-consolidation.md +28 -0
  188. package/docs/adr/0104-trust-gds-api-channel-in-auto-mode.md +42 -0
  189. package/docs/adr/0105-instance-seed-migrations-out-of-core.md +47 -0
  190. package/docs/adr/0106-goal-owner-manager-authority.md +84 -0
  191. package/docs/adr/0107-module-upstreaming-contribution-to-core-catalog.md +83 -0
  192. package/docs/adr/0108-instance-composition-configurable-root.md +87 -0
  193. package/docs/adr/0109-self-describing-openapi-and-hosted-docs.md +44 -0
  194. package/docs/adr/0110-untrack-whole-file-generated-nav-docs.md +49 -0
  195. package/docs/adr/0111-instance-hosting-provisioning-module.md +135 -0
  196. package/docs/adr/0112-private-goals-invitations-join-requests.md +72 -0
  197. package/docs/adr/0113-sandbox-studio-descope-superseded-by-0044-0052.md +41 -0
  198. package/docs/adr/0114-instance-docs-discovery-contract.md +32 -0
  199. package/docs/adr/0115-scheduled-routines-portability.md +31 -0
  200. package/docs/adr/0116-api-error-envelope-and-code-taxonomy.md +62 -0
  201. package/docs/adr/0117-api-versioning-and-deprecation-policy.md +50 -0
  202. package/docs/adr/0118-api-contract-source-of-truth-code-first.md +47 -0
  203. package/docs/adr/0119-api-pagination-contract.md +34 -0
  204. package/docs/adr/0120-pay-on-land-and-builder-owned-rebase-gate.md +108 -0
  205. package/docs/adr/0121-greenfield-vs-brownfield-onboarding-adopt-existing-repo.md +97 -0
  206. package/docs/adr/0122-onboarding-diagram-renders-deploy-rendered-off-git.md +43 -0
  207. package/docs/adr/0123-box-idle-sweep-autosave-before-destroy.md +36 -0
  208. package/docs/adr/0124-typed-api-response-bodies.md +37 -0
  209. package/docs/adr/0125-control-plane-scaffolds-standalone-instance-repo.md +36 -0
  210. package/docs/adr/0126-dedicated-cloudbongos-control-plane-droplet.md +30 -0
  211. package/docs/adr/0127-cross-box-fleet-observability.md +64 -0
  212. package/docs/adr/0128-provisioning-runner-privilege-and-db-auth-model.md +46 -0
  213. package/docs/adr/0129-cloudbongos-control-plane-pull-deploy.md +34 -0
  214. package/docs/adr/0130-provision-remote-exec-over-ssh.md +61 -0
  215. package/docs/adr/0131-rank-scoped-skill-visibility.md +47 -0
  216. package/docs/adr/0132-cohost-fleet-control-plane-migration.md +54 -0
  217. package/docs/adr/0133-github-app-manifest-onboarding-signin.md +42 -0
  218. package/docs/adr/0134-private-first-npm-distribution.md +38 -0
  219. package/docs/adr/README.md +208 -0
  220. package/docs/api/openapi.json +13720 -0
  221. package/docs/api-reference.md +437 -0
  222. package/docs/architecture.md +244 -0
  223. package/docs/branding-contract.md +66 -0
  224. package/docs/branding-fill-prompt.md +25 -0
  225. package/docs/canonical-permissions.md +241 -0
  226. package/docs/design/container-exterior-tiles.md +49 -0
  227. package/docs/design/container-exterior.md +81 -0
  228. package/docs/design/container-interior.md +77 -0
  229. package/docs/design/modular-architecture/00-research-report.md +107 -0
  230. package/docs/design/modular-architecture/01-architecture.md +125 -0
  231. package/docs/design/modular-architecture/02-module-map.md +116 -0
  232. package/docs/design/modular-architecture/03-builder-flows.md +104 -0
  233. package/docs/design/modular-architecture/04-module-lifecycle.md +62 -0
  234. package/docs/design/modular-architecture/README.md +27 -0
  235. package/docs/design/vanilla-hall-ui-redesign-scope.md +284 -0
  236. package/docs/design/world-map-scroll.md +41 -0
  237. package/docs/design-contract.md +320 -0
  238. package/docs/file-map.md +418 -0
  239. package/docs/handoff-template.md +92 -0
  240. package/docs/modules-contract.md +293 -0
  241. package/docs/onboarding/browser-terminal-guide.md +73 -0
  242. package/docs/onboarding/diagrams/01-task-lifecycle.mmd +28 -0
  243. package/docs/onboarding/diagrams/02-rank-ladder.mmd +54 -0
  244. package/docs/onboarding/diagrams/03-drachmae-karma.mmd +37 -0
  245. package/docs/onboarding/diagrams/04-architecture.mmd +51 -0
  246. package/docs/onboarding/diagrams/05-versioning.mmd +29 -0
  247. package/docs/onboarding/diagrams/README.md +94 -0
  248. package/docs/onboarding/diagrams/assertions.json +170 -0
  249. package/docs/onboarding/diagrams/puppeteer-config.json +1 -0
  250. package/docs/onboarding/diagrams/render-config.json +9 -0
  251. package/docs/onboarding/drachmae-and-karma.md +178 -0
  252. package/docs/onboarding/primer.md +202 -0
  253. package/docs/project-context.template.md +67 -0
  254. package/docs/recipes/README.md +35 -0
  255. package/docs/recipes/audit-replay.md +100 -0
  256. package/docs/recipes/bongos-cli-release.md +66 -0
  257. package/docs/recipes/builder-box-lifecycle.md +152 -0
  258. package/docs/recipes/claude-design-loop.md +143 -0
  259. package/docs/recipes/claude-gemini-bongos-doors.md +155 -0
  260. package/docs/recipes/cloudbongos-staging-gate.md +139 -0
  261. package/docs/recipes/cloudbongos-standup.md +111 -0
  262. package/docs/recipes/devbox-app-release.md +93 -0
  263. package/docs/recipes/devbox-app-windows-test.md +128 -0
  264. package/docs/recipes/failed-ship-recovery-proof.md +102 -0
  265. package/docs/recipes/figma-design-loop.md +129 -0
  266. package/docs/recipes/gds-db-backup.md +255 -0
  267. package/docs/recipes/instance-deploy.md +37 -0
  268. package/docs/recipes/managed-settings-remote-control.md +416 -0
  269. package/docs/recipes/memory-backup.md +71 -0
  270. package/docs/recipes/memory-sync-conflicts.md +98 -0
  271. package/docs/recipes/multi-builder-merge.md +78 -0
  272. package/docs/recipes/ops-gotchas.md +212 -0
  273. package/docs/recipes/overnight-builder.md +53 -0
  274. package/docs/recipes/packaging-the-core.md +49 -0
  275. package/docs/recipes/parallel-load-results-2026-05-30.md +63 -0
  276. package/docs/recipes/parallel-load-test.md +32 -0
  277. package/docs/recipes/pixel-art-pipeline.md +411 -0
  278. package/docs/recipes/private-npm-distribution.md +79 -0
  279. package/docs/recipes/search-index-ingestion.md +67 -0
  280. package/docs/recipes/self-host.md +60 -0
  281. package/docs/recipes/ship-and-regrade.md +72 -0
  282. package/docs/recipes/staging-data.md +50 -0
  283. package/docs/recipes/staging-environment.md +215 -0
  284. package/docs/recipes/upgrading-the-core.md +58 -0
  285. package/docs/research/cursor-competitive-analysis.md +255 -0
  286. package/docs/research/llm-model-agnostic-and-cheaper-grader.md +227 -0
  287. package/migrations/003_pms.sql +284 -0
  288. package/migrations/004_pms_cost_idempotency.sql +13 -0
  289. package/migrations/005_pms_smoke_filter.sql +44 -0
  290. package/migrations/006_pms_v2.sql +378 -0
  291. package/migrations/007_pms_v2_hierarchy_trigger.sql +59 -0
  292. package/migrations/008_pms_v2_migration_allocator.sql +27 -0
  293. package/migrations/009_pms_v2_blockers_table.sql +68 -0
  294. package/migrations/010_uptime.sql +29 -0
  295. package/migrations/011_backfill_v2_v3_done_when.sql +20 -0
  296. package/migrations/012_pms_v2_task_lifecycle_states.sql +82 -0
  297. package/migrations/013_claim_locking.sql +22 -0
  298. package/migrations/014_pms_task_discipline.sql +86 -0
  299. package/migrations/015_pms_estimation_calibration.sql +140 -0
  300. package/migrations/016_pms_task_grades.sql +60 -0
  301. package/migrations/017_version_progress_exclude_abandoned.sql +49 -0
  302. package/migrations/019_rename_pms_v3_to_gds_v3.sql +65 -0
  303. package/migrations/020_task_dependencies.sql +99 -0
  304. package/migrations/021_v3_renumber_and_deps.sql +224 -0
  305. package/migrations/022_v3_reconcile_dep_gated_status.sql +41 -0
  306. package/migrations/023_pms_rank.sql +30 -0
  307. package/migrations/025_builder_exit_status.sql +15 -0
  308. package/migrations/027_builders_preferred_disciplines.sql +46 -0
  309. package/migrations/028_tasks_parallel_safe.sql +71 -0
  310. package/migrations/029_rank_three_live.sql +32 -0
  311. package/migrations/030_vulnerability_reports.sql +61 -0
  312. package/migrations/031_reward_simplification.sql +57 -0
  313. package/migrations/032_repo_health_snapshots.sql +56 -0
  314. package/migrations/033_audit_log.sql +64 -0
  315. package/migrations/034_lifecycle_breakdown_leaf_only.sql +62 -0
  316. package/migrations/035_auth_token_hygiene.sql +66 -0
  317. package/migrations/037_builder_skill_model_prefs.sql +44 -0
  318. package/migrations/038_task_override_requests.sql +51 -0
  319. package/migrations/039_newcomer_friendly_column.sql +124 -0
  320. package/migrations/040_builder_karma.sql +58 -0
  321. package/migrations/041_kind_multipliers_and_idea_bonus.sql +42 -0
  322. package/migrations/042_criterion_proposal_workflow.sql +78 -0
  323. package/migrations/043_builder_onboarding_observability.sql +66 -0
  324. package/migrations/044_tasks_requires_rank.sql +97 -0
  325. package/migrations/045_builder_onboarding_state.sql +147 -0
  326. package/migrations/046_cost_log_builder_attribution.sql +42 -0
  327. package/migrations/054_builder_memory.sql +108 -0
  328. package/migrations/055_task_criteria.sql +90 -0
  329. package/migrations/058_peer_votes.sql +41 -0
  330. package/migrations/059_onboarding_pick_disciplines.sql +59 -0
  331. package/migrations/060_access_requests.sql +46 -0
  332. package/migrations/061_backfill_override_ship_attribution.sql +29 -0
  333. package/migrations/062_session_records.sql +34 -0
  334. package/migrations/063_session_records_tool_errors_idx.sql +11 -0
  335. package/migrations/064_grade_attempts.sql +18 -0
  336. package/migrations/065_bfg_principal.sql +58 -0
  337. package/migrations/066_live_notify_triggers.sql +69 -0
  338. package/migrations/067_builder_boxes.sql +117 -0
  339. package/migrations/067_discord_bot_foundation.sql +69 -0
  340. package/migrations/068_thetes_requires_rank.sql +27 -0
  341. package/migrations/069_builder_ssh_keys.sql +66 -0
  342. package/migrations/070_box_intents.sql +101 -0
  343. package/migrations/071_box_first_provision_approval.sql +46 -0
  344. package/migrations/072_onboarding_ship_three_remove_primer.sql +80 -0
  345. package/migrations/073_box_terminal_access.sql +46 -0
  346. package/migrations/075_builder_sound_prefs.sql +28 -0
  347. package/migrations/078_box_claude_active.sql +22 -0
  348. package/migrations/079_box_first_connected_at.sql +20 -0
  349. package/migrations/081_tasks_needs_migration.sql +34 -0
  350. package/migrations/084_tasks_security_sensitive.sql +24 -0
  351. package/migrations/085_security_engagement_docs.sql +34 -0
  352. package/migrations/086_rename_disciplines.sql +125 -0
  353. package/migrations/088_builders_anthropic_link.sql +28 -0
  354. package/migrations/089_builders_monthly_budget.sql +19 -0
  355. package/migrations/090_box_scoped_sessions.sql +38 -0
  356. package/migrations/091_session_token_reward.sql +44 -0
  357. package/migrations/093_tasks_sandbox_review.sql +22 -0
  358. package/migrations/102_remove_box_first_provision_approval.sql +58 -0
  359. package/migrations/103_search_chunks.sql +90 -0
  360. package/migrations/104_builder_box_blocked.sql +34 -0
  361. package/migrations/105_box_host_keys.sql +35 -0
  362. package/migrations/111_medusa_customer0_framing.sql +35 -0
  363. package/migrations/112_drop_track_check.sql +25 -0
  364. package/migrations/113_merge_lease.sql +16 -0
  365. package/migrations/114_cloudbongos_rename_live_strings.sql +32 -0
  366. package/migrations/118_currency_label_decoupling.sql +23 -0
  367. package/migrations/119_builder_behavior_prefs.sql +31 -0
  368. package/migrations/121_box_code_version.sql +37 -0
  369. package/migrations/122_builder_gemini_key.sql +30 -0
  370. package/migrations/123_cost_log_tiers.sql +33 -0
  371. package/migrations/124_cost_baseline.sql +45 -0
  372. package/migrations/125_llm_cache.sql +54 -0
  373. package/migrations/126_autonomy_runs.sql +33 -0
  374. package/migrations/143_cost_calibration.sql +63 -0
  375. package/migrations/145_backfill_task_requires_rank.sql +84 -0
  376. package/migrations/160_goal_tier.sql +88 -0
  377. package/migrations/163_polymorphic_dependencies.sql +187 -0
  378. package/migrations/164_event_sound_prefs.sql +20 -0
  379. package/migrations/165_task_module_key.sql +15 -0
  380. package/migrations/171_claims_published_head_sha.sql +24 -0
  381. package/migrations/174_close_orphaned_claims_on_terminal_tasks.sql +30 -0
  382. package/migrations/175_task_grades_diff_hash.sql +9 -0
  383. package/migrations/176_promotion_respects_open_blockers.sql +58 -0
  384. package/migrations/176_ui_discipline.sql +40 -0
  385. package/migrations/177_require_reward_before_workable.sql +76 -0
  386. package/migrations/178_reward_gate_auto_assign_revert_autopromote.sql +61 -0
  387. package/migrations/179_search_chunk_usefulness.sql +33 -0
  388. package/migrations/180_one_active_claim_per_session.sql +54 -0
  389. package/migrations/181_cost_log_skill_name.sql +33 -0
  390. package/migrations/184_claim_release_requested.sql +25 -0
  391. package/migrations/185_module_upstreaming_goal_wiring.sql +59 -0
  392. package/migrations/187_goal_sort_order.sql +30 -0
  393. package/migrations/187_tasks_kind_check_add_verify.sql +21 -0
  394. package/migrations/README.md +88 -0
  395. package/migrations/core_188_goal_visibility_and_membership_requests.sql +71 -0
  396. package/migrations/core_189_core_upgrades_ledger.sql +37 -0
  397. package/migrations/core_189_credit_log_task_id_index.sql +19 -0
  398. package/migrations/core_190_rebuild_claimable_task_views.sql +71 -0
  399. package/migrations/instance/018_pms_v3_planning_seed.sql +131 -0
  400. package/migrations/instance/080_gds_v4_planning_seed.sql +75 -0
  401. package/migrations/instance/110_medusa_planning_seed.sql +70 -0
  402. package/migrations/instance/120_bongos_v1_planning_seed.sql +69 -0
  403. package/migrations/instance/144_bongos_v1_module_system_criterion.sql +26 -0
  404. package/migrations/instance/159_bongos_v1_goal_hierarchy_criterion.sql +29 -0
  405. package/migrations/instance/172_v2_world_planning_seed.sql +163 -0
  406. package/migrations/instance/173_bongos_v1_c7_core_modularized.sql +33 -0
  407. package/migrations/instance/182_gdsv4_bongos_consolidation.sql +132 -0
  408. package/migrations/instance/183_gdsv4_carryover_themed_goals.sql +116 -0
  409. package/migrations/instance/186_bongos_v1_governance_planning_seed.sql +69 -0
  410. package/modules/autonomy/CLAUDE.md +49 -0
  411. package/modules/autonomy/db.js +50 -0
  412. package/modules/autonomy/module.json +13 -0
  413. package/modules/autonomy/pollers/routine-timers.js +146 -0
  414. package/modules/autonomy/routes/autonomy.js +37 -0
  415. package/modules/builder-settings/CLAUDE.md +58 -0
  416. package/modules/builder-settings/builder-needs.js +115 -0
  417. package/modules/builder-settings/db.js +168 -0
  418. package/modules/builder-settings/display-name.js +84 -0
  419. package/modules/builder-settings/event-sound-prefs.js +169 -0
  420. package/modules/builder-settings/interaction-prefs.js +201 -0
  421. package/modules/builder-settings/model-alloc-prefs.js +147 -0
  422. package/modules/builder-settings/module.json +10 -0
  423. package/modules/builder-settings/render-prefs.js +103 -0
  424. package/modules/builder-settings/routes/settings.js +417 -0
  425. package/modules/builder-settings/settings.js +52 -0
  426. package/modules/builder-settings/skill-prefs.js +196 -0
  427. package/modules/builder-settings/sound-prefs.js +178 -0
  428. package/modules/builder-settings/wandering-prefs.js +282 -0
  429. package/modules/dev-box/CLAUDE.md +38 -0
  430. package/modules/dev-box/box-access.js +275 -0
  431. package/modules/dev-box/box-credential.js +94 -0
  432. package/modules/dev-box/box-onboard.js +486 -0
  433. package/modules/dev-box/boxes.js +842 -0
  434. package/modules/dev-box/db.js +39 -0
  435. package/modules/dev-box/module.json +14 -0
  436. package/modules/dev-box/routes/box.js +977 -0
  437. package/modules/discord/CLAUDE.md +50 -0
  438. package/modules/discord/auth.js +54 -0
  439. package/modules/discord/bug-attachments.js +210 -0
  440. package/modules/discord/db.js +96 -0
  441. package/modules/discord/discord-approvals.js +145 -0
  442. package/modules/discord/discord-audit.js +29 -0
  443. package/modules/discord/discord-bot.js +545 -0
  444. package/modules/discord/discord-bugs.js +257 -0
  445. package/modules/discord/discord-channels.js +412 -0
  446. package/modules/discord/discord-inbound.js +147 -0
  447. package/modules/discord/discord-message-buffer.js +54 -0
  448. package/modules/discord/discord-reaction.js +186 -0
  449. package/modules/discord/discord-role-sync.js +318 -0
  450. package/modules/discord/module.json +14 -0
  451. package/modules/discord/pollers/discord-bot.js +19 -0
  452. package/modules/discord/routes/bug-attachments.js +44 -0
  453. package/modules/discord/routes/discord.js +102 -0
  454. package/modules/discord/ship-broadcast.js +78 -0
  455. package/modules/discord/webhook.js +74 -0
  456. package/modules/economy/CLAUDE.md +48 -0
  457. package/modules/economy/achievements.js +594 -0
  458. package/modules/economy/cost-classify.js +331 -0
  459. package/modules/economy/cost-summary-shape.js +90 -0
  460. package/modules/economy/cost.js +438 -0
  461. package/modules/economy/credits.js +430 -0
  462. package/modules/economy/migrations/economy_001_fix_gds_shipper_description.sql +91 -0
  463. package/modules/economy/module.json +10 -0
  464. package/modules/economy/reward.js +61 -0
  465. package/modules/economy/routes/cost.js +97 -0
  466. package/modules/economy/routes/leaderboard.js +26 -0
  467. package/modules/economy/routes/reward.js +42 -0
  468. package/modules/economy/streak.js +285 -0
  469. package/modules/grading/CLAUDE.md +42 -0
  470. package/modules/grading/db.js +39 -0
  471. package/modules/grading/grader-rubric.json +151 -0
  472. package/modules/grading/grader-workers/efficiency.js +213 -0
  473. package/modules/grading/grader-workers/extract-json.js +80 -0
  474. package/modules/grading/grader-workers/fence.js +100 -0
  475. package/modules/grading/grader-workers/hacker.js +239 -0
  476. package/modules/grading/grader-workers/index.js +183 -0
  477. package/modules/grading/grader-workers/narc.js +207 -0
  478. package/modules/grading/grader-workers/quality.js +198 -0
  479. package/modules/grading/grader-workers/rubric.js +20 -0
  480. package/modules/grading/grader-workers/worker-prompt.js +89 -0
  481. package/modules/grading/grader-workers/worker-verdict.js +50 -0
  482. package/modules/grading/grader.js +1790 -0
  483. package/modules/grading/module.json +12 -0
  484. package/modules/grading/routes/grade.js +51 -0
  485. package/modules/hall-ui/CLAUDE.md +61 -0
  486. package/modules/hall-ui/module.json +14 -0
  487. package/modules/hall-ui/public/atlas.html +180 -0
  488. package/modules/hall-ui/public/atlas.js +336 -0
  489. package/modules/hall-ui/public/box-panel.js +423 -0
  490. package/modules/hall-ui/public/brand-apply.js +117 -0
  491. package/modules/hall-ui/public/builders.js +3028 -0
  492. package/modules/hall-ui/public/chime.wav +0 -0
  493. package/modules/hall-ui/public/copy-on-click.js +174 -0
  494. package/modules/hall-ui/public/cursors/arrow.png +0 -0
  495. package/modules/hall-ui/public/cursors/hand-pointer.png +0 -0
  496. package/modules/hall-ui/public/cursors/magnifier-out.png +0 -0
  497. package/modules/hall-ui/public/cursors/magnifier.png +0 -0
  498. package/modules/hall-ui/public/diagram-viewer.css +125 -0
  499. package/modules/hall-ui/public/diagram-viewer.js +330 -0
  500. package/modules/hall-ui/public/diagrams.html +113 -0
  501. package/modules/hall-ui/public/dom-utils.js +212 -0
  502. package/modules/hall-ui/public/drachmae.html +68 -0
  503. package/modules/hall-ui/public/drachmae.js +136 -0
  504. package/modules/hall-ui/public/gate.html +143 -0
  505. package/modules/hall-ui/public/gate.js +228 -0
  506. package/modules/hall-ui/public/goals-page.js +1297 -0
  507. package/modules/hall-ui/public/goals.html +376 -0
  508. package/modules/hall-ui/public/goals.js +119 -0
  509. package/modules/hall-ui/public/hall-widgets.js +189 -0
  510. package/modules/hall-ui/public/harbor.html +167 -0
  511. package/modules/hall-ui/public/harbor.js +335 -0
  512. package/modules/hall-ui/public/index.html +116 -0
  513. package/modules/hall-ui/public/instances-panel.js +172 -0
  514. package/modules/hall-ui/public/md-reader.js +234 -0
  515. package/modules/hall-ui/public/modules.html +119 -0
  516. package/modules/hall-ui/public/modules.js +154 -0
  517. package/modules/hall-ui/public/new-project-wizard.js +186 -0
  518. package/modules/hall-ui/public/not-ready.html +48 -0
  519. package/modules/hall-ui/public/pair.html +119 -0
  520. package/modules/hall-ui/public/pair.js +132 -0
  521. package/modules/hall-ui/public/primer.css +404 -0
  522. package/modules/hall-ui/public/primer.html +85 -0
  523. package/modules/hall-ui/public/primer.js +641 -0
  524. package/modules/hall-ui/public/profile.html +85 -0
  525. package/modules/hall-ui/public/profile.js +236 -0
  526. package/modules/hall-ui/public/ranks.html +69 -0
  527. package/modules/hall-ui/public/ranks.js +206 -0
  528. package/modules/hall-ui/public/roadmap.html +209 -0
  529. package/modules/hall-ui/public/roadmap.js +375 -0
  530. package/modules/hall-ui/public/sessions.html +92 -0
  531. package/modules/hall-ui/public/sessions.js +222 -0
  532. package/modules/hall-ui/public/settings.html +640 -0
  533. package/modules/hall-ui/public/settings.js +1219 -0
  534. package/modules/hall-ui/public/shell.js +495 -0
  535. package/modules/hall-ui/public/style.css +1748 -0
  536. package/modules/hall-ui/public/task-detail.js +253 -0
  537. package/modules/hall-ui/public/task.html +83 -0
  538. package/modules/hall-ui/public/task.js +326 -0
  539. package/modules/hall-ui/public/watch.html +333 -0
  540. package/modules/hall-ui/public/watch.js +859 -0
  541. package/modules/hall-ui/public/work.html +469 -0
  542. package/modules/hall-ui/public/work.js +1292 -0
  543. package/modules/ideas/CLAUDE.md +37 -0
  544. package/modules/ideas/blockers.js +100 -0
  545. package/modules/ideas/inbox.js +459 -0
  546. package/modules/ideas/module.json +10 -0
  547. package/modules/ideas/routes/blockers.js +120 -0
  548. package/modules/ideas/routes/inbox.js +230 -0
  549. package/modules/lifecycle/CLAUDE.md +63 -0
  550. package/modules/lifecycle/conflict-resolve.js +436 -0
  551. package/modules/lifecycle/db.js +4970 -0
  552. package/modules/lifecycle/done-when.js +355 -0
  553. package/modules/lifecycle/github-push.js +969 -0
  554. package/modules/lifecycle/goal-conflicts.js +133 -0
  555. package/modules/lifecycle/hierarchy.js +104 -0
  556. package/modules/lifecycle/lifecycle.js +58 -0
  557. package/modules/lifecycle/merge-lock.js +261 -0
  558. package/modules/lifecycle/migrations/lifecycle_001_goal_first_leads.sql +28 -0
  559. package/modules/lifecycle/migrations/lifecycle_002_backfill_orphan_task_goals.sql +36 -0
  560. package/modules/lifecycle/module.json +13 -0
  561. package/modules/lifecycle/pollers/publish-reconciler.js +20 -0
  562. package/modules/lifecycle/publish-reconciler.js +418 -0
  563. package/modules/lifecycle/routes/analytics.js +63 -0
  564. package/modules/lifecycle/routes/claims.js +506 -0
  565. package/modules/lifecycle/routes/dependencies.js +82 -0
  566. package/modules/lifecycle/routes/done-when.js +136 -0
  567. package/modules/lifecycle/routes/gate-approvals.js +197 -0
  568. package/modules/lifecycle/routes/goals.js +1419 -0
  569. package/modules/lifecycle/routes/lifecycle.js +43 -0
  570. package/modules/lifecycle/routes/tasks.js +1703 -0
  571. package/modules/lifecycle/routes/versions.js +128 -0
  572. package/modules/lifecycle/ship-card.js +505 -0
  573. package/modules/lifecycle/ship-preflight.js +225 -0
  574. package/modules/lifecycle/task-classifier.js +512 -0
  575. package/modules/lifecycle/version-close.js +106 -0
  576. package/modules/memory/CLAUDE.md +40 -0
  577. package/modules/memory/learnings.js +275 -0
  578. package/modules/memory/memory.js +572 -0
  579. package/modules/memory/module.json +12 -0
  580. package/modules/memory/routes/learnings.js +90 -0
  581. package/modules/memory/routes/memory.js +916 -0
  582. package/modules/memory/routes/search.js +188 -0
  583. package/modules/memory/search-chunker.js +252 -0
  584. package/modules/memory/search.js +320 -0
  585. package/modules/onboarding/CLAUDE.md +55 -0
  586. package/modules/onboarding/approval-broadcast.js +59 -0
  587. package/modules/onboarding/db.js +54 -0
  588. package/modules/onboarding/module.json +10 -0
  589. package/modules/onboarding/newcomer-restock.js +212 -0
  590. package/modules/onboarding/onboarding-state.js +532 -0
  591. package/modules/onboarding/onboarding.js +508 -0
  592. package/modules/onboarding/port.js +63 -0
  593. package/modules/onboarding/routes/access-requests.js +208 -0
  594. package/modules/onboarding/routes/onboarding.js +54 -0
  595. package/modules/security/CLAUDE.md +44 -0
  596. package/modules/security/module.json +10 -0
  597. package/modules/security/routes/reports.js +251 -0
  598. package/modules/security/secret-scrub.js +201 -0
  599. package/modules/security/security.js +294 -0
  600. package/modules/sessions/CLAUDE.md +39 -0
  601. package/modules/sessions/bfg.js +357 -0
  602. package/modules/sessions/db.js +326 -0
  603. package/modules/sessions/module.json +10 -0
  604. package/modules/sessions/pulse.js +149 -0
  605. package/modules/sessions/routes/sessions.js +331 -0
  606. package/modules/status-ui/CLAUDE.md +47 -0
  607. package/modules/status-ui/module.json +14 -0
  608. package/modules/status-ui/public/README.md +29 -0
  609. package/modules/status-ui/public/cursors/arrow.png +0 -0
  610. package/modules/status-ui/public/cursors/hand-pointer.png +0 -0
  611. package/modules/status-ui/public/index.html +363 -0
  612. package/modules/status-ui/public/snapshots/.gitkeep +0 -0
  613. package/modules/status-ui/public/status.js +1375 -0
  614. package/modules/status-ui/public/style.css +1342 -0
  615. package/package-lock.json +975 -0
  616. package/package.json +28 -0
  617. package/public-docs/index.html +35 -0
  618. package/public-docs/redoc.standalone.js +1838 -0
  619. package/public-docs/redoc.standalone.js.LICENSE.txt +102 -0
  620. package/public-landing/buddha.png +0 -0
  621. package/public-landing/create.html +868 -0
  622. package/public-landing/index.html +168 -0
  623. package/scripts/bongos-mcp.js +28 -0
  624. package/scripts/cron/style_guide_audit.sh +134 -0
  625. package/scripts/discord/README.md +52 -0
  626. package/scripts/discord/lib.js +80 -0
  627. package/scripts/discord/post.js +51 -0
  628. package/scripts/gds/CLAUDE.md +144 -0
  629. package/scripts/gds/abandon.js +58 -0
  630. package/scripts/gds/adopt-detect.js +123 -0
  631. package/scripts/gds/adopt-import.js +111 -0
  632. package/scripts/gds/adopt-preflight.js +121 -0
  633. package/scripts/gds/api.js +78 -0
  634. package/scripts/gds/architect-audit.js +288 -0
  635. package/scripts/gds/art-stage.js +152 -0
  636. package/scripts/gds/audit-authorship.sh +243 -0
  637. package/scripts/gds/audit-deps.js +121 -0
  638. package/scripts/gds/audit-exports.js +128 -0
  639. package/scripts/gds/audit-replay.sh +161 -0
  640. package/scripts/gds/audit-ships.js +251 -0
  641. package/scripts/gds/autonomy-gate.js +135 -0
  642. package/scripts/gds/backfill-cost.js +165 -0
  643. package/scripts/gds/backfill-sessions.js +351 -0
  644. package/scripts/gds/backfill-task-rewards.js +408 -0
  645. package/scripts/gds/backup.js +94 -0
  646. package/scripts/gds/bfg-principal-token.js +65 -0
  647. package/scripts/gds/box-auth-check.js +160 -0
  648. package/scripts/gds/box-connect-lib.js +453 -0
  649. package/scripts/gds/box.js +1258 -0
  650. package/scripts/gds/build-bongos.js +94 -0
  651. package/scripts/gds/capture.js +123 -0
  652. package/scripts/gds/cf-tunnel.js +419 -0
  653. package/scripts/gds/ci-grade.js +82 -0
  654. package/scripts/gds/claim-recall.js +112 -0
  655. package/scripts/gds/claim.js +333 -0
  656. package/scripts/gds/classify-discipline.js +153 -0
  657. package/scripts/gds/claude-materialize.js +176 -0
  658. package/scripts/gds/cleanup-orphans.js +74 -0
  659. package/scripts/gds/cli-lib.js +518 -0
  660. package/scripts/gds/code.js +101 -0
  661. package/scripts/gds/codemod-rename-src-gds.js +145 -0
  662. package/scripts/gds/conductor-dispatch.js +426 -0
  663. package/scripts/gds/context-pack.js +228 -0
  664. package/scripts/gds/coreB-carve-proof.sh +716 -0
  665. package/scripts/gds/cost.js +47 -0
  666. package/scripts/gds/db-backup-nightly.sh +160 -0
  667. package/scripts/gds/db-backup-offsite.sh +152 -0
  668. package/scripts/gds/demote.js +40 -0
  669. package/scripts/gds/design-sync.js +62 -0
  670. package/scripts/gds/diagnose-achievements.js +118 -0
  671. package/scripts/gds/diagram-facts.js +319 -0
  672. package/scripts/gds/discipline-modes.json +25 -0
  673. package/scripts/gds/discord-channels.js +66 -0
  674. package/scripts/gds/do-api.js +176 -0
  675. package/scripts/gds/docs-entropy.js +403 -0
  676. package/scripts/gds/doctor.js +389 -0
  677. package/scripts/gds/feedback-latest.js +94 -0
  678. package/scripts/gds/fetch-art-key.js +106 -0
  679. package/scripts/gds/fetch-sound-prefs.js +88 -0
  680. package/scripts/gds/figma-design-sync.js +61 -0
  681. package/scripts/gds/fitness.js +1446 -0
  682. package/scripts/gds/gate-review.js +322 -0
  683. package/scripts/gds/gating.js +325 -0
  684. package/scripts/gds/gds-db-backup.service +24 -0
  685. package/scripts/gds/gds-db-backup.timer +20 -0
  686. package/scripts/gds/gds-db-offsite.service +29 -0
  687. package/scripts/gds/gds-db-offsite.timer +18 -0
  688. package/scripts/gds/gen-api-client.js +508 -0
  689. package/scripts/gds/gen-api-docs.js +1037 -0
  690. package/scripts/gds/gen-atlas.js +366 -0
  691. package/scripts/gds/gen-diagrams.js +608 -0
  692. package/scripts/gds/gen-file-map.js +196 -0
  693. package/scripts/gds/gen-repo-map.js +519 -0
  694. package/scripts/gds/gen-session-index.js +210 -0
  695. package/scripts/gds/git-merge-regen.js +147 -0
  696. package/scripts/gds/grade-smoke.js +205 -0
  697. package/scripts/gds/hooks/pre-push.js +169 -0
  698. package/scripts/gds/http-api-client.js +88 -0
  699. package/scripts/gds/init.js +903 -0
  700. package/scripts/gds/install-git-hooks.js +114 -0
  701. package/scripts/gds/key-card.js +213 -0
  702. package/scripts/gds/key-status.js +126 -0
  703. package/scripts/gds/land-watch.js +168 -0
  704. package/scripts/gds/learning-capture.js +145 -0
  705. package/scripts/gds/lib.sh +44 -0
  706. package/scripts/gds/linkify-refs.js +473 -0
  707. package/scripts/gds/local-preview-lib.js +159 -0
  708. package/scripts/gds/local-preview.js +95 -0
  709. package/scripts/gds/login.js +225 -0
  710. package/scripts/gds/main-audit.js +279 -0
  711. package/scripts/gds/me.js +88 -0
  712. package/scripts/gds/memory-map.js +174 -0
  713. package/scripts/gds/merge-lock.js +71 -0
  714. package/scripts/gds/migration-namespace.js +200 -0
  715. package/scripts/gds/mirror-lineage-check.js +319 -0
  716. package/scripts/gds/mirror-redact.js +305 -0
  717. package/scripts/gds/module.js +482 -0
  718. package/scripts/gds/newcomer-floor.config.json +13 -0
  719. package/scripts/gds/newcomer-floor.js +415 -0
  720. package/scripts/gds/newcomer-floor.templates.json +86 -0
  721. package/scripts/gds/newcomer-floor.test.js +235 -0
  722. package/scripts/gds/next-number.js +163 -0
  723. package/scripts/gds/oauth-secret.js +197 -0
  724. package/scripts/gds/onboard.js +217 -0
  725. package/scripts/gds/onboarding-analyze.js +226 -0
  726. package/scripts/gds/onboarding-config.js +178 -0
  727. package/scripts/gds/onboarding-ship.js +92 -0
  728. package/scripts/gds/package-core.js +684 -0
  729. package/scripts/gds/paste-token.js +131 -0
  730. package/scripts/gds/portability-smoke-art.sh +85 -0
  731. package/scripts/gds/portability-smoke-memory.sh +147 -0
  732. package/scripts/gds/provision.js +1324 -0
  733. package/scripts/gds/publish-manifest.js +307 -0
  734. package/scripts/gds/pulse.js +309 -0
  735. package/scripts/gds/push-main.js +45 -0
  736. package/scripts/gds/read-session-export.js +359 -0
  737. package/scripts/gds/reauth.js +149 -0
  738. package/scripts/gds/recall.js +135 -0
  739. package/scripts/gds/release.js +71 -0
  740. package/scripts/gds/render-ideas.js +173 -0
  741. package/scripts/gds/repo-metrics.js +363 -0
  742. package/scripts/gds/resolve-skill-model.js +57 -0
  743. package/scripts/gds/run-routine.js +197 -0
  744. package/scripts/gds/run-unit-tests.js +178 -0
  745. package/scripts/gds/sandbox-stage.js +653 -0
  746. package/scripts/gds/search-ingest-db.js +209 -0
  747. package/scripts/gds/search-ingest-md.js +225 -0
  748. package/scripts/gds/security-baseline-check.sh +180 -0
  749. package/scripts/gds/seed-adopt-tasks.js +75 -0
  750. package/scripts/gds/seed-api-contract-goal-tasks.js +141 -0
  751. package/scripts/gds/seed-bongos-app-tasks.js +294 -0
  752. package/scripts/gds/seed-bongos-coreB-tranche1-tasks.js +81 -0
  753. package/scripts/gds/seed-bongos-coreB-tranche2-tasks.js +100 -0
  754. package/scripts/gds/seed-bongos-goal-hierarchy-tasks.js +307 -0
  755. package/scripts/gds/seed-bongos-module-system-tasks.js +346 -0
  756. package/scripts/gds/seed-bongos-tasks.js +214 -0
  757. package/scripts/gds/seed-gds-v4-tasks.js +624 -0
  758. package/scripts/gds/seed-governance-tasks.js +340 -0
  759. package/scripts/gds/seed-medusa-tasks.js +440 -0
  760. package/scripts/gds/seed-module-upstreaming.js +219 -0
  761. package/scripts/gds/seed-productization-tasks.js +235 -0
  762. package/scripts/gds/seed-provisioning-tasks.js +83 -0
  763. package/scripts/gds/seed-staging.sh +104 -0
  764. package/scripts/gds/seed-v2-world-tasks.js +283 -0
  765. package/scripts/gds/seed-v3-tasks.js +1981 -0
  766. package/scripts/gds/session-cost.js +393 -0
  767. package/scripts/gds/session-digest.js +179 -0
  768. package/scripts/gds/session-eval.js +576 -0
  769. package/scripts/gds/session.js +404 -0
  770. package/scripts/gds/setup.js +504 -0
  771. package/scripts/gds/shell.js +100 -0
  772. package/scripts/gds/ship.js +3618 -0
  773. package/scripts/gds/smoke-batch-claim.sh +213 -0
  774. package/scripts/gds/smoke-batch-release.sh +296 -0
  775. package/scripts/gds/smoke-box.sh +150 -0
  776. package/scripts/gds/smoke-claim-consistency.sh +212 -0
  777. package/scripts/gds/smoke-dependencies.sh +84 -0
  778. package/scripts/gds/smoke-gds.js +125 -0
  779. package/scripts/gds/smoke-write.sh +168 -0
  780. package/scripts/gds/start.js +782 -0
  781. package/scripts/gds/status.js +365 -0
  782. package/scripts/gds/triage.js +366 -0
  783. package/scripts/gds/upgrade.js +421 -0
  784. package/scripts/gds/validate-design.js +254 -0
  785. package/scripts/gds/version-close.js +549 -0
  786. package/scripts/migrate.sh +183 -0
  787. package/scripts/migrate_cost_ledger.sh +178 -0
  788. package/scripts/public-mirror-export.js +291 -0
  789. package/scripts/render-diagrams.sh +88 -0
  790. package/scripts/security/dep-audit.js +276 -0
  791. package/scripts/setup-builder.js +274 -0
  792. package/scripts/setup-builder.sh +28 -0
  793. package/scripts/snapshots/backfill-history.mjs +292 -0
  794. package/scripts/snapshots/maybe-render-on-deploy.sh +66 -0
  795. package/scripts/snapshots/render-pre-art-eras.mjs +329 -0
  796. package/scripts/snapshots/render-world.mjs +322 -0
  797. package/scripts/status-mirror-sync.js +192 -0
  798. package/scripts/status-probe.mjs +185 -0
  799. package/server.js +212 -0
  800. package/src/asset-version.js +71 -0
  801. package/src/bongos/CLAUDE.md +64 -0
  802. package/src/bongos/api-errors.js +281 -0
  803. package/src/bongos/api-path-404.js +67 -0
  804. package/src/bongos/api-prefix.js +38 -0
  805. package/src/bongos/app-pair.js +172 -0
  806. package/src/bongos/auth.js +979 -0
  807. package/src/bongos/cascade-dispatch.js +123 -0
  808. package/src/bongos/db-kernel.js +252 -0
  809. package/src/bongos/db.js +911 -0
  810. package/src/bongos/goal-scope-check.js +120 -0
  811. package/src/bongos/hierarchy-config.js +189 -0
  812. package/src/bongos/live-channel.js +145 -0
  813. package/src/bongos/llm-cache.js +260 -0
  814. package/src/bongos/llm-pricing.js +220 -0
  815. package/src/bongos/metrics.js +286 -0
  816. package/src/bongos/middleware/audit.js +274 -0
  817. package/src/bongos/middleware/error-envelope.js +93 -0
  818. package/src/bongos/middleware/rate-limit.js +195 -0
  819. package/src/bongos/module-scope-map.js +271 -0
  820. package/src/bongos/optimizer.js +166 -0
  821. package/src/bongos/path-match.js +47 -0
  822. package/src/bongos/permission-path-check.js +248 -0
  823. package/src/bongos/pool.js +55 -0
  824. package/src/bongos/repo-info.js +91 -0
  825. package/src/bongos/route-rank-check.js +420 -0
  826. package/src/bongos/routes/CLAUDE.md +43 -0
  827. package/src/bongos/routes/_helpers.js +369 -0
  828. package/src/bongos/routes/audit-log.js +59 -0
  829. package/src/bongos/routes/auth.js +755 -0
  830. package/src/bongos/routes/backup.js +104 -0
  831. package/src/bongos/routes/builders.js +526 -0
  832. package/src/bongos/routes/healthz.js +16 -0
  833. package/src/bongos/routes/instance.js +129 -0
  834. package/src/bongos/routes/live.js +125 -0
  835. package/src/bongos/routes/llm-cache.js +119 -0
  836. package/src/bongos/routes/me.js +380 -0
  837. package/src/bongos/routes/modules.js +131 -0
  838. package/src/bongos/routes/optimizer.js +54 -0
  839. package/src/bongos/routes/public.js +476 -0
  840. package/src/bongos/routes/security.js +207 -0
  841. package/src/bongos/routes.js +166 -0
  842. package/src/bongos/secret-box.js +177 -0
  843. package/src/bongos/sensitive-surfaces.js +30 -0
  844. package/src/bongos/serve-internal.js +889 -0
  845. package/src/bongos/uptime-poller.js +90 -0
  846. package/src/bongos-downloads.js +70 -0
  847. package/src/branding.js +182 -0
  848. package/src/build-info.js +77 -0
  849. package/src/devbox-downloads.js +67 -0
  850. package/src/instance-config.js +200 -0
  851. package/src/module-api.js +366 -0
  852. package/src/module-loader/loader.js +268 -0
  853. package/src/module-loader/manifest-schema.js +140 -0
  854. package/src/module-loader/provenance.js +28 -0
  855. package/src/module-loader/semver.js +100 -0
  856. package/src/module-seams.js +291 -0
  857. package/src/modules.js +254 -0
  858. package/src/platform-server.js +135 -0
  859. package/src/ui/adapters/claude-design/index.js +333 -0
  860. package/src/ui/adapters/figma/index.js +328 -0
  861. package/tests/abandon_task.mjs +138 -0
  862. package/tests/achievements_reconcile.mjs +167 -0
  863. package/tests/adopt_detect.mjs +141 -0
  864. package/tests/adopt_import.mjs +132 -0
  865. package/tests/adopt_preflight.mjs +105 -0
  866. package/tests/anti_grid.mjs +89 -0
  867. package/tests/api_client.mjs +231 -0
  868. package/tests/api_docs.mjs +283 -0
  869. package/tests/api_error_envelope_buildcheck.mjs +56 -0
  870. package/tests/api_error_envelope_middleware.mjs +99 -0
  871. package/tests/api_error_registry.mjs +108 -0
  872. package/tests/api_pagination_contract.mjs +51 -0
  873. package/tests/api_path_404.mjs +96 -0
  874. package/tests/app_pair.mjs +168 -0
  875. package/tests/architect.mjs +140 -0
  876. package/tests/art_key.mjs +121 -0
  877. package/tests/art_stage.mjs +121 -0
  878. package/tests/asset_version.mjs +94 -0
  879. package/tests/atlas.mjs +69 -0
  880. package/tests/audit_deps.mjs +74 -0
  881. package/tests/audit_middleware.mjs +385 -0
  882. package/tests/auth_admission_gate.mjs +164 -0
  883. package/tests/auth_anthropic_identity.mjs +95 -0
  884. package/tests/auth_duplicate_cookie.mjs +56 -0
  885. package/tests/auth_error_brand.mjs +77 -0
  886. package/tests/auth_flow_slice1.mjs +223 -0
  887. package/tests/auth_inactive_builder.mjs +94 -0
  888. package/tests/auth_page_gate.mjs +92 -0
  889. package/tests/auth_session_ttl.mjs +155 -0
  890. package/tests/autonomous_fit_protected_paths.mjs +115 -0
  891. package/tests/autonomy_gate.mjs +85 -0
  892. package/tests/autotile_variants.mjs +256 -0
  893. package/tests/autotile_xverify.mjs +133 -0
  894. package/tests/backfill_sessions.mjs +101 -0
  895. package/tests/backfill_task_rewards.mjs +152 -0
  896. package/tests/backup_cli.mjs +104 -0
  897. package/tests/bfg.mjs +327 -0
  898. package/tests/bfg_write.mjs +423 -0
  899. package/tests/bongos_box_connect.mjs +89 -0
  900. package/tests/bongos_dispatch.mjs +74 -0
  901. package/tests/bongos_downloads.mjs +118 -0
  902. package/tests/bongos_login.mjs +34 -0
  903. package/tests/box_access.mjs +186 -0
  904. package/tests/box_auth_check.mjs +114 -0
  905. package/tests/box_code_staleness.mjs +116 -0
  906. package/tests/box_connect_e2e.mjs +196 -0
  907. package/tests/box_cost.mjs +70 -0
  908. package/tests/box_dns_repoint.mjs +151 -0
  909. package/tests/box_host_keys.mjs +102 -0
  910. package/tests/box_onboard.mjs +317 -0
  911. package/tests/box_scope_session.mjs +115 -0
  912. package/tests/box_ship_permissions.mjs +71 -0
  913. package/tests/box_terminal_ssrf.mjs +90 -0
  914. package/tests/boxes.mjs +1255 -0
  915. package/tests/branding.mjs +88 -0
  916. package/tests/bug_attachments.mjs +189 -0
  917. package/tests/build_info.mjs +88 -0
  918. package/tests/builder_needs.mjs +99 -0
  919. package/tests/cap_newcomer_tasks.mjs +114 -0
  920. package/tests/cascade-dispatch.mjs +95 -0
  921. package/tests/cf_tunnel.mjs +459 -0
  922. package/tests/claim_recall.mjs +150 -0
  923. package/tests/claims_bind_session.mjs +47 -0
  924. package/tests/claude_materialize.mjs +148 -0
  925. package/tests/cli_box_scope.mjs +52 -0
  926. package/tests/cli_lib_formatters.mjs +87 -0
  927. package/tests/cli_token_reissue.mjs +147 -0
  928. package/tests/client_bundle_route.mjs +47 -0
  929. package/tests/cloud_bongos_pack.mjs +85 -0
  930. package/tests/conductor.mjs +496 -0
  931. package/tests/confirm_attribution.mjs +156 -0
  932. package/tests/conflict_resolve.mjs +462 -0
  933. package/tests/context_pack.mjs +240 -0
  934. package/tests/cost.mjs +94 -0
  935. package/tests/cost_audit.mjs +122 -0
  936. package/tests/cost_budget_status.mjs +78 -0
  937. package/tests/cost_reconcile.mjs +102 -0
  938. package/tests/cost_summary_shape.mjs +94 -0
  939. package/tests/credit_grant.mjs +376 -0
  940. package/tests/criterion_progress.mjs +220 -0
  941. package/tests/criterion_proposal.mjs +109 -0
  942. package/tests/criterion_ref_parse.mjs +53 -0
  943. package/tests/currency_label.mjs +119 -0
  944. package/tests/day_cycle.mjs +197 -0
  945. package/tests/dependencies_migration.mjs +77 -0
  946. package/tests/dependencies_polymorphic.mjs +212 -0
  947. package/tests/deploy_instance.mjs +78 -0
  948. package/tests/devbox_downloads.mjs +122 -0
  949. package/tests/diff_derived_signals.mjs +115 -0
  950. package/tests/discord_approvals.mjs +174 -0
  951. package/tests/discord_autojoin.mjs +158 -0
  952. package/tests/discord_bot.mjs +94 -0
  953. package/tests/discord_bugs.mjs +297 -0
  954. package/tests/discord_channels.mjs +337 -0
  955. package/tests/discord_foundation.mjs +76 -0
  956. package/tests/discord_inbound.mjs +190 -0
  957. package/tests/discord_message_buffer.mjs +87 -0
  958. package/tests/discord_oauth.mjs +55 -0
  959. package/tests/discord_reaction.mjs +170 -0
  960. package/tests/discord_role_sync.mjs +151 -0
  961. package/tests/display_name.mjs +123 -0
  962. package/tests/dns_preflight.mjs +74 -0
  963. package/tests/event_sound_prefs.mjs +194 -0
  964. package/tests/eviction_policy.mjs +279 -0
  965. package/tests/figma_adapter.mjs +199 -0
  966. package/tests/file_map.mjs +91 -0
  967. package/tests/first_admin_bootstrap.mjs +40 -0
  968. package/tests/fitness.mjs +435 -0
  969. package/tests/fitness_two_root.mjs +104 -0
  970. package/tests/fixtures/session-506-digest.json +34 -0
  971. package/tests/gate_approvals.mjs +451 -0
  972. package/tests/gate_review.mjs +152 -0
  973. package/tests/git_merge_regen.mjs +151 -0
  974. package/tests/github_push_ancestry.mjs +119 -0
  975. package/tests/github_push_land_proof.mjs +127 -0
  976. package/tests/github_push_merge_driver.mjs +175 -0
  977. package/tests/goal_abuse_matrix.mjs +187 -0
  978. package/tests/goal_achievement.mjs +146 -0
  979. package/tests/goal_conflicts.mjs +305 -0
  980. package/tests/goal_dependencies_read.mjs +73 -0
  981. package/tests/goal_member_roles.mjs +420 -0
  982. package/tests/goal_membership_requests.mjs +332 -0
  983. package/tests/goal_routes.mjs +203 -0
  984. package/tests/goal_scope_check.mjs +167 -0
  985. package/tests/goal_tier.mjs +312 -0
  986. package/tests/goal_wall_proof.mjs +112 -0
  987. package/tests/grade_attempts.mjs +73 -0
  988. package/tests/grade_attribution.mjs +117 -0
  989. package/tests/grade_diff_hash.mjs +191 -0
  990. package/tests/grade_server_authoritative.mjs +305 -0
  991. package/tests/grade_unavailable_display.mjs +66 -0
  992. package/tests/grader.mjs +1960 -0
  993. package/tests/grader_bypass.mjs +57 -0
  994. package/tests/grader_panel.mjs +252 -0
  995. package/tests/grader_prompt_fencing.mjs +192 -0
  996. package/tests/helpers.mjs +52 -0
  997. package/tests/hierarchy-config.mjs +81 -0
  998. package/tests/init.mjs +463 -0
  999. package/tests/instance_config.mjs +76 -0
  1000. package/tests/instance_manifest.mjs +110 -0
  1001. package/tests/interaction_prefs.mjs +72 -0
  1002. package/tests/key-card.mjs +110 -0
  1003. package/tests/key-status.mjs +45 -0
  1004. package/tests/land_watch.mjs +115 -0
  1005. package/tests/lifecycle-card.mjs +145 -0
  1006. package/tests/lifecycle_breakdown_view.mjs +236 -0
  1007. package/tests/linkify_refs.mjs +264 -0
  1008. package/tests/live_channel.mjs +128 -0
  1009. package/tests/live_sse.mjs +122 -0
  1010. package/tests/llm-cache-route.mjs +110 -0
  1011. package/tests/llm-cache.mjs +140 -0
  1012. package/tests/llm-pricing.mjs +152 -0
  1013. package/tests/local_preview.mjs +136 -0
  1014. package/tests/main_audit.mjs +137 -0
  1015. package/tests/memory_isolation.mjs +431 -0
  1016. package/tests/memory_map.mjs +107 -0
  1017. package/tests/memory_pull.mjs +429 -0
  1018. package/tests/merge_lease.mjs +100 -0
  1019. package/tests/merge_lock.mjs +312 -0
  1020. package/tests/metrics.mjs +147 -0
  1021. package/tests/migration_allocation.mjs +146 -0
  1022. package/tests/migration_namespace.mjs +185 -0
  1023. package/tests/migrations_fresh_replay_guard.mjs +38 -0
  1024. package/tests/mirror_lineage_fulltree.mjs +45 -0
  1025. package/tests/mirror_lineage_proof.mjs +72 -0
  1026. package/tests/mirror_redact.mjs +193 -0
  1027. package/tests/model_alloc_prefs.mjs +63 -0
  1028. package/tests/module-scope-map.mjs +88 -0
  1029. package/tests/module_api.mjs +110 -0
  1030. package/tests/module_cli.mjs +292 -0
  1031. package/tests/module_contributions.mjs +148 -0
  1032. package/tests/module_loader.mjs +286 -0
  1033. package/tests/module_manifest.mjs +194 -0
  1034. package/tests/module_owned_tests.mjs +105 -0
  1035. package/tests/module_provenance.mjs +34 -0
  1036. package/tests/module_route_rank.mjs +147 -0
  1037. package/tests/module_seams.mjs +68 -0
  1038. package/tests/modules.mjs +156 -0
  1039. package/tests/modules_route.mjs +38 -0
  1040. package/tests/newcomer_floor.mjs +17 -0
  1041. package/tests/newcomer_restock.mjs +80 -0
  1042. package/tests/next_number.mjs +154 -0
  1043. package/tests/oauth_secret.mjs +60 -0
  1044. package/tests/onboard.mjs +70 -0
  1045. package/tests/onboarding_gate.mjs +267 -0
  1046. package/tests/onboarding_observability.mjs +344 -0
  1047. package/tests/onboarding_route_signals.mjs +175 -0
  1048. package/tests/onboarding_state.mjs +346 -0
  1049. package/tests/one_active_claim_per_session.mjs +111 -0
  1050. package/tests/orphaned_claim_ship.mjs +135 -0
  1051. package/tests/override_attribution.mjs +142 -0
  1052. package/tests/override_request_ownership.mjs +235 -0
  1053. package/tests/package_core.mjs +435 -0
  1054. package/tests/parallel-load.mjs +402 -0
  1055. package/tests/path_match.mjs +62 -0
  1056. package/tests/permission_path.mjs +151 -0
  1057. package/tests/platform_boot.mjs +167 -0
  1058. package/tests/pre_compact.mjs +60 -0
  1059. package/tests/preview_server.mjs +127 -0
  1060. package/tests/profile_route.mjs +197 -0
  1061. package/tests/provision.mjs +805 -0
  1062. package/tests/provisioning_github_app_routes.mjs +98 -0
  1063. package/tests/public_mirror_export.mjs +124 -0
  1064. package/tests/public_task_resolver.mjs +112 -0
  1065. package/tests/publish_branch_route.mjs +422 -0
  1066. package/tests/publish_manifest.mjs +184 -0
  1067. package/tests/publish_reconciler.mjs +728 -0
  1068. package/tests/rank_gate.mjs +463 -0
  1069. package/tests/rate_limit_public.mjs +217 -0
  1070. package/tests/read_session_export.mjs +89 -0
  1071. package/tests/recall_cli.mjs +82 -0
  1072. package/tests/recall_hints.mjs +119 -0
  1073. package/tests/recent_shipped_window.mjs +80 -0
  1074. package/tests/regrade_eligibility.mjs +202 -0
  1075. package/tests/render_prefs.mjs +122 -0
  1076. package/tests/repo_map.mjs +92 -0
  1077. package/tests/reward_gate.mjs +284 -0
  1078. package/tests/route_rank_check.mjs +243 -0
  1079. package/tests/route_validation.mjs +490 -0
  1080. package/tests/routine_timers.mjs +72 -0
  1081. package/tests/rubric_fixtures.mjs +140 -0
  1082. package/tests/run_routine.mjs +69 -0
  1083. package/tests/sandbox_stage.mjs +582 -0
  1084. package/tests/scrubber_corpus.mjs +257 -0
  1085. package/tests/search_chunker.mjs +209 -0
  1086. package/tests/search_ingest.mjs +96 -0
  1087. package/tests/search_ingest_db.mjs +88 -0
  1088. package/tests/search_isolation.mjs +204 -0
  1089. package/tests/search_route.mjs +95 -0
  1090. package/tests/secret_box.mjs +189 -0
  1091. package/tests/security_docs.mjs +186 -0
  1092. package/tests/security_route.mjs +108 -0
  1093. package/tests/served_paths_shipped.mjs +118 -0
  1094. package/tests/session-cards.mjs +142 -0
  1095. package/tests/session_506_regression.mjs +69 -0
  1096. package/tests/session_caps.mjs +238 -0
  1097. package/tests/session_cost.mjs +133 -0
  1098. package/tests/session_digest.mjs +73 -0
  1099. package/tests/session_eval.mjs +386 -0
  1100. package/tests/session_index.mjs +105 -0
  1101. package/tests/session_records.mjs +369 -0
  1102. package/tests/session_search.mjs +78 -0
  1103. package/tests/session_start_freshness.mjs +123 -0
  1104. package/tests/setup_smoke.mjs +154 -0
  1105. package/tests/ship_autolinkify.mjs +60 -0
  1106. package/tests/ship_automerge_injection.mjs +174 -0
  1107. package/tests/ship_broadcast_routing.mjs +125 -0
  1108. package/tests/ship_broadcast_server.mjs +175 -0
  1109. package/tests/ship_card.mjs +280 -0
  1110. package/tests/ship_ci_deploy.mjs +374 -0
  1111. package/tests/ship_preflight.mjs +368 -0
  1112. package/tests/ship_premerge.mjs +75 -0
  1113. package/tests/ship_resume.mjs +129 -0
  1114. package/tests/ship_upload_skip.mjs +89 -0
  1115. package/tests/ship_zero_commit.mjs +60 -0
  1116. package/tests/single_host_hall.mjs +98 -0
  1117. package/tests/skill_prefs.mjs +150 -0
  1118. package/tests/smoke_preflight.mjs +66 -0
  1119. package/tests/snapshot_picker.mjs +233 -0
  1120. package/tests/sound_prefs.mjs +198 -0
  1121. package/tests/sprite_facing.mjs +35 -0
  1122. package/tests/status-probe.mjs +89 -0
  1123. package/tests/status_backfill.mjs +94 -0
  1124. package/tests/stop_refcheck.mjs +127 -0
  1125. package/tests/streak.mjs +175 -0
  1126. package/tests/subdomain_auth.mjs +58 -0
  1127. package/tests/task_classifier_vocab.mjs +41 -0
  1128. package/tests/task_create_idempotency.mjs +96 -0
  1129. package/tests/task_goal_link.mjs +93 -0
  1130. package/tests/task_ownership_gate.mjs +125 -0
  1131. package/tests/terrain_picker.mjs +298 -0
  1132. package/tests/touches_overlap.mjs +75 -0
  1133. package/tests/triage_development.mjs +140 -0
  1134. package/tests/turn_in_place.mjs +280 -0
  1135. package/tests/upgrade.mjs +357 -0
  1136. package/tests/verify_task_guard.mjs +64 -0
  1137. package/tests/version_close.mjs +76 -0
  1138. package/tests/versions_route.mjs +93 -0
  1139. package/tests/walkability.mjs +188 -0
  1140. package/tests/wandering_prefs.mjs +176 -0
  1141. package/tests/worktree_disambiguation.mjs +105 -0
  1142. package/tests/worktree_guard.mjs +90 -0
@@ -0,0 +1,979 @@
1
+ // GDS auth (renamed from PMS in V3.R01).
2
+ //
3
+ // Two flows that produce the same `builder_sessions` row:
4
+ //
5
+ // - Device Flow (CLI):
6
+ // 1. POST /api/gds/auth/device/start
7
+ // → { user_code, verification_uri, device_code, interval, expires_in }
8
+ // The CLI shows user_code to the user and asks them to open
9
+ // verification_uri in a browser.
10
+ // 2. CLI polls POST /api/gds/auth/device/poll { device_code }
11
+ // → 'pending' | 'slow_down' | 'expired' | 'denied' |
12
+ // { token, builder: {...} }
13
+ //
14
+ // - Web Flow (browser):
15
+ // 1. GET /builders/auth/github → 302 to github.com/login/oauth/authorize
16
+ // 2. GET /builders/auth/github/callback?code=... → exchanges, sets cookie, 302 /builders
17
+ //
18
+ // Both flows hit GitHub's `GET /user` to learn the GitHub user id+login,
19
+ // then upsert into `builders` and create a `builder_sessions` row.
20
+ //
21
+ // Credentials:
22
+ // GDS_GITHUB_CLIENT_ID / GDS_GITHUB_CLIENT_SECRET in env (systemd EnvironmentFile).
23
+ // Back-compat: also accepts the legacy PMS_GITHUB_CLIENT_ID / PMS_GITHUB_CLIENT_SECRET
24
+ // names so the droplet's EnvironmentFile rename can lag the code deploy.
25
+ // Local-dev fallback: ~/.config/otb/gds-github.json (or legacy pms-github.json).
26
+
27
+ const crypto = require('node:crypto');
28
+ const fs = require('node:fs');
29
+ const path = require('node:path');
30
+ const os = require('node:os');
31
+
32
+ // Identity/auth/admission + the rank vocabulary are KERNEL primitives — auth.js is
33
+ // a KERNEL_FILE, so it resolves them from ./db-kernel, never the ./db domain residue
34
+ // (ADR 0102, finishing ADR 0091 §1). db.js re-exports the same names for its other
35
+ // callers, so nothing downstream changed.
36
+ const {
37
+ upsertBuilder,
38
+ seatFirstAdminIfUnclaimed,
39
+ createSession,
40
+ lookupSession,
41
+ getBuilderById,
42
+ getBuilderByGithubId,
43
+ hasInvitedAccessRequest,
44
+ RANKS,
45
+ } = require('./db-kernel');
46
+ const { pool } = require('./pool');
47
+ // The onboarding state machine carved into modules/onboarding/ (BV1.R81 / ADR
48
+ // 0093 §1). The auth callbacks mark `auth_complete` through the `onboarding`
49
+ // kernel port — never a direct require of a module. Default-on (present in prod);
50
+ // the optional `?.` degrades to a no-op when the module is off (the stage tick is
51
+ // best-effort anyway).
52
+ const seams = require('../module-seams');
53
+ const ic = require('../instance-config');
54
+ const { branding, userAgent } = require('../branding');
55
+
56
+ const SCOPE = 'read:user';
57
+
58
+ // In-memory map of device_code → { interval, expires_at, last_polled_at, github_creds }
59
+ // Device flow is ephemeral; restart of the Node process invalidates pending flows
60
+ // (the user just re-runs the setup skill). Acceptable for V1 — scaling up means
61
+ // moving this to Redis or a tiny `device_flows` table.
62
+ const deviceFlows = new Map();
63
+
64
+ function loadGithubCreds() {
65
+ // Resolve the configured instance prefix first (CLOUDBONGOS_ on a fresh
66
+ // instance, OTB_ on this one), then the legacy GDS_/PMS_ names — so the
67
+ // droplet's EnvironmentFile rename can lag the code deploy without breaking
68
+ // auth (R61 / task 1200).
69
+ const clientId = ic.resolveEnv('GITHUB_CLIENT_ID');
70
+ const clientSecret = ic.resolveEnv('GITHUB_CLIENT_SECRET');
71
+ if (clientId && clientSecret) {
72
+ return { client_id: clientId, client_secret: clientSecret };
73
+ }
74
+ // Local-dev fallback: try the new filename first, then the legacy one — each
75
+ // across the configured config dir and the legacy ~/.config/otb dir.
76
+ for (const name of ['gds-github.json', 'pms-github.json']) {
77
+ try {
78
+ const raw = ic.readConfigFileSync(name);
79
+ if (raw) {
80
+ const creds = JSON.parse(raw);
81
+ if (creds.client_id && creds.client_secret) return creds;
82
+ }
83
+ } catch (_) {
84
+ /* try next */
85
+ }
86
+ }
87
+ return null;
88
+ }
89
+
90
+ function authConfigured() {
91
+ return !!loadGithubCreds();
92
+ }
93
+
94
+ // Admission posture (SR-19 / task #1000). BOTH enrollment paths — the web hall
95
+ // sign-in (completeWebFlow) and the CLI device flow (pollDeviceFlow) — are
96
+ // "invite-gated by default": a FIRST-TIME GitHub account is admitted only if an
97
+ // Archon marked their access_request 'invited'. An EXISTING builder (a row
98
+ // already exists for their github_id) ALWAYS passes — the gate never locks out
99
+ // someone already enrolled.
100
+ //
101
+ // OTB_OPEN_XENOS_ENROLLMENT is the escape-hatch: set it truthy ('1' or 'true')
102
+ // to restore fully-open self-enrollment (anyone with a GitHub account becomes a
103
+ // Xenos) WITHOUT a code change, for an owner who deliberately wants open
104
+ // enrollment. Default (unset/anything-else) = invite required. See ADR 0050.
105
+ function openEnrollmentEnabled() {
106
+ const v = ic.resolveEnv('OPEN_XENOS_ENROLLMENT');
107
+ return v === '1' || v === 'true';
108
+ }
109
+
110
+ // PURE admission decision shared by both flows so they CANNOT drift (SR-19).
111
+ // Returns true when a first-time account must be blocked. `existing` is the
112
+ // builder row (or null) from getBuilderByGithubId; `approved` is the result of
113
+ // hasInvitedAccessRequest. Blocks only when: not already a builder AND open
114
+ // enrollment is off AND not invite-approved. Exported for direct unit testing.
115
+ function firstTimeAdmissionBlocked({ existing, approved, login }) {
116
+ if (existing || openEnrollmentEnabled() || approved) return false;
117
+ // The configured firstAdmin is always admitted on a fresh instance so the
118
+ // bootstrap (maybeSeatFirstAdmin) can seat them as the first Archon — the owner
119
+ // must not be gated out of their own new instance when no Archon exists yet to
120
+ // approve them (the chicken-and-egg R62 / ADR 0062 §6 left open).
121
+ // seatFirstAdminIfUnclaimed is race-safe and only fires while NO Archon exists,
122
+ // so this never escalates an account on an established instance; it only lets
123
+ // the configured owner login through admission.
124
+ const fa = firstAdminLogin();
125
+ if (fa && String(login || '').toLowerCase() === fa.toLowerCase()) return false;
126
+ return true;
127
+ }
128
+
129
+ // First-admin bootstrap (R62 / task 1201, ADR 0062). The configured firstAdmin
130
+ // login — an install-time env override (<PREFIX>_FIRST_ADMIN, incl. legacy) wins,
131
+ // else branding.firstAdmin. Empty/unset (the vanilla starter) means "no one is
132
+ // auto-seated"; the owner sets it when scaffolding the instance. Trimmed; '' → null.
133
+ function firstAdminLogin() {
134
+ let login = ic.resolveEnv('FIRST_ADMIN');
135
+ if (login === undefined) {
136
+ try { login = branding().firstAdmin; } catch { login = undefined; }
137
+ }
138
+ login = (login || '').trim();
139
+ return login || null;
140
+ }
141
+
142
+ // Seat the configured firstAdmin as Archon at their first enrollment, but only
143
+ // while NO Archon exists yet (the db guard is atomic + race-safe, so this can
144
+ // never escalate an account once an owner exists). Best-effort: a bootstrap
145
+ // hiccup must never fail the sign-in. Returns the builder, with rank updated to
146
+ // 'archon' on the returned object if this call seated it.
147
+ async function maybeSeatFirstAdmin(builder) {
148
+ const configured = firstAdminLogin();
149
+ if (!configured || !builder || builder.rank === 'archon') return builder;
150
+ if (String(builder.github_login || '').toLowerCase() !== configured.toLowerCase()) return builder;
151
+ try {
152
+ const seated = await seatFirstAdminIfUnclaimed(builder.id, builder.github_login);
153
+ if (seated) {
154
+ console.log(`[auth] first-admin bootstrap: seated ${builder.github_login} as Archon (configured firstAdmin, no prior Archon).`);
155
+ return { ...builder, rank: 'archon' };
156
+ }
157
+ } catch (err) {
158
+ console.error('[auth] first-admin bootstrap failed (non-fatal):', err.message);
159
+ }
160
+ return builder;
161
+ }
162
+
163
+ function newSessionToken() {
164
+ return crypto.randomBytes(32).toString('hex');
165
+ }
166
+
167
+ // -------------------------------------------------------------------------
168
+ // GitHub helpers
169
+ // -------------------------------------------------------------------------
170
+
171
+ async function fetchGithubUser(accessToken) {
172
+ const res = await fetch('https://api.github.com/user', {
173
+ headers: {
174
+ Authorization: `Bearer ${accessToken}`,
175
+ 'User-Agent': userAgent(),
176
+ Accept: 'application/vnd.github+json',
177
+ },
178
+ });
179
+ if (!res.ok) {
180
+ const text = await res.text();
181
+ throw new Error(`GitHub /user failed (${res.status}): ${text}`);
182
+ }
183
+ return res.json();
184
+ }
185
+
186
+ async function exchangeCodeForToken(code, redirectUri) {
187
+ const creds = loadGithubCreds();
188
+ if (!creds) throw new Error('GitHub OAuth credentials not configured');
189
+ const res = await fetch('https://github.com/login/oauth/access_token', {
190
+ method: 'POST',
191
+ headers: {
192
+ 'Content-Type': 'application/x-www-form-urlencoded',
193
+ Accept: 'application/json',
194
+ },
195
+ body: new URLSearchParams({
196
+ client_id: creds.client_id,
197
+ client_secret: creds.client_secret,
198
+ code,
199
+ redirect_uri: redirectUri,
200
+ }).toString(),
201
+ });
202
+ const data = await res.json();
203
+ if (data.error) throw new Error(`GitHub token exchange: ${data.error_description || data.error}`);
204
+ return data.access_token;
205
+ }
206
+
207
+ // -------------------------------------------------------------------------
208
+ // Device Flow (CLI)
209
+ // -------------------------------------------------------------------------
210
+
211
+ async function startDeviceFlow() {
212
+ const creds = loadGithubCreds();
213
+ if (!creds) throw new Error('GitHub OAuth credentials not configured');
214
+ const res = await fetch('https://github.com/login/device/code', {
215
+ method: 'POST',
216
+ headers: {
217
+ 'Content-Type': 'application/x-www-form-urlencoded',
218
+ Accept: 'application/json',
219
+ },
220
+ body: new URLSearchParams({ client_id: creds.client_id, scope: SCOPE }).toString(),
221
+ });
222
+ if (!res.ok) {
223
+ const text = await res.text();
224
+ throw new Error(`device/code failed (${res.status}): ${text}`);
225
+ }
226
+ const data = await res.json();
227
+ // data: { device_code, user_code, verification_uri, expires_in, interval }
228
+ deviceFlows.set(data.device_code, {
229
+ interval: data.interval || 5,
230
+ expires_at: Date.now() + data.expires_in * 1000,
231
+ last_polled_at: 0,
232
+ });
233
+ // Reap expired flows (cheap)
234
+ for (const [k, v] of deviceFlows) {
235
+ if (v.expires_at < Date.now()) deviceFlows.delete(k);
236
+ }
237
+ return {
238
+ device_code: data.device_code,
239
+ user_code: data.user_code,
240
+ verification_uri: data.verification_uri,
241
+ expires_in: data.expires_in,
242
+ interval: data.interval || 5,
243
+ };
244
+ }
245
+
246
+ async function pollDeviceFlow(deviceCode, userAgent) {
247
+ const flow = deviceFlows.get(deviceCode);
248
+ if (!flow) return { state: 'expired' };
249
+ if (flow.expires_at < Date.now()) {
250
+ deviceFlows.delete(deviceCode);
251
+ return { state: 'expired' };
252
+ }
253
+ // Server-side throttling: GitHub returns slow_down if we hammer; we
254
+ // also enforce the interval client-to-server.
255
+ const now = Date.now();
256
+ if (now - flow.last_polled_at < flow.interval * 1000) {
257
+ return { state: 'slow_down', interval: flow.interval };
258
+ }
259
+ flow.last_polled_at = now;
260
+
261
+ const creds = loadGithubCreds();
262
+ const res = await fetch('https://github.com/login/oauth/access_token', {
263
+ method: 'POST',
264
+ headers: {
265
+ 'Content-Type': 'application/x-www-form-urlencoded',
266
+ Accept: 'application/json',
267
+ },
268
+ body: new URLSearchParams({
269
+ client_id: creds.client_id,
270
+ device_code: deviceCode,
271
+ grant_type: 'urn:ietf:params:oauth:grant-type:device_code',
272
+ }).toString(),
273
+ });
274
+ const data = await res.json();
275
+ if (data.error === 'authorization_pending') return { state: 'pending' };
276
+ if (data.error === 'slow_down') {
277
+ flow.interval = (flow.interval || 5) + 5;
278
+ return { state: 'slow_down', interval: flow.interval };
279
+ }
280
+ if (data.error === 'expired_token') {
281
+ deviceFlows.delete(deviceCode);
282
+ return { state: 'expired' };
283
+ }
284
+ if (data.error === 'access_denied') {
285
+ deviceFlows.delete(deviceCode);
286
+ return { state: 'denied' };
287
+ }
288
+ if (data.access_token) {
289
+ deviceFlows.delete(deviceCode);
290
+ const ghUser = await fetchGithubUser(data.access_token);
291
+ // Admission gate (SR-19 / task #1000): mirror completeWebFlow EXACTLY. The
292
+ // CLI device flow used to call upsertBuilder unconditionally, so any GitHub
293
+ // account could self-enroll as a Xenos + mint a CLI session with no Archon
294
+ // approval — contradicting the "Archon admission is the only gate" rule. Now
295
+ // a FIRST-TIME account (no existing builder row for this github_id) is
296
+ // admitted only if open enrollment is on OR an Archon marked their
297
+ // access_request 'invited'. An EXISTING builder ALWAYS passes, so an approved
298
+ // builder can never be locked out of CLI enrollment. See ADR 0050.
299
+ const existing = await getBuilderByGithubId(ghUser.id);
300
+ // Only consult the access_request table for a genuine first-timer (and only
301
+ // when open enrollment is off) — an existing builder short-circuits to
302
+ // ALWAYS-pass, matching completeWebFlow's `if (!existing)` lookup.
303
+ if (!existing && !openEnrollmentEnabled()) {
304
+ const approved = await hasInvitedAccessRequest(ghUser.login);
305
+ if (firstTimeAdmissionBlocked({ existing, approved, login: ghUser.login })) {
306
+ const err = new Error('not_approved');
307
+ err.code = 'NOT_APPROVED';
308
+ throw err;
309
+ }
310
+ }
311
+ let builder = await upsertBuilder({
312
+ githubId: ghUser.id,
313
+ githubLogin: ghUser.login,
314
+ displayName: ghUser.name || ghUser.login,
315
+ avatarUrl: ghUser.avatar_url,
316
+ });
317
+ builder = await maybeSeatFirstAdmin(builder);
318
+ const token = newSessionToken();
319
+ await createSession({
320
+ builderId: builder.id,
321
+ token,
322
+ source: 'cli',
323
+ userAgent: userAgent ?? null,
324
+ // CLI tokens have no expiry (manually revocable). Web tokens expire.
325
+ expiresAt: null,
326
+ });
327
+ // V3.R75 (#294): tick the onboarding state machine. Best-effort — auth
328
+ // success must NOT fail because the onboarding plumbing tripped.
329
+ await seams.resolveOptional('onboarding')?.safeMarkStage(pool, builder.id, 'auth_complete', builder);
330
+ return { state: 'ok', token, builder };
331
+ }
332
+ return { state: 'pending' };
333
+ }
334
+
335
+ // -------------------------------------------------------------------------
336
+ // Web Flow (browser)
337
+ // -------------------------------------------------------------------------
338
+
339
+ function buildAuthorizeUrl(state, redirectUri) {
340
+ const creds = loadGithubCreds();
341
+ if (!creds) throw new Error('GitHub OAuth credentials not configured');
342
+ const u = new URL('https://github.com/login/oauth/authorize');
343
+ u.searchParams.set('client_id', creds.client_id);
344
+ u.searchParams.set('redirect_uri', redirectUri);
345
+ u.searchParams.set('scope', SCOPE);
346
+ u.searchParams.set('state', state);
347
+ return u.toString();
348
+ }
349
+
350
+ async function completeWebFlow({ code, redirectUri, userAgent }) {
351
+ const accessToken = await exchangeCodeForToken(code, redirectUri);
352
+ const ghUser = await fetchGithubUser(accessToken);
353
+ // Approval gate (2026-05-30): the web hall sign-in no longer admits anyone
354
+ // with a GitHub account. A first-time visitor must have been approved by an
355
+ // Archon (their access_request marked 'invited') before a builder row is
356
+ // created. This Archon approval is the ONLY gate — newcomers build on hosted
357
+ // dev boxes, so there is no GitHub repo-access / collaborator check anywhere
358
+ // (2026-06-07). Established builders (a row already exists for this github_id)
359
+ // always pass — this only blocks NEW first-time entries.
360
+ //
361
+ // SR-19 / task #1000 (2026-06-13): the CLI device flow (pollDeviceFlow,
362
+ // /builder-setup) is now gated the SAME way — it previously called
363
+ // upsertBuilder unconditionally, letting any GitHub account self-enroll as a
364
+ // Xenos with no Archon approval. Both paths share firstTimeAdmissionBlocked()
365
+ // so they cannot drift. An owner who wants fully-open self-enrollment sets
366
+ // OTB_OPEN_XENOS_ENROLLMENT truthy (no code change); default = invite required.
367
+ // See ADR 0050.
368
+ const existing = await getBuilderByGithubId(ghUser.id);
369
+ if (!existing && !openEnrollmentEnabled()) {
370
+ const approved = await hasInvitedAccessRequest(ghUser.login);
371
+ // Pass `login` so the configured-firstAdmin bypass (firstTimeAdmissionBlocked
372
+ // L124-125) fires on WEB sign-in too — without it the founding owner is gated
373
+ // out of their own fresh instance at "The Gate is Closed", a chicken-and-egg
374
+ // since no Archon exists yet to approve them. The device flow (pollDeviceFlow,
375
+ // ~L305) already passes login; this keeps the two paths from drifting (SR-19).
376
+ if (firstTimeAdmissionBlocked({ existing, approved, login: ghUser.login })) {
377
+ const err = new Error('not_approved');
378
+ err.code = 'NOT_APPROVED';
379
+ throw err;
380
+ }
381
+ }
382
+ let builder = await upsertBuilder({
383
+ githubId: ghUser.id,
384
+ githubLogin: ghUser.login,
385
+ displayName: ghUser.name || ghUser.login,
386
+ avatarUrl: ghUser.avatar_url,
387
+ });
388
+ builder = await maybeSeatFirstAdmin(builder);
389
+ const token = newSessionToken();
390
+ // Web sessions expire after 30 days; the user re-auths on the page
391
+ // when the cookie expires.
392
+ const expiresAt = new Date(Date.now() + 30 * 24 * 60 * 60 * 1000);
393
+ await createSession({
394
+ builderId: builder.id,
395
+ token,
396
+ source: 'web',
397
+ userAgent: userAgent ?? null,
398
+ expiresAt,
399
+ });
400
+ // V3.R75 (#294): tick the onboarding state machine. Best-effort.
401
+ await seams.resolveOptional('onboarding')?.safeMarkStage(pool, builder.id, 'auth_complete', builder);
402
+ return { token, builder, expiresAt };
403
+ }
404
+
405
+ // -------------------------------------------------------------------------
406
+ // Discord OAuth (account-linking — Phase 3 F2, ADR 0033 §2)
407
+ // -------------------------------------------------------------------------
408
+ // Links a Discord account to an ALREADY-AUTHENTICATED builder. Client creds
409
+ // come from env (DISCORD_CLIENT_ID / DISCORD_CLIENT_SECRET) or, for local dev,
410
+ // ~/.config/otb/discord-bot.json — same env-first-then-file shape as GitHub.
411
+ // scope=identify (the Discord user id, for the link) + guilds.join (so the
412
+ // server can drop the just-linked builder straight into the guild — the
413
+ // "link-first auto-join" onboarding gate, F7/ADR 0033: a builder approves the
414
+ // link BEFORE entering, so their rank role is already correct on arrival; there
415
+ // is no invite link to share or leak). guilds.join is only USABLE alongside the
416
+ // bot adding the member, and only if the bot holds CREATE_INSTANT_INVITE — see
417
+ // addDiscordGuildMember + docs/discord/role-sync.md. The pure helpers
418
+ // (discordAuthorizeUrl, isValidDiscordUserId) are exported for unit testing
419
+ // without creds or network.
420
+ const DISCORD_SCOPE = 'identify guilds.join';
421
+
422
+ function loadDiscordCreds() {
423
+ const clientId = process.env.DISCORD_CLIENT_ID;
424
+ const clientSecret = process.env.DISCORD_CLIENT_SECRET;
425
+ if (clientId && clientSecret) return { client_id: clientId, client_secret: clientSecret };
426
+ try {
427
+ const raw = ic.readConfigFileSync('discord-bot.json');
428
+ if (raw) {
429
+ const creds = JSON.parse(raw);
430
+ if (creds.client_id && creds.client_secret) {
431
+ return { client_id: creds.client_id, client_secret: creds.client_secret };
432
+ }
433
+ }
434
+ } catch (_) { /* not configured */ }
435
+ return null;
436
+ }
437
+
438
+ function discordConfigured() {
439
+ return !!loadDiscordCreds();
440
+ }
441
+
442
+ // Bot token loader (Phase 3 F3, ADR 0033 §F3). The bot authenticates to the
443
+ // Discord GATEWAY with a bot token — distinct from the OAuth client_id/secret
444
+ // used for account-linking (F2). Same env-first-then-file shape as everything
445
+ // else: env DISCORD_BOT_TOKEN wins (the systemd path ADR 0022 prefers for the
446
+ // always-on droplet process), else the bot_token field of the local
447
+ // ~/.config/otb/discord-bot.json file (already on the droplet, mode 600). The
448
+ // token is a secret — never log it, never return it anywhere user-facing.
449
+ function loadDiscordBotToken() {
450
+ const envToken = process.env.DISCORD_BOT_TOKEN;
451
+ if (envToken) return envToken;
452
+ try {
453
+ const raw = ic.readConfigFileSync('discord-bot.json');
454
+ if (raw) {
455
+ const creds = JSON.parse(raw);
456
+ if (creds.bot_token) return creds.bot_token;
457
+ }
458
+ } catch (_) { /* not configured */ }
459
+ return null;
460
+ }
461
+
462
+ function discordBotConfigured() {
463
+ return !!loadDiscordBotToken();
464
+ }
465
+
466
+ // PURE — build the Discord authorize URL (testable without creds/network).
467
+ function discordAuthorizeUrl(clientId, state, redirectUri) {
468
+ const u = new URL('https://discord.com/api/oauth2/authorize');
469
+ u.searchParams.set('client_id', clientId);
470
+ u.searchParams.set('redirect_uri', redirectUri);
471
+ u.searchParams.set('response_type', 'code');
472
+ u.searchParams.set('scope', DISCORD_SCOPE);
473
+ u.searchParams.set('state', state);
474
+ return u.toString();
475
+ }
476
+
477
+ function buildDiscordAuthorizeUrl(state, redirectUri) {
478
+ const creds = loadDiscordCreds();
479
+ if (!creds) throw new Error('Discord OAuth credentials not configured');
480
+ return discordAuthorizeUrl(creds.client_id, state, redirectUri);
481
+ }
482
+
483
+ async function exchangeDiscordCode(code, redirectUri) {
484
+ const creds = loadDiscordCreds();
485
+ if (!creds) throw new Error('Discord OAuth credentials not configured');
486
+ const res = await fetch('https://discord.com/api/oauth2/token', {
487
+ method: 'POST',
488
+ headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
489
+ body: new URLSearchParams({
490
+ client_id: creds.client_id,
491
+ client_secret: creds.client_secret,
492
+ grant_type: 'authorization_code',
493
+ code,
494
+ redirect_uri: redirectUri,
495
+ }).toString(),
496
+ });
497
+ if (!res.ok) {
498
+ const text = await res.text();
499
+ throw new Error(`Discord token exchange failed (${res.status}): ${text}`);
500
+ }
501
+ const data = await res.json();
502
+ return data.access_token;
503
+ }
504
+
505
+ async function fetchDiscordUser(accessToken) {
506
+ const res = await fetch('https://discord.com/api/users/@me', {
507
+ headers: { Authorization: `Bearer ${accessToken}` },
508
+ });
509
+ if (!res.ok) {
510
+ const text = await res.text();
511
+ throw new Error(`Discord /users/@me failed (${res.status}): ${text}`);
512
+ }
513
+ return res.json(); // { id, username, ... }
514
+ }
515
+
516
+ // Kick a member from the guild when they unlink their Discord account (F7
517
+ // follow-up). Uses the bot token only — no user OAuth needed for a kick.
518
+ // Requires the bot to hold Kick Members. Returns { removed: true } on 204
519
+ // (success) or { removed: false, reason } when the member wasn't in the guild
520
+ // (404) or the bot lacks permission (403) — both non-fatal for an unlink.
521
+ // Throws on unexpected errors so the caller can log; unlink itself must still
522
+ // succeed even if the kick can't.
523
+ async function removeDiscordGuildMember({ guildId, discordUserId }) {
524
+ const botToken = loadDiscordBotToken();
525
+ if (!botToken) throw new Error('Discord bot token not configured');
526
+ if (!guildId) throw new Error('Discord guild id not configured');
527
+ const res = await fetch(`https://discord.com/api/guilds/${guildId}/members/${discordUserId}`, {
528
+ method: 'DELETE',
529
+ headers: { Authorization: `Bot ${botToken}` },
530
+ });
531
+ if (res.status === 204) return { removed: true };
532
+ if (res.status === 404) return { removed: false, reason: 'not_in_guild' };
533
+ if (res.status === 403) return { removed: false, reason: 'missing_permission' };
534
+ const text = await res.text();
535
+ throw new Error(`Discord guild remove failed (${res.status}): ${text}`);
536
+ }
537
+
538
+ // PURE — a Discord user id is a numeric snowflake (17–20 digits). Reject
539
+ // anything else BEFORE it reaches db.linkDiscord (F1 Hacker advisory).
540
+ function isValidDiscordUserId(id) {
541
+ return typeof id === 'string' && /^\d{17,20}$/.test(id);
542
+ }
543
+
544
+ // Add the just-linked builder straight into the guild (F7, ADR 0033 — the
545
+ // "link-first auto-join" gate). Uses the user's own OAuth access token (granted
546
+ // via the guilds.join scope) together with the BOT token — Discord's add-member
547
+ // endpoint requires both: the user consent (access_token) and a bot with the
548
+ // CREATE_INSTANT_INVITE permission. `roleIds` are applied at add-time so the
549
+ // member's rank badge is present the instant they arrive (Discord only honors
550
+ // the roles array when CREATING the membership — for an already-present member
551
+ // it 204s and ignores them, which is why the callback also runs syncBuilderRole
552
+ // belt-and-braces). Returns { added } — 201 = newly added, 204 = already a
553
+ // member. Throws on any other status so the caller can log; auto-join is
554
+ // best-effort and must never break the link itself.
555
+ async function addDiscordGuildMember({ guildId, discordUserId, accessToken, roleIds }) {
556
+ const botToken = loadDiscordBotToken();
557
+ if (!botToken) throw new Error('Discord bot token not configured');
558
+ if (!guildId) throw new Error('Discord guild id not configured');
559
+ const res = await fetch(`https://discord.com/api/guilds/${guildId}/members/${discordUserId}`, {
560
+ method: 'PUT',
561
+ headers: { Authorization: `Bot ${botToken}`, 'Content-Type': 'application/json' },
562
+ body: JSON.stringify({ access_token: accessToken, roles: Array.isArray(roleIds) ? roleIds : [] }),
563
+ });
564
+ if (res.status === 201 || res.status === 204) return { added: res.status === 201 };
565
+ const text = await res.text();
566
+ throw new Error(`Discord guild add failed (${res.status}): ${text}`);
567
+ }
568
+
569
+ // -------------------------------------------------------------------------
570
+ // Express middleware: authenticate by Bearer token (CLI) or gds_session cookie
571
+ // (web). Legacy pms_session cookie still accepted on read so a builder mid-
572
+ // session at the V3.R01 rollout doesn't have to re-auth.
573
+ // -------------------------------------------------------------------------
574
+
575
+ // Collect EVERY candidate auth token on the request, in priority order, with
576
+ // duplicates removed. A browser can legitimately carry MORE THAN ONE
577
+ // gds_session cookie: a host-only one (set on amazonprimea.com before the
578
+ // #726/#734 apex-scoping rollout) AND the current apex-scoped one
579
+ // (.amazonprimea.com). The browser sends both in the Cookie header. We must try
580
+ // them ALL — picking only the first (as the old single-value extractToken did)
581
+ // meant a stale/expired duplicate could shadow the live session and 401 the
582
+ // builder (hit on the Discord link callback for builder 25). Order: Bearer
583
+ // (CLI) first, then every gds_session value in header order, then legacy
584
+ // pms_session — so a valid current session always wins over a stale one.
585
+ // SECURITY (#871): cap the number of distinct candidate tokens we will try per
586
+ // request. resolveSession does ONE DB lookup per candidate, so an attacker who
587
+ // stuffs the Cookie header with dozens of gds_session=<junk> values could force
588
+ // dozens of DB round-trips per request (amplification / DoS) even while
589
+ // unauthenticated. A legitimate browser carries at most a Bearer + a couple of
590
+ // gds_session cookies (host-only + apex during the #734 rollout) + one legacy
591
+ // pms_session, so a small cap never hurts a real client.
592
+ const MAX_AUTH_TOKEN_CANDIDATES = 6;
593
+
594
+ function extractTokens(req) {
595
+ const out = [];
596
+ const push = (t) => {
597
+ if (t && out.length < MAX_AUTH_TOKEN_CANDIDATES && !out.includes(t)) out.push(t);
598
+ };
599
+ const auth = req.headers['authorization'] || '';
600
+ const m = /^Bearer\s+([a-zA-Z0-9]+)$/.exec(auth);
601
+ if (m) push(m[1]);
602
+ // Cookie parsing: minimal, no dep. Prefer new name, fall back to legacy.
603
+ const cookieHeader = req.headers['cookie'] || '';
604
+ const legacy = [];
605
+ for (const part of cookieHeader.split(';')) {
606
+ const idx = part.indexOf('=');
607
+ if (idx < 0) continue;
608
+ const k = part.slice(0, idx).trim();
609
+ const v = part.slice(idx + 1).trim();
610
+ if (k === 'gds_session' && v) push(v);
611
+ else if (k === 'pms_session' && v) legacy.push(v);
612
+ }
613
+ for (const v of legacy) push(v);
614
+ return out;
615
+ }
616
+
617
+ // Resolve the request to a live session by trying every candidate token until
618
+ // one maps to a non-revoked, unexpired session. Returns null if none do.
619
+ async function resolveSession(req) {
620
+ for (const token of extractTokens(req)) {
621
+ const session = await lookupSession(token);
622
+ if (session) return session;
623
+ }
624
+ return null;
625
+ }
626
+
627
+ // SECURITY (#919): opt-in marker for the handful of dev-box bootstrap routes a
628
+ // box-scoped session (source='box') is permitted to reach. Mount it BEFORE
629
+ // requireBuilder on those routes; it only flags the request, and requireBuilder's
630
+ // deny-by-default box-scope gate reads the flag. A route WITHOUT this middleware
631
+ // rejects a box session, so this allow-list IS the complete box surface and any
632
+ // new route is denied to boxes automatically (fail closed). No-op for cli/web
633
+ // sessions — requireBuilder only consults the flag when source === 'box'.
634
+ function allowBoxScope(req, _res, next) {
635
+ req.allowBoxScope = true;
636
+ next();
637
+ }
638
+
639
+ async function requireBuilder(req, res, next) {
640
+ const session = await resolveSession(req);
641
+ if (!session) {
642
+ res.fail('unauthenticated', 401);
643
+ return;
644
+ }
645
+ // SECURITY (#871): a deactivated/offboarded builder must not retain API access
646
+ // even if a live (unrevoked) session token survives. deactivateBuilder now
647
+ // revokes sessions on exit, but this is the belt-and-braces hot-path gate that
648
+ // also closes any session minted before that fix (and the never-expiring CLI
649
+ // tokens). Deny ONLY on the known 'inactive' status — never on a missing/
650
+ // unknown value — so an active builder can never be locked out by this check.
651
+ // status is read fresh per request via lookupSession (never cached), so a
652
+ // later reactivation takes effect on the builder's very next request.
653
+ if (session.status === 'inactive') {
654
+ res.fail('builder_inactive', 403);
655
+ return;
656
+ }
657
+ // SECURITY (#919, SR#2): box-scoped sessions are DENY-BY-DEFAULT. The dev-box
658
+ // provisioner (scripts/gds/box.js) bakes a session into cloud-init so the box can
659
+ // bootstrap (#818 — box-source-fetch clones the rank-scoped repo before
660
+ // /builder-setup exists). That token lives ON the box, which is OUTSIDE the ADR
661
+ // 0016 trust boundary, so it must NOT carry the builder's full authority. A
662
+ // source='box' session may reach ONLY the routes that opted in via allowBoxScope
663
+ // (the 4 endpoints the on-box crons call — see routes/box.js); EVERY other route
664
+ // (all of /tasks, /me, the rest of /box …) fails closed here, including any route
665
+ // added later. A builder who wants to do real work FROM the box runs
666
+ // /builder-reauth, minting a normal cli/web session (no box scope) like any laptop.
667
+ if (session.source === 'box' && !req.allowBoxScope) {
668
+ res.fail('box_scope', { status: 403, message: 'This is a box-scoped session; it may only call dev-box bootstrap endpoints.' });
669
+ return;
670
+ }
671
+ // Anthropic-identity pin — REMOVED (task 1039, idea 258). This used to 403
672
+ // `identity_mismatch` when a linked builder's CLI echoed an X-Anthropic-User
673
+ // header that differed from the stored anthropic_user_id (V3.R10 #235, C3).
674
+ // It is gone because:
675
+ // - The drachmae reward values work at a UNIFORM server-side list price
676
+ // (ADR 0054), NOT against each builder's own Anthropic account, so there
677
+ // is no economic reason to bind a session to one Anthropic identity —
678
+ // attribution is simply the GDS session-token holder.
679
+ // - The pin locked a linked builder out of their OWN session the moment
680
+ // they used a second Claude account; multi-account is fine.
681
+ // - It was attestation, not strong auth — an attacker bypasses it by
682
+ // omitting the header — so it added friction with no real security value.
683
+ // anthropic_email / anthropic_user_id are still captured at link time as
684
+ // optional cosmetic attribution; they are no longer an auth boundary, and the
685
+ // X-Anthropic-User header is no longer read here.
686
+ req.gdsSession = session;
687
+ req.builder = {
688
+ id: session.builder_id,
689
+ github_id: session.github_id,
690
+ github_login: session.github_login,
691
+ display_name: session.display_name,
692
+ avatar_url: session.avatar_url,
693
+ total_credits: session.total_credits,
694
+ status: session.status ?? null,
695
+ // rank is the single source of authority (ADR 0016). Read fresh from the
696
+ // DB on every request via lookupSession — never cached, never from the
697
+ // token — so demotion takes effect on the builder's very next request.
698
+ rank: session.rank,
699
+ // system_role identifies a non-human service principal (NULL for every
700
+ // human builder). The BFG cross-builder write gate (requireBfgPrincipal)
701
+ // keys on this EXACT value — never on rank — so an Archon token cannot
702
+ // reach the write path (ADR 0026 §6C). Read fresh per request like rank.
703
+ system_role: session.system_role ?? null,
704
+ };
705
+ next();
706
+ }
707
+
708
+ // Page-level sign-in gate for the authenticated /builders HTML surface and its
709
+ // content assets (#488). Same identity check as requireBuilder — token from the
710
+ // gds_session cookie (or legacy pms_session / Bearer), looked up fresh against
711
+ // builder_sessions — but on failure it REDIRECTS the browser into the web
712
+ // sign-in flow (302 → /api/gds/auth/web/start) instead of returning a JSON 401,
713
+ // because these requests are page navigations and asset loads, not API calls.
714
+ //
715
+ // This is the *actual* access control for the citizen primer + system-maps:
716
+ // server.js mounts it ahead of the static handlers so an unauthenticated
717
+ // request is bounced before the file is ever served (curl-proof, JS-disabled-
718
+ // proof). The client-side link-hiding in builders.js is only cosmetic UX.
719
+ // Build a `?return=<absolute current URL>` for a page-gate sign-in redirect
720
+ // (#734) so a builder bounced into sign-in from the builders subdomain lands
721
+ // back on the page they wanted. The web flow validates it (open-redirect guard)
722
+ // and also falls back to Referer, so this is belt-and-braces for navigations
723
+ // where Referer may be stripped. Returns '' if the host can't be determined.
724
+ function signInWithReturn(req) {
725
+ const base = '/api/gds/auth/web/start';
726
+ const proto = req.headers['x-forwarded-proto'] || (req.secure ? 'https' : 'http');
727
+ const host = req.headers['x-forwarded-host'] || req.headers['host'];
728
+ if (!host) return base;
729
+ return `${base}?return=${encodeURIComponent(`${proto}://${host}${req.originalUrl}`)}`;
730
+ }
731
+
732
+ async function requireBuilderPage(req, res, next) {
733
+ try {
734
+ const session = await resolveSession(req);
735
+ if (!session) {
736
+ res.redirect(302, signInWithReturn(req));
737
+ return;
738
+ }
739
+ next();
740
+ } catch (err) {
741
+ // Fail CLOSED: if the session lookup itself errors (e.g. DB blip), send the
742
+ // visitor to sign-in rather than leaking the gated page. The content the
743
+ // gate protects is the priority here, not availability.
744
+ console.error('[gds] requireBuilderPage lookup failed', err);
745
+ res.redirect(302, signInWithReturn(req));
746
+ }
747
+ }
748
+
749
+ // requireArchonPage — page-level Archon gate for the /watch monitoring surface
750
+ // (#726). Same identity resolution as requireBuilderPage, but additionally
751
+ // checks the LIVE rank from the session lookup:
752
+ // - no session → 302 into the web sign-in flow (page navigation)
753
+ // - signed in, not Archon → 302 back to the hall (/builders) — they ARE a
754
+ // citizen, just not the Ruling Hand; bouncing to
755
+ // sign-in would loop, so send them home instead.
756
+ // - Archon → next()
757
+ // This is defense-in-depth: every data endpoint the page calls is ALSO
758
+ // requireRank('archon') server-side, so no cross-builder data can leak even if
759
+ // this gate were bypassed. But it keeps a non-Archon from ever seeing the page
760
+ // shell, honoring "only Archons have access".
761
+ async function requireArchonPage(req, res, next) {
762
+ try {
763
+ const session = await resolveSession(req);
764
+ if (!session) {
765
+ res.redirect(302, signInWithReturn(req));
766
+ return;
767
+ }
768
+ if (session.rank !== 'archon') {
769
+ res.redirect(302, '/builders');
770
+ return;
771
+ }
772
+ next();
773
+ } catch (err) {
774
+ // Fail CLOSED — a lookup error sends the visitor to sign-in rather than
775
+ // leaking the gated page shell.
776
+ console.error('[gds] requireArchonPage lookup failed', err);
777
+ res.redirect(302, signInWithReturn(req));
778
+ }
779
+ }
780
+
781
+ // requireNonXenosPage — page-level gate for the orientation reading rooms (the
782
+ // citizen's primer, the system-maps/diagrams) that #766 closed to newcomers
783
+ // and #770 enforces at the URL. A Xenos's path is action-only — find work,
784
+ // claim, ship three — so these pages are reserved for graduated citizens
785
+ // (Thetes+). Same identity resolution as requireBuilderPage, plus a live-rank
786
+ // check:
787
+ // - no session → 302 into the web sign-in flow (page navigation)
788
+ // - Xenos → 302 to /builders/not-ready — they ARE a citizen, just not
789
+ // ready for the reading rooms; the not-ready page offers the
790
+ // rank ladder + a way back to the hall. Bouncing to sign-in
791
+ // would loop, so send them to the friendly notice.
792
+ // - Thetes+ → next()
793
+ // Defense-in-depth: this is the curl-proof / no-JS-proof gate. The link-hiding
794
+ // in builders.js is only cosmetic.
795
+ async function requireNonXenosPage(req, res, next) {
796
+ try {
797
+ const session = await resolveSession(req);
798
+ if (!session) {
799
+ res.redirect(302, signInWithReturn(req));
800
+ return;
801
+ }
802
+ if (session.rank === 'xenos') {
803
+ res.redirect(302, '/builders/not-ready');
804
+ return;
805
+ }
806
+ next();
807
+ } catch (err) {
808
+ // Fail CLOSED — a lookup error sends the visitor to sign-in rather than
809
+ // leaking the gated page shell.
810
+ console.error('[gds] requireNonXenosPage lookup failed', err);
811
+ res.redirect(302, signInWithReturn(req));
812
+ }
813
+ }
814
+
815
+ // ---------------------------------------------------------------------------
816
+ // Route-gate inventory (V3.R06 #231) — the canonical "who can call what" list.
817
+ // Derived from ADR 0016's fail-closed rule: privileged routes that mutate
818
+ // SHARED plan/scope state default to archon; review/triage routes go to
819
+ // metic+archon (#360); routes scoped to a builder's OWN work/contributions
820
+ // stay open to any authenticated builder (a Xenos additionally limited to
821
+ // xenos_claimable tasks at claim time). Reads + auth/identity are never gated.
822
+ //
823
+ // archon-only (requireRank('archon')) — ratification / scope / infra:
824
+ // POST /tasks create work
825
+ // PATCH /tasks/:id edit work (incl. xenos_claimable)
826
+ // POST /tasks/:id/promote backlog→ready
827
+ // POST/PUT/DELETE /tasks/:id/dependencies reshape the dep graph
828
+ // POST /tasks/:id/confirm grader-bypass override
829
+ // POST /done-when/:id/{satisfy,unsatisfy} version scope
830
+ // PATCH /builders/:id/rank, /builders/:id/status rank/exit (Archon-only)
831
+ //
832
+ // metic+archon (requireRank('metic','archon')) — the approved working rank's
833
+ // review/triage powers, per criterion C2 (#360 / ADR 0018):
834
+ // PATCH /inbox/:id idea triage (promote/discard/merge)
835
+ // POST /blockers/:id/resolve unblocks tasks (blocker review)
836
+ // POST /blockers/:id/link blocker review
837
+ //
838
+ // open to any AUTHENTICATED builder (requireBuilder only) — own work, BUT a
839
+ // Xenos can only CLAIM tasks flagged xenos_claimable (gated in db.claimTask /
840
+ // claimTasksBatch, #360). Contributions:
841
+ // POST /claims, /claims/batch, /claims/:id/resolve claim + ship own work
842
+ // POST /tasks/:id/ship, /tasks/:id/grade own ship flow
843
+ // POST /cost log cost on own claim
844
+ // POST /learnings capture a learning
845
+ // POST /blockers FILE a blocker
846
+ // POST /inbox FILE an idea
847
+ // POST /session/optimize pick own next work
848
+ // POST /sessions upload own session digest (6D.1)
849
+ // POST /auth/logout own session
850
+ //
851
+ // reads / public / auth: GET *, /healthz, /public/*, /auth/device|web/*.
852
+ //
853
+ // Three ranks are LIVE (#360 / ADR 0018): archon, metic, xenos (default).
854
+ // Fail-closed rule stands: a NEW privileged mutating route defaults to
855
+ // requireRank('archon') until a lower rank is explicitly granted. The periodic
856
+ // auditor (#265 / V3.R45) mechanically asserts no privileged mutating route is
857
+ // left without a rank gate.
858
+ // ---------------------------------------------------------------------------
859
+
860
+ // requireRank(...allowed) — authorization middleware (V3.R05 #106; made
861
+ // threshold-based in #591).
862
+ //
863
+ // Runs AFTER requireBuilder, so req.builder.rank is the live DB value. 403s
864
+ // with a structured body if the builder's rank is below the gate. This is the
865
+ // enforcement point of the trust boundary documented in ADR 0016: the server
866
+ // checks a value the builder's machine cannot write.
867
+ //
868
+ // THRESHOLD, not exact-match (#591). The rank ladder (db.RANKS) is hierarchical
869
+ // — lower index = higher authority (olympian … archon … metic … helot). The old
870
+ // gate authorized by exact set membership (`allowedSet.has(actual)`), which
871
+ // silently denied every rank ABOVE the listed one: a builder promoted to a
872
+ // "divine" rank (olympian/hermes/oracle/demigod — documented as above-the-system,
873
+ // strictly above archon) FAILED requireRank('archon') and lost all archon-gated
874
+ // authority instead of inheriting it. We now admit any rank AT-OR-ABOVE the
875
+ // lowest-authority rank named in `allowed`. So requireRank('archon') admits
876
+ // archon and every divine rank above it; requireRank('metic','archon') admits
877
+ // metic, archon, and everything above — and still blocks thetes/xenos. The
878
+ // `required` field in the 403 body keeps reporting the literal allow-list so the
879
+ // route's intent (and existing clients) read unchanged.
880
+ //
881
+ // Fail-closed (matches the posture of xenosClaimAllowed/#510): an actual rank
882
+ // not in RANKS (unknown / typo / legacy / null) is denied, and a misconfigured
883
+ // gate whose allowed ranks are ALL unknown denies everyone.
884
+ //
885
+ // Usage: router.post('/tasks', auth.requireBuilder, auth.requireRank('archon'), handler)
886
+ function rankMeetsThreshold(actual, allowed) {
887
+ const actualIdx = RANKS.indexOf(actual);
888
+ if (actualIdx === -1) return false; // unknown/missing actual rank → fail closed
889
+ // The gate's floor is the LOWEST-authority (largest-index) rank in `allowed`;
890
+ // unknown allowed ranks are ignored so a typo can't silently widen the gate.
891
+ const floorIdx = allowed.reduce((max, r) => {
892
+ const i = RANKS.indexOf(r);
893
+ return i === -1 ? max : Math.max(max, i);
894
+ }, -1);
895
+ if (floorIdx === -1) return false; // no valid allowed rank → fail closed
896
+ return actualIdx <= floorIdx; // at or above the floor
897
+ }
898
+
899
+ function requireRank(...allowed) {
900
+ return function rankGate(req, res, next) {
901
+ const actual = req.builder && req.builder.rank;
902
+ if (!rankMeetsThreshold(actual, allowed)) {
903
+ res.fail('rank_forbidden', 403, { required: [...allowed], actual: actual ?? null });
904
+ return;
905
+ }
906
+ next();
907
+ };
908
+ }
909
+
910
+ // requireBfgPrincipal — the identity gate for the cross-builder memory WRITE
911
+ // path (ADR 0026 §6C / task #446). Runs AFTER requireBuilder. This is
912
+ // DELIBERATELY NOT a rank gate: the trust-boundary exception is bound to ONE
913
+ // service principal, so the gate checks the EXACT identity (system_role='bfg'),
914
+ // which only the BFG principal's `builders` row carries. An Archon token — or
915
+ // any rank-escalated / stolen token — has system_role=NULL and is rejected with
916
+ // 403. This is the enforcement the ADR demands ("gate on the exact principal
917
+ // identity, not on rank... an Archon token must be rejected"), not prose intent.
918
+ // Pure + exported so the gate behavior is unit-tested directly.
919
+ function requireBfgPrincipal(req, res, next) {
920
+ const role = req.builder && req.builder.system_role;
921
+ if (role !== 'bfg') {
922
+ res.fail('not_bfg_principal', 403);
923
+ return;
924
+ }
925
+ next();
926
+ }
927
+
928
+ // requireDiscordBot — the identity gate for the Discord bot service principal
929
+ // (Discord Phase 3, ADR 0033 §1). Like requireBfgPrincipal, this is an IDENTITY
930
+ // gate, NOT a rank gate: it keys on the EXACT system_role='discord_bot', which
931
+ // only the bot principal's `builders` row carries. An Archon token — or any
932
+ // rank-escalated / stolen human token — has system_role=NULL and is rejected
933
+ // (403); so is a DIFFERENT system principal (the BFG). The bot is trusted ONLY
934
+ // to assert a self-linked Discord→builder mapping; every endpoint behind this
935
+ // gate still re-checks the linked builder's LIVE rank before any write, so a
936
+ // Discord reaction can never escalate past what that builder may already do.
937
+ function requireDiscordBot(req, res, next) {
938
+ const role = req.builder && req.builder.system_role;
939
+ if (role !== 'discord_bot') {
940
+ res.fail('not_discord_bot_principal', 403);
941
+ return;
942
+ }
943
+ next();
944
+ }
945
+
946
+ module.exports = {
947
+ authConfigured,
948
+ openEnrollmentEnabled,
949
+ firstTimeAdmissionBlocked,
950
+ firstAdminLogin,
951
+ maybeSeatFirstAdmin,
952
+ startDeviceFlow,
953
+ pollDeviceFlow,
954
+ buildAuthorizeUrl,
955
+ completeWebFlow,
956
+ extractTokens,
957
+ resolveSession,
958
+ allowBoxScope,
959
+ requireBuilder,
960
+ requireBuilderPage,
961
+ requireArchonPage,
962
+ requireNonXenosPage,
963
+ requireRank,
964
+ rankMeetsThreshold,
965
+ requireBfgPrincipal,
966
+ requireDiscordBot,
967
+ // Discord OAuth (account-linking — Phase 3 F2, ADR 0033)
968
+ loadDiscordCreds,
969
+ discordConfigured,
970
+ loadDiscordBotToken,
971
+ discordBotConfigured,
972
+ discordAuthorizeUrl,
973
+ buildDiscordAuthorizeUrl,
974
+ exchangeDiscordCode,
975
+ fetchDiscordUser,
976
+ isValidDiscordUserId,
977
+ addDiscordGuildMember,
978
+ removeDiscordGuildMember,
979
+ };