@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.
- package/.bongos-core.json +5727 -0
- package/.claude/skills/blocker-review/SKILL.md +91 -0
- package/.claude/skills/blocker-solve/SKILL.md +96 -0
- package/.claude/skills/builder-backup/SKILL.md +54 -0
- package/.claude/skills/builder-box/SKILL.md +45 -0
- package/.claude/skills/builder-claim/SKILL.md +76 -0
- package/.claude/skills/builder-connect/SKILL.md +59 -0
- package/.claude/skills/builder-cost/SKILL.md +47 -0
- package/.claude/skills/builder-end/SKILL.md +81 -0
- package/.claude/skills/builder-exit/SKILL.md +88 -0
- package/.claude/skills/builder-key/SKILL.md +40 -0
- package/.claude/skills/builder-reauth/SKILL.md +50 -0
- package/.claude/skills/builder-redteam/SKILL.md +109 -0
- package/.claude/skills/builder-release/SKILL.md +41 -0
- package/.claude/skills/builder-setup/SKILL.md +62 -0
- package/.claude/skills/builder-ship/SKILL.md +79 -0
- package/.claude/skills/builder-stage/SKILL.md +51 -0
- package/.claude/skills/builder-start/SKILL.md +46 -0
- package/.claude/skills/close-box/SKILL.md +43 -0
- package/.claude/skills/design-sync/SKILL.md +69 -0
- package/.claude/skills/dev/SKILL.md +49 -0
- package/.claude/skills/feedback/SKILL.md +32 -0
- package/.claude/skills/figma-design-sync/SKILL.md +74 -0
- package/.claude/skills/goal-review/SKILL.md +89 -0
- package/.claude/skills/idea-triage/SKILL.md +144 -0
- package/.claude/skills/ideate/SKILL.md +82 -0
- package/.claude/skills/merge-mode/SKILL.md +169 -0
- package/.claude/skills/new-project/SKILL.md +108 -0
- package/.claude/skills/otb-character-review/SKILL.md +71 -0
- package/.claude/skills/otb-design-review/SKILL.md +47 -0
- package/.claude/skills/otb-feedback-capture/SKILL.md +45 -0
- package/.claude/skills/otb-figma-sync/SKILL.md +54 -0
- package/.claude/skills/otb-tile-generate/SKILL.md +45 -0
- package/.claude/skills/paint/SKILL.md +70 -0
- package/.claude/skills/planning-session/SKILL.md +299 -0
- package/.claude/skills/priority-session/SKILL.md +134 -0
- package/.claude/skills/read-session-export/SKILL.md +58 -0
- package/.claude/skills/recall/SKILL.md +52 -0
- package/.claude/skills/session-handoff/SKILL.md +46 -0
- package/.claude/skills/status/SKILL.md +33 -0
- package/.devcontainer/Dockerfile +69 -0
- package/.devcontainer/README.md +73 -0
- package/.devcontainer/devcontainer-lock.json +19 -0
- package/.devcontainer/devcontainer.json +93 -0
- package/.devcontainer/healthcheck.sh +84 -0
- package/.devcontainer/init-firewall.sh +198 -0
- package/.devcontainer/post-create.sh +153 -0
- package/.dockerignore +36 -0
- package/.env.local.example +32 -0
- package/.gitattributes +83 -0
- package/.gitignore +91 -0
- package/CLAUDE.md +242 -0
- package/CODE_OF_CONDUCT.md +46 -0
- package/CONTRIBUTING.md +51 -0
- package/Dockerfile +46 -0
- package/GOVERNANCE.md +44 -0
- package/LICENSE +661 -0
- package/README.md +88 -0
- package/bin/bongos.js +222 -0
- package/clients/bongos-client/README.md +48 -0
- package/clients/bongos-client/bongos-client.global.js +617 -0
- package/clients/bongos-client/examples/hello-world.mjs +62 -0
- package/clients/bongos-client/index.cjs +619 -0
- package/clients/bongos-client/index.d.ts +793 -0
- package/clients/bongos-client/index.mjs +614 -0
- package/clients/bongos-client/package.json +32 -0
- package/config/branding.neutral.json +60 -0
- package/config/design-tokens.neutral.json +51 -0
- package/config/design-tokens.schema.json +96 -0
- package/config/modules.neutral.json +9 -0
- package/docker-compose.yml +52 -0
- package/docker-entrypoint.sh +30 -0
- package/docs/adr/0001-stack-choice-single-vps.md +45 -0
- package/docs/adr/0002-digitalocean-over-hetzner.md +27 -0
- package/docs/adr/0003-cloudflare-origin-cert-over-letsencrypt.md +35 -0
- package/docs/adr/0004-amazonprimea-name-and-trademark-acceptance.md +45 -0
- package/docs/adr/0005-invisible-other-players-in-v1.md +37 -0
- package/docs/adr/0006-singleton-worldroom-plus-queueroom.md +30 -0
- package/docs/adr/0007-persistent-identity-via-localstorage-uuid.md +31 -0
- package/docs/adr/0008-family-based-tile-generation.md +86 -0
- package/docs/adr/0008-google-chat-oauth-user-auth.md +37 -0
- package/docs/adr/0009-autotile-architecture.md +181 -0
- package/docs/adr/0010-pms-architecture.md +70 -0
- package/docs/adr/0011-terrain-id-rendering-model.md +174 -0
- package/docs/adr/0012-ground-object-cell-split.md +208 -0
- package/docs/adr/0013-paths-as-object-overlays-with-autotile-dispatch.md +158 -0
- package/docs/adr/0014-families-canonical-over-vocabulary.md +103 -0
- package/docs/adr/0015-task-dependencies-and-auto-promotion.md +125 -0
- package/docs/adr/0016-trust-boundary-server-enforced-permissions.md +132 -0
- package/docs/adr/0017-code-simplification-and-repo-cleanliness.md +74 -0
- package/docs/adr/0018-three-rank-model-goes-live.md +97 -0
- package/docs/adr/0019-red-team-bounty-payout-table.md +52 -0
- package/docs/adr/0020-security-model-public-stub.md +70 -0
- package/docs/adr/0021-per-builder-skill-model-preferences.md +99 -0
- package/docs/adr/0022-secrets-policy.md +124 -0
- package/docs/adr/0023-kind-multipliers-and-idea-bonus.md +118 -0
- package/docs/adr/0024-cloneable-repo-local-first-memory.md +277 -0
- package/docs/adr/0024-multi-agent-system-architecture.md +125 -0
- package/docs/adr/0025-offsite-backup-vendor-digitalocean-spaces.md +59 -0
- package/docs/adr/0025-structured-criterion-task-link.md +44 -0
- package/docs/adr/0026-bfg-memory-hygiene-and-cross-builder-learning.md +117 -0
- package/docs/adr/0027-bfg-session-inefficiency-evaluator.md +170 -0
- package/docs/adr/0028-honest-llm-spend-accounting.md +109 -0
- package/docs/adr/0029-status-page-outage-resilience.md +59 -0
- package/docs/adr/0030-builders-hall-live-updates.md +59 -0
- package/docs/adr/0031-cloud-dev-environments-for-builders.md +222 -0
- package/docs/adr/0032-discord-mirror-of-gds.md +50 -0
- package/docs/adr/0033-discord-bot-service-principal.md +82 -0
- package/docs/adr/0034-thetes-graduated-newcomer-rank.md +53 -0
- package/docs/adr/0035-builder-onboarding-three-paths.md +128 -0
- package/docs/adr/0036-builders-subdomain-and-ui-reorg.md +40 -0
- package/docs/adr/0037-discord-channel-management.md +51 -0
- package/docs/adr/0038-chromebook-ttyd-cloudflare-tunnel.md +111 -0
- package/docs/adr/0039-setup-first-onboarding-ux.md +49 -0
- package/docs/adr/0040-remote-control-default-browser-onramp.md +47 -0
- package/docs/adr/0041-temporary-grader-bypass-killswitch.md +40 -0
- package/docs/adr/0042-builder-self-deploy-ci-auto-merge.md +82 -0
- package/docs/adr/0043-git-ssh-trust-boundary-and-rank-floor-on-permission-paths.md +74 -0
- package/docs/adr/0044-mediterranean-palette-replacement.md +73 -0
- package/docs/adr/0044-per-box-live-game-preview.md +57 -0
- package/docs/adr/0045-devbox-desktop-app.md +133 -0
- package/docs/adr/0046-sandbox-first-review-gate.md +72 -0
- package/docs/adr/0047-discord-bugs-intake-anti-abuse.md +36 -0
- package/docs/adr/0048-works-in-progress-ia-split.md +32 -0
- package/docs/adr/0049-split-parallel-safety-contract.md +46 -0
- package/docs/adr/0050-device-flow-admission-invite-gated-by-default.md +43 -0
- package/docs/adr/0051-full-session-transcript-corpus.md +57 -0
- package/docs/adr/0052-sandbox-for-everyone-game-only-preview.md +70 -0
- package/docs/adr/0053-scoped-dev-box-session.md +55 -0
- package/docs/adr/0054-session-token-reward-cost-plus.md +63 -0
- package/docs/adr/0055-server-mediated-branch-publish.md +47 -0
- package/docs/adr/0056-prod-deploy-script-mirror-and-healthcheck-retry.md +62 -0
- package/docs/adr/0057-container-cost-ledger.md +28 -0
- package/docs/adr/0057-discord-archon-approval-channels.md +64 -0
- package/docs/adr/0058-automatic-gate-surface-gatekeeper.md +64 -0
- package/docs/adr/0059-single-approval-remove-devbox-approval-gate.md +37 -0
- package/docs/adr/0060-gds-retrieval-layer.md +97 -0
- package/docs/adr/0061-context-layer-decomposition.md +61 -0
- package/docs/adr/0062-medusa-instance-model-decoupling.md +107 -0
- package/docs/adr/0063-generated-repo-map-symbol-skeleton.md +44 -0
- package/docs/adr/0064-rename-medusa-to-cloud-bongos.md +49 -0
- package/docs/adr/0065-agpl-nonprofit-ai-first-governance.md +69 -0
- package/docs/adr/0066-generated-file-map-sections.md +51 -0
- package/docs/adr/0067-session-start-fast-forward-pull.md +43 -0
- package/docs/adr/0068-shared-art-key-for-newcomers.md +38 -0
- package/docs/adr/0069-hall-live-channel-sse-decouple.md +35 -0
- package/docs/adr/0070-deterministic-session-card-delivery.md +44 -0
- package/docs/adr/0071-box-confirm-before-destroyed-and-drift-reconcile.md +59 -0
- package/docs/adr/0072-bongos-app-mac-signed-first-windows-deferred.md +35 -0
- package/docs/adr/0072-dev-box-code-staleness-visibility.md +39 -0
- package/docs/adr/0073-builder-needs-signal-and-byok-gemini-key.md +64 -0
- package/docs/adr/0073-secrets-scan-exclude-uri-detector.md +37 -0
- package/docs/adr/0074-memory-map-stays-light.md +32 -0
- package/docs/adr/0075-per-system-cost-attribution.md +37 -0
- package/docs/adr/0076-in-session-deterministic-compaction.md +58 -0
- package/docs/adr/0077-content-addressed-llm-cache.md +32 -0
- package/docs/adr/0078-deterministic-first-cron.md +48 -0
- package/docs/adr/0079-value-gated-autonomous-engagement.md +39 -0
- package/docs/adr/0080-llm-cache-api-transport-and-trust-model.md +46 -0
- package/docs/adr/0081-tool-agnostic-design-layer.md +62 -0
- package/docs/adr/0082-productized-key-storage-provisioning.md +32 -0
- package/docs/adr/0082-server-side-merge-conflict-auto-resolution.md +55 -0
- package/docs/adr/0083-modular-architecture-strangler-build-plan.md +143 -0
- package/docs/adr/0084-auto-derived-per-task-rank-floor.md +86 -0
- package/docs/adr/0085-remove-default-per-builder-spend-cap.md +39 -0
- package/docs/adr/0086-goal-scoped-work-hierarchy.md +123 -0
- package/docs/adr/0087-bongos-app-architecture-and-handoff-contract.md +49 -0
- package/docs/adr/0087-compete-on-governance-not-tooling-cursor.md +65 -0
- package/docs/adr/0088-consistent-character-generative-animation.md +169 -0
- package/docs/adr/0089-modular-multi-model-adversarial-grader.md +179 -0
- package/docs/adr/0090-metic-task-authoring.md +73 -0
- package/docs/adr/0091-bounding-the-kernel-and-db-carve.md +106 -0
- package/docs/adr/0092-hosting-module-and-on-prem-target.md +117 -0
- package/docs/adr/0093-tranche-2-core-carve-sequence.md +72 -0
- package/docs/adr/0094-repo-atlas-self-drawing-codebase-map.md +49 -0
- package/docs/adr/0095-borrowed-memory-and-retrieval-concepts.md +65 -0
- package/docs/adr/0095-cross-agent-context-management.md +58 -0
- package/docs/adr/0096-require-reward-before-workable.md +209 -0
- package/docs/adr/0097-one-active-claim-per-session-and-worktree-binding.md +46 -0
- package/docs/adr/0097-retroactive-reward-backfill.md +124 -0
- package/docs/adr/0098-public-mirror-publish-manifest.md +92 -0
- package/docs/adr/0099-delayed-redacted-mirror-export.md +55 -0
- package/docs/adr/0100-two-repo-instance-model.md +60 -0
- package/docs/adr/0101-goal-hierarchy-phase-5-judge-membership-reward.md +90 -0
- package/docs/adr/0102-finish-bounding-the-kernel.md +36 -0
- package/docs/adr/0103-core-first-extraction-cloud-bongos-trunk.md +70 -0
- package/docs/adr/0103-gdsv4-bongos-consolidation.md +28 -0
- package/docs/adr/0104-trust-gds-api-channel-in-auto-mode.md +42 -0
- package/docs/adr/0105-instance-seed-migrations-out-of-core.md +47 -0
- package/docs/adr/0106-goal-owner-manager-authority.md +84 -0
- package/docs/adr/0107-module-upstreaming-contribution-to-core-catalog.md +83 -0
- package/docs/adr/0108-instance-composition-configurable-root.md +87 -0
- package/docs/adr/0109-self-describing-openapi-and-hosted-docs.md +44 -0
- package/docs/adr/0110-untrack-whole-file-generated-nav-docs.md +49 -0
- package/docs/adr/0111-instance-hosting-provisioning-module.md +135 -0
- package/docs/adr/0112-private-goals-invitations-join-requests.md +72 -0
- package/docs/adr/0113-sandbox-studio-descope-superseded-by-0044-0052.md +41 -0
- package/docs/adr/0114-instance-docs-discovery-contract.md +32 -0
- package/docs/adr/0115-scheduled-routines-portability.md +31 -0
- package/docs/adr/0116-api-error-envelope-and-code-taxonomy.md +62 -0
- package/docs/adr/0117-api-versioning-and-deprecation-policy.md +50 -0
- package/docs/adr/0118-api-contract-source-of-truth-code-first.md +47 -0
- package/docs/adr/0119-api-pagination-contract.md +34 -0
- package/docs/adr/0120-pay-on-land-and-builder-owned-rebase-gate.md +108 -0
- package/docs/adr/0121-greenfield-vs-brownfield-onboarding-adopt-existing-repo.md +97 -0
- package/docs/adr/0122-onboarding-diagram-renders-deploy-rendered-off-git.md +43 -0
- package/docs/adr/0123-box-idle-sweep-autosave-before-destroy.md +36 -0
- package/docs/adr/0124-typed-api-response-bodies.md +37 -0
- package/docs/adr/0125-control-plane-scaffolds-standalone-instance-repo.md +36 -0
- package/docs/adr/0126-dedicated-cloudbongos-control-plane-droplet.md +30 -0
- package/docs/adr/0127-cross-box-fleet-observability.md +64 -0
- package/docs/adr/0128-provisioning-runner-privilege-and-db-auth-model.md +46 -0
- package/docs/adr/0129-cloudbongos-control-plane-pull-deploy.md +34 -0
- package/docs/adr/0130-provision-remote-exec-over-ssh.md +61 -0
- package/docs/adr/0131-rank-scoped-skill-visibility.md +47 -0
- package/docs/adr/0132-cohost-fleet-control-plane-migration.md +54 -0
- package/docs/adr/0133-github-app-manifest-onboarding-signin.md +42 -0
- package/docs/adr/0134-private-first-npm-distribution.md +38 -0
- package/docs/adr/README.md +208 -0
- package/docs/api/openapi.json +13720 -0
- package/docs/api-reference.md +437 -0
- package/docs/architecture.md +244 -0
- package/docs/branding-contract.md +66 -0
- package/docs/branding-fill-prompt.md +25 -0
- package/docs/canonical-permissions.md +241 -0
- package/docs/design/container-exterior-tiles.md +49 -0
- package/docs/design/container-exterior.md +81 -0
- package/docs/design/container-interior.md +77 -0
- package/docs/design/modular-architecture/00-research-report.md +107 -0
- package/docs/design/modular-architecture/01-architecture.md +125 -0
- package/docs/design/modular-architecture/02-module-map.md +116 -0
- package/docs/design/modular-architecture/03-builder-flows.md +104 -0
- package/docs/design/modular-architecture/04-module-lifecycle.md +62 -0
- package/docs/design/modular-architecture/README.md +27 -0
- package/docs/design/vanilla-hall-ui-redesign-scope.md +284 -0
- package/docs/design/world-map-scroll.md +41 -0
- package/docs/design-contract.md +320 -0
- package/docs/file-map.md +418 -0
- package/docs/handoff-template.md +92 -0
- package/docs/modules-contract.md +293 -0
- package/docs/onboarding/browser-terminal-guide.md +73 -0
- package/docs/onboarding/diagrams/01-task-lifecycle.mmd +28 -0
- package/docs/onboarding/diagrams/02-rank-ladder.mmd +54 -0
- package/docs/onboarding/diagrams/03-drachmae-karma.mmd +37 -0
- package/docs/onboarding/diagrams/04-architecture.mmd +51 -0
- package/docs/onboarding/diagrams/05-versioning.mmd +29 -0
- package/docs/onboarding/diagrams/README.md +94 -0
- package/docs/onboarding/diagrams/assertions.json +170 -0
- package/docs/onboarding/diagrams/puppeteer-config.json +1 -0
- package/docs/onboarding/diagrams/render-config.json +9 -0
- package/docs/onboarding/drachmae-and-karma.md +178 -0
- package/docs/onboarding/primer.md +202 -0
- package/docs/project-context.template.md +67 -0
- package/docs/recipes/README.md +35 -0
- package/docs/recipes/audit-replay.md +100 -0
- package/docs/recipes/bongos-cli-release.md +66 -0
- package/docs/recipes/builder-box-lifecycle.md +152 -0
- package/docs/recipes/claude-design-loop.md +143 -0
- package/docs/recipes/claude-gemini-bongos-doors.md +155 -0
- package/docs/recipes/cloudbongos-staging-gate.md +139 -0
- package/docs/recipes/cloudbongos-standup.md +111 -0
- package/docs/recipes/devbox-app-release.md +93 -0
- package/docs/recipes/devbox-app-windows-test.md +128 -0
- package/docs/recipes/failed-ship-recovery-proof.md +102 -0
- package/docs/recipes/figma-design-loop.md +129 -0
- package/docs/recipes/gds-db-backup.md +255 -0
- package/docs/recipes/instance-deploy.md +37 -0
- package/docs/recipes/managed-settings-remote-control.md +416 -0
- package/docs/recipes/memory-backup.md +71 -0
- package/docs/recipes/memory-sync-conflicts.md +98 -0
- package/docs/recipes/multi-builder-merge.md +78 -0
- package/docs/recipes/ops-gotchas.md +212 -0
- package/docs/recipes/overnight-builder.md +53 -0
- package/docs/recipes/packaging-the-core.md +49 -0
- package/docs/recipes/parallel-load-results-2026-05-30.md +63 -0
- package/docs/recipes/parallel-load-test.md +32 -0
- package/docs/recipes/pixel-art-pipeline.md +411 -0
- package/docs/recipes/private-npm-distribution.md +79 -0
- package/docs/recipes/search-index-ingestion.md +67 -0
- package/docs/recipes/self-host.md +60 -0
- package/docs/recipes/ship-and-regrade.md +72 -0
- package/docs/recipes/staging-data.md +50 -0
- package/docs/recipes/staging-environment.md +215 -0
- package/docs/recipes/upgrading-the-core.md +58 -0
- package/docs/research/cursor-competitive-analysis.md +255 -0
- package/docs/research/llm-model-agnostic-and-cheaper-grader.md +227 -0
- package/migrations/003_pms.sql +284 -0
- package/migrations/004_pms_cost_idempotency.sql +13 -0
- package/migrations/005_pms_smoke_filter.sql +44 -0
- package/migrations/006_pms_v2.sql +378 -0
- package/migrations/007_pms_v2_hierarchy_trigger.sql +59 -0
- package/migrations/008_pms_v2_migration_allocator.sql +27 -0
- package/migrations/009_pms_v2_blockers_table.sql +68 -0
- package/migrations/010_uptime.sql +29 -0
- package/migrations/011_backfill_v2_v3_done_when.sql +20 -0
- package/migrations/012_pms_v2_task_lifecycle_states.sql +82 -0
- package/migrations/013_claim_locking.sql +22 -0
- package/migrations/014_pms_task_discipline.sql +86 -0
- package/migrations/015_pms_estimation_calibration.sql +140 -0
- package/migrations/016_pms_task_grades.sql +60 -0
- package/migrations/017_version_progress_exclude_abandoned.sql +49 -0
- package/migrations/019_rename_pms_v3_to_gds_v3.sql +65 -0
- package/migrations/020_task_dependencies.sql +99 -0
- package/migrations/021_v3_renumber_and_deps.sql +224 -0
- package/migrations/022_v3_reconcile_dep_gated_status.sql +41 -0
- package/migrations/023_pms_rank.sql +30 -0
- package/migrations/025_builder_exit_status.sql +15 -0
- package/migrations/027_builders_preferred_disciplines.sql +46 -0
- package/migrations/028_tasks_parallel_safe.sql +71 -0
- package/migrations/029_rank_three_live.sql +32 -0
- package/migrations/030_vulnerability_reports.sql +61 -0
- package/migrations/031_reward_simplification.sql +57 -0
- package/migrations/032_repo_health_snapshots.sql +56 -0
- package/migrations/033_audit_log.sql +64 -0
- package/migrations/034_lifecycle_breakdown_leaf_only.sql +62 -0
- package/migrations/035_auth_token_hygiene.sql +66 -0
- package/migrations/037_builder_skill_model_prefs.sql +44 -0
- package/migrations/038_task_override_requests.sql +51 -0
- package/migrations/039_newcomer_friendly_column.sql +124 -0
- package/migrations/040_builder_karma.sql +58 -0
- package/migrations/041_kind_multipliers_and_idea_bonus.sql +42 -0
- package/migrations/042_criterion_proposal_workflow.sql +78 -0
- package/migrations/043_builder_onboarding_observability.sql +66 -0
- package/migrations/044_tasks_requires_rank.sql +97 -0
- package/migrations/045_builder_onboarding_state.sql +147 -0
- package/migrations/046_cost_log_builder_attribution.sql +42 -0
- package/migrations/054_builder_memory.sql +108 -0
- package/migrations/055_task_criteria.sql +90 -0
- package/migrations/058_peer_votes.sql +41 -0
- package/migrations/059_onboarding_pick_disciplines.sql +59 -0
- package/migrations/060_access_requests.sql +46 -0
- package/migrations/061_backfill_override_ship_attribution.sql +29 -0
- package/migrations/062_session_records.sql +34 -0
- package/migrations/063_session_records_tool_errors_idx.sql +11 -0
- package/migrations/064_grade_attempts.sql +18 -0
- package/migrations/065_bfg_principal.sql +58 -0
- package/migrations/066_live_notify_triggers.sql +69 -0
- package/migrations/067_builder_boxes.sql +117 -0
- package/migrations/067_discord_bot_foundation.sql +69 -0
- package/migrations/068_thetes_requires_rank.sql +27 -0
- package/migrations/069_builder_ssh_keys.sql +66 -0
- package/migrations/070_box_intents.sql +101 -0
- package/migrations/071_box_first_provision_approval.sql +46 -0
- package/migrations/072_onboarding_ship_three_remove_primer.sql +80 -0
- package/migrations/073_box_terminal_access.sql +46 -0
- package/migrations/075_builder_sound_prefs.sql +28 -0
- package/migrations/078_box_claude_active.sql +22 -0
- package/migrations/079_box_first_connected_at.sql +20 -0
- package/migrations/081_tasks_needs_migration.sql +34 -0
- package/migrations/084_tasks_security_sensitive.sql +24 -0
- package/migrations/085_security_engagement_docs.sql +34 -0
- package/migrations/086_rename_disciplines.sql +125 -0
- package/migrations/088_builders_anthropic_link.sql +28 -0
- package/migrations/089_builders_monthly_budget.sql +19 -0
- package/migrations/090_box_scoped_sessions.sql +38 -0
- package/migrations/091_session_token_reward.sql +44 -0
- package/migrations/093_tasks_sandbox_review.sql +22 -0
- package/migrations/102_remove_box_first_provision_approval.sql +58 -0
- package/migrations/103_search_chunks.sql +90 -0
- package/migrations/104_builder_box_blocked.sql +34 -0
- package/migrations/105_box_host_keys.sql +35 -0
- package/migrations/111_medusa_customer0_framing.sql +35 -0
- package/migrations/112_drop_track_check.sql +25 -0
- package/migrations/113_merge_lease.sql +16 -0
- package/migrations/114_cloudbongos_rename_live_strings.sql +32 -0
- package/migrations/118_currency_label_decoupling.sql +23 -0
- package/migrations/119_builder_behavior_prefs.sql +31 -0
- package/migrations/121_box_code_version.sql +37 -0
- package/migrations/122_builder_gemini_key.sql +30 -0
- package/migrations/123_cost_log_tiers.sql +33 -0
- package/migrations/124_cost_baseline.sql +45 -0
- package/migrations/125_llm_cache.sql +54 -0
- package/migrations/126_autonomy_runs.sql +33 -0
- package/migrations/143_cost_calibration.sql +63 -0
- package/migrations/145_backfill_task_requires_rank.sql +84 -0
- package/migrations/160_goal_tier.sql +88 -0
- package/migrations/163_polymorphic_dependencies.sql +187 -0
- package/migrations/164_event_sound_prefs.sql +20 -0
- package/migrations/165_task_module_key.sql +15 -0
- package/migrations/171_claims_published_head_sha.sql +24 -0
- package/migrations/174_close_orphaned_claims_on_terminal_tasks.sql +30 -0
- package/migrations/175_task_grades_diff_hash.sql +9 -0
- package/migrations/176_promotion_respects_open_blockers.sql +58 -0
- package/migrations/176_ui_discipline.sql +40 -0
- package/migrations/177_require_reward_before_workable.sql +76 -0
- package/migrations/178_reward_gate_auto_assign_revert_autopromote.sql +61 -0
- package/migrations/179_search_chunk_usefulness.sql +33 -0
- package/migrations/180_one_active_claim_per_session.sql +54 -0
- package/migrations/181_cost_log_skill_name.sql +33 -0
- package/migrations/184_claim_release_requested.sql +25 -0
- package/migrations/185_module_upstreaming_goal_wiring.sql +59 -0
- package/migrations/187_goal_sort_order.sql +30 -0
- package/migrations/187_tasks_kind_check_add_verify.sql +21 -0
- package/migrations/README.md +88 -0
- package/migrations/core_188_goal_visibility_and_membership_requests.sql +71 -0
- package/migrations/core_189_core_upgrades_ledger.sql +37 -0
- package/migrations/core_189_credit_log_task_id_index.sql +19 -0
- package/migrations/core_190_rebuild_claimable_task_views.sql +71 -0
- package/migrations/instance/018_pms_v3_planning_seed.sql +131 -0
- package/migrations/instance/080_gds_v4_planning_seed.sql +75 -0
- package/migrations/instance/110_medusa_planning_seed.sql +70 -0
- package/migrations/instance/120_bongos_v1_planning_seed.sql +69 -0
- package/migrations/instance/144_bongos_v1_module_system_criterion.sql +26 -0
- package/migrations/instance/159_bongos_v1_goal_hierarchy_criterion.sql +29 -0
- package/migrations/instance/172_v2_world_planning_seed.sql +163 -0
- package/migrations/instance/173_bongos_v1_c7_core_modularized.sql +33 -0
- package/migrations/instance/182_gdsv4_bongos_consolidation.sql +132 -0
- package/migrations/instance/183_gdsv4_carryover_themed_goals.sql +116 -0
- package/migrations/instance/186_bongos_v1_governance_planning_seed.sql +69 -0
- package/modules/autonomy/CLAUDE.md +49 -0
- package/modules/autonomy/db.js +50 -0
- package/modules/autonomy/module.json +13 -0
- package/modules/autonomy/pollers/routine-timers.js +146 -0
- package/modules/autonomy/routes/autonomy.js +37 -0
- package/modules/builder-settings/CLAUDE.md +58 -0
- package/modules/builder-settings/builder-needs.js +115 -0
- package/modules/builder-settings/db.js +168 -0
- package/modules/builder-settings/display-name.js +84 -0
- package/modules/builder-settings/event-sound-prefs.js +169 -0
- package/modules/builder-settings/interaction-prefs.js +201 -0
- package/modules/builder-settings/model-alloc-prefs.js +147 -0
- package/modules/builder-settings/module.json +10 -0
- package/modules/builder-settings/render-prefs.js +103 -0
- package/modules/builder-settings/routes/settings.js +417 -0
- package/modules/builder-settings/settings.js +52 -0
- package/modules/builder-settings/skill-prefs.js +196 -0
- package/modules/builder-settings/sound-prefs.js +178 -0
- package/modules/builder-settings/wandering-prefs.js +282 -0
- package/modules/dev-box/CLAUDE.md +38 -0
- package/modules/dev-box/box-access.js +275 -0
- package/modules/dev-box/box-credential.js +94 -0
- package/modules/dev-box/box-onboard.js +486 -0
- package/modules/dev-box/boxes.js +842 -0
- package/modules/dev-box/db.js +39 -0
- package/modules/dev-box/module.json +14 -0
- package/modules/dev-box/routes/box.js +977 -0
- package/modules/discord/CLAUDE.md +50 -0
- package/modules/discord/auth.js +54 -0
- package/modules/discord/bug-attachments.js +210 -0
- package/modules/discord/db.js +96 -0
- package/modules/discord/discord-approvals.js +145 -0
- package/modules/discord/discord-audit.js +29 -0
- package/modules/discord/discord-bot.js +545 -0
- package/modules/discord/discord-bugs.js +257 -0
- package/modules/discord/discord-channels.js +412 -0
- package/modules/discord/discord-inbound.js +147 -0
- package/modules/discord/discord-message-buffer.js +54 -0
- package/modules/discord/discord-reaction.js +186 -0
- package/modules/discord/discord-role-sync.js +318 -0
- package/modules/discord/module.json +14 -0
- package/modules/discord/pollers/discord-bot.js +19 -0
- package/modules/discord/routes/bug-attachments.js +44 -0
- package/modules/discord/routes/discord.js +102 -0
- package/modules/discord/ship-broadcast.js +78 -0
- package/modules/discord/webhook.js +74 -0
- package/modules/economy/CLAUDE.md +48 -0
- package/modules/economy/achievements.js +594 -0
- package/modules/economy/cost-classify.js +331 -0
- package/modules/economy/cost-summary-shape.js +90 -0
- package/modules/economy/cost.js +438 -0
- package/modules/economy/credits.js +430 -0
- package/modules/economy/migrations/economy_001_fix_gds_shipper_description.sql +91 -0
- package/modules/economy/module.json +10 -0
- package/modules/economy/reward.js +61 -0
- package/modules/economy/routes/cost.js +97 -0
- package/modules/economy/routes/leaderboard.js +26 -0
- package/modules/economy/routes/reward.js +42 -0
- package/modules/economy/streak.js +285 -0
- package/modules/grading/CLAUDE.md +42 -0
- package/modules/grading/db.js +39 -0
- package/modules/grading/grader-rubric.json +151 -0
- package/modules/grading/grader-workers/efficiency.js +213 -0
- package/modules/grading/grader-workers/extract-json.js +80 -0
- package/modules/grading/grader-workers/fence.js +100 -0
- package/modules/grading/grader-workers/hacker.js +239 -0
- package/modules/grading/grader-workers/index.js +183 -0
- package/modules/grading/grader-workers/narc.js +207 -0
- package/modules/grading/grader-workers/quality.js +198 -0
- package/modules/grading/grader-workers/rubric.js +20 -0
- package/modules/grading/grader-workers/worker-prompt.js +89 -0
- package/modules/grading/grader-workers/worker-verdict.js +50 -0
- package/modules/grading/grader.js +1790 -0
- package/modules/grading/module.json +12 -0
- package/modules/grading/routes/grade.js +51 -0
- package/modules/hall-ui/CLAUDE.md +61 -0
- package/modules/hall-ui/module.json +14 -0
- package/modules/hall-ui/public/atlas.html +180 -0
- package/modules/hall-ui/public/atlas.js +336 -0
- package/modules/hall-ui/public/box-panel.js +423 -0
- package/modules/hall-ui/public/brand-apply.js +117 -0
- package/modules/hall-ui/public/builders.js +3028 -0
- package/modules/hall-ui/public/chime.wav +0 -0
- package/modules/hall-ui/public/copy-on-click.js +174 -0
- package/modules/hall-ui/public/cursors/arrow.png +0 -0
- package/modules/hall-ui/public/cursors/hand-pointer.png +0 -0
- package/modules/hall-ui/public/cursors/magnifier-out.png +0 -0
- package/modules/hall-ui/public/cursors/magnifier.png +0 -0
- package/modules/hall-ui/public/diagram-viewer.css +125 -0
- package/modules/hall-ui/public/diagram-viewer.js +330 -0
- package/modules/hall-ui/public/diagrams.html +113 -0
- package/modules/hall-ui/public/dom-utils.js +212 -0
- package/modules/hall-ui/public/drachmae.html +68 -0
- package/modules/hall-ui/public/drachmae.js +136 -0
- package/modules/hall-ui/public/gate.html +143 -0
- package/modules/hall-ui/public/gate.js +228 -0
- package/modules/hall-ui/public/goals-page.js +1297 -0
- package/modules/hall-ui/public/goals.html +376 -0
- package/modules/hall-ui/public/goals.js +119 -0
- package/modules/hall-ui/public/hall-widgets.js +189 -0
- package/modules/hall-ui/public/harbor.html +167 -0
- package/modules/hall-ui/public/harbor.js +335 -0
- package/modules/hall-ui/public/index.html +116 -0
- package/modules/hall-ui/public/instances-panel.js +172 -0
- package/modules/hall-ui/public/md-reader.js +234 -0
- package/modules/hall-ui/public/modules.html +119 -0
- package/modules/hall-ui/public/modules.js +154 -0
- package/modules/hall-ui/public/new-project-wizard.js +186 -0
- package/modules/hall-ui/public/not-ready.html +48 -0
- package/modules/hall-ui/public/pair.html +119 -0
- package/modules/hall-ui/public/pair.js +132 -0
- package/modules/hall-ui/public/primer.css +404 -0
- package/modules/hall-ui/public/primer.html +85 -0
- package/modules/hall-ui/public/primer.js +641 -0
- package/modules/hall-ui/public/profile.html +85 -0
- package/modules/hall-ui/public/profile.js +236 -0
- package/modules/hall-ui/public/ranks.html +69 -0
- package/modules/hall-ui/public/ranks.js +206 -0
- package/modules/hall-ui/public/roadmap.html +209 -0
- package/modules/hall-ui/public/roadmap.js +375 -0
- package/modules/hall-ui/public/sessions.html +92 -0
- package/modules/hall-ui/public/sessions.js +222 -0
- package/modules/hall-ui/public/settings.html +640 -0
- package/modules/hall-ui/public/settings.js +1219 -0
- package/modules/hall-ui/public/shell.js +495 -0
- package/modules/hall-ui/public/style.css +1748 -0
- package/modules/hall-ui/public/task-detail.js +253 -0
- package/modules/hall-ui/public/task.html +83 -0
- package/modules/hall-ui/public/task.js +326 -0
- package/modules/hall-ui/public/watch.html +333 -0
- package/modules/hall-ui/public/watch.js +859 -0
- package/modules/hall-ui/public/work.html +469 -0
- package/modules/hall-ui/public/work.js +1292 -0
- package/modules/ideas/CLAUDE.md +37 -0
- package/modules/ideas/blockers.js +100 -0
- package/modules/ideas/inbox.js +459 -0
- package/modules/ideas/module.json +10 -0
- package/modules/ideas/routes/blockers.js +120 -0
- package/modules/ideas/routes/inbox.js +230 -0
- package/modules/lifecycle/CLAUDE.md +63 -0
- package/modules/lifecycle/conflict-resolve.js +436 -0
- package/modules/lifecycle/db.js +4970 -0
- package/modules/lifecycle/done-when.js +355 -0
- package/modules/lifecycle/github-push.js +969 -0
- package/modules/lifecycle/goal-conflicts.js +133 -0
- package/modules/lifecycle/hierarchy.js +104 -0
- package/modules/lifecycle/lifecycle.js +58 -0
- package/modules/lifecycle/merge-lock.js +261 -0
- package/modules/lifecycle/migrations/lifecycle_001_goal_first_leads.sql +28 -0
- package/modules/lifecycle/migrations/lifecycle_002_backfill_orphan_task_goals.sql +36 -0
- package/modules/lifecycle/module.json +13 -0
- package/modules/lifecycle/pollers/publish-reconciler.js +20 -0
- package/modules/lifecycle/publish-reconciler.js +418 -0
- package/modules/lifecycle/routes/analytics.js +63 -0
- package/modules/lifecycle/routes/claims.js +506 -0
- package/modules/lifecycle/routes/dependencies.js +82 -0
- package/modules/lifecycle/routes/done-when.js +136 -0
- package/modules/lifecycle/routes/gate-approvals.js +197 -0
- package/modules/lifecycle/routes/goals.js +1419 -0
- package/modules/lifecycle/routes/lifecycle.js +43 -0
- package/modules/lifecycle/routes/tasks.js +1703 -0
- package/modules/lifecycle/routes/versions.js +128 -0
- package/modules/lifecycle/ship-card.js +505 -0
- package/modules/lifecycle/ship-preflight.js +225 -0
- package/modules/lifecycle/task-classifier.js +512 -0
- package/modules/lifecycle/version-close.js +106 -0
- package/modules/memory/CLAUDE.md +40 -0
- package/modules/memory/learnings.js +275 -0
- package/modules/memory/memory.js +572 -0
- package/modules/memory/module.json +12 -0
- package/modules/memory/routes/learnings.js +90 -0
- package/modules/memory/routes/memory.js +916 -0
- package/modules/memory/routes/search.js +188 -0
- package/modules/memory/search-chunker.js +252 -0
- package/modules/memory/search.js +320 -0
- package/modules/onboarding/CLAUDE.md +55 -0
- package/modules/onboarding/approval-broadcast.js +59 -0
- package/modules/onboarding/db.js +54 -0
- package/modules/onboarding/module.json +10 -0
- package/modules/onboarding/newcomer-restock.js +212 -0
- package/modules/onboarding/onboarding-state.js +532 -0
- package/modules/onboarding/onboarding.js +508 -0
- package/modules/onboarding/port.js +63 -0
- package/modules/onboarding/routes/access-requests.js +208 -0
- package/modules/onboarding/routes/onboarding.js +54 -0
- package/modules/security/CLAUDE.md +44 -0
- package/modules/security/module.json +10 -0
- package/modules/security/routes/reports.js +251 -0
- package/modules/security/secret-scrub.js +201 -0
- package/modules/security/security.js +294 -0
- package/modules/sessions/CLAUDE.md +39 -0
- package/modules/sessions/bfg.js +357 -0
- package/modules/sessions/db.js +326 -0
- package/modules/sessions/module.json +10 -0
- package/modules/sessions/pulse.js +149 -0
- package/modules/sessions/routes/sessions.js +331 -0
- package/modules/status-ui/CLAUDE.md +47 -0
- package/modules/status-ui/module.json +14 -0
- package/modules/status-ui/public/README.md +29 -0
- package/modules/status-ui/public/cursors/arrow.png +0 -0
- package/modules/status-ui/public/cursors/hand-pointer.png +0 -0
- package/modules/status-ui/public/index.html +363 -0
- package/modules/status-ui/public/snapshots/.gitkeep +0 -0
- package/modules/status-ui/public/status.js +1375 -0
- package/modules/status-ui/public/style.css +1342 -0
- package/package-lock.json +975 -0
- package/package.json +28 -0
- package/public-docs/index.html +35 -0
- package/public-docs/redoc.standalone.js +1838 -0
- package/public-docs/redoc.standalone.js.LICENSE.txt +102 -0
- package/public-landing/buddha.png +0 -0
- package/public-landing/create.html +868 -0
- package/public-landing/index.html +168 -0
- package/scripts/bongos-mcp.js +28 -0
- package/scripts/cron/style_guide_audit.sh +134 -0
- package/scripts/discord/README.md +52 -0
- package/scripts/discord/lib.js +80 -0
- package/scripts/discord/post.js +51 -0
- package/scripts/gds/CLAUDE.md +144 -0
- package/scripts/gds/abandon.js +58 -0
- package/scripts/gds/adopt-detect.js +123 -0
- package/scripts/gds/adopt-import.js +111 -0
- package/scripts/gds/adopt-preflight.js +121 -0
- package/scripts/gds/api.js +78 -0
- package/scripts/gds/architect-audit.js +288 -0
- package/scripts/gds/art-stage.js +152 -0
- package/scripts/gds/audit-authorship.sh +243 -0
- package/scripts/gds/audit-deps.js +121 -0
- package/scripts/gds/audit-exports.js +128 -0
- package/scripts/gds/audit-replay.sh +161 -0
- package/scripts/gds/audit-ships.js +251 -0
- package/scripts/gds/autonomy-gate.js +135 -0
- package/scripts/gds/backfill-cost.js +165 -0
- package/scripts/gds/backfill-sessions.js +351 -0
- package/scripts/gds/backfill-task-rewards.js +408 -0
- package/scripts/gds/backup.js +94 -0
- package/scripts/gds/bfg-principal-token.js +65 -0
- package/scripts/gds/box-auth-check.js +160 -0
- package/scripts/gds/box-connect-lib.js +453 -0
- package/scripts/gds/box.js +1258 -0
- package/scripts/gds/build-bongos.js +94 -0
- package/scripts/gds/capture.js +123 -0
- package/scripts/gds/cf-tunnel.js +419 -0
- package/scripts/gds/ci-grade.js +82 -0
- package/scripts/gds/claim-recall.js +112 -0
- package/scripts/gds/claim.js +333 -0
- package/scripts/gds/classify-discipline.js +153 -0
- package/scripts/gds/claude-materialize.js +176 -0
- package/scripts/gds/cleanup-orphans.js +74 -0
- package/scripts/gds/cli-lib.js +518 -0
- package/scripts/gds/code.js +101 -0
- package/scripts/gds/codemod-rename-src-gds.js +145 -0
- package/scripts/gds/conductor-dispatch.js +426 -0
- package/scripts/gds/context-pack.js +228 -0
- package/scripts/gds/coreB-carve-proof.sh +716 -0
- package/scripts/gds/cost.js +47 -0
- package/scripts/gds/db-backup-nightly.sh +160 -0
- package/scripts/gds/db-backup-offsite.sh +152 -0
- package/scripts/gds/demote.js +40 -0
- package/scripts/gds/design-sync.js +62 -0
- package/scripts/gds/diagnose-achievements.js +118 -0
- package/scripts/gds/diagram-facts.js +319 -0
- package/scripts/gds/discipline-modes.json +25 -0
- package/scripts/gds/discord-channels.js +66 -0
- package/scripts/gds/do-api.js +176 -0
- package/scripts/gds/docs-entropy.js +403 -0
- package/scripts/gds/doctor.js +389 -0
- package/scripts/gds/feedback-latest.js +94 -0
- package/scripts/gds/fetch-art-key.js +106 -0
- package/scripts/gds/fetch-sound-prefs.js +88 -0
- package/scripts/gds/figma-design-sync.js +61 -0
- package/scripts/gds/fitness.js +1446 -0
- package/scripts/gds/gate-review.js +322 -0
- package/scripts/gds/gating.js +325 -0
- package/scripts/gds/gds-db-backup.service +24 -0
- package/scripts/gds/gds-db-backup.timer +20 -0
- package/scripts/gds/gds-db-offsite.service +29 -0
- package/scripts/gds/gds-db-offsite.timer +18 -0
- package/scripts/gds/gen-api-client.js +508 -0
- package/scripts/gds/gen-api-docs.js +1037 -0
- package/scripts/gds/gen-atlas.js +366 -0
- package/scripts/gds/gen-diagrams.js +608 -0
- package/scripts/gds/gen-file-map.js +196 -0
- package/scripts/gds/gen-repo-map.js +519 -0
- package/scripts/gds/gen-session-index.js +210 -0
- package/scripts/gds/git-merge-regen.js +147 -0
- package/scripts/gds/grade-smoke.js +205 -0
- package/scripts/gds/hooks/pre-push.js +169 -0
- package/scripts/gds/http-api-client.js +88 -0
- package/scripts/gds/init.js +903 -0
- package/scripts/gds/install-git-hooks.js +114 -0
- package/scripts/gds/key-card.js +213 -0
- package/scripts/gds/key-status.js +126 -0
- package/scripts/gds/land-watch.js +168 -0
- package/scripts/gds/learning-capture.js +145 -0
- package/scripts/gds/lib.sh +44 -0
- package/scripts/gds/linkify-refs.js +473 -0
- package/scripts/gds/local-preview-lib.js +159 -0
- package/scripts/gds/local-preview.js +95 -0
- package/scripts/gds/login.js +225 -0
- package/scripts/gds/main-audit.js +279 -0
- package/scripts/gds/me.js +88 -0
- package/scripts/gds/memory-map.js +174 -0
- package/scripts/gds/merge-lock.js +71 -0
- package/scripts/gds/migration-namespace.js +200 -0
- package/scripts/gds/mirror-lineage-check.js +319 -0
- package/scripts/gds/mirror-redact.js +305 -0
- package/scripts/gds/module.js +482 -0
- package/scripts/gds/newcomer-floor.config.json +13 -0
- package/scripts/gds/newcomer-floor.js +415 -0
- package/scripts/gds/newcomer-floor.templates.json +86 -0
- package/scripts/gds/newcomer-floor.test.js +235 -0
- package/scripts/gds/next-number.js +163 -0
- package/scripts/gds/oauth-secret.js +197 -0
- package/scripts/gds/onboard.js +217 -0
- package/scripts/gds/onboarding-analyze.js +226 -0
- package/scripts/gds/onboarding-config.js +178 -0
- package/scripts/gds/onboarding-ship.js +92 -0
- package/scripts/gds/package-core.js +684 -0
- package/scripts/gds/paste-token.js +131 -0
- package/scripts/gds/portability-smoke-art.sh +85 -0
- package/scripts/gds/portability-smoke-memory.sh +147 -0
- package/scripts/gds/provision.js +1324 -0
- package/scripts/gds/publish-manifest.js +307 -0
- package/scripts/gds/pulse.js +309 -0
- package/scripts/gds/push-main.js +45 -0
- package/scripts/gds/read-session-export.js +359 -0
- package/scripts/gds/reauth.js +149 -0
- package/scripts/gds/recall.js +135 -0
- package/scripts/gds/release.js +71 -0
- package/scripts/gds/render-ideas.js +173 -0
- package/scripts/gds/repo-metrics.js +363 -0
- package/scripts/gds/resolve-skill-model.js +57 -0
- package/scripts/gds/run-routine.js +197 -0
- package/scripts/gds/run-unit-tests.js +178 -0
- package/scripts/gds/sandbox-stage.js +653 -0
- package/scripts/gds/search-ingest-db.js +209 -0
- package/scripts/gds/search-ingest-md.js +225 -0
- package/scripts/gds/security-baseline-check.sh +180 -0
- package/scripts/gds/seed-adopt-tasks.js +75 -0
- package/scripts/gds/seed-api-contract-goal-tasks.js +141 -0
- package/scripts/gds/seed-bongos-app-tasks.js +294 -0
- package/scripts/gds/seed-bongos-coreB-tranche1-tasks.js +81 -0
- package/scripts/gds/seed-bongos-coreB-tranche2-tasks.js +100 -0
- package/scripts/gds/seed-bongos-goal-hierarchy-tasks.js +307 -0
- package/scripts/gds/seed-bongos-module-system-tasks.js +346 -0
- package/scripts/gds/seed-bongos-tasks.js +214 -0
- package/scripts/gds/seed-gds-v4-tasks.js +624 -0
- package/scripts/gds/seed-governance-tasks.js +340 -0
- package/scripts/gds/seed-medusa-tasks.js +440 -0
- package/scripts/gds/seed-module-upstreaming.js +219 -0
- package/scripts/gds/seed-productization-tasks.js +235 -0
- package/scripts/gds/seed-provisioning-tasks.js +83 -0
- package/scripts/gds/seed-staging.sh +104 -0
- package/scripts/gds/seed-v2-world-tasks.js +283 -0
- package/scripts/gds/seed-v3-tasks.js +1981 -0
- package/scripts/gds/session-cost.js +393 -0
- package/scripts/gds/session-digest.js +179 -0
- package/scripts/gds/session-eval.js +576 -0
- package/scripts/gds/session.js +404 -0
- package/scripts/gds/setup.js +504 -0
- package/scripts/gds/shell.js +100 -0
- package/scripts/gds/ship.js +3618 -0
- package/scripts/gds/smoke-batch-claim.sh +213 -0
- package/scripts/gds/smoke-batch-release.sh +296 -0
- package/scripts/gds/smoke-box.sh +150 -0
- package/scripts/gds/smoke-claim-consistency.sh +212 -0
- package/scripts/gds/smoke-dependencies.sh +84 -0
- package/scripts/gds/smoke-gds.js +125 -0
- package/scripts/gds/smoke-write.sh +168 -0
- package/scripts/gds/start.js +782 -0
- package/scripts/gds/status.js +365 -0
- package/scripts/gds/triage.js +366 -0
- package/scripts/gds/upgrade.js +421 -0
- package/scripts/gds/validate-design.js +254 -0
- package/scripts/gds/version-close.js +549 -0
- package/scripts/migrate.sh +183 -0
- package/scripts/migrate_cost_ledger.sh +178 -0
- package/scripts/public-mirror-export.js +291 -0
- package/scripts/render-diagrams.sh +88 -0
- package/scripts/security/dep-audit.js +276 -0
- package/scripts/setup-builder.js +274 -0
- package/scripts/setup-builder.sh +28 -0
- package/scripts/snapshots/backfill-history.mjs +292 -0
- package/scripts/snapshots/maybe-render-on-deploy.sh +66 -0
- package/scripts/snapshots/render-pre-art-eras.mjs +329 -0
- package/scripts/snapshots/render-world.mjs +322 -0
- package/scripts/status-mirror-sync.js +192 -0
- package/scripts/status-probe.mjs +185 -0
- package/server.js +212 -0
- package/src/asset-version.js +71 -0
- package/src/bongos/CLAUDE.md +64 -0
- package/src/bongos/api-errors.js +281 -0
- package/src/bongos/api-path-404.js +67 -0
- package/src/bongos/api-prefix.js +38 -0
- package/src/bongos/app-pair.js +172 -0
- package/src/bongos/auth.js +979 -0
- package/src/bongos/cascade-dispatch.js +123 -0
- package/src/bongos/db-kernel.js +252 -0
- package/src/bongos/db.js +911 -0
- package/src/bongos/goal-scope-check.js +120 -0
- package/src/bongos/hierarchy-config.js +189 -0
- package/src/bongos/live-channel.js +145 -0
- package/src/bongos/llm-cache.js +260 -0
- package/src/bongos/llm-pricing.js +220 -0
- package/src/bongos/metrics.js +286 -0
- package/src/bongos/middleware/audit.js +274 -0
- package/src/bongos/middleware/error-envelope.js +93 -0
- package/src/bongos/middleware/rate-limit.js +195 -0
- package/src/bongos/module-scope-map.js +271 -0
- package/src/bongos/optimizer.js +166 -0
- package/src/bongos/path-match.js +47 -0
- package/src/bongos/permission-path-check.js +248 -0
- package/src/bongos/pool.js +55 -0
- package/src/bongos/repo-info.js +91 -0
- package/src/bongos/route-rank-check.js +420 -0
- package/src/bongos/routes/CLAUDE.md +43 -0
- package/src/bongos/routes/_helpers.js +369 -0
- package/src/bongos/routes/audit-log.js +59 -0
- package/src/bongos/routes/auth.js +755 -0
- package/src/bongos/routes/backup.js +104 -0
- package/src/bongos/routes/builders.js +526 -0
- package/src/bongos/routes/healthz.js +16 -0
- package/src/bongos/routes/instance.js +129 -0
- package/src/bongos/routes/live.js +125 -0
- package/src/bongos/routes/llm-cache.js +119 -0
- package/src/bongos/routes/me.js +380 -0
- package/src/bongos/routes/modules.js +131 -0
- package/src/bongos/routes/optimizer.js +54 -0
- package/src/bongos/routes/public.js +476 -0
- package/src/bongos/routes/security.js +207 -0
- package/src/bongos/routes.js +166 -0
- package/src/bongos/secret-box.js +177 -0
- package/src/bongos/sensitive-surfaces.js +30 -0
- package/src/bongos/serve-internal.js +889 -0
- package/src/bongos/uptime-poller.js +90 -0
- package/src/bongos-downloads.js +70 -0
- package/src/branding.js +182 -0
- package/src/build-info.js +77 -0
- package/src/devbox-downloads.js +67 -0
- package/src/instance-config.js +200 -0
- package/src/module-api.js +366 -0
- package/src/module-loader/loader.js +268 -0
- package/src/module-loader/manifest-schema.js +140 -0
- package/src/module-loader/provenance.js +28 -0
- package/src/module-loader/semver.js +100 -0
- package/src/module-seams.js +291 -0
- package/src/modules.js +254 -0
- package/src/platform-server.js +135 -0
- package/src/ui/adapters/claude-design/index.js +333 -0
- package/src/ui/adapters/figma/index.js +328 -0
- package/tests/abandon_task.mjs +138 -0
- package/tests/achievements_reconcile.mjs +167 -0
- package/tests/adopt_detect.mjs +141 -0
- package/tests/adopt_import.mjs +132 -0
- package/tests/adopt_preflight.mjs +105 -0
- package/tests/anti_grid.mjs +89 -0
- package/tests/api_client.mjs +231 -0
- package/tests/api_docs.mjs +283 -0
- package/tests/api_error_envelope_buildcheck.mjs +56 -0
- package/tests/api_error_envelope_middleware.mjs +99 -0
- package/tests/api_error_registry.mjs +108 -0
- package/tests/api_pagination_contract.mjs +51 -0
- package/tests/api_path_404.mjs +96 -0
- package/tests/app_pair.mjs +168 -0
- package/tests/architect.mjs +140 -0
- package/tests/art_key.mjs +121 -0
- package/tests/art_stage.mjs +121 -0
- package/tests/asset_version.mjs +94 -0
- package/tests/atlas.mjs +69 -0
- package/tests/audit_deps.mjs +74 -0
- package/tests/audit_middleware.mjs +385 -0
- package/tests/auth_admission_gate.mjs +164 -0
- package/tests/auth_anthropic_identity.mjs +95 -0
- package/tests/auth_duplicate_cookie.mjs +56 -0
- package/tests/auth_error_brand.mjs +77 -0
- package/tests/auth_flow_slice1.mjs +223 -0
- package/tests/auth_inactive_builder.mjs +94 -0
- package/tests/auth_page_gate.mjs +92 -0
- package/tests/auth_session_ttl.mjs +155 -0
- package/tests/autonomous_fit_protected_paths.mjs +115 -0
- package/tests/autonomy_gate.mjs +85 -0
- package/tests/autotile_variants.mjs +256 -0
- package/tests/autotile_xverify.mjs +133 -0
- package/tests/backfill_sessions.mjs +101 -0
- package/tests/backfill_task_rewards.mjs +152 -0
- package/tests/backup_cli.mjs +104 -0
- package/tests/bfg.mjs +327 -0
- package/tests/bfg_write.mjs +423 -0
- package/tests/bongos_box_connect.mjs +89 -0
- package/tests/bongos_dispatch.mjs +74 -0
- package/tests/bongos_downloads.mjs +118 -0
- package/tests/bongos_login.mjs +34 -0
- package/tests/box_access.mjs +186 -0
- package/tests/box_auth_check.mjs +114 -0
- package/tests/box_code_staleness.mjs +116 -0
- package/tests/box_connect_e2e.mjs +196 -0
- package/tests/box_cost.mjs +70 -0
- package/tests/box_dns_repoint.mjs +151 -0
- package/tests/box_host_keys.mjs +102 -0
- package/tests/box_onboard.mjs +317 -0
- package/tests/box_scope_session.mjs +115 -0
- package/tests/box_ship_permissions.mjs +71 -0
- package/tests/box_terminal_ssrf.mjs +90 -0
- package/tests/boxes.mjs +1255 -0
- package/tests/branding.mjs +88 -0
- package/tests/bug_attachments.mjs +189 -0
- package/tests/build_info.mjs +88 -0
- package/tests/builder_needs.mjs +99 -0
- package/tests/cap_newcomer_tasks.mjs +114 -0
- package/tests/cascade-dispatch.mjs +95 -0
- package/tests/cf_tunnel.mjs +459 -0
- package/tests/claim_recall.mjs +150 -0
- package/tests/claims_bind_session.mjs +47 -0
- package/tests/claude_materialize.mjs +148 -0
- package/tests/cli_box_scope.mjs +52 -0
- package/tests/cli_lib_formatters.mjs +87 -0
- package/tests/cli_token_reissue.mjs +147 -0
- package/tests/client_bundle_route.mjs +47 -0
- package/tests/cloud_bongos_pack.mjs +85 -0
- package/tests/conductor.mjs +496 -0
- package/tests/confirm_attribution.mjs +156 -0
- package/tests/conflict_resolve.mjs +462 -0
- package/tests/context_pack.mjs +240 -0
- package/tests/cost.mjs +94 -0
- package/tests/cost_audit.mjs +122 -0
- package/tests/cost_budget_status.mjs +78 -0
- package/tests/cost_reconcile.mjs +102 -0
- package/tests/cost_summary_shape.mjs +94 -0
- package/tests/credit_grant.mjs +376 -0
- package/tests/criterion_progress.mjs +220 -0
- package/tests/criterion_proposal.mjs +109 -0
- package/tests/criterion_ref_parse.mjs +53 -0
- package/tests/currency_label.mjs +119 -0
- package/tests/day_cycle.mjs +197 -0
- package/tests/dependencies_migration.mjs +77 -0
- package/tests/dependencies_polymorphic.mjs +212 -0
- package/tests/deploy_instance.mjs +78 -0
- package/tests/devbox_downloads.mjs +122 -0
- package/tests/diff_derived_signals.mjs +115 -0
- package/tests/discord_approvals.mjs +174 -0
- package/tests/discord_autojoin.mjs +158 -0
- package/tests/discord_bot.mjs +94 -0
- package/tests/discord_bugs.mjs +297 -0
- package/tests/discord_channels.mjs +337 -0
- package/tests/discord_foundation.mjs +76 -0
- package/tests/discord_inbound.mjs +190 -0
- package/tests/discord_message_buffer.mjs +87 -0
- package/tests/discord_oauth.mjs +55 -0
- package/tests/discord_reaction.mjs +170 -0
- package/tests/discord_role_sync.mjs +151 -0
- package/tests/display_name.mjs +123 -0
- package/tests/dns_preflight.mjs +74 -0
- package/tests/event_sound_prefs.mjs +194 -0
- package/tests/eviction_policy.mjs +279 -0
- package/tests/figma_adapter.mjs +199 -0
- package/tests/file_map.mjs +91 -0
- package/tests/first_admin_bootstrap.mjs +40 -0
- package/tests/fitness.mjs +435 -0
- package/tests/fitness_two_root.mjs +104 -0
- package/tests/fixtures/session-506-digest.json +34 -0
- package/tests/gate_approvals.mjs +451 -0
- package/tests/gate_review.mjs +152 -0
- package/tests/git_merge_regen.mjs +151 -0
- package/tests/github_push_ancestry.mjs +119 -0
- package/tests/github_push_land_proof.mjs +127 -0
- package/tests/github_push_merge_driver.mjs +175 -0
- package/tests/goal_abuse_matrix.mjs +187 -0
- package/tests/goal_achievement.mjs +146 -0
- package/tests/goal_conflicts.mjs +305 -0
- package/tests/goal_dependencies_read.mjs +73 -0
- package/tests/goal_member_roles.mjs +420 -0
- package/tests/goal_membership_requests.mjs +332 -0
- package/tests/goal_routes.mjs +203 -0
- package/tests/goal_scope_check.mjs +167 -0
- package/tests/goal_tier.mjs +312 -0
- package/tests/goal_wall_proof.mjs +112 -0
- package/tests/grade_attempts.mjs +73 -0
- package/tests/grade_attribution.mjs +117 -0
- package/tests/grade_diff_hash.mjs +191 -0
- package/tests/grade_server_authoritative.mjs +305 -0
- package/tests/grade_unavailable_display.mjs +66 -0
- package/tests/grader.mjs +1960 -0
- package/tests/grader_bypass.mjs +57 -0
- package/tests/grader_panel.mjs +252 -0
- package/tests/grader_prompt_fencing.mjs +192 -0
- package/tests/helpers.mjs +52 -0
- package/tests/hierarchy-config.mjs +81 -0
- package/tests/init.mjs +463 -0
- package/tests/instance_config.mjs +76 -0
- package/tests/instance_manifest.mjs +110 -0
- package/tests/interaction_prefs.mjs +72 -0
- package/tests/key-card.mjs +110 -0
- package/tests/key-status.mjs +45 -0
- package/tests/land_watch.mjs +115 -0
- package/tests/lifecycle-card.mjs +145 -0
- package/tests/lifecycle_breakdown_view.mjs +236 -0
- package/tests/linkify_refs.mjs +264 -0
- package/tests/live_channel.mjs +128 -0
- package/tests/live_sse.mjs +122 -0
- package/tests/llm-cache-route.mjs +110 -0
- package/tests/llm-cache.mjs +140 -0
- package/tests/llm-pricing.mjs +152 -0
- package/tests/local_preview.mjs +136 -0
- package/tests/main_audit.mjs +137 -0
- package/tests/memory_isolation.mjs +431 -0
- package/tests/memory_map.mjs +107 -0
- package/tests/memory_pull.mjs +429 -0
- package/tests/merge_lease.mjs +100 -0
- package/tests/merge_lock.mjs +312 -0
- package/tests/metrics.mjs +147 -0
- package/tests/migration_allocation.mjs +146 -0
- package/tests/migration_namespace.mjs +185 -0
- package/tests/migrations_fresh_replay_guard.mjs +38 -0
- package/tests/mirror_lineage_fulltree.mjs +45 -0
- package/tests/mirror_lineage_proof.mjs +72 -0
- package/tests/mirror_redact.mjs +193 -0
- package/tests/model_alloc_prefs.mjs +63 -0
- package/tests/module-scope-map.mjs +88 -0
- package/tests/module_api.mjs +110 -0
- package/tests/module_cli.mjs +292 -0
- package/tests/module_contributions.mjs +148 -0
- package/tests/module_loader.mjs +286 -0
- package/tests/module_manifest.mjs +194 -0
- package/tests/module_owned_tests.mjs +105 -0
- package/tests/module_provenance.mjs +34 -0
- package/tests/module_route_rank.mjs +147 -0
- package/tests/module_seams.mjs +68 -0
- package/tests/modules.mjs +156 -0
- package/tests/modules_route.mjs +38 -0
- package/tests/newcomer_floor.mjs +17 -0
- package/tests/newcomer_restock.mjs +80 -0
- package/tests/next_number.mjs +154 -0
- package/tests/oauth_secret.mjs +60 -0
- package/tests/onboard.mjs +70 -0
- package/tests/onboarding_gate.mjs +267 -0
- package/tests/onboarding_observability.mjs +344 -0
- package/tests/onboarding_route_signals.mjs +175 -0
- package/tests/onboarding_state.mjs +346 -0
- package/tests/one_active_claim_per_session.mjs +111 -0
- package/tests/orphaned_claim_ship.mjs +135 -0
- package/tests/override_attribution.mjs +142 -0
- package/tests/override_request_ownership.mjs +235 -0
- package/tests/package_core.mjs +435 -0
- package/tests/parallel-load.mjs +402 -0
- package/tests/path_match.mjs +62 -0
- package/tests/permission_path.mjs +151 -0
- package/tests/platform_boot.mjs +167 -0
- package/tests/pre_compact.mjs +60 -0
- package/tests/preview_server.mjs +127 -0
- package/tests/profile_route.mjs +197 -0
- package/tests/provision.mjs +805 -0
- package/tests/provisioning_github_app_routes.mjs +98 -0
- package/tests/public_mirror_export.mjs +124 -0
- package/tests/public_task_resolver.mjs +112 -0
- package/tests/publish_branch_route.mjs +422 -0
- package/tests/publish_manifest.mjs +184 -0
- package/tests/publish_reconciler.mjs +728 -0
- package/tests/rank_gate.mjs +463 -0
- package/tests/rate_limit_public.mjs +217 -0
- package/tests/read_session_export.mjs +89 -0
- package/tests/recall_cli.mjs +82 -0
- package/tests/recall_hints.mjs +119 -0
- package/tests/recent_shipped_window.mjs +80 -0
- package/tests/regrade_eligibility.mjs +202 -0
- package/tests/render_prefs.mjs +122 -0
- package/tests/repo_map.mjs +92 -0
- package/tests/reward_gate.mjs +284 -0
- package/tests/route_rank_check.mjs +243 -0
- package/tests/route_validation.mjs +490 -0
- package/tests/routine_timers.mjs +72 -0
- package/tests/rubric_fixtures.mjs +140 -0
- package/tests/run_routine.mjs +69 -0
- package/tests/sandbox_stage.mjs +582 -0
- package/tests/scrubber_corpus.mjs +257 -0
- package/tests/search_chunker.mjs +209 -0
- package/tests/search_ingest.mjs +96 -0
- package/tests/search_ingest_db.mjs +88 -0
- package/tests/search_isolation.mjs +204 -0
- package/tests/search_route.mjs +95 -0
- package/tests/secret_box.mjs +189 -0
- package/tests/security_docs.mjs +186 -0
- package/tests/security_route.mjs +108 -0
- package/tests/served_paths_shipped.mjs +118 -0
- package/tests/session-cards.mjs +142 -0
- package/tests/session_506_regression.mjs +69 -0
- package/tests/session_caps.mjs +238 -0
- package/tests/session_cost.mjs +133 -0
- package/tests/session_digest.mjs +73 -0
- package/tests/session_eval.mjs +386 -0
- package/tests/session_index.mjs +105 -0
- package/tests/session_records.mjs +369 -0
- package/tests/session_search.mjs +78 -0
- package/tests/session_start_freshness.mjs +123 -0
- package/tests/setup_smoke.mjs +154 -0
- package/tests/ship_autolinkify.mjs +60 -0
- package/tests/ship_automerge_injection.mjs +174 -0
- package/tests/ship_broadcast_routing.mjs +125 -0
- package/tests/ship_broadcast_server.mjs +175 -0
- package/tests/ship_card.mjs +280 -0
- package/tests/ship_ci_deploy.mjs +374 -0
- package/tests/ship_preflight.mjs +368 -0
- package/tests/ship_premerge.mjs +75 -0
- package/tests/ship_resume.mjs +129 -0
- package/tests/ship_upload_skip.mjs +89 -0
- package/tests/ship_zero_commit.mjs +60 -0
- package/tests/single_host_hall.mjs +98 -0
- package/tests/skill_prefs.mjs +150 -0
- package/tests/smoke_preflight.mjs +66 -0
- package/tests/snapshot_picker.mjs +233 -0
- package/tests/sound_prefs.mjs +198 -0
- package/tests/sprite_facing.mjs +35 -0
- package/tests/status-probe.mjs +89 -0
- package/tests/status_backfill.mjs +94 -0
- package/tests/stop_refcheck.mjs +127 -0
- package/tests/streak.mjs +175 -0
- package/tests/subdomain_auth.mjs +58 -0
- package/tests/task_classifier_vocab.mjs +41 -0
- package/tests/task_create_idempotency.mjs +96 -0
- package/tests/task_goal_link.mjs +93 -0
- package/tests/task_ownership_gate.mjs +125 -0
- package/tests/terrain_picker.mjs +298 -0
- package/tests/touches_overlap.mjs +75 -0
- package/tests/triage_development.mjs +140 -0
- package/tests/turn_in_place.mjs +280 -0
- package/tests/upgrade.mjs +357 -0
- package/tests/verify_task_guard.mjs +64 -0
- package/tests/version_close.mjs +76 -0
- package/tests/versions_route.mjs +93 -0
- package/tests/walkability.mjs +188 -0
- package/tests/wandering_prefs.mjs +176 -0
- package/tests/worktree_disambiguation.mjs +105 -0
- package/tests/worktree_guard.mjs +90 -0
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
# ADR 0028 — Honest LLM-spend accounting (grader envelope cost + session transcript pricing)
|
|
2
|
+
|
|
3
|
+
- **Status:** Accepted
|
|
4
|
+
- **Date:** 2026-05-30
|
|
5
|
+
- **Deciders:** spike [#483](https://example.com/builders#/task/483) (design), commissioned by spike [#482](https://example.com/builders#/task/482) audit ([docs/audits/<redacted>.md](../audits/<redacted>.md))
|
|
6
|
+
- **Supersedes the implicit "log cost manually" posture** that left `cost_log`'s `api` category at $0.60 across the whole project.
|
|
7
|
+
|
|
8
|
+
## Context
|
|
9
|
+
|
|
10
|
+
The [#482](https://example.com/builders#/task/482) audit established that the dominant real cost of the project — Claude API/token spend — is essentially uncaptured. `cost_log` has one writer (`db.logCost`) reached only by manual `POST /api/gds/cost` and the art-pipeline backfill. Two large, *automatic* sources of spend land nowhere:
|
|
11
|
+
|
|
12
|
+
1. **The per-ship grader panel** — 4 workers (Narc, Quality, Hacker, Efficiency), each an Opus/Sonnet `claude -p` subprocess, run on every confirm (149+ GDS-V3 ships).
|
|
13
|
+
2. **The interactive builder sessions** — the Opus 4.x 1M-context sessions builders actually work in.
|
|
14
|
+
|
|
15
|
+
The audit's F4 lesson: a capture that requires a manual step won't happen (17 consecutive $0 days proved it). Capture must be **automatic** and sit at a seam that always executes.
|
|
16
|
+
|
|
17
|
+
This ADR decides *how* spend is captured for each source. It is the design deliverable of spike [#483](https://example.com/builders#/task/483) and the blocking input for the two implementation tasks ([#486](https://example.com/builders#/task/486) grader; the session-capture task).
|
|
18
|
+
|
|
19
|
+
## Decision
|
|
20
|
+
|
|
21
|
+
Capture the two sources at **two different seams**, because their cost data lives in two different places. Do **not** force one mechanism to cover both.
|
|
22
|
+
|
|
23
|
+
### 1. Grader cost — read it from the subprocess envelope (client-side, exact)
|
|
24
|
+
|
|
25
|
+
`grader.runSubagent` spawns each worker as `claude -p --print --output-format json` ([src/bongos/grader.js](../../src/bongos/grader.js)). With `--output-format json`, the CLI returns a result envelope that includes **`total_cost_usd`** (and a `usage` block) — *verified* on a real call: `{type, …, total_cost_usd: 0.198, usage, modelUsage}`. The old `extractAssistantResult` parsed only `env.result` and **discarded the cost**.
|
|
26
|
+
|
|
27
|
+
> **Correction (made during [#486](https://example.com/builders#/task/486) implementation):** the panel does **not** run server-side. `gradeSubagent` is invoked **client-side in `scripts/gds/ship.js`** (and `conductor-dispatch.js`); the server's `/tasks/:id/grade` route only *validates/scores* the result ship.js POSTs. So there is no in-process DB handle at grade time — the cost is logged over HTTP, exactly like the session path. The substance below (exact envelope cost, no pricing table) is unchanged; only the seam moved from `db.logCost` to `POST /api/gds/cost`.
|
|
28
|
+
|
|
29
|
+
**Decision:** stop discarding it. `runSubagent` now attaches `cost_usd` (from `extractCostUsd(stdout)`); `runWorker` carries it onto each `WorkerResult`; `aggregatePanel`/`aggregateShots` sum it into **`panel_cost_usd`**. After the grade POST succeeds, `ship.js` POSTs one `cost_log` row:
|
|
30
|
+
|
|
31
|
+
```
|
|
32
|
+
POST /api/gds/cost {
|
|
33
|
+
task_id, // the task being graded
|
|
34
|
+
amount_usd: panel_cost_usd, // Σ env.total_cost_usd across workers/shots
|
|
35
|
+
category: 'api',
|
|
36
|
+
source: 'grader',
|
|
37
|
+
source_ref: `grade-${taskId}-${headSha}`, // idempotent; a new commit = a new row
|
|
38
|
+
// builder_id defaults to the authed shipper server-side
|
|
39
|
+
}
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
- **Exact, not estimated** — the CLI computes `total_cost_usd` itself, so no pricing table is needed on this path. This is why the grader path is decoupled from the pricing module below.
|
|
43
|
+
- **Idempotent** — `source='grader'` + `source_ref=grade-<task>-<shortSha>` rides the existing `ON CONFLICT (source, source_ref) DO NOTHING`. A re-ship at the **same** HEAD is a no-op; a re-grade after new commits is a distinct row (a real, countable event per ADR 0027's grade-attempt log).
|
|
44
|
+
- **Best-effort, never blocks the gate** — the POST runs after the grade is already recorded, checks `.ok`, and only logs a line on failure. A cost-logging failure can never fail or roll back a grade, and it runs on **both** pass and fail (the panel costs money either way).
|
|
45
|
+
- **Fallback:** if a future CLI omits `total_cost_usd`, `extractCostUsd` returns null → `panel_cost_usd` is 0 → no row written (graceful degrade). Pricing `env.usage` via the shared module is a possible later fallback; not needed while the envelope carries cost.
|
|
46
|
+
|
|
47
|
+
### 2. Session cost — price the transcript JSONL (client-side, table-priced)
|
|
48
|
+
|
|
49
|
+
The interactive session is not a `-p` subprocess, so there is no envelope. But Claude Code writes a per-session transcript at `~/.claude/projects/<encoded-cwd>/<session-id>.jsonl`, and **each assistant turn carries `message.usage`** (`input_tokens`, `output_tokens`, `cache_creation_input_tokens` with a `cache_creation.{ephemeral_5m,ephemeral_1h}` breakdown, `cache_read_input_tokens`) and `message.model`. *(Verified on this machine 2026-05-30: 6/23 lines carried usage; model `claude-opus-4-8`.)*
|
|
50
|
+
|
|
51
|
+
**Decision:** a client-side script `scripts/gds/session-cost.js` reads a transcript, sums usage per model, prices it via a shared `src/bongos/llm-pricing.js` table, and POSTs one row to `/api/gds/cost` (`category='api'`, `source='session'`, `source_ref=<session-id>`, builder defaults to the authed caller). It runs automatically via a **`SessionEnd` hook** (`.claude/settings.json`).
|
|
52
|
+
|
|
53
|
+
> **Correction (made during [#560](https://example.com/builders#/task/560) implementation):** the first draft said "wired into the `builder-exit` skill." That was wrong — `builder-exit` is Archon **offboarding** (deactivate a builder), not a per-session seam. The correct seam is the Claude Code **`SessionEnd`** hook, which fires *exactly once* when a session ends and receives `{ session_id, transcript_path, cwd, reason }` on stdin (confirmed against the hooks docs). `Stop` was rejected: it fires at the end of *every* turn (many times per session), so it would need an upsert to avoid undercounting — `SessionEnd` needs none (one POST at end, idempotent `DO NOTHING` on `session_id`). The hook pipes its stdin payload to `session-cost.js`, which is also runnable by hand (`--transcript`/`--session`, or newest-for-cwd) and supports `--dry-run`.
|
|
54
|
+
|
|
55
|
+
- **Table-priced** — the transcript has tokens, not dollars, so a small auditable `model → $/Mtok` table is required. One file, easy to update when prices change; a `decision`-worthy change gets its own task.
|
|
56
|
+
- **Idempotent on session-id** — re-running on the same transcript is a no-op (`ON CONFLICT`). A long session re-run after more turns would need a re-priced delta; v1 keeps it simple: one row per session, written once at exit. (A periodic delta-capture is a later refinement; see Consequences.)
|
|
57
|
+
- **`task_id` is null** — a session is not 1:1 with a task (it may span several or none). Session spend is attributed to the **builder**, not a task. Grader spend (path 1) is the task-attributed path. Together they give per-builder totals; only the grader path gives per-task.
|
|
58
|
+
- **Pricing model (per Mtok), v1 table** (Anthropic public list, editable):
|
|
59
|
+
| family | input | output | cache-write 5m (1.25×) | cache-write 1h (2×) | cache-read (0.1×) |
|
|
60
|
+
|---|---|---|---|---|---|
|
|
61
|
+
| opus | $15 | $75 | $18.75 | $30 | $1.50 |
|
|
62
|
+
| sonnet | $3 | $15 | $3.75 | $6 | $0.30 |
|
|
63
|
+
| haiku | $0.80 | $4 | $1.00 | $1.60 | $0.08 |
|
|
64
|
+
|
|
65
|
+
Family is matched by substring on `message.model` (`opus`/`sonnet`/`haiku`), mirroring `grader.js`'s existing family-token convention. Unknown model → priced at the opus rate (conservative over-estimate) **and** a warning, so an unpriced model is loud, not silently $0.
|
|
66
|
+
|
|
67
|
+
### 3. Shared pricing module
|
|
68
|
+
|
|
69
|
+
`src/bongos/llm-pricing.js` exports `priceUsage(model, usage) → usd` and the `PRICES` table. Owned by the session-capture task; imported by the session script and available to the grader as its envelope-absent fallback. Pure, unit-tested, no I/O.
|
|
70
|
+
|
|
71
|
+
## Source/category conventions (so the audit can reason about completeness)
|
|
72
|
+
|
|
73
|
+
| source | category | written by | builder_id | task_id |
|
|
74
|
+
|---|---|---|---|---|
|
|
75
|
+
| `grader` | `api` | `ship.js` → `POST /api/gds/cost` (path 1) | shipper | the graded task |
|
|
76
|
+
| `session` | `api` | `POST /api/gds/cost` (path 2) | authed builder | null |
|
|
77
|
+
| `art-cost-ledger` | `art`/`api` | backfill import | null (shared) | null |
|
|
78
|
+
| `manual` | any | `/builder-cost` | authed builder | optional |
|
|
79
|
+
|
|
80
|
+
`auditCostAttribution`'s completeness check (the [#485](https://example.com/builders#/task/485) task) keys off these: real `category='api'` spend from `source IN ('grader','session')` should be non-trivial relative to ship volume; if it's ~$0, that is the F1 finding re-detected.
|
|
81
|
+
|
|
82
|
+
## Capture boundary & non-double-counting ([#562](https://example.com/builders#/task/562), [#563](https://example.com/builders#/task/563))
|
|
83
|
+
|
|
84
|
+
What the paths read is **disjoint** — confirmed by inspecting the on-disk transcripts:
|
|
85
|
+
|
|
86
|
+
- **Path 2 (session)** prices the interactive session's transcript — the file the `SessionEnd` hook passes via `transcript_path` (only the interactive model's own turns: verified all `claude-opus-4-8`, **0 sidechain**).
|
|
87
|
+
- **Path 1 (grader)** takes its number from each worker's `claude -p` result **envelope** (`total_cost_usd`) — never from a transcript.
|
|
88
|
+
- **Path 3 (subagents, [#563](https://example.com/builders#/task/563))** prices this session's ad-hoc `Agent`/`Task` subagents, which persist at `<project-dir>/<session-id>/subagents/...` (a sibling subtree of the main transcript FILE), **skipping any grader-signature transcript** so the envelope-captured grader (path 1) is never re-priced.
|
|
89
|
+
- The grader workers DO persist their own transcripts (separate `claude-sonnet-4-6` files), but no path prices a grader transcript. So there is **no double count**.
|
|
90
|
+
|
|
91
|
+
> **INVARIANT (do not violate):** a grader `claude -p` transcript MUST NOT be priced by the session or subagent paths — its cost is already captured via the envelope (path 1). The grader CAN nest inside a `subagents/` dir (an autonomous-builder subagent that ran a ship), so exclusion is by the **grader-worker signature**, not by location. Both the session fallback and the subagent walk (`priceSubagents`) enforce this; the `SessionEnd` hook is also safe by construction. Any future "price all transcripts" idea must apply the same signature skip.
|
|
92
|
+
|
|
93
|
+
**Subagent capture ([#563](https://example.com/builders#/task/563)) — closed.** `Agent`/`Task`-tool subagents (e.g. a `claude-code-guide` lookup, an autonomous builder) write to `<session-id>/subagents/agent-*.jsonl`; `session-cost.js` now walks that subtree, prices each non-grader transcript via the same model table, and folds it into the one `source='session'` row (the description breaks out `main` vs `N subagent(s)`). Measured impact on a real session: the two ad-hoc subagents added **$5.45** that previously leaked. With this, the `honest-spend-accounting` criterion (proposal-146) is met for **main session + grader + ad-hoc subagents**. (Residual edge: a subagent killed before its transcript flushes, or one nested under a session that itself never fires `SessionEnd`, is still uncaptured — small, noted, not chased.)
|
|
94
|
+
|
|
95
|
+
## Consequences
|
|
96
|
+
|
|
97
|
+
- **Positive:** the `api` category stops being a rounding error; per-builder spend renders real numbers; the grader's own cost becomes visible (and gradeable for efficiency); the audit can detect *missing* spend, not just mis-attribution.
|
|
98
|
+
- **Cost:** a small pricing table to maintain (prices drift), and session-capture only fires if the `SessionEnd` hook runs (a session killed before clean exit is uncaptured). The grader path has neither weakness — it's exact and always runs on the ship path.
|
|
99
|
+
- **Deliberately deferred (later refinements, not blockers):**
|
|
100
|
+
- **Conductor-dispatch grader spend.** `conductor-dispatch.js` also invokes `gradeSubagent` (mid-flight, advisory — no gate, no credits). Its workers now carry `cost_usd` (free, via the shared `runSubagent` change), but only `ship.js` aggregates+logs it. The dispatch path is lower-volume and not on the credit-bearing ship path, so capturing it is a fast-follow, not a [#486](https://example.com/builders#/task/486) blocker (panel-flagged nit on the [#486](https://example.com/builders#/task/486) re-grade; filed to idea_inbox).
|
|
101
|
+
- Mid-session delta capture (today: one row at exit). A crashed session that never hits `builder-exit` is uncaptured; the Stop hook narrows this.
|
|
102
|
+
- Cost calibration medians à la migration 015 (idea_inbox [#132](https://example.com/builders#/idea/132)) — needs this real data first.
|
|
103
|
+
- Reconciling against the true Anthropic billing API — out of scope; the transcript + envelope are the cheapest honest proxy.
|
|
104
|
+
|
|
105
|
+
## Implementation tasks
|
|
106
|
+
|
|
107
|
+
- **[#486](https://example.com/builders#/task/486)** — grader envelope cost → `cost_log` (path 1). Decoupled (no pricing dep).
|
|
108
|
+
- **[#560](https://example.com/builders#/task/560)** — `llm-pricing.js` + `session-cost.js` + `SessionEnd`-hook wiring (path 2 + module 3). *(Task title says "builder-exit wiring" — superseded by the SessionEnd seam; see the path-2 correction above.)*
|
|
109
|
+
- **[#485](https://example.com/builders#/task/485)** — `auditCostAttribution` completeness check keyed off the source conventions above.
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# 0029 — Status-page outage resilience: measure from outside, host off-box, prevent at the source
|
|
2
|
+
|
|
3
|
+
- **Status:** Accepted. **Slice 1 (external prober) built this session** under task **[#565](https://example.com/builders#/task/565)**: `.github/workflows/status-probe.yml` + `scripts/status-probe.mjs` + this ADR. **Follow-up B (page off-box) has since SHIPPED** — `status.example.com` is served off the droplet from GitHub Pages (repo `example-owner/example-status`), synced by `.github/workflows/sync-status-mirror.yml`. Incident-backfill (follow-up C) is **not yet built**.
|
|
4
|
+
- **Date:** 2026-06-03
|
|
5
|
+
- **Deciders:** Lars Kristensen (Archon), Claude
|
|
6
|
+
- **Track:** `internal` (the status dashboard / The Watch)
|
|
7
|
+
- **Context tasks:** **[#565](https://example.com/builders#/task/565)** (slice 1 — external prober + alert + this ADR) · **blocker [#6](https://example.com/builders#/blocker/6)** (DigitalOcean backup payment + billing alerts — root-cause prevention, Lars-only) · follow-up **B** (move `public-status/` off-box + CORS on `/api/gds/public/*` + repoint `status.` DNS) · follow-up **C** (backfill the 2026-06-03 incident into the honest uptime history).
|
|
8
|
+
- **Relates to:** [ADR 0003 — Cloudflare Origin Cert](<redacted>.md) (the CF↔origin leg) · the in-process poller [`src/bongos/uptime-poller.js`](../../src/bongos/uptime-poller.js) (now demoted from public source-of-truth) · [`docs/recipes/ops-gotchas.md`](../recipes/ops-gotchas.md) (the new "status page shared fate" gotcha).
|
|
9
|
+
|
|
10
|
+
## Context
|
|
11
|
+
|
|
12
|
+
On **2026-06-03, ~10:00–14:39 UTC (~4h 39m)**, DigitalOcean **powered off the production droplet after a payment-method charge failed**. The game *and* the status page went dark together, and Lars discovered it by eye — via a Cloudflare 522 — with no alert.
|
|
13
|
+
|
|
14
|
+
Three independent design flaws turned a billing hiccup into a silent, invisible outage:
|
|
15
|
+
|
|
16
|
+
1. **The status page shares fate with the product.** `status.example.com` is served by the **same Node process on the same droplet** as the game. Whatever kills the product kills the status page. A status page's entire job is to be up when the product is down.
|
|
17
|
+
2. **It measures itself from inside itself.** [`src/bongos/uptime-poller.js`](../../src/bongos/uptime-poller.js) runs *in that Node process* and probes `http://127.0.0.1`. When the droplet died, the poller died — so it wrote **zero rows** during the outage (not "down" rows — *none*). The Watch renders that gap as 100% green. **A monitor that lives inside the thing it monitors can never witness its own death.** It also can't see Cloudflare, DNS, or "is the box reachable from the world" — only "can the server talk to itself."
|
|
18
|
+
3. **Nothing alerted a human.** Detection depended on someone noticing.
|
|
19
|
+
|
|
20
|
+
The cardinal industry rule (GitHub → `githubstatus.com` on Atlassian's infra; status accounts on a separate platform as a backup channel) is: **a status page must not share fate with the system it reports on, and availability must be measured from outside it.**
|
|
21
|
+
|
|
22
|
+
## Decision
|
|
23
|
+
|
|
24
|
+
Keep The Watch's custom design (Lars's call), but fix all three flaws as three independent layers:
|
|
25
|
+
|
|
26
|
+
### 1. Measure from outside (built this session — slice 1, [#565](https://example.com/builders#/task/565))
|
|
27
|
+
An **external prober runs on GitHub-hosted runners** — off our DigitalOcean infrastructure — and probes the **public** URL exactly as a visitor would:
|
|
28
|
+
- [`scripts/status-probe.mjs`](../../scripts/status-probe.mjs) — dependency-free Node; probes `https://example.com/healthz` with in-run confirmation retries (UP if any of 3 attempts is a 200; DOWN only if all fail, which suppresses transient blips). Appends to `history.jsonl`, rewrites `status.json` (snapshot + 24h/7d/90d uptime), prunes to 90 days.
|
|
29
|
+
- [`.github/workflows/status-probe.yml`](../../.github/workflows/status-probe.yml) — `*/5` cron + `workflow_dispatch`; publishes the result to an **orphan `status-data` branch** (off-box, web-readable, survives a droplet outage); **fails the job when the site is confirmed down**, which emails the repo owner — the alert.
|
|
30
|
+
|
|
31
|
+
### 2. Host the page off-box (follow-up B — SHIPPED)
|
|
32
|
+
`status.example.com` is now served off the droplet from **GitHub Pages** (repo `example-owner/example-status`), synced by [`.github/workflows/sync-status-mirror.yml`](../../.github/workflows/sync-status-mirror.yml); the page no longer shares fate with the product. Original plan, for the record:
|
|
33
|
+
Move `public-status/` to free static hosting **separate from the droplet** (Cloudflare Pages or GitHub Pages). The page shell + the **uptime/incident section** are served off-box and read the `status-data` source, so they stay truthful during an outage. The rich GDS panels (treasury, grades, progress) keep loading client-side from `example.com/api/gds/public/*` and **degrade gracefully** to "origin unreachable" when the droplet is down (requires adding CORS to those public routes). Then repoint `status.example.com` DNS at the off-box host.
|
|
34
|
+
|
|
35
|
+
### 3. Prevent at the source + alert (blocker [#6](https://example.com/builders#/blocker/6))
|
|
36
|
+
The actual root cause was billing. Add a **backup payment method + billing/balance alerts** on DigitalOcean (Lars-only — DO account access). Preventing the outage beats reporting it. The slice-1 prober is the detection net if prevention ever fails.
|
|
37
|
+
|
|
38
|
+
## Why this mechanism (and the trade-offs)
|
|
39
|
+
|
|
40
|
+
- **Free and already-owned.** The repo is on GitHub (`example-owner/example`); GitHub Actions + a git branch cost $0 and run off our droplet. Prior art: the open-source **Upptime** project is exactly this pattern (Actions probe → commit to git → issues on downtime → Pages status site); we build a minimal, theme-preserving version instead of adopting its UI.
|
|
41
|
+
- **Trade-offs, accepted for a bootstrapped project:**
|
|
42
|
+
- GitHub cron granularity is **5 min** and runs can be delayed under load → detection latency is minutes, not seconds.
|
|
43
|
+
- Scheduled workflows fire **only from the default branch**, so the monitor is live only once [#565](https://example.com/builders#/task/565) merges to `main`; use the "Run workflow" button until then.
|
|
44
|
+
- GitHub disables schedules after **60 days of repo inactivity** (not a concern at our daily-commit cadence).
|
|
45
|
+
- The alert is a **failed-run email** to the repo owner; richer alerting (auto-opened issue, Google-Chat webhook reusing `scripts/chat/`, or Better Stack SMS/phone) is a documented upgrade path.
|
|
46
|
+
|
|
47
|
+
## Alternatives considered
|
|
48
|
+
|
|
49
|
+
- **Hosted status SaaS (Better Stack / UptimeRobot / Atlassian Statuspage).** The most common professional answer; $0 on free tiers; sub-minute checks + real SMS/phone alerting + their own hosted page. **Rejected as the primary** because Lars wants to keep the custom Greek "The Watch" design and full ownership. Retained as the **upgrade path** if we later want faster checks or phone alerts — it composes with this design (it can be the prober feeding our off-box page).
|
|
50
|
+
- **Keep self-measurement but render gaps as "unknown" (grey) instead of green.** Makes the page honest but still dies with the product and still can't alert. Insufficient on its own.
|
|
51
|
+
- **A second always-on box to host the status page.** Defeats the cost posture ($5k/yr budget) and just moves the shared-fate problem.
|
|
52
|
+
|
|
53
|
+
## Consequences
|
|
54
|
+
|
|
55
|
+
- [`src/bongos/uptime-poller.js`](../../src/bongos/uptime-poller.js) is **no longer the public source of truth** for availability. It stays useful as an *internal* component-liveness signal (can the process reach its own DB?), but The Watch's availability section will read the off-box `status-data` (follow-up B). Whether to retire the in-process poller entirely is decided in B.
|
|
56
|
+
- Honest uptime history starts accumulating off-box from the first prober run.
|
|
57
|
+
- The 2026-06-03 incident is backfilled into the record (follow-up C) so The Watch stops claiming 100%.
|
|
58
|
+
- The lesson — "a status page must not share fate with the product, and cannot measure itself" — is captured as **GDS learning `#35`** and a new "Status page + monitoring" section in [`docs/recipes/ops-gotchas.md`](../recipes/ops-gotchas.md) (incl. the 522 / total-outage diagnostic runbook).
|
|
59
|
+
- Because it's off-box, `status.example.com` doubles as the fallback when the hall (`example.com/builders`, CLAUDE.md's canonical doc-reading surface — [#758](https://example.com/builders#/task/758)) is unreachable — it won't render full docs, but it stays up to show whether the droplet itself is down.
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# ADR 0030 — Builders-hall live updates via Postgres NOTIFY → WebSocket
|
|
2
|
+
|
|
3
|
+
- **Status:** Accepted — **transport superseded by [ADR 0069](<redacted>.md)** (the hall now delivers nudges over SSE, not a Colyseus WebSocket room, so it works on a game-less Cloud Bongos boot; R55). The **change-signal layer below (§1 — `NOTIFY` + `src/bongos/live-channel.js`) is unchanged and still authoritative**; only the §2 delivery transport moved.
|
|
4
|
+
- **Date:** 2026-06-03
|
|
5
|
+
- **Deciders:** task [#569](https://example.com/builders#/task/569) (commissioned by Lars — "these checks aren't very live, need to refresh every time"); transport chosen by Lars (WebSocket)
|
|
6
|
+
- **Track:** `internal` (the builders hall)
|
|
7
|
+
|
|
8
|
+
## Context
|
|
9
|
+
|
|
10
|
+
The builders hall (`public-builders/`) fetched **every panel exactly once** at `DOMContentLoaded` via `loadAll()` and then never refreshed. Onboarding checklist, standing (drachmae/karma), claimable works, leaderboard, karma feed, bounty table — all frozen until the visitor manually reloaded. The only things that updated without a reload were the two buttons a builder clicks themselves (mark primer read, pick a craft).
|
|
11
|
+
|
|
12
|
+
The reported symptom: a builder runs `/builder-ship` in the terminal, the "Ship thy first work" onboarding step clears server-side, but the open hall tab keeps showing the stale checklist. The underlying gap is broader than onboarding — the page had **no live-update mechanism at all**.
|
|
13
|
+
|
|
14
|
+
A large share of the staleness comes from changes the *browser's own actions never caused*: background routines (autonomous builders, the BFG), another builder climbing the leaderboard, and **direct `psql` writes** (which the methodology explicitly treats as first-class work). Any fix that only re-broadcasts from API route handlers would miss all of those.
|
|
15
|
+
|
|
16
|
+
## Decision
|
|
17
|
+
|
|
18
|
+
A live-push channel in two layers — a transport-agnostic change-signal, and a WebSocket delivery path.
|
|
19
|
+
|
|
20
|
+
### 1. Change signal — Postgres `NOTIFY` from statement-level triggers (migration 066)
|
|
21
|
+
|
|
22
|
+
A single generic trigger function `gds_notify_live()` fires `pg_notify('gds_live', '{"table":…,"op":…}')`. One `AFTER INSERT OR UPDATE OR DELETE … FOR EACH STATEMENT` trigger is attached to each table that backs a live panel: `tasks`, `claims`, `builders`, `builder_onboarding_state`, `credit_log`, `karma_log`.
|
|
23
|
+
|
|
24
|
+
- **Trigger lives in the DB, below the application** → it catches *every* write path, including direct `psql`. This is the decisive property; it's why the signal is a DB trigger and not an app-layer emit.
|
|
25
|
+
- **Statement-level, not row-level** → the payload carries no row data (only table + op), so we don't need `OLD`/`NEW`, and a bulk write coalesces to one nudge instead of N. Negligible overhead on these low-volume internal tables.
|
|
26
|
+
|
|
27
|
+
The GDS process holds **one persistent `LISTEN` connection** (`src/bongos/live-channel.js`, a dedicated `pg.Client` — *not* a pooled checkout, since `LISTEN` must stay pinned to one connection) and fans each notification out to subscribers. It opens lazily on the first subscriber and closes when the last leaves.
|
|
28
|
+
|
|
29
|
+
### 2. Transport — a Colyseus WebSocket room, not SSE or a new HTTP route
|
|
30
|
+
|
|
31
|
+
Delivery to the browser rides the game's **existing Colyseus WebSocket server**. A broadcast-only room `builders_hall` (`src/rooms/BuildersHallRoom.js`, a sibling of `QueueRoom` with no state schema) subscribes to the `live-channel` relay in `onCreate` and `broadcast('nudge', {})` on each notification — an **opaque "something changed" signal**, not the `{table, op}` (see the security note below). The hall page joins via `client.joinOrCreate('builders_hall')` and, on each `nudge`, re-runs `loadAll()` **debounced (~700 ms)** so a burst coalesces into one refresh. The room is `autoDispose: true` — created on the first viewer, disposed (and the `LISTEN` connection dropped) when the last one leaves.
|
|
32
|
+
|
|
33
|
+
**Why WebSocket / Colyseus** (the chosen option):
|
|
34
|
+
|
|
35
|
+
- It **reuses infrastructure that already exists and is proven through the Caddy → Cloudflare proxy** — the game has run Colyseus WS in production since V1, so there's no new transport to validate against the edge. The hall is same-origin (`example.com`), so `wss://example.com` and the vendored `/vendor/colyseus.js` client are both already reachable.
|
|
36
|
+
- A `Room` is a natural fan-out primitive: `broadcast()` to all members is one call, and Colyseus owns the connection lifecycle.
|
|
37
|
+
- It keeps the hall's live path on the **same stack as the rest of the realtime system**, rather than introducing a second long-lived-connection style (SSE) alongside it.
|
|
38
|
+
|
|
39
|
+
**No row data crosses this channel** — and, after the hacker dispatch on [#569](https://example.com/builders#/task/569), not even the `{table, op}`: the relay passes `{table, op}` to the room internally, but the room broadcasts only an empty `{}` so an unauthenticated client can't read internal table names. The hall ignores the payload regardless (it refreshes on any nudge), so this costs nothing. The room is therefore left **unauthenticated**, matching the game rooms (`world`/`queue`); access control stays where the data actually is, on the authenticated per-panel endpoints the hall re-fetches. The only residual signal is *that* a write happened (a timing oracle) — no more than the public status dashboard already exposes.
|
|
40
|
+
|
|
41
|
+
### Resilience & client guards
|
|
42
|
+
|
|
43
|
+
- **Reconnect:** unlike `EventSource`, a Colyseus room doesn't auto-reconnect — so the client retries `joinOrCreate` on `onLeave`/error with a fixed 4 s backoff, and refreshes once on reconnect (it may have missed a nudge while offline).
|
|
44
|
+
- **No mid-interaction clobber:** the debounced refresh is deferred (and re-armed) while a user is focused on an input/textarea/select or the tab is hidden, so a nudge can't overwrite unsent input or re-render mid-interaction.
|
|
45
|
+
- **Backstop:** the hall also refreshes on tab `visibilitychange` (regaining focus), and re-opens a dropped room then — covering anything missed while the laptop slept.
|
|
46
|
+
|
|
47
|
+
## Alternatives considered
|
|
48
|
+
|
|
49
|
+
- **Server-Sent Events (`GET /api/gds/live`)** — initially built. SSE is arguably the textbook fit for a *receive-only* dashboard (no client lib, native auto-reconnect, plain HTTP GET). Rejected in favor of WebSocket to **reuse the game's existing, proxy-proven WS stack** rather than stand up and maintain a second long-lived-connection mechanism. The change-signal layer (NOTIFY + `live-channel.js`) is identical either way, so this was purely a delivery-path choice.
|
|
50
|
+
- **Per-table → per-panel routing** — mapping each changed table to a subset of panels instead of a full `loadAll()`. Rejected as fragile: a single task ship touches ~6 panels at once, so the mapping is large and easy to get wrong. A full reload is strictly correct (never misses a derived panel) and cheap (cached GETs, only when something actually changed).
|
|
51
|
+
- **Naive client polling** (`setInterval`) — simplest, but either wasteful (polls when nothing changed) or laggy (long interval), and still wouldn't feel live. The push model costs nothing when idle.
|
|
52
|
+
|
|
53
|
+
## Consequences
|
|
54
|
+
|
|
55
|
+
- The hall now feels live: finishing a step in the terminal reflects in the open tab within ~1 s, no manual reload.
|
|
56
|
+
- New realtime surface: a `builders_hall` Colyseus room + a persistent Postgres `LISTEN` connection (lazy — zero cost when the hall is empty). The hall page now loads `/vendor/colyseus.js`.
|
|
57
|
+
- **Deliberate omission:** `peer_votes` (the acclaim feed, which spans all builders and loads fire-and-forget) is *not* triggered — it's the least time-sensitive panel, and keeping it out avoids hall-wide refreshes on every cross-builder vote. Easy to add later.
|
|
58
|
+
- Not applied to the public status dashboard (`status.example.com`) — out of scope; that surface already has its own polling cadence.
|
|
59
|
+
- The unauthenticated room is acceptable only because the payload is non-sensitive `{table, op}`. If a future change ever puts row data on this channel, it must gain an `onAuth` cookie/session gate first.
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
# ADR 0031 — Cloud dev environments for builders (hybrid: Claude Desktop → SSH → containerized DigitalOcean box)
|
|
2
|
+
|
|
3
|
+
**Date:** 2026-06-04
|
|
4
|
+
**Context:** GDS-V3, task [#595](https://example.com/builders#/task/595) (claim `#477`). Feeds done-when criteria **C1** (`onboarding ≤ 2h`) and **C8** (`cloneable repo / local-first memory, server-canonical on ship`). Companion to [ADR 0016](<redacted>.md) (trust boundary — same server-enforced posture), [ADR 0018](<redacted>.md) (Xenos → Metic → Archon), [ADR 0022](0022-secrets-policy.md) (no secrets in repo — precondition for a cloneable box), [ADR 0024](<redacted>.md) (memory follows the builder — unchanged by where the box runs), and [ADR 0002](<redacted>.md) (we already run on DigitalOcean).
|
|
5
|
+
**Status:** Accepted. *Decision recorded this session; implementation tasks to be seeded separately (see the Implementation sub-tasks at the end).*
|
|
6
|
+
**Track:** `internal` (dev-system architecture).
|
|
7
|
+
|
|
8
|
+
> This is a decision record, not an implementation. Nothing here is built yet. The point is to lock the shape so a future session can build it without relitigating the design.
|
|
9
|
+
|
|
10
|
+
## Problem
|
|
11
|
+
|
|
12
|
+
Today there is exactly one builder (`example-owner`) on one laptop. GDS-V3 opens the system to many builders, and "every builder runs Claude Code locally" has two problems we want to solve *before* we onboard people:
|
|
13
|
+
|
|
14
|
+
1. **Accessibility.** Local-only requires each builder to own a capable computer (RAM, disk, CPU) *and* hand-install a toolchain (Homebrew, Node 22, Python 3.10, a native `sharp` build, env vars). That toolchain step is the single biggest onboarding failure today, and it's exactly the kind of thing the non-engineer audience we want to attract cannot debug. It directly blocks the C1 "onboard in ≤2h" goal.
|
|
15
|
+
2. **Cost at scale.** Builders will eventually be **paid real money per task**, and the project runs on a ~$5,000/yr budget. We want builders to bear and optimize their own tooling cost (so the incentive to be efficient sits with them), without the org taking on a fixed per-builder cost that grows linearly and untenably with headcount.
|
|
16
|
+
|
|
17
|
+
The decisive reframe (established during scoping): **two very different costs get bundled as "the cost of running Claude," and they differ by 10–100×.**
|
|
18
|
+
|
|
19
|
+
- **Inference** — what the AI model costs to *think* (Claude tokens). **$400–1,200+/mo** for a heavy builder. This is the dominant cost, and it is already remote (Anthropic's servers).
|
|
20
|
+
- **Compute / "the box"** — the machine that holds the repo and runs the dev server + art pipeline. **~$3–30/mo.** This is what "local vs cloud" is actually about. It is the *small* cost.
|
|
21
|
+
|
|
22
|
+
So the cost lever that matters is **inference, not the box.** The box can be a near-trivial, bounded line item. This single fact shapes every choice below.
|
|
23
|
+
|
|
24
|
+
## The mental model (preserve this for onboarding)
|
|
25
|
+
|
|
26
|
+
> This explainer is the canonical "how you'll run Claude" framing. It now lives in the onboarding primer — see [`docs/onboarding/primer.md`](../onboarding/primer.md) § "Where your work runs — screen, worker, brain" — so new builders meet it on day one. It was carried there during the ADR 0035 primer rewrite ([#718](https://example.com/builders#/task/718)); keep the two in sync if you revise the model (task 601).
|
|
27
|
+
|
|
28
|
+
Claude Code is **three pieces that can live in different places**:
|
|
29
|
+
|
|
30
|
+
1. **The screen** (the interface) — the window you look at and type into. **Always on your device** (the Claude Desktop app, or a browser tab). It just displays files/diffs and sends prompts.
|
|
31
|
+
2. **The worker** (the agent) — the part that opens files, runs commands, edits code, runs the dev server. Runs in **one place you choose.**
|
|
32
|
+
3. **The brain** (the AI model) — the intelligence. **Always on Anthropic's servers**, no matter what. This is the "inference" the subscription pays for.
|
|
33
|
+
|
|
34
|
+
You only ever choose **where the worker goes**. The screen is always local; the brain is always Anthropic's.
|
|
35
|
+
|
|
36
|
+
```
|
|
37
|
+
YOUR DEVICE THE BOX (worker) ANTHROPIC
|
|
38
|
+
┌───────────┐ SSH / ┌────────────────────┐ ┌──────────┐
|
|
39
|
+
│ screen │── Remote ─────▶│ containerized DO box│──tokens─▶│ brain │
|
|
40
|
+
│ (Desktop/ │ Control │ repo · dev server · │◀─────────│ (model) │
|
|
41
|
+
│ browser) │◀──────────────▶│ art pipeline · git │ └──────────┘
|
|
42
|
+
└───────────┘ └─────────┬───────────┘
|
|
43
|
+
│ HTTPS (GDS API) · git push (GitHub)
|
|
44
|
+
▼
|
|
45
|
+
GDS Postgres · GitHub (private repo) · CI deploy
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
In our model the device is a **window**, the **box** is where the code lives and the work happens, and **Anthropic's servers** are the brain. The repo never lands on the builder's personal disk.
|
|
49
|
+
|
|
50
|
+
## Decision
|
|
51
|
+
|
|
52
|
+
We will adopt a **hybrid** model with a single shared foundation. In plain terms: **a clean Claude Desktop GUI on the builder's own device, driving Claude Code on an org-controlled, containerized DigitalOcean box, with the builder bringing their own Claude subscription.**
|
|
53
|
+
|
|
54
|
+
### 1. UI — Claude Desktop is the primary interface
|
|
55
|
+
|
|
56
|
+
> **Superseded in part (2026-06-06, [ADR 0038](<redacted>.md)):** the thin-device `claude rc` always-on-service on-ramp described in the Thin-device bullet below is retired. Headless `claude rc` can't bootstrap (Trusted-Devices enrollment is interactive-only); the browser-only on-ramp is now a box-served `ttyd` web terminal over an outbound Cloudflare Tunnel where the builder runs normal interactive `claude`. See [ADR 0038](<redacted>.md) (and [ADR 0040](<redacted>.md) for in-session Remote Control as the default browser experience). The Desktop→SSH and Local paths below are unchanged.
|
|
57
|
+
|
|
58
|
+
Most builders want a clean GUI, not a terminal or an IDE. The redesigned Claude Desktop app (April 2026) has a **Code** tab whose session **Environment** selector offers three mutually-exclusive choices for *where the worker runs*: **Local**, **Remote** (Anthropic-managed cloud VM), and **SSH** (a machine the org controls). We build on **SSH**.
|
|
59
|
+
|
|
60
|
+
- **Desktop builders (Mac/Windows): Claude Desktop → SSH → our box.** Point-and-click; Desktop auto-installs Claude Code on the remote on first connect. We pre-distribute and lock the connection via Desktop **managed settings** (`sshConfigs` + `sshHostAllowlist`) so a non-technical builder sees exactly one approved box ("Example Dev Box") and cannot misconfigure or wander off it.
|
|
61
|
+
- **Thin-device builders (Chromebook/iPad/phone): browser/mobile → Remote Control → the same box.** On the web (`claude.ai/code`) the "your own box" path is **Remote Control**: `claude rc` runs on the box (as an always-on service) and the builder drives it from the browser. Same destination as SSH, different on-ramp. *Caveat: Claude Desktop is macOS/Windows-only — a literal Chromebook uses the web/Remote-Control path, not the Desktop app.*
|
|
62
|
+
- **Power users:** Claude Desktop **Local** (native toolchain on a capable machine) or the VS Code dev-container flow remain available off the same container spec (§3). Not the default.
|
|
63
|
+
|
|
64
|
+
### 2. Execution substrate — an org-controlled, containerized DigitalOcean box, one per builder
|
|
65
|
+
|
|
66
|
+
The worker runs on a **DigitalOcean droplet (or a container on a DO host) that the org controls**, reached over SSH/Remote-Control. **One box per builder** (a shared box collides on files, ports, the DB connection, and breaks per-builder isolation). DigitalOcean over GitHub Codespaces — see §7 and Alternatives.
|
|
67
|
+
|
|
68
|
+
### 3. The container — the box is defined as code
|
|
69
|
+
|
|
70
|
+
The box is defined by a **`devcontainer.json` + Dockerfile** committed to the repo: base Linux + Node 22 + Python 3.10 and the five art-pipeline libs + `sharp` build deps + git + Anthropic's Claude Code container feature (which installs Claude Code and a **default-deny outbound firewall** allow-listing only the Anthropic API, GitHub, and npm). Today's `setup-builder.sh` work folds into the container's first-run step; `doctor.js` becomes its health check. There is **no containerization in the repo today** — this is greenfield. The same spec runs identically on a DO box, on a laptop (Docker Desktop), or in Codespaces — so the substrate stays portable and we are not locked to DO.
|
|
71
|
+
|
|
72
|
+
### 4. Inference — bring-your-own Claude subscription, no API key
|
|
73
|
+
|
|
74
|
+
Every builder authenticates Claude Code with **their own Pro/Max subscription**. The org provisions **no shared Anthropic API key.** This is both the cheapest path (a flat subscription is 15–30× cheaper than per-token API for heavy users) and the cleanest contributor model (the 1099 "bring your own tools" arrangement). It also means the org's inference cost is **$0**.
|
|
75
|
+
|
|
76
|
+
- **Interactive sessions** (Desktop-SSH or web-Remote-Control) are "ordinary use of Claude Code" under Anthropic's terms — subscription auth is permitted, and the consumer GUI/Remote-Control paths do **not** accept API keys anyway.
|
|
77
|
+
- **Autonomous runs** (`overnight-builder`, MAS dispatch) are launched **as scheduled Claude Code sessions under a subscription**, which the decider judges to be ordinary Claude Code use, not the prohibited Agent-SDK/`--bare`/shared-key path. *Residual risk (recorded, accepted): unattended subscription sessions could trip Anthropic's abuse heuristics, and a 15 Jun 2026 change alters how subscription-based automation draws credits. Re-verify before scaling the autonomous lane; this is the one place an API key might later become unavoidable.*
|
|
78
|
+
|
|
79
|
+
### 5. Cost & access model — org fronts an auto-suspended starter box; builders self-fund from earnings
|
|
80
|
+
|
|
81
|
+
Two rules dissolve the "front every box (linear cost) vs. gate by payment (barrier)" dilemma:
|
|
82
|
+
|
|
83
|
+
**(a) Auto-suspend is mandatory.** A box that runs only while the builder is working costs **~$3–6/mo** instead of ~$24–30/mo always-on. DigitalOcean has no native auto-suspend, so we **build it** (snapshot-and-park on idle, recreate on demand, or a Coder layer — see Open questions below). Dormant/abandoned boxes get de-provisioned (a stale-claim-style sweep).
|
|
84
|
+
|
|
85
|
+
**(b) Cost follows proven value, not signup** — riding the existing rank ladder + onboarding funnel:
|
|
86
|
+
|
|
87
|
+
| Stage | How they work | Org cost | Builder cost |
|
|
88
|
+
|---|---|---|---|
|
|
89
|
+
| **Newcomer** (Xenos / onboarding) | Org provisions an **auto-suspended starter box** so there is zero payment barrier to begin | A bounded ~$5/active-newcomer (suspends when idle; de-provisioned if dormant) | Just their Claude subscription |
|
|
90
|
+
| **Proven** (graduated → Metic) | Keeps a box | Transitions toward $0 | **Self-funds** (own DO account, or org provisions + rebills via the `costs` table, deducted from drachmae/earnings) |
|
|
91
|
+
| **Established** | Own box, runs it how they like | **$0** | Self-funds (1099 own-tools) |
|
|
92
|
+
|
|
93
|
+
The barrier to *start* is zero (org pays the first box, per the decision to **not** use Anthropic's Cloud VM for onboarding). Real cost attaches only to builders who are earning. **Scale math:** naïve 100 builders × $30 always-on ≈ $36k/yr (untenable); this model ≈ $0 ongoing + a small bounded starter-box pool. None of it needs new machinery — it rides the rank ladder, the onboarding funnel, the drachmae/credits system, and the cost-logging we already have.
|
|
94
|
+
|
|
95
|
+
### 6. Security — the container is the rank-gated source-access boundary
|
|
96
|
+
|
|
97
|
+
The repo is **private** (today under a personal account — see §7; a move to a GitHub Organization would change the enforcement options below). Putting the worker on a box we control, reached only through the GUI, turns repo access from *"every builder holds a permanent local clone forever"* into *"access is to a box we own — gated, revocable, audited, egress-restricted."*
|
|
98
|
+
|
|
99
|
+
- **Gate source access by rank.** Below a chosen rank: no box, no source. The box (not the builder) holds the scoped GitHub credential; below-rank builders have no direct clone rights. Extends the [ADR 0016](<redacted>.md) trust boundary from "writes are gated" to "source access is gated."
|
|
100
|
+
- **No permanent local copy** (the code lives on the box; SSH/Remote-Control bypass any local folder).
|
|
101
|
+
- **Central revocation + clean offboarding** (revoke rank → de-provision box → source access gone).
|
|
102
|
+
- **Prod deploy moves to CI** (GitHub Actions, like staging already is), so **no builder box ever holds the production droplet SSH key.**
|
|
103
|
+
- **Honest limit (recorded):** an authorized, logged-in builder can still read/copy code they have a session on. This is defense-in-depth, revocation, and audit — **not** insider-proof DRM.
|
|
104
|
+
|
|
105
|
+
### 7. Substrate — DigitalOcean, not Codespaces
|
|
106
|
+
|
|
107
|
+
**DigitalOcean** is the org substrate because it fits the decisions above:
|
|
108
|
+
|
|
109
|
+
- **Clean fit for Claude Desktop SSH** (a real IP/port/key) and for an always-on `claude rc` service; Codespaces' SSH is a `gh` CLI tunnel (no stable host) and its 30-min auto-suspend kills `claude rc`.
|
|
110
|
+
- **Full host control** → we are root, the builder is a guest, so the firewall/lockdown/idle policy are *enforceable*. On a **personal** repo, Codespaces gives the collaborator root of their own box and the org **cannot enforce** environment/network/idle policy (those are <redacted> features).
|
|
111
|
+
- **Org can fund the first box.** On a personal repo, GitHub bills the Codespace *creator* and a personal account has **no mechanism to pay on a collaborator's behalf** — directly incompatible with the "org pays the first box" decision (it would require converting to a paid GitHub Org, which then flips billing org-wide).
|
|
112
|
+
- **Unified stack** (we already run DO; existing deploy tooling) and **flexible billing** (org account or the builder's own DO account).
|
|
113
|
+
- **Escape hatch:** because the devcontainer is portable, a VS-Code-preferring builder may **self-fund their own Codespaces** off the same spec. Codespaces is an opt-in individual lane, not the foundation.
|
|
114
|
+
|
|
115
|
+
## Alternatives considered
|
|
116
|
+
|
|
117
|
+
- **Fully remote with centralized inference (org runs everything on one provider + a shared API key).** Rejected. Centralizing inference forces the 15–30× more expensive per-token rate onto the org and concentrates provider/price/sunset risk (cf. AWS Cloud9's 2024 closure) — exactly the margin pressure we want to avoid. The hybrid contains "fully remote" as one configuration without forcing centralized inference.
|
|
118
|
+
- **GitHub Codespaces as the org substrate.** Rejected as the *default* (kept as an opt-in self-funded lane). On our private *personal* repo it preserves access-control and revocation (those live in GitHub permissions, independent of who pays) but **loses enforced environment control** (the collaborator is root), is **awkward to drive from Claude Desktop**, its auto-suspend **kills Remote Control**, and the org **cannot pay the first box**. Its genuine strengths (native self-funding, native auto-suspend, zero ops) collide with three firmer priorities (Desktop-first UI, org-funded onboarding, enforced control).
|
|
119
|
+
- **Anthropic-managed Cloud VM for onboarding.** Rejected by the decider. Though free to the org (runs on the builder's subscription), it is ephemeral, GitHub-centric, network-restricted, cannot host our full stack, and cannot cleanly SSH out to deploy. We pay for a real starter box instead.
|
|
120
|
+
- **Pure bring-your-own box (builder creates + pays from day one).** Rejected as the entry path — it reintroduces the payment/signup barrier that suppresses early growth. It is the *destination* (the "established" stage), not the on-ramp.
|
|
121
|
+
- **Shared org Anthropic API key.** Rejected — 15–30× costlier than subscriptions and the wrong side of the headless-use terms. BYO-subscription is both cheaper and cleaner.
|
|
122
|
+
- **Keep local-only.** Rejected — it fails C1 (toolchain install is the worst onboarding burden) and excludes builders without capable hardware.
|
|
123
|
+
|
|
124
|
+
## Consequences
|
|
125
|
+
|
|
126
|
+
**Good:**
|
|
127
|
+
- Onboarding drops from "install a toolchain and hope" to "open Claude Desktop, pick the Example Dev Box" — the direct lever on C1 (≤2h).
|
|
128
|
+
- Org inference cost = $0 (BYO subscription); org compute cost ≈ $0 ongoing (auto-suspend + self-funding), bounded by a small starter-box pool.
|
|
129
|
+
- The container is a real security upgrade: rank-gated source access, no permanent local clones, central revocation, clean offboarding, egress restriction — strengthening [ADR 0016](<redacted>.md).
|
|
130
|
+
- The box stays portable (devcontainer), so we are not locked to DigitalOcean, and VS-Code power-users can self-fund Codespaces off the same spec.
|
|
131
|
+
- [ADR 0024](<redacted>.md) memory sync is unaffected — it keys on `builder_id`, not on where the box runs.
|
|
132
|
+
|
|
133
|
+
**Costs / what it commits us to:**
|
|
134
|
+
- Real build work: author + test the devcontainer across a DO box and a laptop (the `sharp` native build is the usual snag), build the auto-suspend automation (DO has none native), wire prod-deploy to CI, build rank-gated box provisioning, and write the onboarding primer page.
|
|
135
|
+
- An ongoing operational surface: per-builder box lifecycle (provision, suspend, de-provision), managed-settings distribution, and the `claude rc` service for thin-device builders.
|
|
136
|
+
- The autonomous-lane ToS posture (§4) is a judgment call with a recorded residual risk to re-verify.
|
|
137
|
+
|
|
138
|
+
**Operationally:**
|
|
139
|
+
- Auto-suspend and prod-deploy-to-CI should land before the first non-Lars builder, or the cost and security properties this ADR promises aren't real yet.
|
|
140
|
+
- The onboarding repo-access wrinkle (see Open questions) must be resolved before a Xenos's starter box is provisioned.
|
|
141
|
+
|
|
142
|
+
## Open questions (flagged, not blocking)
|
|
143
|
+
|
|
144
|
+
1. **Per-builder box ownership transition.** Mechanics of moving a builder from the org-funded starter box to self-funding — recreate on their own DO account vs. org-provisions-and-rebills via the `costs` table. Lean: rebill-via-drachmae for simplicity; revisit.
|
|
145
|
+
2. **Onboarding repo-access scoping.** ✅ **RESOLVED 2026-06-05 ([#600](https://example.com/builders#/task/600)) — rank-gated, server-issued, fetched-not-baked.** A Xenos box sparse-checks out only the safe surface; full creds arrive at graduation; the box fetches a scoped credential gated on live rank rather than holding a baked one. See the resolution note below.
|
|
146
|
+
3. **Auto-suspend implementation.** ✅ **RESOLVED 2026-06-04 ([#598](https://example.com/builders#/task/598)) — snapshot-and-park, NOT Coder.** See the resolution note below.
|
|
147
|
+
4. **Thin-device on-ramp friction.** ✅ **RESOLVED 2026-06-06 ([#701](https://example.com/builders#/task/701)) — box reports its pairing link UP to the GDS; the builder reads it from the web.** See §9.4 below.
|
|
148
|
+
5. **Codespaces self-fund lane.** Confirm (empirically) that a collaborator self-funds on the private personal repo, and decide whether to actively support the VS-Code lane or merely allow it.
|
|
149
|
+
6. **Desktop SSH on the chosen DO image.** SSH-to-macOS/Linux and the redesign are recent (April 2026, parts research-preview); pin versions and verify on the actual image before rollout.
|
|
150
|
+
|
|
151
|
+
## Open-question resolutions
|
|
152
|
+
|
|
153
|
+
### §9.2 — Onboarding repo-access scoping: rank-gated, server-issued, fetched-not-baked (2026-06-05, [#600](https://example.com/builders#/task/600))
|
|
154
|
+
|
|
155
|
+
**Decision: the box holds NO baked credential — it fetches a rank-scoped clone spec from the GDS at boot + on a cron, gated on the builder's LIVE rank.** This extends the [ADR 0016](<redacted>.md) trust boundary from "writes are gated" to "source is gated": `GET /api/gds/box/source-access` (`requireBuilder`; the scope + allow/deny decided inside from the live DB rank, not a static `requireRank`) returns either a 403 or a scope-appropriate spec + a short-lived, operator-supplied credential.
|
|
156
|
+
|
|
157
|
+
- **Scope by rank** (`src/bongos/box-access.js`, `boxScopeForRank` from [#598](https://example.com/builders#/task/598)): **Xenos → `starter`** (cone-mode sparse-checkout of `.devcontainer`, `art`, `docs`, `public-status`, `generative-ideas.md` — the "first tasks on a scoped surface" this question asked for); **Metic+ → `full`**. Graduation rescopes the box (the box re-clones the full surface on its next refresh). Fail-closed: any unknown scope collapses to `starter`.
|
|
158
|
+
- **Credential is fetched, never baked** (`src/bongos/box-credential.js`, env-backed default; the secret lives in the prod env per [ADR 0022](0022-secrets-policy.md), `configured:false` until set). The box clones via a one-shot authenticated URL then scrubs the token from `.git/config` (`infra/box-source-fetch.sh`). Because access is re-checked live on every fetch, **revocation is instant at the credential layer** — a demoted/deactivated builder is 403'd on their next refresh.
|
|
159
|
+
- **The clawback (clean offboarding).** `setBuilderRank` / `deactivateBuilder` flag a box reconcile post-commit; `scripts/gds/box.js reconcile` deprovisions below-floor / inactive boxes and rescopes graduated ones. Credential revocation is instant; droplet teardown is the async control-plane half. Every grant/denial/reconcile is audited to `box_events`.
|
|
160
|
+
- **Honest limit (recorded, unchanged from §6).** Git cannot sub-path-scope a single private repo and sparse-checkout is client-side, so the default `starter` surface is **defense-in-depth + audit + revocation, not insider-proof DRM**. A HARD boundary is available via `BOX_SOURCE_STARTER_REPO_URL` (a physically separate starter repo) — left as an operator choice.
|
|
161
|
+
|
|
162
|
+
Operator runbook: `docs/recipes/managed-settings-remote-control.md` Part 4. **The <redacted> leg (a live box fetching + sparse-cloning against a configured GitHub credential) is unverified on hardware** — same posture as [#598](https://example.com/builders#/task/598)/[#599](https://example.com/builders#/task/599); the decision core, the route, the clawback, the cloud-init injection, and the box-side script are verified hermetically (`tests/box_access.mjs`, `scripts/gds/smoke-box.sh`).
|
|
163
|
+
|
|
164
|
+
### §9.3 — Auto-suspend: snapshot-and-park, not a Coder control-plane (2026-06-04, [#598](https://example.com/builders#/task/598))
|
|
165
|
+
|
|
166
|
+
**Decision: build snapshot-and-park.** When a box goes idle, snapshot the droplet then **destroy** it; recreate from the snapshot on demand. Implemented in `scripts/gds/box.js` (the lifecycle CLI), `src/bongos/boxes.js` (the pure decision core), `src/bongos/do-api.js` (the DO client), the `builder_boxes` + `box_events` tables (migration 067), and two control-plane sweeps (`box-idle-suspend` every 15 min, `box-dormant-reclaim` daily).
|
|
167
|
+
|
|
168
|
+
**Why not Coder.** Coder is a full control-plane: it needs an **always-on droplet to host it** (~$12–24/mo fixed) plus its own Postgres, auth, and an upgrade treadmill. That is exactly the *builder-count-independent fixed cost* this ADR's §5 cost model is built to avoid, and a heavy new operational surface for a project that has one builder today. Snapshot-and-park rides `doctl`/the DO API + the existing systemd-timer sweep pattern (it is the direct analogue of `stale-claim-release`), so its fixed cost is ≈ $0 — a parked box costs only its snapshot (~$0.30/mo for a ~5 GB image) instead of ~$24/mo running. Coder's genuine wins (a polished per-workspace model, web terminal) are not things we need: the UI is Claude Desktop (§1), and the per-builder model is one droplet keyed by `builder_id` in our own table.
|
|
169
|
+
|
|
170
|
+
**The DO footgun this encodes.** A *powered-off* DigitalOcean droplet **still bills** (its disk/resources stay reserved). So "park" is not "power off" — it is snapshot → **destroy**, keeping only the cheap snapshot; "wake" is recreate-from-snapshot. This is why the schema's `droplet_id`/`ip` are NULL while parked but `snapshot_id` persists, and why a box accrues compute cost only while `state='active'`.
|
|
171
|
+
|
|
172
|
+
**Stable hostname (handoff to [#599](https://example.com/builders#/task/599)).** A recreated droplet gets a new IP. The lifecycle records a per-builder `hostname` (`box-<login>.dev.example.com`) and offers a `BOX_DNS_HOOK` to repoint it on each wake; the actual Desktop managed-settings cutover to that hostname is [#599](https://example.com/builders#/task/599).
|
|
173
|
+
|
|
174
|
+
## Decision update — container pool rejected; droplet-per-builder confirmed; hardware status (2026-06-06, [#699](https://example.com/builders#/task/699))
|
|
175
|
+
|
|
176
|
+
Two things were settled this session.
|
|
177
|
+
|
|
178
|
+
### Newcomer substrate: a shared-host container pool was investigated and REJECTED.
|
|
179
|
+
|
|
180
|
+
The question raised: should the Xenos/newcomer substrate be a **pool of containers packed onto a shared host** (denser, cheaper) instead of the §2 one-droplet-per-builder model? Investigated via two research threads plus a hardware check. Findings:
|
|
181
|
+
|
|
182
|
+
- **Plain Docker is not a security boundary for this workload.** Builders run an AI agent executing arbitrary code — "hostile by default." Containers share one host kernel, so a single kernel/`runc` escape exposes every co-tenant's source, scoped credential, and Claude session. The escape cadence is roughly annual (runc "Leaky Vessels" Jan 2024; a runc CVE cluster Nov 2025, several of which **bypass AppArmor/SELinux**; Dirty-Pipe-class kernel LPEs every 1–2 years). Revealed preference confirms it: every vendor that runs strangers' untrusted code — AWS Lambda/Fargate, Fly.io, e2b, Vercel, CodeSandbox (Firecracker), Modal, Google Cloud Run (gVisor) — moved **off** plain Docker.
|
|
183
|
+
- **A *secure* pool needs gVisor or Firecracker/Kata microVMs**, plus the full in-container hardening checklist, plus a placement/pool manager, plus snapshot-suspend to reclaim idle RAM. DO **does** expose `/dev/kvm` (verified on the prod droplet: `vmx` flag, `kvm_intel` loaded), so microVMs are technically viable here.
|
|
184
|
+
- **The cost win is real but modest, and the "less middleware" premise is inverted.** The ~$0/idle-container economics depend on scale-to-zero *plain* containers, which security forbids. Secure warm cost ≈ $1.65–2.65/tenant/mo (≈ $0.30–0.60 with Firecracker snapshot-suspend) vs ≈ $5–12 for a per-builder droplet — i.e. cheaper only at dozens of concurrent newcomers. And the droplet model gets isolation **free** from the VM boundary, whereas the pool must *re-engineer* isolation (security runtime + pool manager + snapshot orchestration) — strictly **more** to build and run, plus a hard network-isolation requirement (a pool host co-locating many builders' credentials must not be able to reach the control plane / GDS Postgres).
|
|
185
|
+
|
|
186
|
+
**Decision: droplet-per-builder only — §2's "one box per builder" stands unqualified. The container pool is rejected.** At one builder it earns nothing; it pays off only at scale, and even then trades dollars for multi-tenant security engineering. Revisit *only* if concurrent-newcomer volume reaches the dozens **and** the gVisor/microVM security work is funded. (Sources: [runc Nov-2025 CVEs](https://www.sysdig.com/blog/runc-container-escape-vulnerabilities), [container-isolation consensus](https://www.aquasec.com/blog/container-isolation/), [microVM isolation 2026](https://emirb.github.io/blog/microvm-2026/).)
|
|
187
|
+
|
|
188
|
+
### Hardware-verification status (sharpens the §9.2 note).
|
|
189
|
+
|
|
190
|
+
- **The §9.3 lifecycle ([#598](https://example.com/builders#/task/598)) is verified on real hardware (2026-06-04).** `box_events` records a full `provision → park → wake → deprovision` cycle that day (droplets `575387789`/`575388325`, snapshot `231499516`). **Operator gotcha:** the first attempt failed with `DO POST /droplets … missing the required permission tag:create` — the `DO_API_TOKEN` must carry **tag-create** scope (use a Full-Access token, or a custom token that includes tag write).
|
|
191
|
+
- **The [#685](https://example.com/builders#/task/685) builder-side `connect.sh` is verified on a real Mac (2026-06-06)** — GitHub device sign-in, `~/.ssh/otb_builder` key generation + registration, and `managed-settings.json` install all succeeded end-to-end.
|
|
192
|
+
- **Still unverified on hardware:** the §9.2 ([#600](https://example.com/builders#/task/600)) source-fetch leg (sparse clone against a configured GitHub credential — `BOX_REPO_URL`/credential were not set), the operator `box.js onboard` welcome-packet on a live box, and the actual Claude Desktop **connect to a running box**. The token was removed after the 2026-06-04 run, so no `box.env` is currently configured.
|
|
193
|
+
|
|
194
|
+
### The real remaining onboarding gap → ✅ CLOSED ([#701](https://example.com/builders#/task/701), 2026-06-06).
|
|
195
|
+
|
|
196
|
+
Droplet-vs-pool was a *backend* choice; it did not address the newcomer on-ramp. For a non-technical / Chromebook builder, three server-side pieces were unbuilt: **auto-provision** (no builder-held DO token, no manual operator CLI), **wake-on-connect** (a returning builder's parked box wakes itself), and the **web/Chromebook Remote-Control pairing** path. All three shipped under [#701](https://example.com/builders#/task/701) — see §9.4.
|
|
197
|
+
|
|
198
|
+
### §9.4 — Box onboarding middleware: an intent queue, never the DO token in the web process (2026-06-06, [#701](https://example.com/builders#/task/701))
|
|
199
|
+
|
|
200
|
+
> **Superseded in part (2026-06-06, [ADR 0038](<redacted>.md)):** the `claude rc` Remote-Control pairing path in this section — `infra/box-report-pairing.sh`, the `box/pairing` + `box/pairing-link` routes, and the "box reports its link UP" Chromebook flow — is retired. `claude rc` can't run headless on a box (see ADR 0038 *Problem*). The browser-only on-ramp is now a box-served `ttyd` web terminal over a Cloudflare Tunnel. The intent-queue / `box/ensure` auto-provision + wake-on-connect machinery below is **unaffected**.
|
|
201
|
+
|
|
202
|
+
**Decision: the web process records a REQUEST (an "intent"); the control-plane runner executes it.** The hard constraint (§6 + the `routes/box.js` header) is that the internet-facing Node process must never hold `DO_API_TOKEN` — a stolen builder token could otherwise spin up or destroy infrastructure. So [#701](https://example.com/builders#/task/701) does NOT move provisioning into the web tier. Instead:
|
|
203
|
+
|
|
204
|
+
- **Auto-provision + wake-on-connect = one primitive: `POST /api/gds/box/ensure`** (`requireBuilder`; allow/deny + the chosen action decided inside from the builder's LIVE rank+status via `boxes.decideEnsureAction`, mirroring `/box/source-access`). It writes a row to the new **`box_intents`** queue (migration 070): `provision` when the builder has no box (auto-provision), `wake` when their box is parked (wake-on-connect), a no-op heartbeat when already active. At most ONE open intent per builder (a partial unique index), so a flaky Chromebook tab can't pile up duplicates.
|
|
205
|
+
- **The control-plane runner drains the queue.** `scripts/gds/box.js run-intents --apply`, on a 1-minute `box-intent-runner.timer` (the same systemd-timer pattern as the idle/dormant sweeps), claims each pending intent (`FOR UPDATE SKIP LOCKED`) and calls the SAME `cmdProvision`/`cmdWake` an operator uses — with a small attempt cap so a poison intent retires to `error` rather than looping. The web tier stays powerless over infra; it can only ask.
|
|
206
|
+
- **Web/Chromebook pairing: the box reports its link UP.** A Chromebook has no Claude Desktop, so its on-ramp is the `claude rc` Remote-Control pairing link — which previously existed only in the box's journal (reachable only via SSH, defeating the purpose). Now `infra/box-report-pairing.sh` (a 1-min box cron, authenticated as the builder via the GDS token already on the box) POSTs the current link to **`POST /api/gds/box/pairing`** (stored on `builder_boxes.remote_pairing_url`, only while the box is `active`), and the builder reads it from the browser via **`GET /api/gds/box/pairing-link`** (returned only to the box's own builder). The Chromebook flow: `POST /box/ensure` → poll `GET /box/pairing-link` until a link appears → open it.
|
|
207
|
+
|
|
208
|
+
**Honest limit (recorded).** The server-side decision core (`decideEnsureAction`, the intent queue, the three routes, the runner) is verified hermetically (`tests/boxes.mjs` 33 assertions, `scripts/gds/smoke-box.sh`). The **live legs are unverified on hardware** — same posture as §9.2/§9.3: a real box actually being auto-provisioned by the timer, a parked box waking on `ensure`, and `claude rc` publishing a real pairing link that reaches the browser. The `box-intent-runner.timer` + `box-report-pairing` cron must be installed on the control plane / baked into the box (the cloud-init wires the box cron; the operator installs the control-plane timer per the lifecycle recipe).
|
|
209
|
+
|
|
210
|
+
## Implementation sub-tasks (proposed — to be seeded in a planning/triage session)
|
|
211
|
+
|
|
212
|
+
Not yet created; listed so the build session has a starting decomposition.
|
|
213
|
+
|
|
214
|
+
| Proposed task | What it builds | Criterion |
|
|
215
|
+
|---|---|---|
|
|
216
|
+
| Devcontainer foundation | `devcontainer.json` + Dockerfile (Node 22, Python + art libs, `sharp` deps, Claude Code feature + firewall); fold in `setup-builder.sh`; `doctor.js` as health check | C1 |
|
|
217
|
+
| Prod-deploy to CI | Move prod deploy to GitHub Actions (mirror `deploy-staging.yml`) so no builder box holds the droplet key | C8 / security |
|
|
218
|
+
| DO box provisioning + auto-suspend | Per-builder box provision, idle snapshot/park + recreate, dormant de-provision; rank-gated | C1 / cost |
|
|
219
|
+
| Managed-settings + Remote Control service | Distribute the locked `sshConfigs`/`sshHostAllowlist`; always-on `claude rc` for thin devices | C1 |
|
|
220
|
+
| Rank-gated source access | Box holds the scoped GitHub credential; access granted/revoked by rank; offboarding clawback | C2 / C7 / C9 |
|
|
221
|
+
| Onboarding primer page ✅ | **Done** — the §"mental model" explainer lives in [`docs/onboarding/primer.md`](../onboarding/primer.md) § "Where your work runs" (extracted in the ADR 0035 rewrite, [#718](https://example.com/builders#/task/718); closed by task 601) | C1 |
|
|
222
|
+
| Cost passthrough wiring | Org-funded starter → self-fund transition via `costs`/drachmae | cost |
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# ADR 0032 — Discord as a one-way mirror of the GDS (outbound broadcast moves off Google Chat)
|
|
2
|
+
|
|
3
|
+
**Date:** 2026-06-04
|
|
4
|
+
**Context:** GDS-V3, task [#607](https://example.com/builders#/task/607) (claim `#482`). Stands up a Discord server for Off the Boats / Example — one server, permission-walled into a player wing and a builder wing — and moves the ship-broadcast off Google Chat. Companion to [ADR 0016](<redacted>.md) (the trust boundary — the load-bearing constraint here), [ADR 0018](<redacted>.md) (Xenos → Metic → Archon, the ranks Discord roles mirror), [ADR 0022](0022-secrets-policy.md) (webhook URLs are secrets, kept out of the repo), and [ADR 0008](0008-google-chat-oauth-user-auth.md) (the Google Chat broadcast this supersedes for outbound).
|
|
5
|
+
**Status:** Accepted. Phase 2 (outbound ship broadcast → Discord) implemented in [#607](https://example.com/builders#/task/607). Phases 3+ (interaction bot, inbound retirement, role sync) scoped below, not yet built.
|
|
6
|
+
**Track:** `internal` (dev-system / team communication).
|
|
7
|
+
|
|
8
|
+
## Problem
|
|
9
|
+
|
|
10
|
+
The project is opening to multiple builders (GDS-V3) and will eventually have players (the game is live; the purchase loop lands in V3-product). We want one community home with two audiences — builders and players — and a single legible place where build progress is announced and where the community can react.
|
|
11
|
+
|
|
12
|
+
Two existing surfaces are relevant:
|
|
13
|
+
|
|
14
|
+
- The **ship broadcast** posts a plain-language "what shipped" summary to the Example team **Google Chat** space ([ADR 0008](0008-google-chat-oauth-user-auth.md); fired from `scripts/gds/ship.js` on deploy). Google Chat is a closed business space — fine for the Example owners, wrong as a builder+player community hub.
|
|
15
|
+
- The **GDS** already holds all authoritative state: ranks, tasks, karma (`peer_upvote` is an existing reason), idea_inbox, drachmae, the builders' hall.
|
|
16
|
+
|
|
17
|
+
The temptation, standing up Discord, is to let it *own* things — roles that grant power, votes that live in Discord, a second scoreboard. That would fracture the source of truth and — for roles — quietly punch a hole in the trust boundary.
|
|
18
|
+
|
|
19
|
+
## Decision
|
|
20
|
+
|
|
21
|
+
**Discord is a one-way mirror of, and input surface for, the GDS — never a source of authority or truth.**
|
|
22
|
+
|
|
23
|
+
1. **One server, two role families, permission-walled.** A public (player) wing and a builder-only wing in a single server. Builder roles **mirror** the live GDS ranks (Xenos / Metic / Archon) and gate the builder wing; players get a `Citizen` baseline (a `Patron` tier later, once purchases ship).
|
|
24
|
+
|
|
25
|
+
2. **Discord roles are cosmetic, never authoritative ([ADR 0016](<redacted>.md)).** A Discord "Archon" badge unlocks Discord *channels* and nothing else. Authority stays in `builders.rank` on the prod DB, re-checked server-side per request. A Discord role is a *sticker*; the GDS holds the *keys*. Roles reflect what the DB already says — they never decide it. When a future bot auto-assigns roles, it reads `builders.rank` and writes the Discord role, never the reverse.
|
|
26
|
+
|
|
27
|
+
3. **The GDS stays the single source of truth.** Logs mirror *into* Discord (webhooks, one-way). "Upvotes" arrive as Discord reactions that a bot writes *back* to the GDS as `peer_upvote` karma / idea votes — Discord is the input device, the GDS records the fact. No state's canonical home moves to Discord.
|
|
28
|
+
|
|
29
|
+
4. **Outbound ship broadcast moves from Google Chat to Discord `#ship-news`** (this task, [#607](https://example.com/builders#/task/607)). `scripts/gds/ship.js` posts the task `--summary` to a Discord webhook via `scripts/discord/`. Same posting rules as before (plain-language, business-facing, no jargon — see [`../google-chat-broadcast.md`](../google-chat-broadcast.md)). Webhook URLs are secrets ([ADR 0022](0022-secrets-policy.md)) at `~/.config/otb/discord-webhooks.json` (mode 600), never committed; the broadcast runs on the operator's machine during `/builder-ship`, so the droplet needs no secret.
|
|
30
|
+
|
|
31
|
+
### Phasing
|
|
32
|
+
|
|
33
|
+
- **Phase 2 (this task, [#607](https://example.com/builders#/task/607)):** outbound `#ship-news` broadcast. **Done.**
|
|
34
|
+
- **Phase 3:** a Discord bot on the droplet for reactions → `peer_upvote` karma / idea votes, and retirement of the Google Chat **inbound** path (team messages → idea pipeline). Reading messages needs a bot, not a webhook. Account-linking (Discord user ↔ GDS builder) must be UI-first — browser clicks, no terminal token paste.
|
|
35
|
+
- **Later:** auto-sync `builders.rank` → Discord role; build out the player wing when the purchase loop ships.
|
|
36
|
+
|
|
37
|
+
## Consequences
|
|
38
|
+
|
|
39
|
+
- The team's "what shipped" updates now land in a community Discord that builders and players can share, instead of a closed business chat.
|
|
40
|
+
- The trust boundary stays intact and legible: exactly zero authority lives in Discord; the one rule to defend in review is "roles mirror the DB, never grant."
|
|
41
|
+
- Google Chat is **half-retired**: outbound is gone (Discord), inbound (`chat-intake-daily`) still runs until Phase 3. `scripts/chat/` stays in the tree until then.
|
|
42
|
+
- A leaked webhook URL is low blast-radius (write-only to one channel) and rotated with one click in Discord — no code change.
|
|
43
|
+
|
|
44
|
+
## Alternatives considered
|
|
45
|
+
|
|
46
|
+
- **Separate servers for builders vs players.** Rejected — one server with a permission-walled builder wing is the standard pattern and keeps the community in one place.
|
|
47
|
+
- **Discord roles grant real permissions.** Rejected outright — violates [ADR 0016](<redacted>.md). Anything a builder's Discord membership could grant must be re-derived server-side from `builders.rank`.
|
|
48
|
+
- **Move idea votes / karma canonical into Discord.** Rejected — the GDS stays source of truth; Discord is an input surface whose reactions a bot writes back.
|
|
49
|
+
- **Mirror outbound to both Google Chat and Discord.** Rejected — replace, don't double-post; the community consolidates on Discord. Google Chat keeps only its inbound role until Phase 3.
|
|
50
|
+
- **A Discord bot (gateway) for outbound too.** Rejected for Phase 2 — a webhook is a one-line HTTP POST with no token or process to host; the bot is only needed for *reading* (Phase 3).
|