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