@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,416 @@
|
|
|
1
|
+
# Recipe - managed-settings SSH + browser-terminal on-ramps
|
|
2
|
+
|
|
3
|
+
> **Task [#599](https://example.com/builders#/task/599) · [ADR 0031](../adr/<redacted>.md) §1.** This is the operator guide for distributing a locked Claude Desktop SSH connection to builders and for serving the browser web-terminal on the box. Read [builder-box-lifecycle.md](builder-box-lifecycle.md) first — this recipe assumes a box is already provisioned and running.
|
|
4
|
+
|
|
5
|
+
> **Chromebook/browser path (ADR 0038 → [ADR 0040](../adr/<redacted>.md), [#760](https://example.com/builders#/task/760) / [#824](https://example.com/builders#/task/824)):** the box serves its own web terminal - `ttyd` bound to localhost, exposed via an outbound-only Cloudflare Tunnel - and the builder runs a NORMAL interactive `claude` inside it from any browser. Because there is a real human at a real browser-backed terminal, `/login`, workspace-trust, and Trusted-Devices enrollment all succeed (the things that made headless `claude rc` unworkable). **ADR 0040 narrows that terminal to a one-time launchpad:** the builder logs in there once, runs the in-session **`/remote-control`** slash command, and then drives the box from the Claude web/mobile app — the terminal is no longer their daily surface. **The SSH path (Mac/Windows) below is unaffected and works.**
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## The two on-ramps
|
|
10
|
+
|
|
11
|
+
| Builder's device | Path | What this recipe wires |
|
|
12
|
+
|---|---|---|
|
|
13
|
+
| **Mac / Windows** | Claude Desktop → SSH → box | Pre-distributed `managed-settings.json` (locked `sshConfigs`) |
|
|
14
|
+
| **Chromebook / any browser** | Browser -> `ttyd` web terminal over an outbound Cloudflare Tunnel -> box (reached at `GET /box/terminal`) -> log in once, then `/remote-control` -> drive the box from the Claude web/mobile app (ADR 0040) | `box-terminal.service` (ttyd, localhost-only) + `cloudflared.service` (outbound tunnel) + `box-report-terminal` cron |
|
|
15
|
+
|
|
16
|
+
Both paths land the builder on the same box and the same `/workspace`. The choice is which GUI they prefer, not which machine they reach.
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## Part 0 — the one-command flow ([#685](https://example.com/builders#/task/685), the default)
|
|
21
|
+
|
|
22
|
+
> **This is the path to use now.** Parts 1, 3 and 4 below document the underlying
|
|
23
|
+
> pieces (and the manual fallbacks); [#685](https://example.com/builders#/task/685) collapses the builder's side to a single
|
|
24
|
+
> command and the operator's side to a single command. Nobody has to `scp` a JSON
|
|
25
|
+
> file, paste a public key into `authorized_keys`, or `sudo cp` anything by hand.
|
|
26
|
+
|
|
27
|
+
**The split:**
|
|
28
|
+
|
|
29
|
+
| Who | Runs | What it does |
|
|
30
|
+
|---|---|---|
|
|
31
|
+
| **Builder** (own Mac/Windows) | `/builder-connect` — or, on a bare laptop, the hosted bootstrap (below) | GitHub sign-in → create+register their SSH key → install `managed-settings.json` |
|
|
32
|
+
| **Operator** (control plane) | `node scripts/gds/box.js onboard <login> --apply` | provision the box (bakes the builder's registered key) + print the welcome packet |
|
|
33
|
+
|
|
34
|
+
**Builder side** — guided in Claude Desktop (`/builder-connect`), or paste the hosted bootstrap on a bare laptop:
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
# macOS / Linux
|
|
38
|
+
curl -fsSL https://example.com/api/gds/box/connect.sh | bash
|
|
39
|
+
# Windows (PowerShell)
|
|
40
|
+
irm https://example.com/api/gds/box/connect.ps1 | iex
|
|
41
|
+
# Plain-text guide (no auth): https://example.com/api/gds/box/connect
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
The bootstrap (`infra/box-connect.sh` / `.ps1`, served by `GET /box/connect.sh|.ps1`)
|
|
45
|
+
is self-contained — only `curl` + `ssh-keygen` (or PowerShell). It device-flow signs
|
|
46
|
+
the builder in, creates `~/.ssh/otb_builder` if absent, registers the **public** key
|
|
47
|
+
(`POST /box/ssh-key`), and downloads + installs `managed-settings.json`
|
|
48
|
+
(`GET /box/managed-settings`) at the OS path. The private key never leaves the machine;
|
|
49
|
+
the token is held only in memory for the two authenticated calls (passed to `curl` via a
|
|
50
|
+
0600 config file, never on the command line).
|
|
51
|
+
|
|
52
|
+
> **Operator note (staging):** the served script's `GDS_API_BASE` is pinned to
|
|
53
|
+
> `GDS_PUBLIC_ORIGIN` (default `https://example.com`) — the SAME env var the OAuth
|
|
54
|
+
> `redirect_uri` uses — and is **never** derived from a request header, so a spoofed
|
|
55
|
+
> `Host` can't redirect a victim's `curl … | bash`. On staging, set
|
|
56
|
+
> `GDS_PUBLIC_ORIGIN=https://staging.example.com` in the server env (you already do,
|
|
57
|
+
> for OAuth) and the connect scripts target staging automatically.
|
|
58
|
+
|
|
59
|
+
**Operator side:**
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
node scripts/gds/box.js onboard <login> # dry-run: shows the plan + key count
|
|
63
|
+
node scripts/gds/box.js onboard <login> --apply # provision + print the welcome packet
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
`onboard` refuses if the builder has **no registered SSH key** (the box would have no
|
|
67
|
+
way in) — the canonical order is **builder connects first, then operator onboards**.
|
|
68
|
+
Override with `--allow-no-keys` only if you will re-provision once they register a key.
|
|
69
|
+
|
|
70
|
+
**How the key reaches the box without an operator SSH round-trip:** `box.js` reads the
|
|
71
|
+
builder's registered keys from the DB and **bakes** them into the cloud-init managed
|
|
72
|
+
block at provision (so the first login works before the box has any GDS token). From
|
|
73
|
+
then on, `infra/<redacted>.sh` (a 10-min cron on the box, wired by the
|
|
74
|
+
cloud-init) keeps `authorized_keys` in sync via `GET /box/authorized-keys` — which is
|
|
75
|
+
**gated on the builder's LIVE rank+status** exactly like source access ([#600](https://example.com/builders#/task/600)). A
|
|
76
|
+
demoted/deactivated builder gets a 403 and the cron empties the managed block — instant
|
|
77
|
+
SSH-login revocation — **without touching the operator's own key** (only the lines
|
|
78
|
+
between the `otb-managed` markers are rewritten). The droplet teardown follows via
|
|
79
|
+
`box.js reconcile`.
|
|
80
|
+
|
|
81
|
+
The rest of this recipe is the manual decomposition + fallbacks.
|
|
82
|
+
|
|
83
|
+
---
|
|
84
|
+
|
|
85
|
+
## Part 1 — Claude Desktop SSH (Mac / Windows)
|
|
86
|
+
|
|
87
|
+
### How it works
|
|
88
|
+
|
|
89
|
+
The builder installs Claude Desktop (free; no paid plan needed for the app shell). You pre-distribute a `managed-settings.json` that contains the SSH connection for their box. When they open Claude Desktop → Code → Environment, they see exactly one entry: **"Example Dev Box"** — no configuration required on their end.
|
|
90
|
+
|
|
91
|
+
Claude Desktop SSHes to the box, auto-installs Claude Code on the host (on first connect), and starts a Code session directly in `/workspace`. The builder is ready to work.
|
|
92
|
+
|
|
93
|
+
### Step 1 — Provision the box
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
node scripts/gds/box.js provision <login> --apply
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
This creates the DigitalOcean droplet, runs the cloud-init, and registers the box. The builder's hostname is `box-<login>.dev.example.com`.
|
|
100
|
+
|
|
101
|
+
### Step 2 — Generate the managed-settings.json
|
|
102
|
+
|
|
103
|
+
```bash
|
|
104
|
+
bash infra/gen-managed-settings.sh <login> > /tmp/<login>-managed-settings.json
|
|
105
|
+
cat /tmp/<login>-managed-settings.json
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
The output is a small JSON file, e.g.:
|
|
109
|
+
|
|
110
|
+
```json
|
|
111
|
+
{
|
|
112
|
+
"sshConfigs": [
|
|
113
|
+
{
|
|
114
|
+
"id": "otb-alice",
|
|
115
|
+
"name": "Example Dev Box",
|
|
116
|
+
"sshHost": "root@box-alice.dev.example.com",
|
|
117
|
+
"sshIdentityFile": "~/.ssh/otb_builder",
|
|
118
|
+
"startDirectory": "/workspace"
|
|
119
|
+
}
|
|
120
|
+
],
|
|
121
|
+
"sshHostAllowlist": ["*.dev.example.com"]
|
|
122
|
+
}
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
### Step 3 — Give it to the builder
|
|
126
|
+
|
|
127
|
+
Send them the file and these instructions (copy-paste friendly):
|
|
128
|
+
|
|
129
|
+
> **Mac:** Open Terminal and run:
|
|
130
|
+
> ```bash
|
|
131
|
+
> sudo mkdir -p "/Library/Application Support/ClaudeCode"
|
|
132
|
+
> sudo cp ~/Downloads/<login>-managed-settings.json "/Library/Application Support/ClaudeCode/managed-settings.json"
|
|
133
|
+
> ```
|
|
134
|
+
> Then restart Claude Desktop. You'll see "Example Dev Box" in Code → Environment.
|
|
135
|
+
>
|
|
136
|
+
> **Windows:** Save the file to `C:\Program Files\ClaudeCode\managed-settings.json` (create the folder if it doesn't exist; needs Administrator). Restart Claude Desktop.
|
|
137
|
+
|
|
138
|
+
> ⚠️ **The directory is `ClaudeCode`, not `Claude`.** Claude Code reads managed settings from `/Library/Application Support/ClaudeCode/` (macOS) and `%ProgramFiles%\ClaudeCode\` (Windows). A file under `…/Claude/` or `%ProgramData%\Claude\` is silently ignored and the box never appears in the Environment picker (the [#808](https://example.com/builders#/task/808) onboarding bug). The legacy `%ProgramData%\ClaudeCode\` Windows path was dropped in v2.1.75.
|
|
139
|
+
|
|
140
|
+
### Step 4 — Builder's SSH key
|
|
141
|
+
|
|
142
|
+
The `managed-settings.json` points to `~/.ssh/otb_builder` as the identity file. The builder needs this key pair generated on their machine, and you need to add the **public key** to the box.
|
|
143
|
+
|
|
144
|
+
**Builder runs (once):**
|
|
145
|
+
```bash
|
|
146
|
+
ssh-keygen -t ed25519 -f ~/.ssh/otb_builder -C "otb-builder"
|
|
147
|
+
cat ~/.ssh/otb_builder.pub
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
**Operator adds the public key to the box:**
|
|
151
|
+
```bash
|
|
152
|
+
ssh root@box-<login>.dev.example.com \
|
|
153
|
+
"echo '<public key here>' >> ~/.ssh/authorized_keys"
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
Or, if the builder's key is already registered as a DO SSH key, pass it via `BOX_SSH_KEY_IDS` at provision time so it's injected automatically.
|
|
157
|
+
|
|
158
|
+
### What `sshHostAllowlist` does
|
|
159
|
+
|
|
160
|
+
The `sshHostAllowlist: ["*.dev.example.com"]` entry prevents the builder from using Claude Desktop SSH to connect to arbitrary hosts. Their SSH picker only shows the pre-configured OTB box — they can't misconfigure or wander off it. This is a `managed-settings`-only field; user-level settings cannot override it.
|
|
161
|
+
|
|
162
|
+
---
|
|
163
|
+
|
|
164
|
+
## Part 2 - Browser on-ramp: terminal launchpad -> Remote Control (Chromebook / any browser) - ADR 0038 -> [ADR 0040](../adr/<redacted>.md), [#760](https://example.com/builders#/task/760) / [#824](https://example.com/builders#/task/824)
|
|
165
|
+
|
|
166
|
+
### How it works
|
|
167
|
+
|
|
168
|
+
The box serves its own web terminal. Two systemd units do the work:
|
|
169
|
+
|
|
170
|
+
- **`box-terminal.service`** runs `ttyd` (a web-terminal daemon) bound to `127.0.0.1:7681`, basic-auth protected with a per-box generated password, dropping whoever connects into a shell in `/workspace`. It listens on localhost only - the box opens **no inbound port**.
|
|
171
|
+
- **`cloudflared.service`** (via `infra/cloudflared-run.sh`) runs a Cloudflare Tunnel that makes that localhost terminal reachable from a browser. The tunnel is **outbound-only**: the box dials out to Cloudflare, so nothing has to be opened in the firewall.
|
|
172
|
+
|
|
173
|
+
The builder opens the tunnel URL in any browser, authenticates at the basic-auth prompt, and then runs a **normal interactive `claude`** in the terminal. The first `claude` run prompts a normal browser `/login`; because there is a real human at a real browser-backed terminal, `/login`, workspace-trust, and Trusted-Devices enrollment all succeed - the exact steps that headless `claude rc` could never perform.
|
|
174
|
+
|
|
175
|
+
**The terminal is a one-time launchpad, not the daily surface (ADR 0040).** Once the builder is signed in to that interactive `claude` session, they run the in-session **`/remote-control`** slash command. It prints a pairing link; opening that link drives the box from the Claude web/mobile app. From there the builder lives in the Claude app — the ttyd terminal is just where they logged in and kicked RC off.
|
|
176
|
+
|
|
177
|
+
> ⚠️ **Use the in-session `/remote-control` slash command — NOT the standalone `claude rc` shell command.** The standalone command mis-checks workspace trust ([anthropics/claude-code#35817](https://github.com/anthropics/claude-code/issues/35817)): it reports `Workspace not trusted` even when the directory is trusted, and a plain shell can't accept the trust dialog. Starting RC from *inside* an already-running, already-trusted `claude` session is the working path (verified live 2026-06-07). Closing the terminal window ends the remote session and closes the box — a property of the session lifecycle, stated up front so builders aren't surprised.
|
|
178
|
+
|
|
179
|
+
> **Workspace trust is pre-seeded ([#824](https://example.com/builders#/task/824)).** The cloud-init writes `/root/.claude.json` with `<redacted>` for `/root` + `/workspace`, so the first `claude` launch usually skips the trust dialog entirely. This is **non-load-bearing** (ADR 0040): the seed is best-effort and only written when the file is absent (upstream pre-seeding is flaky, [#9113](https://github.com/anthropics/claude-code/issues/9113)). If it doesn't take, the builder just accepts trust once — the flow works either way.
|
|
180
|
+
|
|
181
|
+
The cloud-init wires both units automatically (and `infra/box-source-fetch.sh` re-installs them on its post-clone bring-up, so a box provisioned before this shipped picks them up on its next refresh). It copies `infra/box-terminal.service` and `infra/cloudflared.service` to `/etc/systemd/system/`, enables them with `systemctl enable --now`, and wires `infra/box-report-terminal.sh` as a 1-minute cron that publishes the terminal URL + credential **up** to the GDS (`POST /box/terminal`) so the builder's self-serve flow can read them back.
|
|
182
|
+
|
|
183
|
+
**Tunnel modes:**
|
|
184
|
+
|
|
185
|
+
- **Quick tunnel (default, zero operator secrets).** `cloudflared` opens a tokenless quick tunnel and gets a random `https://<id>.trycloudflare.com` hostname. Nothing to configure - works out of the box.
|
|
186
|
+
- **Named tunnel (production upgrade, stable hostname).** When `/etc/otb/box.env` carries `BOX_TUNNEL_TOKEN` + `BOX_TERMINAL_HOSTNAME`, `cloudflared` runs a named tunnel instead and the terminal is reachable at a stable `term-<login>.example.com` host. As of [#771](https://example.com/builders#/task/771) the control plane **creates and bakes those automatically** on `box.js provision` (and tears them down on `deprovision`) — see the [named-tunnel automation](#<redacted>) section below.
|
|
187
|
+
|
|
188
|
+
### Getting onto the terminal (builder self-serve)
|
|
189
|
+
|
|
190
|
+
No SSH and no pairing script - two API calls:
|
|
191
|
+
|
|
192
|
+
```bash
|
|
193
|
+
# 1. Get a box (provisions or wakes it; see Part 0 / #701):
|
|
194
|
+
POST /api/gds/box/ensure
|
|
195
|
+
|
|
196
|
+
# 2. Poll until the box has reported its terminal up:
|
|
197
|
+
GET /api/gds/box/terminal
|
|
198
|
+
# -> {"url": "https://<id>.trycloudflare.com", "credential": "<redacted>"}
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
Then:
|
|
202
|
+
|
|
203
|
+
1. Open the returned `url` in a browser.
|
|
204
|
+
2. At the browser's basic-auth prompt, enter username **`otb`** and the password (the `credential` value).
|
|
205
|
+
3. In the terminal, type `claude` and press enter. On first run it walks the normal browser `/login` (workspace trust is pre-seeded, [#824](https://example.com/builders#/task/824), so the trust dialog usually doesn't appear; accept once if it does). You're now in an interactive Claude Code session in `/workspace`.
|
|
206
|
+
4. Inside that session, run the **`/remote-control`** slash command. It prints a pairing link.
|
|
207
|
+
5. Open that link to drive the box from the Claude web/mobile app. The terminal was the launchpad — work from the Claude app from here. (Closing the terminal window ends the remote session and closes the box.)
|
|
208
|
+
|
|
209
|
+
### Service lifecycle
|
|
210
|
+
|
|
211
|
+
| State | What happens |
|
|
212
|
+
|---|---|
|
|
213
|
+
| Box first boots | `box-terminal.service` (ttyd) + `cloudflared.service` start; `box-report-terminal` cron publishes the URL + credential to the GDS within ~1 min |
|
|
214
|
+
| Builder connects (first time) | Opens the tunnel URL, basic-auths as `otb`, runs `claude` (one-time browser `/login`), then `/remote-control` to get a pairing link and drive the box from the Claude app (ADR 0040) |
|
|
215
|
+
| Builder works (each session) | Lives in the Claude web/mobile app via the RC link; the ttyd terminal is only the launchpad they logged in through |
|
|
216
|
+
| Session ends | Builder closes the browser tab; the terminal + tunnel stay up for the next visit |
|
|
217
|
+
| Box is parked (idle-suspend) | Both units stop naturally when the droplet is destroyed; they restart clean on wake and the cron re-publishes a fresh URL |
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
### Checking the services
|
|
221
|
+
|
|
222
|
+
```bash
|
|
223
|
+
# On the box:
|
|
224
|
+
sudo systemctl status box-terminal # the ttyd web terminal
|
|
225
|
+
sudo systemctl status cloudflared # the outbound tunnel
|
|
226
|
+
journalctl -u box-terminal -f # live tail
|
|
227
|
+
journalctl -u cloudflared -f # tunnel logs - quick-tunnel URL appears here
|
|
228
|
+
journalctl -u cloudflared -n 100 # last 100 lines
|
|
229
|
+
sudo systemctl restart box-terminal cloudflared
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
The quick-tunnel hostname is printed in the `cloudflared` journal; the same value is published to the GDS by the `box-report-terminal` cron (verify with `crontab -l | grep box-report-terminal`).
|
|
233
|
+
|
|
234
|
+
### Named-tunnel automation (control plane, [#771](https://example.com/builders#/task/771))
|
|
235
|
+
|
|
236
|
+
The named-tunnel upgrade is automated on the control plane (`scripts/gds/box.js` + `src/bongos/cf-tunnel.js`). It is **OPT-IN, default OFF** — leave it off and every box uses the zero-secret quick tunnel.
|
|
237
|
+
|
|
238
|
+
**Turning it on (operator, in the control-plane `box.env` — the same file that holds `DO_API_TOKEN`, NEVER the web tier):**
|
|
239
|
+
|
|
240
|
+
```bash
|
|
241
|
+
BOX_NAMED_TUNNEL=1
|
|
242
|
+
CLOUDFLARE_API_TOKEN=<token with Account:Argo Tunnel:Edit + Zone:DNS:Edit>
|
|
243
|
+
CLOUDFLARE_ACCOUNT_ID=<the Cloudflare account id>
|
|
244
|
+
# optional (defaults shown):
|
|
245
|
+
CLOUDFLARE_ZONE=example.com # the DNS zone the record lives in
|
|
246
|
+
BOX_TERMINAL_ZONE=example.com # hostname suffix → term-<login>.example.com
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
This reuses the **same `CLOUDFLARE_API_TOKEN`** as `box-dns-cloudflare.sh` (the [#725](https://example.com/builders#/task/725) `BOX_DNS_HOOK`) — just add the Tunnel scope to it. In the Cloudflare token UI the permission is **Account → Argo Tunnel → Edit** (the older label for the Cloudflare Tunnel / `cfd_tunnel` API; "Cloudflare Tunnel" on newer accounts). Keep the existing **Zone → DNS → Edit**.
|
|
250
|
+
|
|
251
|
+
> ⚠️ **TLS coverage — why the host is at the apex, not `dev.*` ([#771](https://example.com/builders#/task/771)).** The terminal rides a **proxied** tunnel (Cloudflare terminates HTTPS at its edge), so the hostname needs an edge certificate. Free **Universal SSL covers `example.com` and `*.example.com` (one label) but NOT a two-label host** like `term-x.dev.example.com` — a `dev.*` host fails the TLS handshake at the edge (verified live). So `BOX_TERMINAL_ZONE` defaults to the **apex** (`term-<login>.example.com`, one label, covered for free). Only override it to `dev.example.com` if you've bought an **Advanced Certificate** for `*.dev.example.com`. (The SSH A-record path stays on `dev.*` — it's `proxied=false`/direct-IP and needs no edge cert.)
|
|
252
|
+
|
|
253
|
+
**What the lifecycle does when it's on:**
|
|
254
|
+
|
|
255
|
+
| Op | Tunnel action |
|
|
256
|
+
|---|---|
|
|
257
|
+
| `provision` | create-or-reuse a per-builder named tunnel (`otb-term-<login>`), point its ingress at `127.0.0.1:7681`, route a proxied CNAME `term-<login>.example.com → <tunnel-id>.cfargotunnel.com`, fetch the connector token, and **bake** `BOX_TUNNEL_TOKEN` + `BOX_TERMINAL_HOSTNAME` into the box's `/etc/otb/box.env` via cloud-init. The tunnel is created **before** the droplet so its token is present at first boot. |
|
|
258
|
+
| `park` → `wake` | nothing — the tunnel + DNS are IP-independent and durable, and `box.env` rides the snapshot, so the stable hostname survives park/wake with no control-plane call. |
|
|
259
|
+
| `deprovision` (incl. `sweep-dormant` / `reconcile` clawback) | best-effort teardown: delete the CNAME and the tunnel (the droplet is already gone, so connections have dropped). Never blocks the deprovision. |
|
|
260
|
+
|
|
261
|
+
`box.js provision` (dry-run, the default) prints the tunnel plan without touching Cloudflare; pass `--apply` to create it for real. If `BOX_NAMED_TUNNEL=1` but the token/account id are missing, provision **fails loudly** rather than silently falling back.
|
|
262
|
+
|
|
263
|
+
**Per-box manual override (no automation):** you can still set the two values by hand on a single box and restart — `cloudflared-run.sh` detects the token and switches modes:
|
|
264
|
+
|
|
265
|
+
```bash
|
|
266
|
+
# In /etc/otb/box.env on the box:
|
|
267
|
+
BOX_TUNNEL_TOKEN=<redacted> named-tunnel token>
|
|
268
|
+
BOX_TERMINAL_HOSTNAME=term-<login>.example.com
|
|
269
|
+
sudo systemctl restart cloudflared
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
### Verifying the browser on-ramp end-to-end ([#771](https://example.com/builders#/task/771))
|
|
273
|
+
|
|
274
|
+
The build is unit- and smoke-covered, but the loop crosses real hardware + a human at a browser. Run this once per change to `ttyd`/`cloudflared`/the terminal routes. **Operator deps:** a box provision (small DO cost) and, for the named-tunnel leg, the CF token with Tunnel scope.
|
|
275
|
+
|
|
276
|
+
**Leg A — quick tunnel (zero secrets).**
|
|
277
|
+
|
|
278
|
+
1. Provision a box for a test builder: `node scripts/gds/box.js onboard <login> --apply` (builder must have a registered SSH key, or `--allow-no-keys`). Confirm cloud-init finished: SSH in and `cloud-init status --wait` → `done`; `systemctl is-active box-terminal cloudflared` → `active`.
|
|
279
|
+
2. Confirm the box published its terminal: `tail /tmp/box-report-terminal.log` should show `terminal access reported`. Then as the builder: `GET /api/gds/box/terminal` (or `/builder-box`) → returns `{ url: https://<id>.trycloudflare.com, credential }`.
|
|
280
|
+
3. Open the `url` in a browser. Basic-auth as **`otb`** + the `credential`. You should land in a `/workspace` shell.
|
|
281
|
+
4. In that terminal run `claude`. Complete the browser `/login` (trust is pre-seeded, [#824](https://example.com/builders#/task/824) — accept once if still prompted) and Trusted-Devices enrollment. Confirm an interactive session starts. **This is the leg `claude rc` could never do** ([#745](https://example.com/builders#/task/745)).
|
|
282
|
+
5. Inside that session, run **`/remote-control`** (the slash command, NOT `claude rc`). Confirm it prints a pairing link; open the link and confirm the Claude web/mobile app drives the box (ADR 0040, verified live 2026-06-07).
|
|
283
|
+
6. Full Chromebook loop: from a browser-only device, `POST /box/ensure` → poll `GET /box/terminal` → open → `claude` → `/remote-control` → drive from the Claude app. No SSH, no Desktop.
|
|
284
|
+
|
|
285
|
+
**Leg B — named tunnel (stable hostname).**
|
|
286
|
+
|
|
287
|
+
1. With the CF token (Tunnel + DNS scope) in the control-plane `box.env`, set `BOX_NAMED_TUNNEL=1` + `CLOUDFLARE_ACCOUNT_ID`. Dry-run first: `node scripts/gds/box.js provision <login>` — confirm it prints the `term-<login>.example.com` plan and the CF calls without touching anything.
|
|
288
|
+
2. `node scripts/gds/box.js provision <login> --apply`. Confirm in the Cloudflare dashboard: a tunnel `otb-term-<login>` exists with one healthy connector, and a proxied CNAME `term-<login>.example.com → <id>.cfargotunnel.com`.
|
|
289
|
+
3. `GET /box/terminal` returns the stable `https://term-<login>.example.com`. Open it, basic-auth, `claude` — same as Leg A but on the stable host.
|
|
290
|
+
4. Teardown: `node scripts/gds/box.js deprovision <login> --apply`. Confirm the tunnel **and** the CNAME are gone from the Cloudflare dashboard.
|
|
291
|
+
|
|
292
|
+
### Gotchas surfaced by hardware verification ([#771](https://example.com/builders#/task/771))
|
|
293
|
+
|
|
294
|
+
- **Packaged ttyd collision (fixed).** The Ubuntu `ttyd` apt package ships its OWN auto-started `ttyd.service` that binds `127.0.0.1:7681` with **no `--credential`** and runs `login`. It both blocks our `box-terminal.service` (the unit crash-loops on `EADDRINUSE`) and — because the tunnel fronts whatever sits on 7681 — would expose an **unauthenticated** terminal. Cloud-init and the `box-source-fetch` post-clone bring-up now `systemctl mask ttyd` so only our basic-auth ttyd binds the port. On a box provisioned before this fix: `sudo systemctl disable --now ttyd && sudo systemctl mask ttyd && sudo systemctl restart box-terminal`.
|
|
295
|
+
- **IPv4-only Cloudflare calls (fixed).** The CF API token is IP-locked to the droplet's IPv4 (least privilege). The droplet also has IPv6 egress; `cf-tunnel.js` pins IPv4 (undici `Agent{connect:{family:4}}`) or every call 401s — mirroring `box-dns-cloudflare.sh`'s `curl -4`.
|
|
296
|
+
- **⚠ Browser-only bootstrap gap (OPEN — follow-up).** The `ttyd`/`cloudflared` systemd units live in the repo (`/workspace/infra/*.service`), so they only start **after** `box-source-fetch` clones — which needs the **builder's GDS token on the box**. A pure Chromebook builder has no SSH and no terminal yet, so there is **no first-boot path to place that token** → the terminal never comes up for them. Today it only works if the token is seeded another way (SSH, or an operator). The fix (a follow-up) is to bring ttyd up at **first boot independent of the clone** — e.g. cloud-init writes the unit inline rather than copying it from the repo. Until then, the browser on-ramp is verified to work *given a token on the box*, not from a truly bare browser-only device.
|
|
297
|
+
|
|
298
|
+
---
|
|
299
|
+
|
|
300
|
+
## Part 3 — The box-heartbeat cron
|
|
301
|
+
|
|
302
|
+
The cloud-init also wires `infra/box-heartbeat.sh` as a 5-minute cron job. This pings `/api/gds/box/heartbeat` while the box is in active use (interactive SSH session or CPU load > 0.2). Silence = the idle-suspend sweep ([#598](https://example.com/builders#/task/598)) is free to park the box.
|
|
303
|
+
|
|
304
|
+
Without this cron the idle sweep measures **uptime** instead of **activity** and may park a box someone is actively using. Always verify it's wired:
|
|
305
|
+
|
|
306
|
+
```bash
|
|
307
|
+
crontab -l | grep box-heartbeat
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
If missing (pre-[#599](https://example.com/builders#/task/599) box):
|
|
311
|
+
```bash
|
|
312
|
+
(crontab -l 2>/dev/null; echo "*/5 * * * * /workspace/infra/box-heartbeat.sh >> /tmp/box-heartbeat.log 2>&1") | crontab -
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
---
|
|
316
|
+
|
|
317
|
+
## Part 4 — Rank-gated source access ([#600](https://example.com/builders#/task/600), ADR 0031 §6 / §9.2)
|
|
318
|
+
|
|
319
|
+
The box holds **no baked credential**. Instead it fetches a rank-scoped clone spec
|
|
320
|
+
from the GDS at boot and on a 10-minute cron, via `infra/box-source-fetch.sh`
|
|
321
|
+
(carried onto the box as base64 inside the cloud-init `write_files`). The flow:
|
|
322
|
+
|
|
323
|
+
1. The box reads the builder's GDS session token (it appears after they authenticate
|
|
324
|
+
Claude Code / run `/builder-reauth` — so the **first clone lands after the builder
|
|
325
|
+
connects**, not at boot).
|
|
326
|
+
2. It calls `GET /api/gds/box/source-access`. The GDS checks the builder's **live**
|
|
327
|
+
rank + status (ADR 0016 — unforgeable from the box):
|
|
328
|
+
- **403 `SOURCE_ACCESS_REVOKED`** — builder demoted below the floor or deactivated.
|
|
329
|
+
The script cuts the credentialed remote (box-side clawback); `box.js reconcile`
|
|
330
|
+
deprovisions the droplet.
|
|
331
|
+
- **200 `configured:false`** — the server's source credential isn't set yet (see
|
|
332
|
+
env below); the box logs guidance and no-ops.
|
|
333
|
+
- **200 `configured:true`** — returns the scope-appropriate spec + a short-lived
|
|
334
|
+
credential. The box clones/refreshes `/workspace`, **never persisting the token
|
|
335
|
+
in `.git/config`** (clones via a one-shot authenticated URL, then resets the
|
|
336
|
+
remote to the token-less URL).
|
|
337
|
+
3. **Scope by rank** (`src/bongos/box-access.js`): a **Xenos → `starter`** sparse-checkout
|
|
338
|
+
of the safe surface (`.devcontainer`, `art`, `docs`, `modules/status-ui`,
|
|
339
|
+
`generative-ideas.md` — the §9.2 "first tasks on a scoped surface"); a **Metic+ →
|
|
340
|
+
`full`** clone. Graduation (`box.js reconcile`) rescopes a starter box to full; the
|
|
341
|
+
box re-clones on its next refresh.
|
|
342
|
+
|
|
343
|
+
**Operator: configure the source credential** (in the **prod env**, never the repo —
|
|
344
|
+
ADR 0022). The default provider (`src/bongos/box-credential.js`) is env-backed:
|
|
345
|
+
|
|
346
|
+
```bash
|
|
347
|
+
# In the prod environment (e.g. the droplet's systemd unit / .env):
|
|
348
|
+
BOX_SOURCE_REPO_URL=https://github.com/<org>/example.git
|
|
349
|
+
BOX_SOURCE_FULL_TOKEN=<redacted> PAT or installation token, contents:read>
|
|
350
|
+
# Optional — make 'starter' a HARD boundary (a physically separate repo that cannot
|
|
351
|
+
# read the crown jewels) instead of the default sparse-checkout defense-in-depth:
|
|
352
|
+
BOX_SOURCE_STARTER_REPO_URL=https://github.com/<org>/example-starter.git
|
|
353
|
+
BOX_SOURCE_STARTER_TOKEN=<token scoped to the starter repo>
|
|
354
|
+
# Optional — advisory token TTL (how often the box re-fetches + re-checks live rank):
|
|
355
|
+
BOX_SOURCE_TOKEN_TTL_SECONDS=3600
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
> **Honest limit (ADR 0031 §6).** Git cannot sub-path-scope a single private repo and
|
|
359
|
+
> sparse-checkout is client-side, so the default `starter` surface is
|
|
360
|
+
> **defense-in-depth + audit + revocation, not insider-proof DRM**. Set
|
|
361
|
+
> `BOX_SOURCE_STARTER_REPO_URL` for a hard boundary. Every grant/denial is audited to
|
|
362
|
+
> `box_events` (`source_grant` / `source_deny` / `source_revoke` / `source_rescope`).
|
|
363
|
+
|
|
364
|
+
**The clawback (clean offboarding).** Revoking rank or deactivating a builder cuts
|
|
365
|
+
source on two timelines: the **credential layer is instant** (their next
|
|
366
|
+
`/box/source-access` fetch 403s), and the **droplet teardown** follows when the
|
|
367
|
+
control plane runs reconcile:
|
|
368
|
+
|
|
369
|
+
```bash
|
|
370
|
+
node scripts/gds/box.js reconcile # dry-run: show what would change
|
|
371
|
+
node scripts/gds/box.js reconcile --apply # deprovision below-floor/inactive boxes,
|
|
372
|
+
# rescope graduated ones (run on a schedule)
|
|
373
|
+
```
|
|
374
|
+
|
|
375
|
+
Verify the source-fetch cron + check progress on the box:
|
|
376
|
+
|
|
377
|
+
```bash
|
|
378
|
+
crontab -l | grep box-source-fetch
|
|
379
|
+
tail /tmp/box-source-fetch.log
|
|
380
|
+
WORKSPACE=/workspace /usr/local/bin/box-source-fetch.sh # manual run
|
|
381
|
+
```
|
|
382
|
+
|
|
383
|
+
---
|
|
384
|
+
|
|
385
|
+
## Quick reference: full operator flow
|
|
386
|
+
|
|
387
|
+
```bash
|
|
388
|
+
# 0. One-time: set BOX_SOURCE_REPO_URL + BOX_SOURCE_FULL_TOKEN in the prod env (Part 4)
|
|
389
|
+
|
|
390
|
+
# 1. Provision the box
|
|
391
|
+
node scripts/gds/box.js provision alice --apply
|
|
392
|
+
|
|
393
|
+
# 2. Add the builder's SSH public key to the box
|
|
394
|
+
ssh root@box-alice.dev.example.com "echo '...' >> ~/.ssh/authorized_keys"
|
|
395
|
+
|
|
396
|
+
# 3. Generate and send managed-settings.json (Desktop / SSH path)
|
|
397
|
+
bash infra/gen-managed-settings.sh alice > /tmp/alice-managed-settings.json
|
|
398
|
+
# → email/DM to builder with placement instructions
|
|
399
|
+
|
|
400
|
+
# 4. For thin-device (Chromebook) path: the web terminal is ready automatically
|
|
401
|
+
# (box-terminal.service + cloudflared.service start at boot; box-report-terminal
|
|
402
|
+
# publishes the URL + credential to the GDS within ~1 min)
|
|
403
|
+
# Builder self-serves: POST /api/gds/box/ensure, then poll GET /api/gds/box/terminal
|
|
404
|
+
# -> open the url, basic-auth as user 'otb' with the returned credential, run: claude
|
|
405
|
+
# -> then /remote-control to drive the box from the Claude app (ADR 0040)
|
|
406
|
+
|
|
407
|
+
# 5. When the builder is done for the day, the idle sweep parks the box:
|
|
408
|
+
node scripts/gds/box.js sweep-idle --apply # control plane / prod cron
|
|
409
|
+
|
|
410
|
+
# 6. Source materializes automatically once the builder authenticates on the box
|
|
411
|
+
# (the box-source-fetch cron clones the rank-scoped surface). Nothing to do here.
|
|
412
|
+
|
|
413
|
+
# 7. Offboarding / demotion: the credential is cut on the builder's next fetch;
|
|
414
|
+
# reconcile then deprovisions the box (run on a schedule alongside the sweeps):
|
|
415
|
+
node scripts/gds/box.js reconcile --apply
|
|
416
|
+
```
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# Recipe — Builder memory backup + restore drill
|
|
2
|
+
|
|
3
|
+
> **Filed by:** task [#281](https://example.com/builders#/task/281) (V3.R62), GDS-V3 criterion C8 ("memory is local-first, server-canonical on ship"). Depends on the server-side memory store ([#273](https://example.com/builders#/task/273) R54) and the DB backup pipeline ([#412](https://example.com/builders#/task/412) local nightly + [#415](https://example.com/builders#/task/415) weekly offsite). Companion to [`gds-db-backup.md`](gds-db-backup.md); vendor choice in [ADR 0025](../adr/<redacted>.md).
|
|
4
|
+
|
|
5
|
+
## Key decision — memory is backed up *as part of the full-DB backup*, not separately
|
|
6
|
+
|
|
7
|
+
C8 makes each builder's local memory dir **server-canonical on ship**: `/builder-ship` upserts the builder's memory files into the `builder_memory` Postgres table ([#274](https://example.com/builders#/task/274) R55). Because that table lives in the same `example` database as everything else, **it is already captured by the full-DB backup pipeline** — there is deliberately **no separate `backup-memory.sh`**. A memory-only dump would duplicate what `pg_dump` already writes nightly and ship offsite weekly, and would be a second thing to keep in sync. One backup story, not two (same principle as [ADR 0025](../adr/<redacted>.md)'s retirement of the redundant [#7](https://example.com/builders#/task/7) cron).
|
|
8
|
+
|
|
9
|
+
So builder memory inherits both layers:
|
|
10
|
+
|
|
11
|
+
| Layer | Mechanism | Cadence | Retention |
|
|
12
|
+
|---|---|---|---|
|
|
13
|
+
| Local | `gds-db-backup.timer` → `pg_dump` → `/var/backups/gds/` ([#412](https://example.com/builders#/task/412)) | daily 04:00 UTC | 30 days |
|
|
14
|
+
| Offsite | `gds-db-offsite.timer` → latest dump → DO Space `gds-offsite/` ([#415](https://example.com/builders#/task/415)) | weekly Sun 05:00 UTC | 4 weeks |
|
|
15
|
+
|
|
16
|
+
The `builder_memory` table is in every dump. No extra moving parts.
|
|
17
|
+
|
|
18
|
+
## The `builder_memory` table
|
|
19
|
+
|
|
20
|
+
```
|
|
21
|
+
builder_memory(builder_id, path, content, mtime, sha256, byte_len, created_at, updated_at)
|
|
22
|
+
PK (builder_id, path)
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Each row is one memory file for one builder. The `sha256` + `byte_len` columns are the integrity fingerprint — which is what lets the restore drill below verify a restore **without ever reading the private `content`** (builder memory is per-builder private + isolated; see [#276](https://example.com/builders#/task/276) R57).
|
|
26
|
+
|
|
27
|
+
## Restore drill — verify a builder's memory survives a restore
|
|
28
|
+
|
|
29
|
+
Cadence: **quarterly** (alongside the general restore drill in [`gds-db-backup.md`](gds-db-backup.md)). The drill restores the latest dump into a throwaway sandbox DB and confirms a chosen builder's memory rows are byte-identical to production — by fingerprint, so no private content is printed.
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
# 1. Restore the latest local dump into a sandbox DB.
|
|
33
|
+
DUMP=$(ls -1t /var/backups/gds/example-*.sql.gz | head -1)
|
|
34
|
+
sudo -u postgres dropdb --if-exists example_mem_restore_test
|
|
35
|
+
sudo -u postgres createdb example_mem_restore_test
|
|
36
|
+
set -o pipefail
|
|
37
|
+
gunzip -c "$DUMP" | sudo -u postgres psql -q -d example_mem_restore_test
|
|
38
|
+
|
|
39
|
+
# 2. Pick a builder with memory (builder_id=1 here) and compare a
|
|
40
|
+
# privacy-preserving fingerprint: count + md5 over (path, sha256, byte_len).
|
|
41
|
+
# Never selects `content`.
|
|
42
|
+
FP="SELECT builder_id, count(*),
|
|
43
|
+
md5(string_agg(path||':'||sha256||':'||byte_len, ',' ORDER BY path))
|
|
44
|
+
FROM builder_memory WHERE builder_id=1 GROUP BY builder_id;"
|
|
45
|
+
psql -d example -tAc "$FP" # production
|
|
46
|
+
sudo -u postgres psql -d example_mem_restore_test -tAc "$FP" # restored
|
|
47
|
+
|
|
48
|
+
# 3. The two lines must be identical. Then clean up.
|
|
49
|
+
sudo -u postgres dropdb example_mem_restore_test
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### Last run
|
|
53
|
+
|
|
54
|
+
- **2026-05-30** (task [#281](https://example.com/builders#/task/281), claim `#349`): builder 1, **3 files**, prod and restored fingerprints **MATCH** (`…<redacted>`). Restore completed with zero errors.
|
|
55
|
+
|
|
56
|
+
## Caveat the drill caught — point-in-time vs. the data
|
|
57
|
+
|
|
58
|
+
The first drill run **failed correctly**: the most recent nightly dump (04:02 UTC) predated migration 054 and the memory rows (which synced at 08:47 UTC that day), so the table wasn't in it. Two lessons baked in here:
|
|
59
|
+
|
|
60
|
+
1. **A dump only contains what existed when it ran.** Memory synced since the last 04:00 UTC nightly is not in any backup until the next nightly; and not *offsite* until the next weekly run. Worst-case memory-loss window on whole-droplet loss is ~1 week (the offsite cadence). Tighten the offsite timer to daily if that ever matters.
|
|
61
|
+
2. **After a migration that adds/relocates memory data, take a one-shot dump** rather than waiting for the nightly, if you want it backed up immediately:
|
|
62
|
+
```bash
|
|
63
|
+
sudo systemctl start gds-db-backup.service # fresh local dump now
|
|
64
|
+
sudo systemctl start gds-db-offsite.service # push it offsite now
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## See also
|
|
68
|
+
|
|
69
|
+
- [`gds-db-backup.md`](gds-db-backup.md) — the full backup pipeline, install steps, and the general (non-memory-specific) restore drills.
|
|
70
|
+
- [ADR 0025](../adr/<redacted>.md) — offsite vendor decision.
|
|
71
|
+
- [ADR 0024](../adr/<redacted>.md) — the local-first / server-canonical memory model this backs up.
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
# Recipe — Memory sync conflicts: policy + clobber recovery
|
|
2
|
+
|
|
3
|
+
> **Filed by:** task [#277](https://example.com/builders#/task/277) (V3.R58), GDS-V3 criterion C8. Documents the conflict policy chosen by the R04 spike ([ADR 0024](../adr/<redacted>.md) §3) and implemented across [#274](https://example.com/builders#/task/274) (push), [#275](https://example.com/builders#/task/275) (pull), [#276](https://example.com/builders#/task/276) (isolation). Recovery leans on the backup pipeline from [#281](https://example.com/builders#/task/281) ([`memory-backup.md`](memory-backup.md)).
|
|
4
|
+
|
|
5
|
+
## The policy in one line
|
|
6
|
+
|
|
7
|
+
**Last-write-wins, per file, server clock authoritative.** No merge, no rejection, no warning.
|
|
8
|
+
|
|
9
|
+
## How sync works (so the policy makes sense)
|
|
10
|
+
|
|
11
|
+
Memory is **local-first, server-canonical on ship**. There are exactly two sync moments — no background polling, no mid-session sync:
|
|
12
|
+
|
|
13
|
+
| Trigger | Direction | What happens |
|
|
14
|
+
|---|---|---|
|
|
15
|
+
| `/builder-ship` succeeds ([#274](https://example.com/builders#/task/274) R55) | **push** local → server | `POST /api/gds/memory/sync` upserts each local memory file into `builder_memory`. Every file is written unconditionally — the **last push to land wins**. |
|
|
16
|
+
| `SessionStart` / `/builder-start` ([#275](https://example.com/builders#/task/275) R56) | **pull** server → local | client reports its newest local `mtime`; `GET /api/gds/memory/since?mtime=…` returns files the server holds that are **newer**; client writes them over local copies. |
|
|
17
|
+
|
|
18
|
+
The stored `mtime` is the client's reported file mtime and is **advisory** — used only by the pull side's newer-than check. The server's own `updated_at` (trigger-maintained) is the authority for "when did this row actually change." A client cannot win a conflict by forging a future mtime: an unparseable mtime is normalized to `NULL`, and the server clock governs.
|
|
19
|
+
|
|
20
|
+
`builder_id` is the GitHub user, not a session — so "two different builders racing on the same file" is impossible by construction. Every conflict below is **one builder, more than one machine or session**.
|
|
21
|
+
|
|
22
|
+
## Edge cases
|
|
23
|
+
|
|
24
|
+
### 1. Same file edited in two parallel sessions (two machines / two worktrees)
|
|
25
|
+
|
|
26
|
+
Builder edits `MEMORY.md` on the desktop and, in a parallel session, on the laptop. Both ship.
|
|
27
|
+
|
|
28
|
+
- Both sessions push. **Whichever push lands at the server second overwrites the first** — the server now holds exactly one version (the later push). No merge is attempted; the earlier push's content is gone from the server.
|
|
29
|
+
- The "losing" machine still has its own local copy until its **next** `SessionStart` pull, which then overwrites it with the server's surviving version. After that pull, both machines agree.
|
|
30
|
+
- **Net:** one version survives end-to-end; the other is lost. Lost-write window ≈ one session boundary. If the lost version mattered, recover it from backup (below).
|
|
31
|
+
|
|
32
|
+
> Practical avoidance: don't run two live sessions editing the same memory file. Memory edits are cheap to redo; the system optimizes for "never half-merge a plausible-but-broken file" over "never lose a keystroke."
|
|
33
|
+
|
|
34
|
+
### 2. Server has a newer version than local
|
|
35
|
+
|
|
36
|
+
Builder shipped from the laptop yesterday (server now newer), then opens the desktop today.
|
|
37
|
+
|
|
38
|
+
- `SessionStart` pull sees the server's `mtime` is newer than the desktop's local copy and **writes the server version over the local file**. Desktop is now current. This is the intended path — it's how memory "follows you between machines."
|
|
39
|
+
- Risk only arises if the desktop had **unpushed local edits older** (by mtime) than the server's version — those would be overwritten on pull. In normal use local edits are the newest thing, so this is rare; when it bites, recover from backup.
|
|
40
|
+
|
|
41
|
+
### 3. Local newer than server (the common, non-conflict case)
|
|
42
|
+
|
|
43
|
+
Local edits since the last ship are newer; on next `/builder-ship` they push and become canonical. No conflict. This is the steady state.
|
|
44
|
+
|
|
45
|
+
## Operator recovery — restore a clobbered file from backup
|
|
46
|
+
|
|
47
|
+
When a builder confirms a real clobber, the lost version is recoverable from any `pg_dump` taken **before** the overwrite (nightly local, 30-day retention; weekly offsite, 4-week — see [`memory-backup.md`](memory-backup.md) / [`gds-db-backup.md`](gds-db-backup.md)). The dump must predate the bad push.
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
# 1. Restore a pre-clobber dump into a sandbox (NON-destructive to prod).
|
|
51
|
+
DUMP=/var/backups/gds/example-<ts-before-the-clobber>.sql.gz
|
|
52
|
+
sudo -u postgres dropdb --if-exists example_recover
|
|
53
|
+
sudo -u postgres createdb example_recover
|
|
54
|
+
gunzip -c "$DUMP" | sudo -u postgres psql -q -d example_recover
|
|
55
|
+
|
|
56
|
+
# 2. Extract the old content to a FILE (never paste content inline — see the
|
|
57
|
+
# warning below). -tA = unaligned, no headers; one file's content out.
|
|
58
|
+
# (Archon-run, on the droplet; this is the one place reading content is OK —
|
|
59
|
+
# it's a deliberate recovery, logged by this recipe's existence.)
|
|
60
|
+
sudo -u postgres psql -d example_recover -tAc \
|
|
61
|
+
"SELECT content FROM builder_memory WHERE builder_id=<id> AND path='<path>';" \
|
|
62
|
+
> /tmp/recovered.txt
|
|
63
|
+
|
|
64
|
+
# 3. Hand the file back to the builder. Two ways:
|
|
65
|
+
# (a) PREFERRED — give them /tmp/recovered.txt; they drop it into their local
|
|
66
|
+
# memory dir and /builder-ship to re-push it. Keeps the builder as the
|
|
67
|
+
# author of their own memory, and the push path recomputes sha256/byte_len.
|
|
68
|
+
# (b) Archon writes it straight into prod IF the builder can't. Load the
|
|
69
|
+
# content from the FILE via a bound parameter — do NOT inline it into the
|
|
70
|
+
# SQL (a naive dollar-quote breaks if the content itself contains the
|
|
71
|
+
# quote tag, and string-pasting risks injection during a high-stress run):
|
|
72
|
+
sudo -u postgres psql -d example \
|
|
73
|
+
-v content="$(cat /tmp/recovered.txt)" \
|
|
74
|
+
-c "INSERT INTO builder_memory (builder_id, path, content, sha256, byte_len)
|
|
75
|
+
VALUES (<id>, '<path>', :'content', encode(sha256(:'content'::bytea),'hex'),
|
|
76
|
+
octet_length(:'content'))
|
|
77
|
+
ON CONFLICT (builder_id, path) DO UPDATE
|
|
78
|
+
SET content=EXCLUDED.content, sha256=EXCLUDED.sha256, byte_len=EXCLUDED.byte_len;"
|
|
79
|
+
# (`:'content'` is psql variable interpolation with proper literal quoting —
|
|
80
|
+
# safe for arbitrary content, and sha256/byte_len are recomputed in-SQL so
|
|
81
|
+
# they can't drift from the restored bytes.)
|
|
82
|
+
|
|
83
|
+
# 4. Clean up the sandbox and the temp file.
|
|
84
|
+
sudo -u postgres dropdb example_recover && rm -f /tmp/recovered.txt
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
> **Privacy note:** another builder's memory is private ([#276](https://example.com/builders#/task/276) R57 isolation; Archon forensic reads are audit-logged). Recovery is the builder's own file, or an explicitly-authorized Archon action — never a casual cross-builder read. Prefer path (a) so the builder re-pushes their own content.
|
|
88
|
+
|
|
89
|
+
## Why not something smarter? (rejected alternatives, from ADR 0024 §3)
|
|
90
|
+
|
|
91
|
+
- **Three-way markdown merge** — rejected: a half-merged `MEMORY.md` that *looks* plausible but is structurally broken is worse than a clean lose-one. Revisit in V4 only if real collisions show up in V3.
|
|
92
|
+
- **Optimistic concurrency** (reject stale-base pushes, force pull-first) — rejected: builders would just `--force` around it. Last-write-wins + a backup recovery path is simpler and handles the rare bad case.
|
|
93
|
+
|
|
94
|
+
## See also
|
|
95
|
+
|
|
96
|
+
- [ADR 0024](../adr/<redacted>.md) §3 — the decision + rejected alternatives.
|
|
97
|
+
- [`memory-backup.md`](memory-backup.md) — backup cadence + the restore drill the recovery above relies on.
|
|
98
|
+
- [`gds-db-backup.md`](gds-db-backup.md) — the underlying dump/offsite pipeline.
|