@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,755 @@
|
|
|
1
|
+
const express = require('express');
|
|
2
|
+
const crypto = require('node:crypto');
|
|
3
|
+
|
|
4
|
+
const auth = require('../auth');
|
|
5
|
+
const db = require('../db');
|
|
6
|
+
const { pairStore } = require('../app-pair');
|
|
7
|
+
const { publicOrigin, parseCookie, validateOrRespond } = require('./_helpers');
|
|
8
|
+
const { branding } = require('../../branding');
|
|
9
|
+
const seams = require('../../module-seams');
|
|
10
|
+
|
|
11
|
+
function escapeHtml(s) {
|
|
12
|
+
return String(s ?? '').replace(/[&<>"']/g, (c) => ({
|
|
13
|
+
'&': '&', '<': '<', '>': '>', '"': '"', "'": ''',
|
|
14
|
+
}[c]));
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// Render a small standalone HTML page in the builders' hall palette. Used for
|
|
18
|
+
// OAuth error branches so the visitor sees something coherent rather than a
|
|
19
|
+
// text/plain trace. Kept inline (no template engine) so it has no runtime deps.
|
|
20
|
+
// opts (all optional):
|
|
21
|
+
// subtitle — replaces the default header sub-line
|
|
22
|
+
// cta — { href, label } for the primary button (default: retry sign-in)
|
|
23
|
+
// note — replaces the default troubleshooting footnote
|
|
24
|
+
function renderAuthErrorPage(heading, detail, opts = {}) {
|
|
25
|
+
// Every fixed string on this page — the tab-title suffix, epigraph, page
|
|
26
|
+
// heading, sub-line, and retry button — is owned by the instance branding
|
|
27
|
+
// pack (config/branding.json `copy.authError`) so a non-OTB instance renders
|
|
28
|
+
// its OWN words, never OTB lore, on cloudbongos.com and beyond (ADR 0062 §3;
|
|
29
|
+
// task 1439 / idea #351). It is resolved defensively — a branding hiccup
|
|
30
|
+
// falls back to neutral copy rather than crashing the error page, mirroring
|
|
31
|
+
// the worldName pattern below. `authError` is server-only (rendered here), so
|
|
32
|
+
// it is deliberately NOT part of the clientBranding allow-list. Callers may
|
|
33
|
+
// still override the sub-line / cta / note per error branch via `opts`.
|
|
34
|
+
const AUTH_ERROR_FALLBACK = {
|
|
35
|
+
titleSuffix: 'sign-in failed',
|
|
36
|
+
epigraph: '— access —',
|
|
37
|
+
heading: 'Sign-in failed',
|
|
38
|
+
subtitle: "The sign-in didn't complete. Try again, or contact the owner.",
|
|
39
|
+
ctaLabel: 'Try signing in again',
|
|
40
|
+
};
|
|
41
|
+
const copy = (() => {
|
|
42
|
+
try { return { ...AUTH_ERROR_FALLBACK, ...(branding().copy?.authError || {}) }; }
|
|
43
|
+
catch { return AUTH_ERROR_FALLBACK; }
|
|
44
|
+
})();
|
|
45
|
+
const subtitle = opts.subtitle || copy.subtitle;
|
|
46
|
+
const cta = opts.cta || { href: '/api/gds/auth/web/start', label: copy.ctaLabel };
|
|
47
|
+
const note = opts.note || "If this keeps happening, the GitHub OAuth app's authorized callback URL may be misconfigured. See docs/recipes/ops-gotchas.md (GitHub OAuth section).";
|
|
48
|
+
// The instance name in the tab title comes from the branding pack, never a
|
|
49
|
+
// hardcoded host brand — a non-OTB instance sees its own name (ADR 0062 §3).
|
|
50
|
+
const worldName = (() => { try { return branding().identity?.worldName || 'The city'; } catch { return 'The city'; } })();
|
|
51
|
+
return `<!doctype html>
|
|
52
|
+
<html lang="en"><head>
|
|
53
|
+
<meta charset="utf-8">
|
|
54
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
55
|
+
<title>${escapeHtml(worldName)} — ${escapeHtml(copy.titleSuffix)}</title>
|
|
56
|
+
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
57
|
+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
58
|
+
<link href="https://fonts.googleapis.com/css2?family=Cinzel:wght@500;700&family=EB+Garamond:ital,wght@0,400;0,500&display=swap" rel="stylesheet">
|
|
59
|
+
<link rel="stylesheet" href="/builders/style.css">
|
|
60
|
+
</head><body>
|
|
61
|
+
<header class="hall">
|
|
62
|
+
<p class="hall__epigraph">${escapeHtml(copy.epigraph)}</p>
|
|
63
|
+
<h1>${escapeHtml(copy.heading)}</h1>
|
|
64
|
+
<p class="hall__sub">${escapeHtml(subtitle)}</p>
|
|
65
|
+
</header>
|
|
66
|
+
<main>
|
|
67
|
+
<section class="scroll">
|
|
68
|
+
<div class="landing">
|
|
69
|
+
<div class="landing__error">
|
|
70
|
+
<p class="landing__error-h">${escapeHtml(heading)}</p>
|
|
71
|
+
<p>${escapeHtml(detail)}</p>
|
|
72
|
+
</div>
|
|
73
|
+
<p class="landing__cta">
|
|
74
|
+
<a class="landing__btn" href="${escapeHtml(cta.href)}">${escapeHtml(cta.label)}</a>
|
|
75
|
+
</p>
|
|
76
|
+
<p class="landing__note">${escapeHtml(note)}</p>
|
|
77
|
+
</div>
|
|
78
|
+
</section>
|
|
79
|
+
</main>
|
|
80
|
+
</body></html>`;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Cookie names. The web flow writes the new `gds_session` (+ `gds_oauth_state`)
|
|
84
|
+
// names; both flows accept either the new or the legacy `pms_session` /
|
|
85
|
+
// `pms_oauth_state` cookies on read so a builder mid-flow doesn't have to
|
|
86
|
+
// re-auth at the rename rollout.
|
|
87
|
+
const SESSION_COOKIE = 'gds_session';
|
|
88
|
+
const SESSION_COOKIE_LEGACY = 'pms_session';
|
|
89
|
+
const STATE_COOKIE = 'gds_oauth_state';
|
|
90
|
+
const STATE_COOKIE_LEGACY = 'pms_oauth_state';
|
|
91
|
+
const DISCORD_STATE_COOKIE = 'gds_discord_state';
|
|
92
|
+
// Short-lived cookie carrying a validated post-sign-in return URL so the web
|
|
93
|
+
// flow can land a builder back on the subdomain they started from (#726). Set
|
|
94
|
+
// at /auth/web/start, consumed + cleared at the callback. Defaults to /builders.
|
|
95
|
+
const RETURN_COOKIE = 'gds_return';
|
|
96
|
+
|
|
97
|
+
// The apex domain the instance is served under, read from the instance branding
|
|
98
|
+
// pack's cookieDomain (".amazonprimea.com" → "amazonprimea.com",
|
|
99
|
+
// ".cloudbongos.com" → "cloudbongos.com"). The session + OAuth-state cookies are
|
|
100
|
+
// scoped to ".<apex>" so a single sign-in is shared across every subdomain
|
|
101
|
+
// (apex, builders., status.) — the enabler for the dedicated builders subdomain
|
|
102
|
+
// (#726). MUST come from config, not a hardcoded literal: a hardcoded
|
|
103
|
+
// "amazonprimea.com" made every OTHER instance (e.g. cloudbongos.com) set a
|
|
104
|
+
// cookie for the wrong domain, which the browser drops → the OAuth state cookie
|
|
105
|
+
// never persisted → "state mismatch / the gate is closed" (ADR 0062 §3/§5: domains
|
|
106
|
+
// woven into auth, config swaps must not widen cookie scope). Each instance scopes
|
|
107
|
+
// its OWN apex; neither can set the other's cookie. An instance with no
|
|
108
|
+
// cookieDomain (localhost dev, a misconfigured pack) → "" → host-only cookies
|
|
109
|
+
// everywhere (fail-safe, never cross-host). Resolved once at module load (the
|
|
110
|
+
// brand is a per-process singleton).
|
|
111
|
+
function configuredCookieApex() {
|
|
112
|
+
try {
|
|
113
|
+
return String(branding().domains?.cookieDomain || '').replace(/^\./, '').toLowerCase();
|
|
114
|
+
} catch {
|
|
115
|
+
return '';
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
const COOKIE_APEX = configuredCookieApex();
|
|
119
|
+
|
|
120
|
+
// Reqest host, lowercased, port-stripped. x-forwarded-host wins behind the proxy.
|
|
121
|
+
function reqHost(req) {
|
|
122
|
+
const raw = req.headers['x-forwarded-host'] || req.headers['host'] || '';
|
|
123
|
+
return String(raw).toLowerCase().split(',')[0].trim().split(':')[0];
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// The production-facing hosts that share one sign-in. Deliberately enumerated
|
|
127
|
+
// (not "any *.amazonprimea.com") so staging.amazonprimea.com — a SEPARATE
|
|
128
|
+
// process + DB on :3001 — keeps a host-only cookie and never bleeds an
|
|
129
|
+
// apex-scoped token into prod (or vice versa).
|
|
130
|
+
// When COOKIE_APEX is "" (localhost / no configured cookieDomain) the set is
|
|
131
|
+
// EMPTY → every host gets a host-only cookie (never a "." + "" = ".<empty>" leak).
|
|
132
|
+
const APEX_COOKIE_HOSTS = new Set(
|
|
133
|
+
COOKIE_APEX
|
|
134
|
+
? [COOKIE_APEX, 'www.' + COOKIE_APEX, 'builders.' + COOKIE_APEX, 'status.' + COOKIE_APEX]
|
|
135
|
+
: []
|
|
136
|
+
);
|
|
137
|
+
|
|
138
|
+
// Cookie attributes that vary by host. On the production-facing hosts the
|
|
139
|
+
// session cookie is scoped to the apex so it crosses subdomains (the enabler
|
|
140
|
+
// for builders.amazonprimea.com, #726); everywhere else (localhost, staging)
|
|
141
|
+
// it stays host-only. Returns a spread-able object.
|
|
142
|
+
function sessionCookieDomainOpts(req) {
|
|
143
|
+
if (APEX_COOKIE_HOSTS.has(reqHost(req))) {
|
|
144
|
+
return { domain: '.' + COOKIE_APEX };
|
|
145
|
+
}
|
|
146
|
+
return {};
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Validate a caller-supplied post-sign-in return target. Accept ONLY:
|
|
150
|
+
// - a relative path beginning with a single '/' (not '//', no backslash), or
|
|
151
|
+
// - an absolute https URL whose host is the apex or a *.amazonprimea.com sub.
|
|
152
|
+
// Anything else (other origins, javascript:, protocol-relative) → null, so the
|
|
153
|
+
// caller falls back to the safe default. This is an open-redirect guard.
|
|
154
|
+
function safeReturnTo(raw) {
|
|
155
|
+
if (!raw || typeof raw !== 'string' || raw.length > 512) return null;
|
|
156
|
+
// Relative path — must start with exactly one slash and no backslash.
|
|
157
|
+
if (raw.startsWith('/')) {
|
|
158
|
+
if (raw.startsWith('//') || raw.includes('\\')) return null;
|
|
159
|
+
return raw;
|
|
160
|
+
}
|
|
161
|
+
let u;
|
|
162
|
+
try { u = new URL(raw); } catch { return null; }
|
|
163
|
+
if (u.protocol !== 'https:') return null;
|
|
164
|
+
const h = u.hostname.toLowerCase();
|
|
165
|
+
if (h === COOKIE_APEX || h.endsWith('.' + COOKIE_APEX)) return u.toString();
|
|
166
|
+
return null;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// The canonical sign-in origin, read from the instance branding pack (oauthOrigin,
|
|
170
|
+
// else publicOrigin) so a non-OTB Cloud Bongos instance pins its OAuth redirects to
|
|
171
|
+
// ITS OWN apex instead of a hardcoded "amazonprimea.com" (ADR 0062 §3/§5; the
|
|
172
|
+
// branding contract). The fixed GDS_PUBLIC_ORIGIN env knob still wins for
|
|
173
|
+
// dev/staging (auth.js's documented FIXED env name, not a <PREFIX>_ one). Resolved
|
|
174
|
+
// once at module load; falls back to '' if branding fails, which leaves the env knob
|
|
175
|
+
// as the sole source rather than baking in a host brand.
|
|
176
|
+
function configuredOAuthOrigin() {
|
|
177
|
+
try {
|
|
178
|
+
const d = branding().domains || {};
|
|
179
|
+
return String(d.oauthOrigin || d.publicOrigin || '');
|
|
180
|
+
} catch {
|
|
181
|
+
return '';
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// The OAuth redirect_uri must be a FIXED, registered origin — never derived from
|
|
186
|
+
// the request's x-forwarded-host (spoofable; F2 Hacker finding). Configured via
|
|
187
|
+
// env, falling back to the configured canonical public origin, so an injected host
|
|
188
|
+
// cannot rewrite the redirect and Discord's URI registration is not the SOLE backstop.
|
|
189
|
+
const DISCORD_REDIRECT_ORIGIN = process.env.GDS_PUBLIC_ORIGIN || configuredOAuthOrigin();
|
|
190
|
+
const DISCORD_REDIRECT_URI = `${DISCORD_REDIRECT_ORIGIN}/api/gds/auth/discord/callback`;
|
|
191
|
+
|
|
192
|
+
// The GitHub web-OAuth redirect_uri origin (#734). PINNED to the canonical
|
|
193
|
+
// public origin (same env knob as Discord), never the request host — GitHub
|
|
194
|
+
// matches redirect_uri against the app's single registered callback host, so a
|
|
195
|
+
// sign-in started on a builders.* subdomain must still use the apex.
|
|
196
|
+
const WEB_REDIRECT_ORIGIN = process.env.GDS_PUBLIC_ORIGIN || configuredOAuthOrigin();
|
|
197
|
+
|
|
198
|
+
module.exports = function buildAuthRouter() {
|
|
199
|
+
const router = express.Router();
|
|
200
|
+
|
|
201
|
+
// /auth/device — CLI flow
|
|
202
|
+
// rank: public — auth flow itself; identity establishment, not authorization.
|
|
203
|
+
router.post('/auth/device/start', async (_req, res) => {
|
|
204
|
+
if (!auth.authConfigured()) {
|
|
205
|
+
return res.fail('auth_not_configured', { status: 503, message: 'GitHub OAuth App credentials missing on the server. See blockers/blockers.md#github-oauth-app.' });
|
|
206
|
+
}
|
|
207
|
+
try {
|
|
208
|
+
const out = await auth.startDeviceFlow();
|
|
209
|
+
res.json(out);
|
|
210
|
+
} catch (err) {
|
|
211
|
+
console.error('[gds] device/start', err);
|
|
212
|
+
res.fail('device_start_failed', { status: 500, message: 'internal error' });
|
|
213
|
+
}
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
// rank: public — auth flow; the device code itself is the bearer.
|
|
217
|
+
router.post('/auth/device/poll', async (req, res) => {
|
|
218
|
+
if (!auth.authConfigured()) {
|
|
219
|
+
return res.fail('auth_not_configured', 503);
|
|
220
|
+
}
|
|
221
|
+
if (validateOrRespond(req, res, {
|
|
222
|
+
device_code: { required: true, type: 'string', maxLength: 256 },
|
|
223
|
+
})) return;
|
|
224
|
+
const { device_code: deviceCode } = req.body || {};
|
|
225
|
+
if (!deviceCode) return res.fail('device_code_required', 400);
|
|
226
|
+
try {
|
|
227
|
+
const out = await auth.pollDeviceFlow(deviceCode, req.headers['user-agent']);
|
|
228
|
+
res.json(out);
|
|
229
|
+
} catch (err) {
|
|
230
|
+
// Admission gate (SR-19 / task #1000): a first-time GitHub account that
|
|
231
|
+
// hasn't been approved by an Archon can't self-enroll via the CLI device
|
|
232
|
+
// flow either (mirrors the web /auth/web/callback NOT_APPROVED branch).
|
|
233
|
+
// Return a structured 403 with state:'not_approved' so setup.js can print a
|
|
234
|
+
// friendly "ask an Archon to invite you" message instead of a raw error.
|
|
235
|
+
if (err && err.code === 'NOT_APPROVED') {
|
|
236
|
+
return res.status(403).json({ state: 'not_approved', error: 'not_approved' });
|
|
237
|
+
}
|
|
238
|
+
console.error('[gds] device/poll', err);
|
|
239
|
+
res.fail('device_poll_failed', { status: 500, message: 'internal error' });
|
|
240
|
+
}
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
// /auth/web — browser flow (used by the /builders page).
|
|
244
|
+
// rank: public — entry into the GitHub OAuth handshake.
|
|
245
|
+
router.get('/auth/web/start', (req, res) => {
|
|
246
|
+
if (!auth.authConfigured()) {
|
|
247
|
+
return res.status(503).type('text/plain').send('auth not configured');
|
|
248
|
+
}
|
|
249
|
+
const state = crypto.randomBytes(16).toString('hex');
|
|
250
|
+
// redirect_uri is PINNED to the canonical origin (#734) — never derived from
|
|
251
|
+
// the request host. GitHub only knows the apex callback, so a sign-in begun
|
|
252
|
+
// on builders.amazonprimea.com must still hand GitHub the apex redirect_uri
|
|
253
|
+
// or GitHub rejects it. Same posture the Discord flow already uses.
|
|
254
|
+
const redirectUri = `${WEB_REDIRECT_ORIGIN}/api/gds/auth/web/callback`;
|
|
255
|
+
res.cookie(STATE_COOKIE, state, {
|
|
256
|
+
httpOnly: true,
|
|
257
|
+
sameSite: 'lax',
|
|
258
|
+
secure: req.secure || req.headers['x-forwarded-proto'] === 'https',
|
|
259
|
+
maxAge: 10 * 60 * 1000,
|
|
260
|
+
// Apex-scoped (#734) so the state survives the host switch: start may run
|
|
261
|
+
// on the subdomain but the callback always lands on the apex.
|
|
262
|
+
...sessionCookieDomainOpts(req),
|
|
263
|
+
});
|
|
264
|
+
// Post-sign-in return target (#726/#734): an explicit ?return= wins (set by
|
|
265
|
+
// the page gates); otherwise fall back to the Referer (the page that kicked
|
|
266
|
+
// off sign-in), so the dozens of client 401 redirects land a builder back
|
|
267
|
+
// where they were with no per-call-site change. Both go through the
|
|
268
|
+
// open-redirect allowlist (relative path or *.amazonprimea.com https).
|
|
269
|
+
const returnTo = safeReturnTo(req.query.return) || safeReturnTo(req.headers.referer);
|
|
270
|
+
if (returnTo) {
|
|
271
|
+
res.cookie(RETURN_COOKIE, returnTo, {
|
|
272
|
+
httpOnly: true,
|
|
273
|
+
sameSite: 'lax',
|
|
274
|
+
secure: req.secure || req.headers['x-forwarded-proto'] === 'https',
|
|
275
|
+
maxAge: 10 * 60 * 1000,
|
|
276
|
+
...sessionCookieDomainOpts(req),
|
|
277
|
+
});
|
|
278
|
+
}
|
|
279
|
+
res.redirect(auth.buildAuthorizeUrl(state, redirectUri));
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
// rank: public — OAuth callback; the state cookie + code are the bearer.
|
|
283
|
+
router.get('/auth/web/callback', async (req, res) => {
|
|
284
|
+
if (!auth.authConfigured()) {
|
|
285
|
+
return res.status(503).type('text/plain').send('auth not configured');
|
|
286
|
+
}
|
|
287
|
+
const expectedState = parseCookie(req, STATE_COOKIE) || parseCookie(req, STATE_COOKIE_LEGACY);
|
|
288
|
+
const { state, code, error } = req.query;
|
|
289
|
+
if (error) {
|
|
290
|
+
return res.status(400).type('text/html').send(renderAuthErrorPage(
|
|
291
|
+
'GitHub refused the handshake',
|
|
292
|
+
`GitHub returned the error code "${error}". This usually means the OAuth app's authorized callback URL doesn't include this server's /api/gds/auth/web/callback path.`,
|
|
293
|
+
));
|
|
294
|
+
}
|
|
295
|
+
if (!code || !state || state !== expectedState) {
|
|
296
|
+
return res.status(400).type('text/html').send(renderAuthErrorPage(
|
|
297
|
+
'State mismatch',
|
|
298
|
+
'The handshake-cookie did not match the state GitHub returned. This usually means the sign-in took too long (cookie expired after 10 minutes) or the request crossed an origin boundary. Start over and the gate will open.',
|
|
299
|
+
));
|
|
300
|
+
}
|
|
301
|
+
try {
|
|
302
|
+
// Must match the PINNED redirect_uri used at /auth/web/start (#734) — the
|
|
303
|
+
// GitHub token exchange requires an exact string match.
|
|
304
|
+
const redirectUri = `${WEB_REDIRECT_ORIGIN}/api/gds/auth/web/callback`;
|
|
305
|
+
const { token, expiresAt } = await auth.completeWebFlow({
|
|
306
|
+
code,
|
|
307
|
+
redirectUri,
|
|
308
|
+
userAgent: req.headers['user-agent'],
|
|
309
|
+
});
|
|
310
|
+
res.cookie(SESSION_COOKIE, token, {
|
|
311
|
+
httpOnly: true,
|
|
312
|
+
sameSite: 'lax',
|
|
313
|
+
secure: req.secure || req.headers['x-forwarded-proto'] === 'https',
|
|
314
|
+
path: '/',
|
|
315
|
+
expires: expiresAt,
|
|
316
|
+
// Apex-scoped on the production hosts so one sign-in spans
|
|
317
|
+
// amazonprimea.com + builders. + status. (#726); host-only elsewhere.
|
|
318
|
+
...sessionCookieDomainOpts(req),
|
|
319
|
+
});
|
|
320
|
+
// The state cookie is apex-scoped now (#734) — clear it with the same
|
|
321
|
+
// domain or the browser keeps it. Clear the host-only form too for any
|
|
322
|
+
// cookie set before this change.
|
|
323
|
+
const domainOpts = sessionCookieDomainOpts(req);
|
|
324
|
+
res.clearCookie(STATE_COOKIE);
|
|
325
|
+
res.clearCookie(STATE_COOKIE_LEGACY);
|
|
326
|
+
if (domainOpts.domain) {
|
|
327
|
+
res.clearCookie(STATE_COOKIE, domainOpts);
|
|
328
|
+
res.clearCookie(STATE_COOKIE_LEGACY, domainOpts);
|
|
329
|
+
}
|
|
330
|
+
// Land back where sign-in began if a validated return target was stashed
|
|
331
|
+
// (e.g. the builders subdomain bounced through here); else the hall.
|
|
332
|
+
const returnTo = safeReturnTo(parseCookie(req, RETURN_COOKIE));
|
|
333
|
+
res.clearCookie(RETURN_COOKIE, domainOpts);
|
|
334
|
+
res.redirect(returnTo || '/builders');
|
|
335
|
+
} catch (err) {
|
|
336
|
+
// Approval gate (2026-05-30): a first-time GitHub account that hasn't been
|
|
337
|
+
// approved by an Archon can't enter. Send them to the hall to request
|
|
338
|
+
// access rather than dead-ending on a generic error.
|
|
339
|
+
if (err && err.code === 'NOT_APPROVED') {
|
|
340
|
+
return res.status(403).type('text/html').send(renderAuthErrorPage(
|
|
341
|
+
'This account is not approved yet',
|
|
342
|
+
'Signing in with GitHub is only for builders an Archon has already approved. If you are new, request access at the builders’ hall — an Archon will approve you, then this sign-in will let you in. If you were just approved, the Archon may still need to mark you invited; give it a moment and try again.',
|
|
343
|
+
{
|
|
344
|
+
subtitle: 'Sign-in is for approved builders. New here? Request access first.',
|
|
345
|
+
cta: { href: '/builders', label: 'Go to the hall & request access' },
|
|
346
|
+
note: 'Already approved and still blocked? Ask the Archon to confirm your request was marked “invited.”',
|
|
347
|
+
},
|
|
348
|
+
));
|
|
349
|
+
}
|
|
350
|
+
console.error('[gds] web/callback', err);
|
|
351
|
+
res.status(500).type('text/html').send(renderAuthErrorPage(
|
|
352
|
+
'The handshake threw an error',
|
|
353
|
+
`The server tried to exchange the auth code for a session and something went wrong: ${err.message}. Try again; if it persists, the Archon must investigate the server logs.`,
|
|
354
|
+
));
|
|
355
|
+
}
|
|
356
|
+
});
|
|
357
|
+
|
|
358
|
+
// /auth/discord — account-linking OAuth (Phase 3 F2, ADR 0033 §2). Links a
|
|
359
|
+
// Discord account to the ALREADY-AUTHENTICATED builder (gds_session cookie):
|
|
360
|
+
// the link is self-asserted by the builder, never invented by the bot. Inert
|
|
361
|
+
// (503) until the server has Discord client creds. Mirrors the GitHub web
|
|
362
|
+
// flow's CSRF-state discipline above — a hard requirement (ADR 0033 §2).
|
|
363
|
+
// rank: any-builder — links to your OWN identity; no rank gate.
|
|
364
|
+
router.get('/auth/discord/start', auth.requireBuilder, (req, res) => {
|
|
365
|
+
if (!auth.discordConfigured()) {
|
|
366
|
+
return res.status(503).type('text/plain').send('Discord linking not configured on this server yet.');
|
|
367
|
+
}
|
|
368
|
+
const state = crypto.randomBytes(16).toString('hex');
|
|
369
|
+
const redirectUri = DISCORD_REDIRECT_URI;
|
|
370
|
+
res.cookie(DISCORD_STATE_COOKIE, state, {
|
|
371
|
+
httpOnly: true,
|
|
372
|
+
sameSite: 'lax',
|
|
373
|
+
secure: req.secure || req.headers['x-forwarded-proto'] === 'https',
|
|
374
|
+
maxAge: 10 * 60 * 1000,
|
|
375
|
+
// Apex-scoped (#734 parity) so the state survives the host switch: the
|
|
376
|
+
// link may start on builders.amazonprimea.com but DISCORD_REDIRECT_URI
|
|
377
|
+
// always lands the callback on the apex. A host-only cookie would be
|
|
378
|
+
// dropped on the hop and the state check would fail ("Link handshake
|
|
379
|
+
// expired"). Mirrors the GitHub web-flow state cookie above.
|
|
380
|
+
...sessionCookieDomainOpts(req),
|
|
381
|
+
});
|
|
382
|
+
res.redirect(auth.buildDiscordAuthorizeUrl(state, redirectUri));
|
|
383
|
+
});
|
|
384
|
+
|
|
385
|
+
// rank: any-builder — completes the link to the caller's own identity.
|
|
386
|
+
router.get('/auth/discord/callback', auth.requireBuilder, async (req, res) => {
|
|
387
|
+
if (!auth.discordConfigured()) {
|
|
388
|
+
return res.status(503).type('text/plain').send('Discord linking not configured on this server yet.');
|
|
389
|
+
}
|
|
390
|
+
const expectedState = parseCookie(req, DISCORD_STATE_COOKIE);
|
|
391
|
+
const { state, code, error } = req.query;
|
|
392
|
+
// The state cookie is apex-scoped now (#734 parity) — clear it with the
|
|
393
|
+
// same domain or the browser keeps it. Clear the host-only form too for any
|
|
394
|
+
// cookie set before this change.
|
|
395
|
+
const stateDomainOpts = sessionCookieDomainOpts(req);
|
|
396
|
+
res.clearCookie(DISCORD_STATE_COOKIE);
|
|
397
|
+
if (stateDomainOpts.domain) res.clearCookie(DISCORD_STATE_COOKIE, stateDomainOpts);
|
|
398
|
+
// CSRF FIRST: validate the state cookie against the state Discord echoes
|
|
399
|
+
// back BEFORE trusting any other param (including Discord's `error`). A
|
|
400
|
+
// forged callback with no valid state — even one carrying `?error=...` — is
|
|
401
|
+
// rejected here, so it can't surface an attacker-chosen message or burn a
|
|
402
|
+
// mid-flow builder's link. Without this a forged callback could otherwise
|
|
403
|
+
// link an attacker's Discord to this builder (ADR 0033 §2).
|
|
404
|
+
if (!state || state !== expectedState) {
|
|
405
|
+
return res.status(400).type('text/html').send(renderAuthErrorPage(
|
|
406
|
+
'Link handshake expired',
|
|
407
|
+
'The link-cookie did not match what Discord returned (the sign-in may have taken too long, or crossed an origin). Start over from the hall.',
|
|
408
|
+
{ cta: { href: '/builders', label: 'Back to the hall' } },
|
|
409
|
+
));
|
|
410
|
+
}
|
|
411
|
+
if (error) {
|
|
412
|
+
return res.status(400).type('text/html').send(renderAuthErrorPage(
|
|
413
|
+
'Discord refused the link',
|
|
414
|
+
`Discord returned "${error}". Try linking again from the builders' hall.`,
|
|
415
|
+
{ cta: { href: '/builders', label: 'Back to the hall' } },
|
|
416
|
+
));
|
|
417
|
+
}
|
|
418
|
+
if (!code) {
|
|
419
|
+
return res.status(400).type('text/html').send(renderAuthErrorPage(
|
|
420
|
+
'Link handshake incomplete',
|
|
421
|
+
'Discord did not return an authorization code. Start over from the hall.',
|
|
422
|
+
{ cta: { href: '/builders', label: 'Back to the hall' } },
|
|
423
|
+
));
|
|
424
|
+
}
|
|
425
|
+
try {
|
|
426
|
+
const redirectUri = DISCORD_REDIRECT_URI;
|
|
427
|
+
const accessToken = await auth.exchangeDiscordCode(code, redirectUri);
|
|
428
|
+
const dUser = await auth.fetchDiscordUser(accessToken);
|
|
429
|
+
// Validate the snowflake BEFORE touching the DB (F1 Hacker advisory): a
|
|
430
|
+
// malformed/oversized id never enters discord_links.
|
|
431
|
+
if (!auth.isValidDiscordUserId(dUser && dUser.id)) {
|
|
432
|
+
return res.status(400).type('text/html').send(renderAuthErrorPage(
|
|
433
|
+
'Could not read your Discord identity',
|
|
434
|
+
'Discord did not return a valid account id. Try again from the hall.',
|
|
435
|
+
{ cta: { href: '/builders', label: 'Back to the hall' } },
|
|
436
|
+
));
|
|
437
|
+
}
|
|
438
|
+
const result = await db.linkDiscord({ builderId: req.builder.id, discordUserId: dUser.id });
|
|
439
|
+
if (!result.ok && result.error === 'discord_taken') {
|
|
440
|
+
return res.status(409).type('text/html').send(renderAuthErrorPage(
|
|
441
|
+
'That Discord account is already linked',
|
|
442
|
+
'This Discord account is linked to a different builder. Unlink it there first, or link a different Discord account.',
|
|
443
|
+
{ cta: { href: '/builders', label: 'Back to the hall' } },
|
|
444
|
+
));
|
|
445
|
+
}
|
|
446
|
+
// F7 auto-join (ADR 0033 — the link-first onboarding gate): now that the
|
|
447
|
+
// builder has self-asserted this Discord account, drop them straight into
|
|
448
|
+
// the guild with their rank role set at add-time, so the role is correct
|
|
449
|
+
// the instant they arrive (no invite link to share or leak). Best-effort:
|
|
450
|
+
// the link must succeed even if the bot is off / lacks CREATE_INSTANT_INVITE
|
|
451
|
+
// / can't reach Discord. We await so a failure can fall through to a
|
|
452
|
+
// graceful message, but never let it throw past here.
|
|
453
|
+
// The guild + rank-role to apply at add-time come from the discord module
|
|
454
|
+
// through the kernel seam (ADR 0083 / BV1.R47 — no core→module import);
|
|
455
|
+
// degrades to { guildId: null } when discord is off, so the join just skips.
|
|
456
|
+
const joinSpec = await seams.resolveOptional(
|
|
457
|
+
'discord.guildJoinSpec', async () => ({ guildId: null, roleIds: [] }),
|
|
458
|
+
)(req.builder.rank);
|
|
459
|
+
let joined = false;
|
|
460
|
+
if (joinSpec.guildId && auth.discordBotConfigured()) {
|
|
461
|
+
try {
|
|
462
|
+
await auth.addDiscordGuildMember({
|
|
463
|
+
guildId: joinSpec.guildId,
|
|
464
|
+
discordUserId: dUser.id,
|
|
465
|
+
accessToken,
|
|
466
|
+
roleIds: joinSpec.roleIds,
|
|
467
|
+
});
|
|
468
|
+
joined = true;
|
|
469
|
+
} catch (e) {
|
|
470
|
+
console.log('[gds] discord auto-join failed (non-blocking):', e.message);
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
// F5 role-sync belt-and-braces (ADR 0033 §4): set/repair the rank-role even
|
|
474
|
+
// if auto-join 204'd an already-present member (whose add-time roles Discord
|
|
475
|
+
// ignores) or the add step was skipped. Emitted to the discord module's
|
|
476
|
+
// listener; fire-and-forget.
|
|
477
|
+
seams.emit('builder.role-sync-needed', { builderId: req.builder.id });
|
|
478
|
+
// #731: the Discord connect card lives on /builders/settings now, so the
|
|
479
|
+
// callback returns there (settings.js surfaces the ?discord= toast).
|
|
480
|
+
res.redirect(joined ? '/builders/settings?discord=joined' : '/builders/settings?discord=linked');
|
|
481
|
+
} catch (err) {
|
|
482
|
+
console.error('[gds] discord/callback', err);
|
|
483
|
+
res.status(500).type('text/html').send(renderAuthErrorPage(
|
|
484
|
+
'The Discord link threw an error',
|
|
485
|
+
'Linking failed unexpectedly. Try again; if it persists, the Archon must check the server logs.',
|
|
486
|
+
{ cta: { href: '/builders', label: 'Back to the hall' } },
|
|
487
|
+
));
|
|
488
|
+
}
|
|
489
|
+
});
|
|
490
|
+
|
|
491
|
+
// rank: any-builder — unlink your OWN Discord. JSON (called from the hall UI).
|
|
492
|
+
// F7 follow-up: also kicks the builder from the guild so they can't remain in
|
|
493
|
+
// the server without a linked account. The kick is best-effort — the unlink
|
|
494
|
+
// always succeeds even if the bot lacks Kick Members or the member already left.
|
|
495
|
+
router.post('/auth/discord/unlink', auth.requireBuilder, async (req, res) => {
|
|
496
|
+
try {
|
|
497
|
+
// Read the discord_user_id BEFORE deleting the link — we need it for the kick.
|
|
498
|
+
const link = await db.getDiscordLink(req.builder.id);
|
|
499
|
+
const removed = await db.unlinkDiscord(req.builder.id);
|
|
500
|
+
if (link && auth.discordBotConfigured()) {
|
|
501
|
+
const { guildId } = await seams.resolveOptional(
|
|
502
|
+
'discord.guildJoinSpec', async () => ({ guildId: null }),
|
|
503
|
+
)(null);
|
|
504
|
+
if (guildId) {
|
|
505
|
+
auth.removeDiscordGuildMember({ guildId, discordUserId: link.discord_user_id })
|
|
506
|
+
.then((r) => console.log(`[gds] discord unlink kick: ${r.removed ? 'removed' : r.reason}`))
|
|
507
|
+
.catch((e) => console.log('[gds] discord unlink kick failed (non-blocking):', e.message));
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
res.json({ ok: true, unlinked: removed });
|
|
511
|
+
} catch (err) {
|
|
512
|
+
console.error('[gds] discord/unlink', err);
|
|
513
|
+
res.fail('unlink_failed', 500);
|
|
514
|
+
}
|
|
515
|
+
});
|
|
516
|
+
|
|
517
|
+
// rank: any-builder — own session, no rank gate (a Xenos can log themselves out).
|
|
518
|
+
router.post('/auth/logout', auth.requireBuilder, async (req, res) => {
|
|
519
|
+
const token = (req.headers['authorization'] || '').replace(/^Bearer\s+/, '')
|
|
520
|
+
|| parseCookie(req, SESSION_COOKIE)
|
|
521
|
+
|| parseCookie(req, SESSION_COOKIE_LEGACY);
|
|
522
|
+
if (token) await db.revokeSession(token);
|
|
523
|
+
// Clear BOTH the host-only form (older sessions) and the apex-scoped form
|
|
524
|
+
// (#726) — a clearCookie only matches the cookie whose domain attribute it
|
|
525
|
+
// names, so sign-out on a production host must target the apex domain too.
|
|
526
|
+
res.clearCookie(SESSION_COOKIE);
|
|
527
|
+
res.clearCookie(SESSION_COOKIE_LEGACY);
|
|
528
|
+
const domainOpts = sessionCookieDomainOpts(req);
|
|
529
|
+
if (domainOpts.domain) {
|
|
530
|
+
res.clearCookie(SESSION_COOKIE, domainOpts);
|
|
531
|
+
res.clearCookie(SESSION_COOKIE_LEGACY, domainOpts);
|
|
532
|
+
}
|
|
533
|
+
res.json({ ok: true });
|
|
534
|
+
});
|
|
535
|
+
|
|
536
|
+
// POST /auth/cli-token/issue — V3.R91 / #401. Issue a fresh CLI bearer for
|
|
537
|
+
// the requesting builder, gated on a WEB COOKIE session (not a bearer). The
|
|
538
|
+
// browser already proves identity via the gds_session cookie; this endpoint
|
|
539
|
+
// mints a separate cli-source session row the builder can paste into
|
|
540
|
+
// ~/.config/otb/gds-session.json via scripts/gds/paste-token.js. Replaces
|
|
541
|
+
// the ~30-60s Device Flow when the CLI bearer has expired.
|
|
542
|
+
//
|
|
543
|
+
// rank: any-builder (own identity only) — no rank gate.
|
|
544
|
+
// auth source restriction: cookie ONLY. A bearer cannot mint a new bearer
|
|
545
|
+
// (a leaked CLI token shouldn't be able to extend its own lifetime; the
|
|
546
|
+
// httpOnly browser cookie is the harder-to-steal credential).
|
|
547
|
+
router.post('/auth/cli-token/issue', auth.requireBuilder, async (req, res) => {
|
|
548
|
+
const cookieToken = parseCookie(req, SESSION_COOKIE) || parseCookie(req, SESSION_COOKIE_LEGACY);
|
|
549
|
+
if (!cookieToken) {
|
|
550
|
+
// Name the instance's own sign-in host (from the branding pack), not a
|
|
551
|
+
// hardcoded "amazonprimea.com" (ADR 0062 §3/§5).
|
|
552
|
+
const signInHost = configuredOAuthOrigin().replace(/^https?:\/\//, '').split('/')[0] || 'the sign-in page';
|
|
553
|
+
return res.fail('cookie_required', { status: 403, message: `This endpoint mints CLI bearers from an authenticated browser session. Sign in at ${signInHost} first; bearer-only requests are rejected so a leaked CLI token cannot mint replacements for itself.` });
|
|
554
|
+
}
|
|
555
|
+
try {
|
|
556
|
+
const token = crypto.randomBytes(32).toString('hex');
|
|
557
|
+
const row = await db.createSession({
|
|
558
|
+
builderId: req.builder.id,
|
|
559
|
+
token,
|
|
560
|
+
// source must be 'cli' | 'web' (003_pms.sql CHECK). The route name in
|
|
561
|
+
// audit_log distinguishes portal-reissue from Device Flow tokens.
|
|
562
|
+
source: 'cli',
|
|
563
|
+
userAgent: req.headers['user-agent'] ?? null,
|
|
564
|
+
// expiresAt: null → createSession applies SESSION_TTL_MS (24h after V3.R90).
|
|
565
|
+
expiresAt: null,
|
|
566
|
+
});
|
|
567
|
+
res.json({
|
|
568
|
+
ok: true,
|
|
569
|
+
token,
|
|
570
|
+
expires_at: row.expires_at,
|
|
571
|
+
builder_login: req.builder.github_login,
|
|
572
|
+
paste_command: `node scripts/gds/paste-token.js ${token}`,
|
|
573
|
+
});
|
|
574
|
+
} catch (err) {
|
|
575
|
+
console.error('[gds] POST /auth/cli-token/issue', err);
|
|
576
|
+
res.fail('issue_failed', { status: 500, message: 'internal error' });
|
|
577
|
+
}
|
|
578
|
+
});
|
|
579
|
+
|
|
580
|
+
// POST /auth/revoke — V3.R38 / #259. Archon-only. Kill a SINGLE session by
|
|
581
|
+
// its primary-key id. Used for surgical lockouts (e.g. a compromised CLI on
|
|
582
|
+
// a known builder's laptop) without disturbing that builder's other sessions.
|
|
583
|
+
// Body: { session_id: <bigint> }. Response: { ok, revoked: 0|1 }.
|
|
584
|
+
// rank: archon — kills an arbitrary session; can lock other builders out.
|
|
585
|
+
router.post('/auth/revoke', auth.requireBuilder, auth.requireRank('archon'), async (req, res) => {
|
|
586
|
+
if (validateOrRespond(req, res, { session_id: {} })) return; // bigint as number or string; validated below
|
|
587
|
+
const sessionId = req.body && req.body.session_id;
|
|
588
|
+
if (sessionId === undefined || sessionId === null) {
|
|
589
|
+
return res.fail('session_id_required', 400);
|
|
590
|
+
}
|
|
591
|
+
// Accept bigint as string or number; validate it parses cleanly.
|
|
592
|
+
const id = Number(sessionId);
|
|
593
|
+
if (!Number.isInteger(id) || id <= 0) {
|
|
594
|
+
return res.fail('bad_session_id', 400);
|
|
595
|
+
}
|
|
596
|
+
try {
|
|
597
|
+
const revoked = await db.revokeSessionById(id);
|
|
598
|
+
res.json({ ok: true, revoked });
|
|
599
|
+
} catch (err) {
|
|
600
|
+
console.error('[gds] POST /auth/revoke', err);
|
|
601
|
+
res.fail('revoke_failed', { status: 500, message: 'internal error' });
|
|
602
|
+
}
|
|
603
|
+
});
|
|
604
|
+
|
|
605
|
+
// POST /auth/revoke-all — V3.R38 / #259. Archon-only. Kill EVERY active
|
|
606
|
+
// session for a given builder. Used at demotion (auto-called from
|
|
607
|
+
// setBuilderRank when newRank moves down the ladder — C7 rank-change-near-
|
|
608
|
+
// instant) and as a manual operator hammer.
|
|
609
|
+
// Body: { builder_id: <bigint> }. Response: { ok, revoked: <count> }.
|
|
610
|
+
// rank: archon — same authority as PATCH /builders/:id/rank.
|
|
611
|
+
router.post('/auth/revoke-all', auth.requireBuilder, auth.requireRank('archon'), async (req, res) => {
|
|
612
|
+
if (validateOrRespond(req, res, { builder_id: {} })) return; // bigint as number or string; validated below
|
|
613
|
+
const builderId = req.body && req.body.builder_id;
|
|
614
|
+
if (builderId === undefined || builderId === null) {
|
|
615
|
+
return res.fail('builder_id_required', 400);
|
|
616
|
+
}
|
|
617
|
+
const id = Number(builderId);
|
|
618
|
+
if (!Number.isInteger(id) || id <= 0) {
|
|
619
|
+
return res.fail('bad_builder_id', 400);
|
|
620
|
+
}
|
|
621
|
+
try {
|
|
622
|
+
const revoked = await db.revokeAllSessionsForBuilder(id);
|
|
623
|
+
res.json({ ok: true, revoked });
|
|
624
|
+
} catch (err) {
|
|
625
|
+
console.error('[gds] POST /auth/revoke-all', err);
|
|
626
|
+
res.fail('revoke_all_failed', { status: 500, message: 'internal error' });
|
|
627
|
+
}
|
|
628
|
+
});
|
|
629
|
+
|
|
630
|
+
// /auth/app-pair — browser-clicks-only sign-in for the Dev Box desktop app
|
|
631
|
+
// (#904, ADR 0045). The app starts a pairing, opens the browser at
|
|
632
|
+
// /builders/pair?code=…, the signed-in builder clicks ONE Approve button,
|
|
633
|
+
// and the app collects a freshly-minted source='cli' session over its own
|
|
634
|
+
// secret poll channel. Design + threat model in src/bongos/app-pair.js.
|
|
635
|
+
//
|
|
636
|
+
// Route order note: the two PUBLIC routes here sit so no requireBuilder/
|
|
637
|
+
// requireRank from a NEIGHBORING declaration falls inside route-rank-check's
|
|
638
|
+
// 20-line look-ahead — the deterministic auditor must classify them from
|
|
639
|
+
// their own `rank:` annotations, not a false middleware hit.
|
|
640
|
+
|
|
641
|
+
// rank: public — starts a pairing; returns opaque codes only, no identity.
|
|
642
|
+
router.post('/auth/app-pair/start', (req, res) => {
|
|
643
|
+
if (validateOrRespond(req, res, {
|
|
644
|
+
device_name: { type: 'string', maxLength: 80 },
|
|
645
|
+
platform: { type: 'string', maxLength: 20 },
|
|
646
|
+
})) return;
|
|
647
|
+
const body = req.body || {};
|
|
648
|
+
const out = pairStore.start({ deviceName: body.device_name, platform: body.platform });
|
|
649
|
+
if (out.error === 'busy') {
|
|
650
|
+
return res.fail('pairing_busy', { status: 429, message: 'Too many pairings in flight. Try again in a few minutes.' });
|
|
651
|
+
}
|
|
652
|
+
// The approve-page URL is PINNED to the canonical public origin (same
|
|
653
|
+
// posture as the OAuth redirect_uris above) — never derived from the
|
|
654
|
+
// request host, so a spoofed Host header can't point the app's browser
|
|
655
|
+
// hand-off at a look-alike approve page.
|
|
656
|
+
const verifyPath = `/builders/pair?code=${out.pairCode}`;
|
|
657
|
+
res.status(201).json({
|
|
658
|
+
pair_code: out.pairCode,
|
|
659
|
+
poll_token: out.pollToken,
|
|
660
|
+
verify_path: verifyPath,
|
|
661
|
+
verify_url: `${WEB_REDIRECT_ORIGIN}${verifyPath}`,
|
|
662
|
+
expires_in: out.expiresIn,
|
|
663
|
+
interval: out.interval,
|
|
664
|
+
});
|
|
665
|
+
});
|
|
666
|
+
|
|
667
|
+
// rank: any-builder — read-only: what the approve page shows BEFORE the
|
|
668
|
+
// click (device name/platform for the given code). Unknown and expired are
|
|
669
|
+
// indistinguishable (404) so the code space can't be probed for liveness.
|
|
670
|
+
router.get('/auth/app-pair/info', auth.requireBuilder, (req, res) => {
|
|
671
|
+
const info = pairStore.info(req.query.code);
|
|
672
|
+
if (!info) return res.fail('unknown_or_expired', 404);
|
|
673
|
+
res.json({
|
|
674
|
+
pair_code: info.pairCode,
|
|
675
|
+
device_name: info.deviceName,
|
|
676
|
+
platform: info.platform,
|
|
677
|
+
status: info.status,
|
|
678
|
+
});
|
|
679
|
+
});
|
|
680
|
+
|
|
681
|
+
// rank: any-builder (own identity only) — approving signs the app in AS THE
|
|
682
|
+
// APPROVER. Cookie-gated exactly like /auth/cli-token/issue above: a leaked
|
|
683
|
+
// CLI bearer must not be able to approve pairings and mint itself siblings;
|
|
684
|
+
// only the httpOnly browser cookie — the click in the hall — can.
|
|
685
|
+
router.post('/auth/app-pair/approve', auth.requireBuilder, async (req, res) => {
|
|
686
|
+
const cookieToken = parseCookie(req, SESSION_COOKIE) || parseCookie(req, SESSION_COOKIE_LEGACY);
|
|
687
|
+
if (!cookieToken) {
|
|
688
|
+
return res.fail('cookie_required', { status: 403, message: 'Pairings are approved from an authenticated browser session in the builders’ hall, not from a bearer token.' });
|
|
689
|
+
}
|
|
690
|
+
if (validateOrRespond(req, res, {
|
|
691
|
+
pair_code: { required: true, type: 'string', maxLength: 32 },
|
|
692
|
+
})) return;
|
|
693
|
+
try {
|
|
694
|
+
const token = crypto.randomBytes(32).toString('hex');
|
|
695
|
+
const row = await db.createSession({
|
|
696
|
+
builderId: req.builder.id,
|
|
697
|
+
token,
|
|
698
|
+
source: 'cli', // 003_pms.sql CHECK; the app is a CLI-class bearer client
|
|
699
|
+
userAgent: `devbox-app pair (${req.headers['user-agent'] ?? 'unknown'})`,
|
|
700
|
+
expiresAt: null, // createSession applies the 24h refresh-on-use TTL
|
|
701
|
+
});
|
|
702
|
+
const out = pairStore.approve((req.body || {}).pair_code, {
|
|
703
|
+
token,
|
|
704
|
+
expires_at: row.expires_at,
|
|
705
|
+
builder: {
|
|
706
|
+
id: req.builder.id,
|
|
707
|
+
github_login: req.builder.github_login,
|
|
708
|
+
display_name: req.builder.display_name,
|
|
709
|
+
rank: req.builder.rank,
|
|
710
|
+
},
|
|
711
|
+
});
|
|
712
|
+
if (!out.ok) {
|
|
713
|
+
// The pairing was gone or already approved — revoke the session we
|
|
714
|
+
// just minted so an orphaned token never outlives this branch.
|
|
715
|
+
await db.revokeSession(token);
|
|
716
|
+
return out.error === 'already_approved'
|
|
717
|
+
? res.fail('already_approved', 409)
|
|
718
|
+
: res.fail('unknown_or_expired', 404);
|
|
719
|
+
}
|
|
720
|
+
res.json({ ok: true });
|
|
721
|
+
} catch (err) {
|
|
722
|
+
console.error('[gds] POST /auth/app-pair/approve', err);
|
|
723
|
+
res.fail('approve_failed', { status: 500, message: 'internal error' });
|
|
724
|
+
}
|
|
725
|
+
});
|
|
726
|
+
|
|
727
|
+
// rank: public — poll with the secret poll_token; the token IS the bearer
|
|
728
|
+
// (mirrors /auth/device/poll, where the device_code is the bearer). Kept
|
|
729
|
+
// LAST in this router so route-rank-check's look-ahead window stays clear
|
|
730
|
+
// of any neighboring gated declaration (see the block comment above).
|
|
731
|
+
router.post('/auth/app-pair/poll', (req, res) => {
|
|
732
|
+
if (validateOrRespond(req, res, {
|
|
733
|
+
poll_token: { required: true, type: 'string', maxLength: 128 },
|
|
734
|
+
})) return;
|
|
735
|
+
const out = pairStore.poll((req.body || {}).poll_token);
|
|
736
|
+
if (out.status === 'approved') {
|
|
737
|
+
return res.json({ status: 'approved', ...out.grant });
|
|
738
|
+
}
|
|
739
|
+
res.json(out.status === 'pending'
|
|
740
|
+
? { status: 'pending', interval: out.interval }
|
|
741
|
+
: { status: 'expired' });
|
|
742
|
+
});
|
|
743
|
+
|
|
744
|
+
return router;
|
|
745
|
+
};
|
|
746
|
+
|
|
747
|
+
// Exposed for unit tests (#726). These are pure host/URL helpers — the
|
|
748
|
+
// open-redirect guard on the post-sign-in return target and the cross-subdomain
|
|
749
|
+
// cookie-domain decision — worth locking down independently of a running server.
|
|
750
|
+
module.exports.safeReturnTo = safeReturnTo;
|
|
751
|
+
module.exports.sessionCookieDomainOpts = sessionCookieDomainOpts;
|
|
752
|
+
module.exports.reqHost = reqHost;
|
|
753
|
+
// The auth-error page renderer (task 1439): pure over the branding pack, so a
|
|
754
|
+
// test can prove every visible string is brand-owned (no OTB lore baked in).
|
|
755
|
+
module.exports.renderAuthErrorPage = renderAuthErrorPage;
|