@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,842 @@
1
+ // src/bongos/boxes.js — per-builder cloud dev box lifecycle (ADR 0031 §2 / §5, #598).
2
+ //
3
+ // Two layers in one module, deliberately split:
4
+ //
5
+ // 1. PURE helpers (no I/O) — the rank gate, the size/cost catalog, and the
6
+ // idle/dormant SELECTION logic. These carry the lifecycle's real decisions,
7
+ // so they are pure and unit-tested directly (tests/boxes.mjs) with no DB,
8
+ // no DigitalOcean, no clock — `nowMs` is always injected.
9
+ //
10
+ // 2. DB helpers (async, take the pg pool) — read a builder's box, list the
11
+ // roster, append a box_events audit row, bump the activity heartbeat, and
12
+ // the state-transition writers. Shared by BOTH the control-plane CLI
13
+ // (scripts/gds/box.js) and the read/heartbeat routes (routes/box.js).
14
+ //
15
+ // What is NOT here: the DigitalOcean API calls. Those live in src/bongos/do-api.js
16
+ // (the only network module), and the CLI orchestrator composes pure-guard →
17
+ // DO-call → DB-writer. Keeping DO out of this file is what lets the decisions be
18
+ // tested hermetically.
19
+ //
20
+ // §9.3 (resolved in ADR 0031): auto-suspend = snapshot-and-park. A powered-off
21
+ // DO droplet still bills, so "park" snapshots then DESTROYS the droplet and
22
+ // "wake" recreates from the snapshot. The cost model below reflects that: a box
23
+ // only accrues compute cost while `state='active'`.
24
+
25
+ // ---------------------------------------------------------------------------
26
+ // PURE: rank gate
27
+ // ---------------------------------------------------------------------------
28
+
29
+ // Three live ranks (ADR 0018 / migration 029): xenos < metic < archon.
30
+ // ADR 0034 (#692) added `thetes` as the 4th live rank between xenos and metic.
31
+ // It MUST appear here or rankMeetsBoxFloor() fails closed for Thetes (undefined
32
+ // order → below any floor → 403 on /box/ensure), which boxed out the whole tier.
33
+ const RANK_ORDER = { xenos: 0, thetes: 1, metic: 2, archon: 3 };
34
+
35
+ // Default provisioning floor. ADR 0031 §5(b): a Newcomer (Xenos who has cleared
36
+ // onboarding) gets an org-funded auto-suspended STARTER box — zero payment
37
+ // barrier to begin. So the floor is the lowest live rank; the SCOPE (below)
38
+ // is what differs by rank, not the right to a box. Override with
39
+ // BOX_PROVISION_MIN_RANK to raise it.
40
+ const DEFAULT_PROVISION_FLOOR = 'xenos';
41
+
42
+ function rankMeetsBoxFloor(rank, floor = DEFAULT_PROVISION_FLOOR) {
43
+ const r = RANK_ORDER[rank];
44
+ const f = RANK_ORDER[floor];
45
+ return Number.isInteger(r) && Number.isInteger(f) && r >= f;
46
+ }
47
+
48
+ // Source-access scope recorded at provision (consumed by #600). A Xenos or
49
+ // Thetes gets a SCOPED 'starter' surface; a Metic+ gets 'full'. This is the
50
+ // access boundary ADR 0031 §6 / open-question §9.2 describes — #598 records it,
51
+ // #600 enforces it. (ADR 0034: Thetes can hold a box but earns no scope uplift.)
52
+ function boxScopeForRank(rank) {
53
+ return RANK_ORDER[rank] >= RANK_ORDER.metic ? 'full' : 'starter';
54
+ }
55
+
56
+ // The provisioning gate. Pure + throwing so it is unit-tested and so the CLI
57
+ // and any future API path enforce the SAME rule (ADR 0016 trust boundary: the
58
+ // check reads the builder's live DB rank, which their machine cannot write).
59
+ function assertProvisionAllowed({ rank, floor = DEFAULT_PROVISION_FLOOR } = {}) {
60
+ if (!rankMeetsBoxFloor(rank, floor)) {
61
+ const err = new Error(
62
+ `INSUFFICIENT_RANK: provisioning a box requires rank >= ${floor}; builder is ${rank ?? 'unknown'}`
63
+ );
64
+ err.code = 'INSUFFICIENT_RANK';
65
+ err.required = floor;
66
+ err.actual = rank ?? null;
67
+ throw err;
68
+ }
69
+ }
70
+
71
+ // ---------------------------------------------------------------------------
72
+ // PURE: size + cost catalog
73
+ // ---------------------------------------------------------------------------
74
+
75
+ // DigitalOcean shared-CPU droplet sizes we offer, with their list prices
76
+ // (USD; hourly is the monthly/672 DO rounds to). The default is s-2vcpu-4gb:
77
+ // the devcontainer runs Node 22 + the art pipeline + a dev server, and
78
+ // devcontainer.json sets a 4 GB Node heap — 4 GB RAM is the sane floor. Parked,
79
+ // a box costs only its snapshot (~$0.30/mo for a ~5 GB image), not these rates.
80
+ const SIZE_CATALOG = {
81
+ 's-1vcpu-2gb': { hourlyUsd: 0.01786, monthlyUsd: 12, label: '1 vCPU / 2 GB' },
82
+ 's-2vcpu-2gb': { hourlyUsd: 0.02679, monthlyUsd: 18, label: '2 vCPU / 2 GB' },
83
+ 's-2vcpu-4gb': { hourlyUsd: 0.03571, monthlyUsd: 24, label: '2 vCPU / 4 GB' },
84
+ 's-4vcpu-8gb': { hourlyUsd: 0.07143, monthlyUsd: 48, label: '4 vCPU / 8 GB' },
85
+ };
86
+ const DEFAULT_SIZE = 's-2vcpu-4gb';
87
+
88
+ function sizeHourlyUsd(slug) {
89
+ const s = SIZE_CATALOG[slug];
90
+ return s ? s.hourlyUsd : 0;
91
+ }
92
+
93
+ // Compute cost of a live interval = hours running × the size's hourly rate.
94
+ // Negative/zero/invalid intervals → 0 (a box that never went active owes nothing).
95
+ function computeActiveCostUsd({ sizeSlug, activeSinceMs, untilMs } = {}) {
96
+ if (!Number.isFinite(activeSinceMs) || !Number.isFinite(untilMs)) return 0;
97
+ const ms = untilMs - activeSinceMs;
98
+ if (ms <= 0) return 0;
99
+ const hours = ms / 3_600_000;
100
+ return round4(hours * sizeHourlyUsd(sizeSlug));
101
+ }
102
+
103
+ // ---- container cost ledger (#602) ----
104
+ // DigitalOcean charges no provision FEE — billing starts at the first active
105
+ // hour, already captured by computeActiveCostUsd. We still log a spin-up LEDGER
106
+ // EVENT (at $0 by default) so the ledger records every create/resume as a
107
+ // timestamped line; override BOX_SPINUP_COST_USD to model a provisioning overhead.
108
+ const SPINUP_COST_USD = Number(process.env.BOX_SPINUP_COST_USD) || 0;
109
+ // A PARKED box keeps only its ~5 GB snapshot (~$0.30/mo). A RUNNING box's disk is
110
+ // included in the droplet hourly rate, so storage is a separate ledger line only
111
+ // while parked. monthly / 730 ≈ hourly.
112
+ const SNAPSHOT_MONTHLY_USD = Number(process.env.BOX_SNAPSHOT_MONTHLY_USD) || 0.30;
113
+
114
+ // Storage cost of a parked interval = hours parked × the snapshot's hourly rate.
115
+ // Invalid/zero/negative interval → 0.
116
+ function storageCostUsd({ parkedSinceMs, untilMs } = {}) {
117
+ if (!Number.isFinite(parkedSinceMs) || !Number.isFinite(untilMs)) return 0;
118
+ const ms = untilMs - parkedSinceMs;
119
+ if (ms <= 0) return 0;
120
+ return round4((ms / 3_600_000) * (SNAPSHOT_MONTHLY_USD / 730));
121
+ }
122
+
123
+ // Pure: bucket cost_log-shaped rows (the box-* sources) into a per-builder
124
+ // summary { spinup_usd, compute_usd, storage_usd, total_usd }. Keyed on `source`
125
+ // so a $0 spin-up line stays distinct from compute. Exported for the cost views
126
+ // + the unit test.
127
+ function summarizeBoxCost(rows) {
128
+ const out = { spinup_usd: 0, compute_usd: 0, storage_usd: 0, total_usd: 0 };
129
+ for (const r of rows || []) {
130
+ const amt = Number(r.amount_usd) || 0;
131
+ if (r.source === 'box-spinup') out.spinup_usd = round4(out.spinup_usd + amt);
132
+ else if (r.source === 'box-storage') out.storage_usd = round4(out.storage_usd + amt);
133
+ else if (r.source === 'box-lifecycle') out.compute_usd = round4(out.compute_usd + amt);
134
+ out.total_usd = round4(out.total_usd + amt);
135
+ }
136
+ return out;
137
+ }
138
+
139
+ // The cost_log sources this feature owns — the box cost ledger is exactly the
140
+ // rows carrying one of these (compute already shipped under 'box-lifecycle').
141
+ const BOX_COST_SOURCES = ['box-lifecycle', 'box-spinup', 'box-storage'];
142
+
143
+ function round4(n) {
144
+ return Math.round((n + Number.EPSILON) * 10000) / 10000;
145
+ }
146
+
147
+ // ---------------------------------------------------------------------------
148
+ // PURE: idle / dormant selection (the sweep cores)
149
+ // ---------------------------------------------------------------------------
150
+
151
+ function toMs(ts) {
152
+ if (ts == null) return null;
153
+ if (ts instanceof Date) return ts.getTime();
154
+ const n = Date.parse(ts);
155
+ return Number.isNaN(n) ? null : n;
156
+ }
157
+
158
+ // A box's "last activity" for idle purposes: the heartbeat if present, else when
159
+ // the current droplet went active, else when it was first provisioned. The
160
+ // COALESCE means a freshly-provisioned box that has not heartbeated yet is dated
161
+ // from active_since (so it gets a full idle window before its first park, not an
162
+ // instant one).
163
+ function effectiveActivityMs(row) {
164
+ return toMs(row.last_activity_at) ?? toMs(row.active_since) ?? toMs(row.provisioned_at);
165
+ }
166
+
167
+ // Idle = active boxes whose effective activity is older than idleMinutes AND
168
+ // that don't have an active Claude process. A box with claude_active=true is
169
+ // kept alive regardless of last_activity_at — the session ends when Claude stops.
170
+ // Pure: caller passes the candidate rows and nowMs; returns the subset to deprovision.
171
+ function selectIdleBoxes(rows, { idleMinutes, nowMs } = {}) {
172
+ if (!Number.isFinite(idleMinutes) || idleMinutes <= 0) return [];
173
+ if (!Number.isFinite(nowMs)) return [];
174
+ const cutoff = nowMs - idleMinutes * 60_000;
175
+ return (rows || []).filter((r) => {
176
+ if (r.state !== 'active') return false;
177
+ if (r.claude_active) return false;
178
+ const a = effectiveActivityMs(r);
179
+ return a != null && a <= cutoff;
180
+ });
181
+ }
182
+
183
+ // Dormant = parked boxes that have been parked longer than dormantDays. These
184
+ // get fully de-provisioned (snapshot deleted too) to reclaim the last cents.
185
+ function selectDormantBoxes(rows, { dormantDays, nowMs } = {}) {
186
+ if (!Number.isFinite(dormantDays) || dormantDays <= 0) return [];
187
+ if (!Number.isFinite(nowMs)) return [];
188
+ const cutoff = nowMs - dormantDays * 86_400_000;
189
+ return (rows || []).filter((r) => {
190
+ if (r.state !== 'parked') return false;
191
+ const p = toMs(r.parked_at);
192
+ return p != null && p <= cutoff;
193
+ });
194
+ }
195
+
196
+ // ---------------------------------------------------------------------------
197
+ // PURE: state-transition legality
198
+ // ---------------------------------------------------------------------------
199
+
200
+ // Which lifecycle op is legal from which current state. 'error'/'destroyed' are
201
+ // recoverable into a fresh provision; 'deprovision' is legal from anywhere that
202
+ // holds a droplet or snapshot so an operator can always force-reclaim.
203
+ const TRANSITIONS = {
204
+ provision: ['none', 'destroyed', 'error'],
205
+ park: ['active'],
206
+ wake: ['parked'],
207
+ deprovision: ['active', 'parking', 'parked', 'waking', 'error'],
208
+ };
209
+
210
+ function canTransition(op, state) {
211
+ return (TRANSITIONS[op] || []).includes(state);
212
+ }
213
+
214
+ // ---------------------------------------------------------------------------
215
+ // PURE: droplet ⇄ box-row drift detection (the reconcile-drift core, task 1289)
216
+ // ---------------------------------------------------------------------------
217
+
218
+ // States in which a box row legitimately OWNS a running droplet (so its
219
+ // droplet_id should match a real DO droplet). 'parked' is excluded — a parked box
220
+ // has no droplet, only a snapshot. Distinct from SOURCE_HOLDING_STATES (which is
221
+ // about credential issuance, not droplet ownership).
222
+ const DROPLET_HOLDING_STATES = ['active', 'provisioning', 'waking', 'parking'];
223
+
224
+ // Derive the builder login a fleet droplet belongs to, from its `builder:<login>`
225
+ // tag (how box.js tags every droplet) or, failing that, the `otb-box-<login>`
226
+ // name. Lets the reconcile tear down the matching Cloudflare tunnel + DNS for a
227
+ // zombie. null when neither encodes a login.
228
+ function loginFromDroplet(d) {
229
+ const tag = (d.tags || []).find((t) => typeof t === 'string' && t.startsWith('builder:'));
230
+ if (tag) return tag.slice('builder:'.length) || null;
231
+ const m = /^otb-box-(.+)$/.exec(d.name || '');
232
+ return (m && m[1]) || null;
233
+ }
234
+
235
+ // Pure: given the LIVE DigitalOcean droplet list (already filtered to our fleet
236
+ // tag) and the GDS box rows, compute lifecycle drift in BOTH directions — the
237
+ // fix for the task-1289 zombie, where deprovision marked a box 'destroyed' WITHOUT
238
+ // confirming the droplet was deleted, leaving a running droplet no row references.
239
+ //
240
+ // - zombies: a real droplet that NO box row claims in a droplet-holding state
241
+ // (the row is destroyed/error/none, or its droplet_id was nulled). It bills,
242
+ // stays SSH-reachable, and freezes on old code forever → force-delete it.
243
+ // - ghosts: a box row in a droplet-holding state whose droplet_id is no longer
244
+ // present in DO (deleted out-of-band) → mark it 'destroyed' so the DB stops
245
+ // claiming a running box and the builder can re-provision.
246
+ //
247
+ // In-flight guard: a droplet created seconds ago can belong to a box still in
248
+ // 'provisioning' (the row's droplet_id isn't written until the droplet settles
249
+ // 'active'). Droplets younger than minAgeMinutes are NOT treated as zombies — they
250
+ // are returned under `skippedYoung` so the operator sees them without risk of
251
+ // deleting a provision in progress. Pure + injected (droplets/boxRows/nowMs) so it
252
+ // is unit-tested with no DO, DB, or real clock.
253
+ function planDropletDrift({ droplets = [], boxRows = [], nowMs = null, minAgeMinutes = 30 } = {}) {
254
+ const liveDropletIds = new Set((droplets || []).map((d) => String(d.id)));
255
+ const claimedDropletIds = new Set(
256
+ (boxRows || [])
257
+ .filter((b) => DROPLET_HOLDING_STATES.includes(b.state) && b.droplet_id != null)
258
+ .map((b) => String(b.droplet_id))
259
+ );
260
+ const builderIdByLogin = new Map(
261
+ (boxRows || []).filter((b) => b.github_login).map((b) => [b.github_login, b.builder_id])
262
+ );
263
+
264
+ const zombies = [];
265
+ const skippedYoung = [];
266
+ for (const d of droplets || []) {
267
+ if (claimedDropletIds.has(String(d.id))) continue; // a live box owns it — in sync
268
+ const login = loginFromDroplet(d);
269
+ const entry = {
270
+ dropletId: String(d.id), name: d.name || null, login,
271
+ builderId: login ? (builderIdByLogin.get(login) ?? null) : null,
272
+ tags: d.tags || [],
273
+ };
274
+ const createdMs = toMs(d.created_at);
275
+ if (Number.isFinite(nowMs) && createdMs != null && (nowMs - createdMs) < minAgeMinutes * 60_000) {
276
+ skippedYoung.push(entry);
277
+ continue;
278
+ }
279
+ zombies.push(entry);
280
+ }
281
+
282
+ const ghosts = [];
283
+ for (const b of boxRows || []) {
284
+ if (!DROPLET_HOLDING_STATES.includes(b.state)) continue;
285
+ if (b.droplet_id == null) continue;
286
+ if (liveDropletIds.has(String(b.droplet_id))) continue; // droplet present — in sync
287
+ ghosts.push({
288
+ builderId: b.builder_id, login: b.github_login || null,
289
+ dropletId: String(b.droplet_id), state: b.state,
290
+ });
291
+ }
292
+
293
+ return { zombies, ghosts, skippedYoung };
294
+ }
295
+
296
+ // ---------------------------------------------------------------------------
297
+ // PURE: the "make my box ready" decision (#701 box onboarding middleware)
298
+ // ---------------------------------------------------------------------------
299
+
300
+ // Given a box's current state and the builder's LIVE rank+status, decide what a
301
+ // builder's "I want to connect" request (POST /box/ensure) should do. This is the
302
+ // single decision that delivers BOTH halves of #701's onboarding gap:
303
+ // - auto-provision (boxState none/destroyed/error → 'provision')
304
+ // - wake-on-connect (boxState parked → 'wake')
305
+ // plus the no-op / in-flight cases. Pure + tested (tests/boxes.mjs) so the route
306
+ // and any future caller enforce the same rule; the rank gate mirrors
307
+ // box-access.decideSourceAccess (ADR 0016: decided from the live DB rank, which
308
+ // the builder's machine cannot forge). Fail-closed: an unknown rank/state denies.
309
+ //
310
+ // Returns one of:
311
+ // { action: 'ready' } box is active — just heartbeat
312
+ // { action: 'in_progress' } provision/wake/park already under way
313
+ // { action: 'provision' } enqueue a provision intent
314
+ // { action: 'wake' } enqueue a wake intent
315
+ // { action: 'denied', reason, required?, actual? } not allowed a box
316
+ //
317
+ // `blocked` (migration 104, task 1163): an Archon block bars the two "open"
318
+ // actions — provisioning a NEW box and waking a PARKED one — with the specific,
319
+ // actionable reason BOX_PROVISION_BLOCKED. It is checked PER-BRANCH (not up top)
320
+ // so it does NOT touch an already-active box: blocking is "no new opens" by
321
+ // default, and an active box keeps heartbeating ('ready') until it idles out or
322
+ // is closed. Tearing a running box down at block time is the Archon's separate,
323
+ // explicit choice (PATCH /boxes/:id/block { close_current:true } → deprovision).
324
+ function decideEnsureAction({ boxState, rank, status, blocked = false, floor = DEFAULT_PROVISION_FLOOR } = {}) {
325
+ // Same gate + order as decideSourceAccess: inactive first, then rank floor.
326
+ if (status !== 'active') {
327
+ return { action: 'denied', reason: 'BUILDER_INACTIVE' };
328
+ }
329
+ if (!rankMeetsBoxFloor(rank, floor)) {
330
+ return { action: 'denied', reason: 'INSUFFICIENT_RANK', required: floor, actual: rank ?? null };
331
+ }
332
+ switch (boxState) {
333
+ case 'active':
334
+ return { action: 'ready' };
335
+ case 'provisioning':
336
+ case 'waking':
337
+ case 'parking':
338
+ return { action: 'in_progress' };
339
+ case 'parked':
340
+ // Waking a parked box is an "open" — a block bars it (a blocked builder
341
+ // can't resume a box, only keep running one that's already active).
342
+ if (blocked) return { action: 'denied', reason: 'BOX_PROVISION_BLOCKED' };
343
+ return { action: 'wake' };
344
+ case 'none':
345
+ case 'destroyed':
346
+ case 'error':
347
+ case undefined:
348
+ case null:
349
+ if (blocked) return { action: 'denied', reason: 'BOX_PROVISION_BLOCKED' };
350
+ return { action: 'provision' };
351
+ default:
352
+ // An unrecognized state is not safe to act on automatically — fail closed
353
+ // to in_progress so the runner/operator resolves it, never a blind provision.
354
+ return { action: 'in_progress' };
355
+ }
356
+ }
357
+
358
+ // ---------------------------------------------------------------------------
359
+ // DB helpers (async). Each takes the pg pool (or a client mid-transaction).
360
+ // ---------------------------------------------------------------------------
361
+
362
+ async function getBoxByBuilderId(db, builderId) {
363
+ const { rows } = await db.query(
364
+ `SELECT * FROM builder_boxes WHERE builder_id = $1`,
365
+ [builderId]
366
+ );
367
+ return rows[0] || null;
368
+ }
369
+
370
+ // Ensure a row exists for this builder (state defaults to 'none'); returns it.
371
+ async function ensureBoxRow(db, builderId) {
372
+ await db.query(
373
+ `INSERT INTO builder_boxes (builder_id) VALUES ($1)
374
+ ON CONFLICT (builder_id) DO NOTHING`,
375
+ [builderId]
376
+ );
377
+ return getBoxByBuilderId(db, builderId);
378
+ }
379
+
380
+ async function listBoxes(db, { state } = {}) {
381
+ const params = [];
382
+ let where = '';
383
+ if (state) { params.push(state); where = `WHERE bb.state = $1`; }
384
+ const { rows } = await db.query(
385
+ `SELECT bb.*, b.github_login, b.display_name, b.rank,
386
+ b.box_blocked, b.box_blocked_reason, b.box_blocked_at
387
+ FROM builder_boxes bb
388
+ JOIN builders b ON b.id = bb.builder_id
389
+ ${where}
390
+ ORDER BY bb.updated_at DESC
391
+ LIMIT 1000`,
392
+ params
393
+ );
394
+ return rows;
395
+ }
396
+
397
+ async function recordEvent(db, { boxId, builderId, event, detail, costUsd, actor }) {
398
+ await db.query(
399
+ `INSERT INTO box_events (box_id, builder_id, event, detail, cost_usd, actor)
400
+ VALUES ($1, $2, $3, $4, $5, $6)`,
401
+ [boxId, builderId ?? null, event, detail ?? null, costUsd ?? null, actor ?? null]
402
+ );
403
+ }
404
+
405
+ // Heartbeat: bump last_activity_at for a builder's ACTIVE box, and optionally
406
+ // record whether a `claude` process is running (claudeActive). Returns the new
407
+ // state (or null if there is no box). A parked/none box is not "kept alive" by
408
+ // a stray heartbeat — only an active box's idle clock resets.
409
+ //
410
+ // Deliberately does NOT write a box_events row: heartbeats fire ~every 5 min per
411
+ // active box, so logging one per beat would grow the ledger unbounded
412
+ // (~100K rows/builder/year). last_activity_at IS the heartbeat record; box_events
413
+ // is reserved for the rare lifecycle transitions (provision/park/wake/deprovision/cost).
414
+ async function bumpActivity(db, builderId, { claudeActive } = {}) {
415
+ const hasClaude = typeof claudeActive === 'boolean';
416
+ const claudeCol = hasClaude ? ', claude_active = $2' : '';
417
+ // The FIRST time a box reports a live `claude` process, stamp
418
+ // first_connected_at — the write-once, monotonic "this builder has actually
419
+ // reached a working Claude on their box" signal that clears the onboarding
420
+ // 'box_connect' step (#823). COALESCE so a later true beat never moves it,
421
+ // and we only ever set it (never clear it) so a claude_active=false beat
422
+ // leaves it intact. No new bind param — it reuses now() inline.
423
+ const connectedCol = (hasClaude && claudeActive === true)
424
+ ? ', first_connected_at = COALESCE(first_connected_at, now())'
425
+ : '';
426
+ const params = hasClaude ? [builderId, claudeActive] : [builderId];
427
+ const { rows } = await db.query(
428
+ `UPDATE builder_boxes
429
+ SET last_activity_at = now(), updated_at = now()${claudeCol}${connectedCol}
430
+ WHERE builder_id = $1 AND state = 'active'
431
+ RETURNING state`,
432
+ params
433
+ );
434
+ if (rows[0]) return rows[0].state;
435
+ // No active box: report the current state (if any) without bumping.
436
+ const cur = await getBoxByBuilderId(db, builderId);
437
+ return cur ? cur.state : null;
438
+ }
439
+
440
+ // Has this builder's box EVER reported a live `claude` process? first_connected_at
441
+ // is stamped write-once by bumpActivity (the first claude_active=true heartbeat),
442
+ // so this is the monotonic "properly connected to the dev box" signal that backs
443
+ // the onboarding 'box_connect' step (#823). Cheap boolean; null/absent row → false.
444
+ async function hasEverConnected(db, builderId) {
445
+ const { rows } = await db.query(
446
+ `SELECT first_connected_at IS NOT NULL AS connected
447
+ FROM builder_boxes WHERE builder_id = $1`,
448
+ [builderId]
449
+ );
450
+ return !!(rows[0] && rows[0].connected);
451
+ }
452
+
453
+ // Columns setBoxState may write. The VALUES are parameterized, but the column
454
+ // NAMES are interpolated into the SQL, so the keys are whitelisted here: an
455
+ // unexpected key (e.g. if a future caller ever wires user-influenced input into
456
+ // the patch) is rejected, not trusted. Closes the latent-injection surface.
457
+ const SETTABLE_BOX_COLUMNS = new Set([
458
+ 'scope', 'provisioned_rank', 'region', 'size_slug', 'droplet_id', 'droplet_name',
459
+ 'snapshot_id', 'ip', 'hostname', 'last_activity_at', 'provisioned_at',
460
+ 'active_since', 'parked_at', 'destroyed_at', 'error_note', 'claude_active',
461
+ // host_keys/host_keys_at are normally written by setBoxHostKeys(), but
462
+ // deprovisionOne() clears them on teardown so a rebuilt box never serves its
463
+ // predecessor's SSH identity (idea 310) — hence they are settable here too.
464
+ 'host_keys', 'host_keys_at',
465
+ ]);
466
+
467
+ // Generic state-transition writer. `patch` is a column→value map merged into the
468
+ // UPDATE; `updated_at` is always touched. Returns the updated row.
469
+ async function setBoxState(db, builderId, state, patch = {}) {
470
+ const cols = ['state = $2', 'updated_at = now()'];
471
+ const params = [builderId, state];
472
+ let i = 3;
473
+ for (const [k, v] of Object.entries(patch)) {
474
+ if (!SETTABLE_BOX_COLUMNS.has(k)) {
475
+ throw new Error(`setBoxState: refusing to write non-allowlisted column '${k}'`);
476
+ }
477
+ cols.push(`${k} = $${i}`);
478
+ params.push(v);
479
+ i++;
480
+ }
481
+ const { rows } = await db.query(
482
+ `UPDATE builder_boxes SET ${cols.join(', ')} WHERE builder_id = $1 RETURNING *`,
483
+ params
484
+ );
485
+ return rows[0] || null;
486
+ }
487
+
488
+ // Accrue compute cost for a box: add to its running tally, append a 'cost'
489
+ // event, AND mirror to cost_log (category 'compute', builder-attributed) so the
490
+ // public status dashboard's by_builder split sees it. Called at park/deprovision.
491
+ async function recordComputeCost(db, { box, untilMs, actor }) {
492
+ const cost = computeActiveCostUsd({
493
+ sizeSlug: box.size_slug,
494
+ activeSinceMs: toMs(box.active_since),
495
+ untilMs,
496
+ });
497
+ if (cost <= 0) return 0;
498
+ await db.query(
499
+ `UPDATE builder_boxes
500
+ SET compute_cost_usd = compute_cost_usd + $2, updated_at = now()
501
+ WHERE id = $1`,
502
+ [box.id, cost]
503
+ );
504
+ await recordEvent(db, {
505
+ boxId: box.id, builderId: box.builder_id, event: 'cost', costUsd: cost,
506
+ detail: `${box.size_slug} active ${(((untilMs - toMs(box.active_since)) / 3_600_000) || 0).toFixed(2)}h`,
507
+ actor,
508
+ });
509
+ // source_ref is unique PER ACCRUAL INTERVAL (box id + the interval's end ms),
510
+ // not just per box — a box is parked/woken many times, each a distinct compute
511
+ // interval. The cost_log UNIQUE(source, source_ref) (migration 004) would
512
+ // otherwise collide on the second park and crash it. Keying on untilMs keeps
513
+ // idempotency where it matters: replaying the EXACT same accrual de-dupes
514
+ // (ON CONFLICT DO NOTHING) rather than double-charging.
515
+ await db.query(
516
+ `INSERT INTO cost_log (amount_usd, category, description, source, source_ref, builder_id)
517
+ VALUES ($1, 'compute', $2, 'box-lifecycle', $3, $4)
518
+ ON CONFLICT DO NOTHING`,
519
+ [
520
+ cost,
521
+ `Dev box compute (${box.size_slug})`,
522
+ `builder_boxes#${box.id}@${untilMs}`,
523
+ box.builder_id,
524
+ ]
525
+ );
526
+ return cost;
527
+ }
528
+
529
+ // Log a spin-up LEDGER EVENT each time a box is created (provision) or resumed
530
+ // (wake). Mirrors recordComputeCost but ALWAYS writes the row (even at $0) — the
531
+ // point is a timestamped "box came up" line in the per-builder ledger, not a
532
+ // charge. Idempotent per (box, kind, atMs) via the unique cost_log source_ref.
533
+ async function recordSpinupCost(db, { box, kind = 'provision', atMs, actor }) {
534
+ const cost = SPINUP_COST_USD;
535
+ if (cost > 0) {
536
+ await db.query(
537
+ `UPDATE builder_boxes SET compute_cost_usd = compute_cost_usd + $2, updated_at = now() WHERE id = $1`,
538
+ [box.id, cost]
539
+ );
540
+ }
541
+ await recordEvent(db, {
542
+ boxId: box.id, builderId: box.builder_id, event: 'cost', costUsd: cost,
543
+ detail: `Dev box spin-up (${kind})`, actor,
544
+ });
545
+ await db.query(
546
+ `INSERT INTO cost_log (amount_usd, category, description, source, source_ref, builder_id)
547
+ VALUES ($1, 'compute', $2, 'box-spinup', $3, $4)
548
+ ON CONFLICT DO NOTHING`,
549
+ [cost, `Dev box spin-up (${kind})`, `builder_boxes#${box.id}:spinup:${kind}@${atMs}`, box.builder_id]
550
+ );
551
+ return cost;
552
+ }
553
+
554
+ // Accrue STORAGE cost for a box that spent time parked (snapshot retained). The
555
+ // running-box disk is already in compute; this is the snapshot-only line. Called
556
+ // at deprovision when the box has a parked_at. No-op for a never-parked box.
557
+ async function recordStorageCost(db, { box, untilMs, actor }) {
558
+ const parkedSinceMs = toMs(box.parked_at);
559
+ const cost = storageCostUsd({ parkedSinceMs, untilMs });
560
+ if (cost <= 0) return 0;
561
+ await db.query(
562
+ `UPDATE builder_boxes SET compute_cost_usd = compute_cost_usd + $2, updated_at = now() WHERE id = $1`,
563
+ [box.id, cost]
564
+ );
565
+ await recordEvent(db, {
566
+ boxId: box.id, builderId: box.builder_id, event: 'cost', costUsd: cost,
567
+ detail: `Dev box storage (snapshot) ${(((untilMs - parkedSinceMs) / 3_600_000) || 0).toFixed(2)}h`,
568
+ actor,
569
+ });
570
+ await db.query(
571
+ `INSERT INTO cost_log (amount_usd, category, description, source, source_ref, builder_id)
572
+ VALUES ($1, 'compute', $2, 'box-storage', $3, $4)
573
+ ON CONFLICT DO NOTHING`,
574
+ [cost, 'Dev box storage (snapshot)', `builder_boxes#${box.id}:storage@${untilMs}`, box.builder_id]
575
+ );
576
+ return cost;
577
+ }
578
+
579
+ // The caller's own container-cost summary (#602): bucket their box-* cost_log
580
+ // rows into { spinup_usd, compute_usd, storage_usd, total_usd } so a builder sees
581
+ // what their dev box would cost. Own-scoped — callers pass req.builder.id.
582
+ async function boxCostSummaryForBuilder(db, builderId) {
583
+ const { rows } = await db.query(
584
+ `SELECT amount_usd, source FROM cost_log
585
+ WHERE builder_id = $1 AND source = ANY($2)`,
586
+ [builderId, BOX_COST_SOURCES]
587
+ );
588
+ return summarizeBoxCost(rows);
589
+ }
590
+
591
+ // The full container-cost ledger for Archon oversight (#602): every box-* cost
592
+ // row (newest first) joined to the builder, plus per-builder totals — so an
593
+ // Archon sees all box spend without a DB query. limit caps the row list.
594
+ async function boxCostLedger(db, { limit = 500 } = {}) {
595
+ const { rows: ledger } = await db.query(
596
+ `SELECT c.recorded_at, c.amount_usd, c.source, c.description, c.builder_id,
597
+ b.github_login AS builder_login
598
+ FROM cost_log c LEFT JOIN builders b ON b.id = c.builder_id
599
+ WHERE c.source = ANY($1)
600
+ ORDER BY c.recorded_at DESC
601
+ LIMIT $2`,
602
+ [BOX_COST_SOURCES, limit]
603
+ );
604
+ const { rows: totalsRows } = await db.query(
605
+ `SELECT c.builder_id, b.github_login AS builder_login,
606
+ SUM(c.amount_usd)::numeric(12,4) AS total_usd
607
+ FROM cost_log c LEFT JOIN builders b ON b.id = c.builder_id
608
+ WHERE c.source = ANY($1)
609
+ GROUP BY c.builder_id, b.github_login
610
+ ORDER BY total_usd DESC`,
611
+ [BOX_COST_SOURCES]
612
+ );
613
+ return { ledger, totals_by_builder: totalsRows };
614
+ }
615
+
616
+ // ---------------------------------------------------------------------------
617
+ // DB helpers — the #701 intent queue + #760 terminal-access store
618
+ // ---------------------------------------------------------------------------
619
+
620
+ // Enqueue a provision/wake intent for a builder. At most ONE open intent per
621
+ // builder (the ux_box_intents_open partial unique index) — a second concurrent
622
+ // enqueue collides and we return the ALREADY-OPEN intent instead of piling up.
623
+ // Returns { intent, created }: created=false means an open intent already
624
+ // existed (the route reports it rather than queuing a duplicate).
625
+ //
626
+ // The row always inserts state='pending' — the runner drains it on its next tick.
627
+ async function enqueueIntent(db, builderId, action, requestedBy) {
628
+ try {
629
+ const { rows } = await db.query(
630
+ `INSERT INTO box_intents (builder_id, action, requested_by, state)
631
+ VALUES ($1, $2, $3, 'pending') RETURNING *`,
632
+ [builderId, action, requestedBy ?? null]
633
+ );
634
+ return { intent: rows[0], created: true };
635
+ } catch (e) {
636
+ // 23505 = unique_violation on ux_box_intents_open: an open intent exists.
637
+ if (e && e.code === '23505') {
638
+ const existing = await getOpenIntent(db, builderId);
639
+ if (existing) return { intent: existing, created: false };
640
+ }
641
+ throw e;
642
+ }
643
+ }
644
+
645
+ // The builder's currently-open (pending|running) intent, if any.
646
+ async function getOpenIntent(db, builderId) {
647
+ const { rows } = await db.query(
648
+ `SELECT * FROM box_intents
649
+ WHERE builder_id = $1 AND state IN ('pending', 'running')
650
+ ORDER BY created_at DESC LIMIT 1`,
651
+ [builderId]
652
+ );
653
+ return rows[0] || null;
654
+ }
655
+
656
+ // Atomically claim the oldest pending intent for the runner: flip it to
657
+ // 'running', bump attempts. FOR UPDATE SKIP LOCKED so concurrent runner ticks
658
+ // (or an overlapping run) never grab the same row. Returns the claimed row or null.
659
+ async function claimNextIntent(db) {
660
+ const { rows } = await db.query(
661
+ `UPDATE box_intents
662
+ SET state = 'running', attempts = attempts + 1, updated_at = now()
663
+ WHERE id = (
664
+ SELECT id FROM box_intents
665
+ WHERE state = 'pending'
666
+ ORDER BY created_at
667
+ FOR UPDATE SKIP LOCKED
668
+ LIMIT 1
669
+ )
670
+ RETURNING *`
671
+ );
672
+ return rows[0] || null;
673
+ }
674
+
675
+ // Resolve a claimed intent to its terminal state ('done' | 'error').
676
+ async function resolveIntent(db, id, state, lastError) {
677
+ await db.query(
678
+ `UPDATE box_intents
679
+ SET state = $2, last_error = $3, updated_at = now(), resolved_at = now()
680
+ WHERE id = $1`,
681
+ [id, state, lastError ?? null]
682
+ );
683
+ }
684
+
685
+ // Record the web-terminal access a box published (POST /box/terminal, ADR 0038).
686
+ // `url` is the box's current Cloudflare-Tunnel terminal URL; `credential` is its
687
+ // per-box basic-auth password (user:pass behind the URL). BOTH are stored only
688
+ // for an ACTIVE box — they are meaningless for a parked/none box, and storing
689
+ // them would let a stale URL/credential be served after a park. Returns the
690
+ // updated state (or null if there is no box). Mirrors the migration-070 staleness
691
+ // model: the read side (GET /box/terminal) also gates on state='active'.
692
+ async function setTerminalAccess(db, builderId, { url, credential }) {
693
+ const { rows } = await db.query(
694
+ `UPDATE builder_boxes
695
+ SET terminal_url = $2, terminal_credential = $3, terminal_at = now(), updated_at = now()
696
+ WHERE builder_id = $1 AND state = 'active'
697
+ RETURNING state`,
698
+ [builderId, url, credential || null]
699
+ );
700
+ if (rows[0]) return rows[0].state;
701
+ const cur = await getBoxByBuilderId(db, builderId);
702
+ return cur ? cur.state : null;
703
+ }
704
+
705
+ // Record the SSH HOST PUBLIC keys a box reported (POST /box/host-keys — task 1187 /
706
+ // idea 235). `hostKeys` is newline-joined bare `<keytype> <base64>` pairs, already
707
+ // validated + normalized by box-onboard.validateHostKeyLines (no hostname prefix, no
708
+ // comment). Stored ONLY for an ACTIVE box, mirroring setTerminalAccess: a parked/none
709
+ // box's host identity is meaningless to pin, and the value persists across a snapshot
710
+ // wake anyway (so no NULL-clear on park). Returns the updated state, or the current
711
+ // state if there is no active row to write. PUBLIC keys only — never the private half.
712
+ async function setBoxHostKeys(db, builderId, { hostKeys }) {
713
+ const { rows } = await db.query(
714
+ `UPDATE builder_boxes
715
+ SET host_keys = $2, host_keys_at = now(), updated_at = now()
716
+ WHERE builder_id = $1 AND state = 'active'
717
+ RETURNING state`,
718
+ [builderId, hostKeys || null]
719
+ );
720
+ if (rows[0]) return rows[0].state;
721
+ const cur = await getBoxByBuilderId(db, builderId);
722
+ return cur ? cur.state : null;
723
+ }
724
+
725
+ // --- box code-version reporting (idea 332 / task 1316, ADR 0072) -------------
726
+ //
727
+ // A box reports its /workspace HEAD commit so the server can flag "running old
728
+ // code". WRITTEN only when state='active' (same gate as setBoxHostKeys) — we
729
+ // don't record a version for a box we're not serving.
730
+ async function setBoxCodeVersion(db, builderId, { sha, committedAt }) {
731
+ const { rows } = await db.query(
732
+ `UPDATE builder_boxes
733
+ SET code_sha = $2, code_committed_at = $3, code_reported_at = now(), updated_at = now()
734
+ WHERE builder_id = $1 AND state = 'active'
735
+ RETURNING state`,
736
+ [builderId, sha || null, committedAt || null]
737
+ );
738
+ if (rows[0]) return rows[0].state;
739
+ const cur = await getBoxByBuilderId(db, builderId);
740
+ return cur ? cur.state : null;
741
+ }
742
+
743
+ // A fresh box reports its version within ~10 min (the box-source-fetch cron tick).
744
+ // Give a generous grace so a just-provisioned box that hasn't reported YET isn't
745
+ // flagged, and a generous commit-date lag so a box mid-pull right after a deploy
746
+ // isn't flagged — the target is WEEKS-stale boxes, not minutes-behind ones.
747
+ const CODE_REPORT_GRACE_MS = 30 * 60 * 1000; // 30 min after provision
748
+ const CODE_STALE_LAG_MS = 2 * 24 * 60 * 60 * 1000; // 2 days behind deployed code
749
+
750
+ // Pure: decide whether a box is running stale code. Inputs accept Date | ISO
751
+ // string | epoch-ms (pg hands us Dates; tests pass either). Returns
752
+ // { stale, reason } where reason ∈
753
+ // not_active — only active boxes are judged (parked/destroyed → n/a)
754
+ // awaiting_first_report — active, never reported, still inside provision grace
755
+ // never_reported — active, past grace, never reported ⇒ too old to even
756
+ // run the reporter (a pre-self-update box) ⇒ STALE
757
+ // behind — reported, but its commit is > CODE_STALE_LAG_MS older
758
+ // than the server's deployed commit ⇒ STALE (a box whose
759
+ // `pull --ff-only` has been silently failing)
760
+ // current — reported and within the lag window
761
+ // unknown — reported, but we can't compare (no commit dates) ⇒ not stale
762
+ function computeCodeStaleness({ state, provisionedAt, codeReportedAt, codeCommittedAt, serverCommittedAt, now = Date.now() } = {}) {
763
+ const toMs = (x) => {
764
+ if (x == null) return null;
765
+ const t = x instanceof Date ? x.getTime() : new Date(x).getTime();
766
+ return Number.isFinite(t) ? t : null;
767
+ };
768
+ if (state !== 'active') return { stale: false, reason: 'not_active' };
769
+
770
+ const reported = toMs(codeReportedAt);
771
+ if (reported == null) {
772
+ const prov = toMs(provisionedAt);
773
+ const withinGrace = prov != null && (now - prov) < CODE_REPORT_GRACE_MS;
774
+ if (withinGrace) return { stale: false, reason: 'awaiting_first_report' };
775
+ return { stale: true, reason: 'never_reported' };
776
+ }
777
+
778
+ const boxCommit = toMs(codeCommittedAt);
779
+ const srvCommit = toMs(serverCommittedAt);
780
+ if (boxCommit == null || srvCommit == null) return { stale: false, reason: 'unknown' };
781
+ const lagMs = srvCommit - boxCommit;
782
+ if (lagMs > CODE_STALE_LAG_MS) return { stale: true, reason: 'behind', lag_ms: lagMs };
783
+ return { stale: false, reason: 'current' };
784
+ }
785
+
786
+ module.exports = {
787
+ // pure — rank
788
+ RANK_ORDER,
789
+ DEFAULT_PROVISION_FLOOR,
790
+ rankMeetsBoxFloor,
791
+ boxScopeForRank,
792
+ assertProvisionAllowed,
793
+ // pure — size/cost
794
+ SIZE_CATALOG,
795
+ DEFAULT_SIZE,
796
+ sizeHourlyUsd,
797
+ computeActiveCostUsd,
798
+ // pure — container cost ledger (#602)
799
+ SPINUP_COST_USD,
800
+ SNAPSHOT_MONTHLY_USD,
801
+ BOX_COST_SOURCES,
802
+ storageCostUsd,
803
+ summarizeBoxCost,
804
+ // pure — selection
805
+ effectiveActivityMs,
806
+ selectIdleBoxes,
807
+ selectDormantBoxes,
808
+ // pure — transitions
809
+ TRANSITIONS,
810
+ canTransition,
811
+ // pure — code staleness (idea 332 / task 1316)
812
+ computeCodeStaleness,
813
+ CODE_REPORT_GRACE_MS,
814
+ CODE_STALE_LAG_MS,
815
+ // pure — droplet⇄row drift (task 1289)
816
+ DROPLET_HOLDING_STATES,
817
+ loginFromDroplet,
818
+ planDropletDrift,
819
+ // pure — ensure (#701)
820
+ decideEnsureAction,
821
+ // db
822
+ getBoxByBuilderId,
823
+ ensureBoxRow,
824
+ listBoxes,
825
+ recordEvent,
826
+ bumpActivity,
827
+ hasEverConnected,
828
+ setBoxState,
829
+ setBoxCodeVersion,
830
+ recordComputeCost,
831
+ recordSpinupCost,
832
+ recordStorageCost,
833
+ boxCostSummaryForBuilder,
834
+ boxCostLedger,
835
+ // db — intent queue (#701) + terminal access (#760)
836
+ enqueueIntent,
837
+ getOpenIntent,
838
+ claimNextIntent,
839
+ resolveIntent,
840
+ setTerminalAccess,
841
+ setBoxHostKeys,
842
+ };