@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,1037 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
// gen-api-docs.js — generate a standards-compliant OpenAPI 3.1 spec + a human/AI
|
|
4
|
+
// Markdown reference DIRECTLY FROM the live route files (task 1918, BONGOS-V1).
|
|
5
|
+
//
|
|
6
|
+
// WHY: the GDS API had no machine-readable contract — the only endpoint doc was a
|
|
7
|
+
// hand-maintained mirror (docs/routes-permissions.md) that drifted from the code.
|
|
8
|
+
// An AI agent hitting the API had nothing standard to read. This generator emits
|
|
9
|
+
// OpenAPI 3.1 — the format every tool (Postman, Swagger, Redoc, LLM tool-callers)
|
|
10
|
+
// already understands — from the SAME route-introspection engine the ship-time
|
|
11
|
+
// rank gate uses (src/bongos/route-rank-check.js). So the docs enumerate EXACTLY the
|
|
12
|
+
// routes the server mounts and cannot silently drift.
|
|
13
|
+
//
|
|
14
|
+
// DESIGN (mirrors gen-repo-map.js / ADR 0063):
|
|
15
|
+
// - Deterministic: no timestamps, stable sort — so `--check` is a clean CI gate.
|
|
16
|
+
// - Zero runtime deps: a line+regex extractor over the route source, plus a
|
|
17
|
+
// built-in structural OpenAPI validator (no @redocly/cli in the critical path;
|
|
18
|
+
// run `npm run docs:api:lint` for the full standard lint on demand).
|
|
19
|
+
// - Two-root aware (ADR 0108): route discovery + doc writes route through
|
|
20
|
+
// resolveCoreRoot(); never hardcode path.resolve(__dirname,'..','..').
|
|
21
|
+
//
|
|
22
|
+
// Writes (all deterministic):
|
|
23
|
+
// docs/api/openapi.json — OpenAPI 3.1, the machine-readable contract
|
|
24
|
+
// docs/api-reference.md — grouped Markdown reference (for humans + AI)
|
|
25
|
+
// docs/routes-permissions.md — generated pointer stub (the retired hand-mirror)
|
|
26
|
+
//
|
|
27
|
+
// Usage:
|
|
28
|
+
// node scripts/gds/gen-api-docs.js # regenerate the three files
|
|
29
|
+
// node scripts/gds/gen-api-docs.js --check # write nothing; exit 2 on drift (+ structural-validate)
|
|
30
|
+
// node scripts/gds/gen-api-docs.js --json # print the extracted model as JSON (debug)
|
|
31
|
+
|
|
32
|
+
const fs = require('node:fs');
|
|
33
|
+
const path = require('node:path');
|
|
34
|
+
|
|
35
|
+
const { resolveCoreRoot, resolveDocsRoot } = require('../../src/instance-config');
|
|
36
|
+
const rrc = require('../../src/bongos/route-rank-check');
|
|
37
|
+
const { API_PREFIX, API_VERSION, VERSIONED_API_PREFIX } = require('../../src/bongos/api-prefix');
|
|
38
|
+
// Task 2050 (ADR 0118): the enriched extractor resolves named constants used in
|
|
39
|
+
// validate() rules. Both requires are pure (no side effects / no DB / no express):
|
|
40
|
+
// _helpers exports the LIMITS caps; api-errors is the kernel error registry whose
|
|
41
|
+
// category taxonomy the spec advertises (ADR 0116). Keep them pure — the generator
|
|
42
|
+
// must never execute a route file or open a connection.
|
|
43
|
+
const { LIMITS } = require('../../src/bongos/routes/_helpers');
|
|
44
|
+
const apiErrors = require('../../src/bongos/api-errors');
|
|
45
|
+
|
|
46
|
+
const CORE_ROOT = resolveCoreRoot();
|
|
47
|
+
// Routes are READ from the core (CORE_ROOT, via route-rank-check); the generated
|
|
48
|
+
// spec + reference are WRITTEN to the DOCS root — the instance repo when a consumer
|
|
49
|
+
// points at it (`bongos upgrade`), the same coreRoot in a single checkout (ADR 0108,
|
|
50
|
+
// task 2051). So `bongos upgrade` refreshes the CONSUMER's docs, not node_modules.
|
|
51
|
+
const DOCS_ROOT = resolveDocsRoot();
|
|
52
|
+
const OPENAPI_PATH = path.join(DOCS_ROOT, 'docs', 'api', 'openapi.json');
|
|
53
|
+
const REFERENCE_PATH = path.join(DOCS_ROOT, 'docs', 'api-reference.md');
|
|
54
|
+
const PERMISSIONS_PATH = path.join(DOCS_ROOT, 'docs', 'routes-permissions.md');
|
|
55
|
+
|
|
56
|
+
// Spec version tracks the core package version — deterministic (no build clock).
|
|
57
|
+
function specVersion() {
|
|
58
|
+
try {
|
|
59
|
+
return require(path.join(CORE_ROOT, 'package.json')).version || '0.0.0';
|
|
60
|
+
} catch {
|
|
61
|
+
return '0.0.0';
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Human-facing product name for the spec title, from the branding pack. Best-effort:
|
|
66
|
+
// a branding hiccup must not break doc generation (falls back to a neutral name).
|
|
67
|
+
function productName() {
|
|
68
|
+
try {
|
|
69
|
+
const { branding } = require('../../src/branding');
|
|
70
|
+
const b = branding();
|
|
71
|
+
return (b.identity && (b.identity.productName || b.identity.worldName)) || 'Cloud Bongos';
|
|
72
|
+
} catch {
|
|
73
|
+
return 'Cloud Bongos';
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const METHOD_ORDER = ['get', 'post', 'put', 'patch', 'delete'];
|
|
78
|
+
|
|
79
|
+
const RANK_BLURB = {
|
|
80
|
+
public: 'No authentication — any caller.',
|
|
81
|
+
'any-builder': 'Any authenticated builder (row-level ownership enforced in-handler).',
|
|
82
|
+
'metic+archon': 'Metic or Archon rank (review/triage powers).',
|
|
83
|
+
archon: 'Archon only (the single owner — ratification, infra, rank changes).',
|
|
84
|
+
'bfg-principal': 'The BFG cross-builder memory service principal (not a human rank).',
|
|
85
|
+
unknown: 'Gate could not be classified from source — treat as privileged.',
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
// ---------------------------------------------------------------------------
|
|
89
|
+
// Extraction (per route file)
|
|
90
|
+
// ---------------------------------------------------------------------------
|
|
91
|
+
|
|
92
|
+
// The contiguous `//` comment block immediately above line i, `//` stripped and
|
|
93
|
+
// joined. A leading "rank: <val> — " token is dropped so the prose that remains is
|
|
94
|
+
// the human rationale (the rank itself is captured structurally). Blank lines and
|
|
95
|
+
// code end the block.
|
|
96
|
+
// Route-comment descriptions carry bare "#NNN" GDS task refs (e.g. "V3.R91 / #401").
|
|
97
|
+
// Emitted verbatim they mis-render as GitHub *issue* links in the committed
|
|
98
|
+
// docs/api-reference.md AND trip the doc-ref-linkify CI gate — while gen-api-docs
|
|
99
|
+
// --check demands the committed file match this generator's output, so the two
|
|
100
|
+
// checks would fight over the generated file forever. Neutralize the refs at their
|
|
101
|
+
// single origin to plain "task NNN" (brand-neutral, deterministic, and what
|
|
102
|
+
// linkify-refs treats as clean — it never rewrites a hostname). Only a #-prefixed
|
|
103
|
+
// run of digits is touched; "V3.R91", anchors like "#/task", and "# heading" are left
|
|
104
|
+
// alone. (task 1918 follow-up; surfaced landing task 1915.)
|
|
105
|
+
function neutralizeTaskRefs(text) {
|
|
106
|
+
return String(text || '').replace(/#(\d{1,6})\b/g, 'task $1');
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function commentBlockAbove(lines, i) {
|
|
110
|
+
const buf = [];
|
|
111
|
+
for (let j = i - 1; j >= 0; j--) {
|
|
112
|
+
const line = lines[j];
|
|
113
|
+
if (/^\s*\/\//.test(line)) {
|
|
114
|
+
buf.unshift(line.replace(/^\s*\/\/\s?/, ''));
|
|
115
|
+
continue;
|
|
116
|
+
}
|
|
117
|
+
break; // blank or code — stop
|
|
118
|
+
}
|
|
119
|
+
let text = buf.join(' ').replace(/\s+/g, ' ').trim();
|
|
120
|
+
text = text.replace(/^rank:\s*[a-z+-]+\s*(?:—|-|:)?\s*/i, '').trim();
|
|
121
|
+
return neutralizeTaskRefs(text);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// :param → {param}; also return the ordered param name list.
|
|
125
|
+
function pathParams(route) {
|
|
126
|
+
const names = [];
|
|
127
|
+
const openapiPath = route.replace(/:([A-Za-z0-9_]+)/g, (_, n) => {
|
|
128
|
+
names.push(n);
|
|
129
|
+
return `{${n}}`;
|
|
130
|
+
});
|
|
131
|
+
return { openapiPath, names };
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Strip // line comments and /* */ block comments, honoring string literals — so
|
|
135
|
+
// a schema literal's inline comments (`parallel_safe: {}, // note`) don't derail
|
|
136
|
+
// the brace/key scan below.
|
|
137
|
+
function stripComments(str) {
|
|
138
|
+
let out = '';
|
|
139
|
+
let quote = null;
|
|
140
|
+
for (let k = 0; k < str.length; k++) {
|
|
141
|
+
const c = str[k];
|
|
142
|
+
const n = str[k + 1];
|
|
143
|
+
if (quote) {
|
|
144
|
+
out += c;
|
|
145
|
+
if (c === '\\') { out += n == null ? '' : n; k++; continue; }
|
|
146
|
+
if (c === quote) quote = null;
|
|
147
|
+
continue;
|
|
148
|
+
}
|
|
149
|
+
if (c === '"' || c === "'" || c === '`') { quote = c; out += c; continue; }
|
|
150
|
+
if (c === '/' && n === '/') { while (k < str.length && str[k] !== '\n') k++; out += '\n'; continue; }
|
|
151
|
+
if (c === '/' && n === '*') { k += 2; while (k < str.length && !(str[k] === '*' && str[k + 1] === '/')) k++; k++; continue; }
|
|
152
|
+
out += c;
|
|
153
|
+
}
|
|
154
|
+
return out;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// Capture the balanced {...} literal starting at the first '{' at or after `from`.
|
|
158
|
+
// Returns the substring (incl. braces) or null. String/char literals are skipped
|
|
159
|
+
// so a brace inside a quoted default doesn't unbalance the scan.
|
|
160
|
+
function captureBalanced(str, from) {
|
|
161
|
+
const open = str.indexOf('{', from);
|
|
162
|
+
if (open === -1) return null;
|
|
163
|
+
let depth = 0;
|
|
164
|
+
let quote = null;
|
|
165
|
+
for (let k = open; k < str.length; k++) {
|
|
166
|
+
const c = str[k];
|
|
167
|
+
if (quote) {
|
|
168
|
+
if (c === '\\') { k++; continue; }
|
|
169
|
+
if (c === quote) quote = null;
|
|
170
|
+
continue;
|
|
171
|
+
}
|
|
172
|
+
if (c === '"' || c === "'" || c === '`') { quote = c; continue; }
|
|
173
|
+
if (c === '{') depth++;
|
|
174
|
+
else if (c === '}') { depth--; if (depth === 0) return str.slice(open, k + 1); }
|
|
175
|
+
}
|
|
176
|
+
return null;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// Top-level `identifier:` keys of an object-literal string (depth-1 only).
|
|
180
|
+
// Strips comments first so it is robust regardless of caller (idempotent on
|
|
181
|
+
// already-stripped input).
|
|
182
|
+
function topLevelKeys(objLiteral) {
|
|
183
|
+
const inner = stripComments(objLiteral).slice(1, -1);
|
|
184
|
+
const keys = [];
|
|
185
|
+
let depth = 0;
|
|
186
|
+
let quote = null;
|
|
187
|
+
let atFieldStart = true;
|
|
188
|
+
let ident = '';
|
|
189
|
+
for (let k = 0; k < inner.length; k++) {
|
|
190
|
+
const c = inner[k];
|
|
191
|
+
if (quote) {
|
|
192
|
+
if (c === '\\') { k++; continue; }
|
|
193
|
+
if (c === quote) quote = null;
|
|
194
|
+
continue;
|
|
195
|
+
}
|
|
196
|
+
if (c === '"' || c === "'" || c === '`') { quote = c; ident = ''; atFieldStart = false; continue; }
|
|
197
|
+
if (c === '{' || c === '[' || c === '(') { depth++; atFieldStart = false; ident = ''; continue; }
|
|
198
|
+
if (c === '}' || c === ']' || c === ')') { depth--; continue; }
|
|
199
|
+
if (depth === 0) {
|
|
200
|
+
if (c === ',') { atFieldStart = true; ident = ''; continue; }
|
|
201
|
+
if (c === ':' && ident) { keys.push(ident); ident = ''; atFieldStart = false; continue; }
|
|
202
|
+
if (atFieldStart && /[A-Za-z0-9_$]/.test(c)) { ident += c; continue; }
|
|
203
|
+
if (!/\s/.test(c)) { ident = ''; atFieldStart = false; }
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
return keys;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// ---------------------------------------------------------------------------
|
|
210
|
+
// Rule-set extraction (task 2050 / ADR 0118 §1 "enrich the generator")
|
|
211
|
+
// ---------------------------------------------------------------------------
|
|
212
|
+
//
|
|
213
|
+
// The per-route validate() schema object is already structured data (_helpers.js):
|
|
214
|
+
// each field's rule carries type/required/enum/min/max/minLength/maxLength/
|
|
215
|
+
// itemsType/maxItems. We parse that rule set (not just field names) so the spec can
|
|
216
|
+
// emit typed request schemas + named components.schemas. Values are resolved
|
|
217
|
+
// STATICALLY — the generator NEVER executes a route file (the stripComments/
|
|
218
|
+
// captureBalanced philosophy). Literals resolve directly; a named constant
|
|
219
|
+
// (LIMITS.TITLE, a file-local `const CAP = 64`) resolves via a per-file constants
|
|
220
|
+
// map; an unresolvable expression (e.g. a computed bound) is simply omitted from
|
|
221
|
+
// that ONE annotation — the field's type still emits, so a schema degrades
|
|
222
|
+
// gracefully and never emits a wrong constraint.
|
|
223
|
+
|
|
224
|
+
const KNOWN_TYPES = new Set(['string', 'number', 'integer', 'boolean', 'array', 'object']);
|
|
225
|
+
const RULE_NUM_KEYS = ['maxLength', 'minLength', 'min', 'max', 'maxItems'];
|
|
226
|
+
// Only digits, numeric separators, and arithmetic operators — no identifiers. A
|
|
227
|
+
// string matching this is safe to evaluate (it cannot reference anything).
|
|
228
|
+
const NUMERIC_ARITH_RE = /^[-+*/()\s\d._]+$/;
|
|
229
|
+
|
|
230
|
+
// Split an object/array INNER (braces/brackets already stripped) on depth-0 commas,
|
|
231
|
+
// honoring string literals and nesting. Returns the raw segments (untrimmed).
|
|
232
|
+
function splitTopLevel(inner) {
|
|
233
|
+
const parts = [];
|
|
234
|
+
let depth = 0;
|
|
235
|
+
let quote = null;
|
|
236
|
+
let start = 0;
|
|
237
|
+
for (let k = 0; k < inner.length; k++) {
|
|
238
|
+
const c = inner[k];
|
|
239
|
+
if (quote) {
|
|
240
|
+
if (c === '\\') { k++; continue; }
|
|
241
|
+
if (c === quote) quote = null;
|
|
242
|
+
continue;
|
|
243
|
+
}
|
|
244
|
+
if (c === '"' || c === "'" || c === '`') { quote = c; continue; }
|
|
245
|
+
if (c === '{' || c === '[' || c === '(') depth++;
|
|
246
|
+
else if (c === '}' || c === ']' || c === ')') depth--;
|
|
247
|
+
else if (c === ',' && depth === 0) { parts.push(inner.slice(start, k)); start = k + 1; }
|
|
248
|
+
}
|
|
249
|
+
if (inner.slice(start).trim() !== '') parts.push(inner.slice(start));
|
|
250
|
+
return parts;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// Split "key: value" on the FIRST depth-0 colon. Returns { key, value } (both
|
|
254
|
+
// trimmed; key unquoted) or null for a shorthand / non-key-value segment.
|
|
255
|
+
function splitKeyValue(segment) {
|
|
256
|
+
let depth = 0;
|
|
257
|
+
let quote = null;
|
|
258
|
+
for (let k = 0; k < segment.length; k++) {
|
|
259
|
+
const c = segment[k];
|
|
260
|
+
if (quote) {
|
|
261
|
+
if (c === '\\') { k++; continue; }
|
|
262
|
+
if (c === quote) quote = null;
|
|
263
|
+
continue;
|
|
264
|
+
}
|
|
265
|
+
if (c === '"' || c === "'" || c === '`') { quote = c; continue; }
|
|
266
|
+
if (c === '{' || c === '[' || c === '(') depth++;
|
|
267
|
+
else if (c === '}' || c === ']' || c === ')') depth--;
|
|
268
|
+
else if (c === ':' && depth === 0) {
|
|
269
|
+
const key = segment.slice(0, k).trim().replace(/^['"`]|['"`]$/g, '');
|
|
270
|
+
return { key, value: segment.slice(k + 1).trim() };
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
return null;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// Top-level `key: value` entries of an object literal (depth-1), value kept as its
|
|
277
|
+
// raw source substring. Comments are stripped first (idempotent).
|
|
278
|
+
function topLevelEntries(objLiteral) {
|
|
279
|
+
const inner = stripComments(String(objLiteral)).trim().replace(/^\{/, '').replace(/\}$/, '');
|
|
280
|
+
const entries = [];
|
|
281
|
+
for (const seg of splitTopLevel(inner)) {
|
|
282
|
+
const kv = splitKeyValue(seg);
|
|
283
|
+
if (kv && /^[A-Za-z_$][\w$]*$/.test(kv.key)) entries.push(kv);
|
|
284
|
+
}
|
|
285
|
+
return entries;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
// Walk a dotted path (LIMITS.TITLE, TASK_FIELD_BOUNDS.priority.min) against the
|
|
289
|
+
// constants map. Returns the primitive/array value or undefined.
|
|
290
|
+
function resolveConst(pathExpr, constants) {
|
|
291
|
+
let cur = constants;
|
|
292
|
+
for (const p of pathExpr.split('.')) {
|
|
293
|
+
if (cur == null || typeof cur !== 'object' || !(p in cur)) return undefined;
|
|
294
|
+
cur = cur[p];
|
|
295
|
+
}
|
|
296
|
+
return (cur !== null && (typeof cur !== 'object' || Array.isArray(cur))) ? cur : undefined;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
// Resolve a scalar rule value: boolean/number/string literal, pure numeric
|
|
300
|
+
// arithmetic (30 * 1024 * 1024, 2_000_000), or a named constant. Undefined if
|
|
301
|
+
// unresolvable (a computed/dynamic expression).
|
|
302
|
+
function resolveScalar(expr, constants = {}) {
|
|
303
|
+
const s = String(expr).trim();
|
|
304
|
+
if (s === 'true') return true;
|
|
305
|
+
if (s === 'false') return false;
|
|
306
|
+
const str = /^(['"`])([\s\S]*)\1$/.exec(s);
|
|
307
|
+
if (str) return str[2];
|
|
308
|
+
if (NUMERIC_ARITH_RE.test(s) && /\d/.test(s)) {
|
|
309
|
+
try {
|
|
310
|
+
// Safe: charset-restricted to digits/separators/operators/parens — no identifiers.
|
|
311
|
+
const v = Function('"use strict"; return (' + s.replace(/_/g, '') + ');')();
|
|
312
|
+
if (typeof v === 'number' && Number.isFinite(v)) return v;
|
|
313
|
+
} catch { /* not a number after all */ }
|
|
314
|
+
return undefined;
|
|
315
|
+
}
|
|
316
|
+
if (/^[A-Za-z_$][\w$]*(\.[A-Za-z_$][\w$]*)*$/.test(s)) return resolveConst(s, constants);
|
|
317
|
+
return undefined;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
// Resolve an array rule value (enum): a literal array of literals, or a named
|
|
321
|
+
// constant that resolves to such an array. All-or-nothing — if any element is
|
|
322
|
+
// unresolvable the enum is dropped (never emit a partial/wrong enum).
|
|
323
|
+
function resolveArray(expr, constants = {}) {
|
|
324
|
+
const s = String(expr).trim();
|
|
325
|
+
if (s.startsWith('[') && s.endsWith(']')) {
|
|
326
|
+
const out = [];
|
|
327
|
+
for (const seg of splitTopLevel(s.slice(1, -1))) {
|
|
328
|
+
if (seg.trim() === '') continue;
|
|
329
|
+
const v = resolveScalar(seg, constants);
|
|
330
|
+
if (v === undefined) return undefined;
|
|
331
|
+
out.push(v);
|
|
332
|
+
}
|
|
333
|
+
return out;
|
|
334
|
+
}
|
|
335
|
+
const c = resolveConst(s, constants);
|
|
336
|
+
return Array.isArray(c) ? c : undefined;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
// Parse one field's rule object → the recognized, resolved constraint keys.
|
|
340
|
+
function parseRule(ruleLiteral, constants = {}) {
|
|
341
|
+
const rule = {};
|
|
342
|
+
if (!/^\s*\{/.test(String(ruleLiteral))) return rule; // `field: SOMETHING` non-object → unconstrained
|
|
343
|
+
for (const { key, value } of topLevelEntries(ruleLiteral)) {
|
|
344
|
+
if (key === 'required') {
|
|
345
|
+
if (resolveScalar(value, constants) === true) rule.required = true;
|
|
346
|
+
} else if (key === 'type' || key === 'itemsType') {
|
|
347
|
+
const v = resolveScalar(value, constants);
|
|
348
|
+
if (typeof v === 'string' && KNOWN_TYPES.has(v)) rule[key] = v;
|
|
349
|
+
} else if (key === 'enum') {
|
|
350
|
+
const v = resolveArray(value, constants);
|
|
351
|
+
if (Array.isArray(v) && v.length) rule.enum = v;
|
|
352
|
+
} else if (RULE_NUM_KEYS.includes(key)) {
|
|
353
|
+
const v = resolveScalar(value, constants);
|
|
354
|
+
if (typeof v === 'number' && Number.isFinite(v)) rule[key] = v;
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
return rule;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
// Per-file constants map: top-level single-line `const NAME = <literal>` decls
|
|
361
|
+
// (numbers, arithmetic, strings, arrays of literals). Catches the file-local caps
|
|
362
|
+
// + enum arrays (CLAIM_OUTCOMES, INBOX_STATUS_VALUES, SECURITY_DOC_CONTENT_MAX,
|
|
363
|
+
// PUBLISH_BUNDLE_B64_MAX). Merged over the global constants (LIMITS).
|
|
364
|
+
function scanFileConstants(source) {
|
|
365
|
+
const out = {};
|
|
366
|
+
const re = /^\s*const\s+([A-Za-z_$][\w$]*)\s*=\s*([^;\n]+);?\s*$/gm;
|
|
367
|
+
let m;
|
|
368
|
+
while ((m = re.exec(source))) {
|
|
369
|
+
const name = m[1];
|
|
370
|
+
const rhs = m[2].trim();
|
|
371
|
+
const v = rhs.startsWith('[') ? resolveArray(rhs, out) : resolveScalar(rhs, out);
|
|
372
|
+
if (v !== undefined) out[name] = v;
|
|
373
|
+
}
|
|
374
|
+
return out;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
// Global constants available to every route file: the LIMITS caps (the dominant
|
|
378
|
+
// referenced constant). TASK_FIELD_BOUNDS is imported (not file-local) so its
|
|
379
|
+
// bounds are omitted — the affected fields still emit their integer type.
|
|
380
|
+
function globalConstants() {
|
|
381
|
+
return { LIMITS: LIMITS || {} };
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
// The source line where THIS route's handler ends — the next router.<verb>(
|
|
385
|
+
// declaration, or +200 lines, whichever comes first. Bounds the window both the
|
|
386
|
+
// request- and response-shape extractors scan so they never bleed into the next
|
|
387
|
+
// handler.
|
|
388
|
+
function routeEndLine(lines, i) {
|
|
389
|
+
for (let j = i + 1; j < lines.length && j < i + 200; j++) {
|
|
390
|
+
if (/^\s*router\.(get|post|patch|put|delete)\s*\(/.test(lines[j])) return j;
|
|
391
|
+
if (j === i + 199) return j;
|
|
392
|
+
}
|
|
393
|
+
return lines.length;
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
// Request-body shape for a write route: does it read req.body, is it validated, and
|
|
397
|
+
// (best-effort) the top-level field names from its validateOrRespond/validate schema.
|
|
398
|
+
function extractBody(lines, i, method, constants = {}) {
|
|
399
|
+
if (!['POST', 'PUT', 'PATCH', 'DELETE'].includes(method)) return null;
|
|
400
|
+
// Window: from this route to the next route declaration (or +200 lines).
|
|
401
|
+
const end = routeEndLine(lines, i);
|
|
402
|
+
const window = stripComments(lines.slice(i, end).join('\n'));
|
|
403
|
+
const readsBody = /\breq\.body\b/.test(window);
|
|
404
|
+
const orRespond = /validateOrRespond\s*\(\s*req\s*,\s*res\s*,/.exec(window);
|
|
405
|
+
const lowLevel = /\bvalidate\s*\(\s*req\.body/.exec(window);
|
|
406
|
+
const validateCall = orRespond || lowLevel;
|
|
407
|
+
const fields = [];
|
|
408
|
+
const rules = {};
|
|
409
|
+
// validateOrRespond REJECTS unknown fields by default (R11/#1998); the low-level
|
|
410
|
+
// validate() defaults lenient. `strict` drives additionalProperties in the spec.
|
|
411
|
+
let strict = false;
|
|
412
|
+
if (validateCall) {
|
|
413
|
+
const after = validateCall.index + validateCall[0].length;
|
|
414
|
+
const lit = captureBalanced(window, after);
|
|
415
|
+
if (lit) {
|
|
416
|
+
for (const { key, value } of topLevelEntries(lit)) {
|
|
417
|
+
if (/^\d/.test(key)) continue; // defensive: no numeric-leading field names
|
|
418
|
+
fields.push(key);
|
|
419
|
+
rules[key] = parseRule(value, constants);
|
|
420
|
+
}
|
|
421
|
+
if (orRespond) {
|
|
422
|
+
// The opts arg follows the schema literal; strict unless it passes strict:false.
|
|
423
|
+
const optsWindow = window.slice(window.indexOf(lit, after) + lit.length);
|
|
424
|
+
strict = !/\bstrict\s*:\s*false\b/.test(optsWindow);
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
if (!readsBody && !validateCall) return null;
|
|
429
|
+
return { readsBody, validated: Boolean(validateCall), fields, rules, strict };
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
// ---------------------------------------------------------------------------
|
|
433
|
+
// Response-shape extraction (task 2061 / ADR 0124 — type the success RESPONSE)
|
|
434
|
+
// ---------------------------------------------------------------------------
|
|
435
|
+
//
|
|
436
|
+
// Task 2050 typed the REQUEST body from validate(); the SUCCESS response stayed
|
|
437
|
+
// `any`. Routes declare no response schema, but they DO build the body inline —
|
|
438
|
+
// `res.json({ … })` — and that object literal IS the shape. We read it statically
|
|
439
|
+
// (the same captureBalanced/splitTopLevel machinery the request extractor uses),
|
|
440
|
+
// never executing a route. Only the KEYS are provable (a value is usually a
|
|
441
|
+
// variable, so it types as `unknown`); two values type safely — a boolean literal,
|
|
442
|
+
// and the ADR-0119 `pageMeta()` `page` block (→ the shared PageInfo component).
|
|
443
|
+
//
|
|
444
|
+
// DEGRADE-GRACEFULLY CONTRACT — never emit a shape we cannot prove:
|
|
445
|
+
// - a bare-value response (`res.json(row)`), a top-level spread (`{ …rest }`),
|
|
446
|
+
// a computed/quoted key, or an empty `{}` → the route keeps the `ApiResponse`
|
|
447
|
+
// alias (extractResponse returns null). We only ever NARROW `any`, never lie.
|
|
448
|
+
|
|
449
|
+
// The value expression of one response property → a JSON Schema fragment. Only two
|
|
450
|
+
// shapes are typed with confidence; everything else is `{}` (renders as `unknown`).
|
|
451
|
+
function responseValueSchema(expr) {
|
|
452
|
+
const v = String(expr).trim();
|
|
453
|
+
if (v === 'true' || v === 'false') return { type: 'boolean' };
|
|
454
|
+
if (/^pageMeta\s*\(/.test(v)) return { $ref: '#/components/schemas/PageInfo' };
|
|
455
|
+
return {}; // a variable / call / ternary — the KEY is the win; leave the value open
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
// Parse one `res.json({ … })` object literal → { key: valueSchema } (depth-1), or
|
|
459
|
+
// null if the shape can't be fully trusted (spread / non-identifier key / empty).
|
|
460
|
+
function parseResponseObject(objLiteral) {
|
|
461
|
+
const inner = stripComments(String(objLiteral)).trim().replace(/^\{/, '').replace(/\}$/, '');
|
|
462
|
+
const props = {};
|
|
463
|
+
let count = 0;
|
|
464
|
+
for (const seg of splitTopLevel(inner)) {
|
|
465
|
+
const s = seg.trim();
|
|
466
|
+
if (s === '') continue;
|
|
467
|
+
if (s.startsWith('...')) return null; // spread → unknown extra keys
|
|
468
|
+
const kv = splitKeyValue(s);
|
|
469
|
+
let key; let value;
|
|
470
|
+
if (kv) { key = kv.key; value = kv.value; }
|
|
471
|
+
else if (/^[A-Za-z_$][\w$]*$/.test(s)) { key = s; value = s; } // { foo } shorthand
|
|
472
|
+
else return null; // method / unparseable segment
|
|
473
|
+
if (!/^[A-Za-z_$][\w$]*$/.test(key)) return null; // computed/quoted key → give up
|
|
474
|
+
props[key] = responseValueSchema(value);
|
|
475
|
+
count++;
|
|
476
|
+
}
|
|
477
|
+
return count ? props : null;
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
// Merge the shapes of every success `res.json({ … })` in one handler (branches):
|
|
481
|
+
// union the keys; a key present in EVERY branch is required, others optional; a
|
|
482
|
+
// value schema is kept only where all branches agree (else it opens to `unknown`).
|
|
483
|
+
function mergeResponseShapes(shapes) {
|
|
484
|
+
const order = [];
|
|
485
|
+
const byKey = {};
|
|
486
|
+
for (const shape of shapes) {
|
|
487
|
+
for (const [key, schema] of Object.entries(shape)) {
|
|
488
|
+
if (!byKey[key]) { byKey[key] = []; order.push(key); }
|
|
489
|
+
byKey[key].push(schema);
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
const properties = {};
|
|
493
|
+
const required = [];
|
|
494
|
+
for (const key of order) {
|
|
495
|
+
const variants = byKey[key];
|
|
496
|
+
const first = JSON.stringify(variants[0]);
|
|
497
|
+
properties[key] = variants.every((s) => JSON.stringify(s) === first) ? variants[0] : {};
|
|
498
|
+
if (variants.length === shapes.length) required.push(key);
|
|
499
|
+
}
|
|
500
|
+
return { properties, required };
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
// The success-response shape for a route: scan its handler window for success
|
|
504
|
+
// `res.json(…)` / `res.status(2xx).json(…)` calls. Returns { properties, required }
|
|
505
|
+
// when EVERY success body is a provable object literal, else null (degrade).
|
|
506
|
+
function extractResponse(lines, i) {
|
|
507
|
+
const window = stripComments(lines.slice(i, routeEndLine(lines, i)).join('\n'));
|
|
508
|
+
const re = /\bres\s*\.\s*(?:status\s*\(\s*(\d+)\s*\)\s*\.\s*)?json\s*\(/g;
|
|
509
|
+
const shapes = [];
|
|
510
|
+
let m;
|
|
511
|
+
let sawSuccessJson = false;
|
|
512
|
+
while ((m = re.exec(window))) {
|
|
513
|
+
const status = m[1] ? Number(m[1]) : 200;
|
|
514
|
+
if (status < 200 || status > 299) continue; // error body — the envelope covers it
|
|
515
|
+
sawSuccessJson = true;
|
|
516
|
+
let k = m.index + m[0].length; // first char after '('
|
|
517
|
+
while (k < window.length && /\s/.test(window[k])) k++;
|
|
518
|
+
if (window[k] !== '{') return null; // bare value → shape unknowable
|
|
519
|
+
const lit = captureBalanced(window, k);
|
|
520
|
+
const shape = lit && parseResponseObject(lit);
|
|
521
|
+
if (!shape) return null; // couldn't prove the shape
|
|
522
|
+
shapes.push(shape);
|
|
523
|
+
}
|
|
524
|
+
if (!sawSuccessJson || !shapes.length) return null;
|
|
525
|
+
return mergeResponseShapes(shapes);
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
// First path segment → resource tag (tasks, me, public, …). '' → 'root'.
|
|
529
|
+
function tagFor(route) {
|
|
530
|
+
const seg = route.split('/').filter(Boolean)[0] || 'root';
|
|
531
|
+
return seg.replace(/[^A-Za-z0-9_-]/g, '');
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
// The full extracted model: [{ method, route, openapiPath, params, rank, tag,
|
|
535
|
+
// description, body, file }], stable-sorted.
|
|
536
|
+
function extractModel() {
|
|
537
|
+
const routes = [];
|
|
538
|
+
const globals = globalConstants();
|
|
539
|
+
for (const { abs, rel } of rrc.discoverRouteFiles()) {
|
|
540
|
+
const text = fs.readFileSync(abs, 'utf8');
|
|
541
|
+
const lines = text.split('\n');
|
|
542
|
+
// Constants a validate() rule in THIS file may reference (LIMITS + file-local consts).
|
|
543
|
+
const constants = Object.assign({}, globals, scanFileConstants(text));
|
|
544
|
+
for (let i = 0; i < lines.length; i++) {
|
|
545
|
+
const m = /^\s*router\.(get|post|patch|put|delete)\s*\(\s*['"]([^'"]+)['"]/.exec(lines[i]);
|
|
546
|
+
if (!m) continue;
|
|
547
|
+
const method = m[1].toUpperCase();
|
|
548
|
+
const route = m[2];
|
|
549
|
+
const { openapiPath, names } = pathParams(route);
|
|
550
|
+
routes.push({
|
|
551
|
+
method,
|
|
552
|
+
route,
|
|
553
|
+
openapiPath,
|
|
554
|
+
params: names,
|
|
555
|
+
rank: rrc.classifyRank(lines, i),
|
|
556
|
+
tag: tagFor(route),
|
|
557
|
+
description: commentBlockAbove(lines, i),
|
|
558
|
+
body: extractBody(lines, i, method, constants),
|
|
559
|
+
response: extractResponse(lines, i),
|
|
560
|
+
file: rel,
|
|
561
|
+
});
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
routes.sort((a, b) =>
|
|
565
|
+
a.openapiPath.localeCompare(b.openapiPath)
|
|
566
|
+
|| METHOD_ORDER.indexOf(a.method.toLowerCase()) - METHOD_ORDER.indexOf(b.method.toLowerCase())
|
|
567
|
+
|| a.file.localeCompare(b.file));
|
|
568
|
+
return routes;
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
// ---------------------------------------------------------------------------
|
|
572
|
+
// OpenAPI 3.1 assembly
|
|
573
|
+
// ---------------------------------------------------------------------------
|
|
574
|
+
|
|
575
|
+
function operationId(method, openapiPath) {
|
|
576
|
+
const slug = openapiPath.replace(/[{}]/g, '').replace(/[^A-Za-z0-9]+/g, '_').replace(/^_|_$/g, '');
|
|
577
|
+
return `${method.toLowerCase()}_${slug || 'root'}`;
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
function pascalCase(s) {
|
|
581
|
+
return String(s).split(/[^A-Za-z0-9]+/).filter(Boolean)
|
|
582
|
+
.map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join('');
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
// A body field whose validate() rule declared NO type but whose name is a resource
|
|
586
|
+
// id: task_id/builder_id/parent_task_id/… These are Postgres bigint PKs left untyped
|
|
587
|
+
// on purpose (R11/#1998 — they arrive as a JSON number OR a stringified integer).
|
|
588
|
+
// Give them the shared StringifiableId schema instead of degrading to `{}`.
|
|
589
|
+
// version_id is excluded (it is a text slug and always declares type:'string', so it
|
|
590
|
+
// never reaches the untyped branch — the guard is belt-and-braces).
|
|
591
|
+
function isIdField(field) {
|
|
592
|
+
return field !== 'version_id' && (field === 'id' || /_id$/.test(field));
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
// ruleToSchema(field, rule) — one field's parsed rule → a JSON Schema fragment.
|
|
596
|
+
function ruleToSchema(field, rule) {
|
|
597
|
+
if (!rule.type) {
|
|
598
|
+
if (isIdField(field)) return { $ref: '#/components/schemas/StringifiableId' };
|
|
599
|
+
return rule.enum ? { enum: rule.enum } : {}; // untyped, non-id → unconstrained
|
|
600
|
+
}
|
|
601
|
+
const s = {};
|
|
602
|
+
if (rule.type === 'integer' || rule.type === 'number') {
|
|
603
|
+
s.type = rule.type;
|
|
604
|
+
if (rule.min != null) s.minimum = rule.min;
|
|
605
|
+
if (rule.max != null) s.maximum = rule.max;
|
|
606
|
+
} else if (rule.type === 'string') {
|
|
607
|
+
s.type = 'string';
|
|
608
|
+
if (rule.minLength != null) s.minLength = rule.minLength;
|
|
609
|
+
if (rule.maxLength != null) s.maxLength = rule.maxLength;
|
|
610
|
+
} else if (rule.type === 'array') {
|
|
611
|
+
s.type = 'array';
|
|
612
|
+
if (rule.itemsType) s.items = { type: rule.itemsType };
|
|
613
|
+
if (rule.maxItems != null) s.maxItems = rule.maxItems;
|
|
614
|
+
} else {
|
|
615
|
+
s.type = rule.type; // boolean | object
|
|
616
|
+
}
|
|
617
|
+
if (rule.enum) s.enum = rule.enum;
|
|
618
|
+
return s;
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
// requestSchemaFor(r) — the JSON Schema for a write route's body, plus a component
|
|
622
|
+
// name when it carries declared fields (so it lands in components.schemas and the
|
|
623
|
+
// operation references it by $ref). A body with no declared fields (an undocumented
|
|
624
|
+
// or schema-less body) stays an inline open object with no named component.
|
|
625
|
+
function requestSchemaFor(r) {
|
|
626
|
+
const rules = r.body.rules || {};
|
|
627
|
+
const fields = r.body.fields || [];
|
|
628
|
+
if (!fields.length) return { schema: { type: 'object', additionalProperties: true } };
|
|
629
|
+
const properties = {};
|
|
630
|
+
const required = [];
|
|
631
|
+
for (const f of fields) {
|
|
632
|
+
const rule = rules[f] || {};
|
|
633
|
+
properties[f] = ruleToSchema(f, rule);
|
|
634
|
+
if (rule.required) required.push(f);
|
|
635
|
+
}
|
|
636
|
+
const schema = { type: 'object', properties };
|
|
637
|
+
if (required.length) schema.required = required;
|
|
638
|
+
// A validated strict route rejects unknown fields; a lenient/undeclared one allows them.
|
|
639
|
+
schema.additionalProperties = r.body.validated ? !r.body.strict : true;
|
|
640
|
+
return { schema, componentName: pascalCase(operationId(r.method, r.openapiPath)) + 'Request', required };
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
// The reusable component schemas every spec carries (task 2050): the ADR-0116 error
|
|
644
|
+
// envelope + its inner object, and the pg-bigint id convention.
|
|
645
|
+
function baseComponentSchemas() {
|
|
646
|
+
return {
|
|
647
|
+
ApiErrorObject: {
|
|
648
|
+
type: 'object',
|
|
649
|
+
required: ['code', 'message'],
|
|
650
|
+
properties: {
|
|
651
|
+
code: { type: 'string', description: 'Stable machine-readable slug the caller may switch on (e.g. `task_not_found`). Classified into a category (`x-error-categories`); the specific slug is preserved (ADR 0116).' },
|
|
652
|
+
message: { type: 'string', description: 'Human-readable, safe to surface. Never leaks internals unless a handler opts in.' },
|
|
653
|
+
details: { description: 'Optional structured payload — e.g. the validator\'s `[{ field, reason }]` list.' },
|
|
654
|
+
},
|
|
655
|
+
description: 'The inner error object carried by every non-2xx response.',
|
|
656
|
+
},
|
|
657
|
+
ErrorEnvelope: {
|
|
658
|
+
type: 'object',
|
|
659
|
+
required: ['error'],
|
|
660
|
+
properties: { error: { $ref: '#/components/schemas/ApiErrorObject' } },
|
|
661
|
+
description: 'The single error shape returned by EVERY non-2xx response (ADR 0116). A caller reads `error.code`.',
|
|
662
|
+
},
|
|
663
|
+
StringifiableId: {
|
|
664
|
+
type: ['integer', 'string'],
|
|
665
|
+
description: 'A resource id — an integer, or its stringified form (a Postgres bigint serializes as a JSON string, so ids may arrive either way). Accept both.',
|
|
666
|
+
},
|
|
667
|
+
PageInfo: {
|
|
668
|
+
type: 'object',
|
|
669
|
+
required: ['limit', 'offset'],
|
|
670
|
+
properties: {
|
|
671
|
+
limit: { type: 'integer', description: 'Page size actually applied (clamped to the endpoint cap, ADR 0119).' },
|
|
672
|
+
offset: { type: 'integer', description: 'Zero-based row offset of this page.' },
|
|
673
|
+
total: { type: 'integer', description: 'Total matching rows, when cheaply countable; derive "more pages" via `offset + items.length < total`.' },
|
|
674
|
+
},
|
|
675
|
+
description: 'The `page` block of a paginated list response (ADR 0119), built by `pageMeta()`.',
|
|
676
|
+
},
|
|
677
|
+
};
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
// The reusable error responses (ADR 0116) — the envelope, referenced by every
|
|
681
|
+
// non-2xx. Defined ONCE in components.responses; each operation $refs the right one
|
|
682
|
+
// (keeps the committed spec lean instead of inlining the body on 200+ routes).
|
|
683
|
+
function baseComponentResponses() {
|
|
684
|
+
const body = { content: { 'application/json': { schema: { $ref: '#/components/schemas/ErrorEnvelope' } } } };
|
|
685
|
+
return {
|
|
686
|
+
BadRequest: { description: 'Malformed or invalid request (the ADR-0116 error envelope).', ...body },
|
|
687
|
+
ValidationFailed: { description: 'Request body failed schema validation; `error.details` lists the field errors.', ...body },
|
|
688
|
+
Unauthorized: { description: 'No or invalid session.', ...body },
|
|
689
|
+
Forbidden: { description: 'Authenticated but rank/ownership too low for this route.', ...body },
|
|
690
|
+
NotFound: { description: 'The referenced resource does not exist.', ...body },
|
|
691
|
+
};
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
function buildOperation(r, stats, schemas) {
|
|
695
|
+
const op = {
|
|
696
|
+
operationId: operationId(r.method, r.openapiPath),
|
|
697
|
+
tags: [r.tag],
|
|
698
|
+
summary: `${r.method} ${r.route}`,
|
|
699
|
+
description: (r.description ? r.description + '\n\n' : '')
|
|
700
|
+
+ `**Rank:** \`${r.rank}\` — ${RANK_BLURB[r.rank] || RANK_BLURB.unknown}`,
|
|
701
|
+
'x-rank': r.rank,
|
|
702
|
+
'x-source': r.file,
|
|
703
|
+
};
|
|
704
|
+
if (r.params.length) {
|
|
705
|
+
op.parameters = r.params.map((name) => ({
|
|
706
|
+
name, in: 'path', required: true, schema: { type: 'string' },
|
|
707
|
+
description: `Path parameter \`${name}\`.`,
|
|
708
|
+
}));
|
|
709
|
+
}
|
|
710
|
+
if (r.body) {
|
|
711
|
+
const { schema, componentName, required } = requestSchemaFor(r);
|
|
712
|
+
let bodySchema = schema;
|
|
713
|
+
if (componentName) {
|
|
714
|
+
schemas[componentName] = schema; // land it in components.schemas
|
|
715
|
+
bodySchema = { $ref: '#/components/schemas/' + componentName };
|
|
716
|
+
}
|
|
717
|
+
op.requestBody = {
|
|
718
|
+
// A body is REQUIRED when the schema declares at least one required field.
|
|
719
|
+
required: Boolean(required && required.length),
|
|
720
|
+
content: { 'application/json': { schema: bodySchema } },
|
|
721
|
+
'x-validated': r.body.validated,
|
|
722
|
+
};
|
|
723
|
+
if (!r.body.validated) {
|
|
724
|
+
op.requestBody['x-body-undocumented'] = true;
|
|
725
|
+
stats.undocumentedBodies++;
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
const responses = { 200: { description: 'Success.' } };
|
|
729
|
+
// task 2061: attach the statically-proven success shape (from res.json({…})) to
|
|
730
|
+
// 200 as a named component, so the generated client types the return concretely.
|
|
731
|
+
if (r.response && Object.keys(r.response.properties).length) {
|
|
732
|
+
const componentName = pascalCase(operationId(r.method, r.openapiPath)) + 'Response';
|
|
733
|
+
const schema = { type: 'object', properties: r.response.properties };
|
|
734
|
+
if (r.response.required.length) schema.required = r.response.required;
|
|
735
|
+
schemas[componentName] = schema;
|
|
736
|
+
responses[200] = { description: 'Success.', content: { 'application/json': { schema: { $ref: '#/components/schemas/' + componentName } } } };
|
|
737
|
+
stats.responseSchemas++;
|
|
738
|
+
}
|
|
739
|
+
responses[400] = { $ref: (r.body && r.body.validated) ? '#/components/responses/ValidationFailed' : '#/components/responses/BadRequest' };
|
|
740
|
+
if (r.rank !== 'public') {
|
|
741
|
+
responses[401] = { $ref: '#/components/responses/Unauthorized' };
|
|
742
|
+
responses[403] = { $ref: '#/components/responses/Forbidden' };
|
|
743
|
+
}
|
|
744
|
+
if (r.params.length) responses[404] = { $ref: '#/components/responses/NotFound' };
|
|
745
|
+
op.responses = responses;
|
|
746
|
+
op.security = r.rank === 'public' ? [] : [{ builderSession: [] }];
|
|
747
|
+
return op;
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
function buildOpenapi(model) {
|
|
751
|
+
const stats = { undocumentedBodies: 0, responseSchemas: 0 };
|
|
752
|
+
const schemas = baseComponentSchemas();
|
|
753
|
+
const paths = {};
|
|
754
|
+
for (const r of model) {
|
|
755
|
+
if (!paths[r.openapiPath]) paths[r.openapiPath] = {};
|
|
756
|
+
paths[r.openapiPath][r.method.toLowerCase()] = buildOperation(r, stats, schemas);
|
|
757
|
+
}
|
|
758
|
+
// Deterministic key order for components.schemas (base + per-operation request schemas).
|
|
759
|
+
const sortedSchemas = {};
|
|
760
|
+
for (const k of Object.keys(schemas).sort()) sortedSchemas[k] = schemas[k];
|
|
761
|
+
// Stable key order for paths (already sorted in model, but rebuild explicitly).
|
|
762
|
+
const sortedPaths = {};
|
|
763
|
+
for (const p of Object.keys(paths).sort()) sortedPaths[p] = paths[p];
|
|
764
|
+
|
|
765
|
+
const tags = [...new Set(model.map((r) => r.tag))].sort()
|
|
766
|
+
.map((name) => ({ name, description: `Endpoints under \`${API_PREFIX}/${name}\`.` }));
|
|
767
|
+
|
|
768
|
+
const spec = {
|
|
769
|
+
openapi: '3.1.0',
|
|
770
|
+
info: {
|
|
771
|
+
title: `${productName()} — GDS API`,
|
|
772
|
+
version: specVersion(),
|
|
773
|
+
license: { name: 'AGPL-3.0-or-later', url: 'https://www.gnu.org/licenses/agpl-3.0.html' },
|
|
774
|
+
description: [
|
|
775
|
+
`The Game Development System (GDS) API — the build-methodology backend`,
|
|
776
|
+
`(builders, tasks, claims, credits, grading, memory, dev boxes).`,
|
|
777
|
+
``,
|
|
778
|
+
`**API version:** \`${API_VERSION}\` — served at \`${VERSIONED_API_PREFIX}\`. The`,
|
|
779
|
+
`unversioned \`${API_PREFIX}\` and legacy \`/api/gds\` are permanent aliases`,
|
|
780
|
+
`pinned to this version; pin to \`${VERSIONED_API_PREFIX}\` for a stable`,
|
|
781
|
+
`contract (breaking changes ship a new \`/vN\`, ADR 0117). This is the`,
|
|
782
|
+
`CONTRACT version, distinct from the package build in \`info.version\`.`,
|
|
783
|
+
``,
|
|
784
|
+
`**Base path:** \`${API_PREFIX}\` (every path below is relative to it).`,
|
|
785
|
+
``,
|
|
786
|
+
`**Auth:** a builder session — the \`gds_session\` cookie (web) or`,
|
|
787
|
+
`\`Authorization: Bearer <token>\` (CLI). **Ranks** gate writes and are`,
|
|
788
|
+
`enforced server-side per request, never cached (ADR 0016):`,
|
|
789
|
+
`\`public\` < \`any-builder\` < \`metic+archon\` < \`archon\`.`,
|
|
790
|
+
`Each operation carries its required rank in \`x-rank\`.`,
|
|
791
|
+
``,
|
|
792
|
+
`Generated from the live route files by \`scripts/gds/gen-api-docs.js\` —`,
|
|
793
|
+
`do not hand-edit; it regenerates on every ship.`,
|
|
794
|
+
].join('\n'),
|
|
795
|
+
},
|
|
796
|
+
'x-api-version': API_VERSION, // the explicit contract version (ADR 0117)
|
|
797
|
+
servers: [
|
|
798
|
+
{ url: VERSIONED_API_PREFIX, description: 'Versioned API base (same-origin) — pin here for a stable contract.' },
|
|
799
|
+
{ url: API_PREFIX, description: 'Unversioned alias (permanent) — resolves to the current version.' },
|
|
800
|
+
],
|
|
801
|
+
tags,
|
|
802
|
+
paths: sortedPaths,
|
|
803
|
+
components: {
|
|
804
|
+
securitySchemes: {
|
|
805
|
+
builderSession: {
|
|
806
|
+
type: 'apiKey',
|
|
807
|
+
in: 'cookie',
|
|
808
|
+
name: 'gds_session',
|
|
809
|
+
description: 'Builder session. Web clients send the `gds_session` cookie; '
|
|
810
|
+
+ 'CLI clients send `Authorization: Bearer <token>`. Rank is resolved from '
|
|
811
|
+
+ 'the live builder record on every request (ADR 0016).',
|
|
812
|
+
},
|
|
813
|
+
},
|
|
814
|
+
schemas: sortedSchemas,
|
|
815
|
+
responses: baseComponentResponses(),
|
|
816
|
+
},
|
|
817
|
+
// The error-code taxonomy (ADR 0116): every non-2xx `error.code` classifies onto
|
|
818
|
+
// one of these categories, each with a default status + retriable flag. Sourced
|
|
819
|
+
// from the kernel registry (src/bongos/api-errors.js) so docs can't drift from it.
|
|
820
|
+
'x-error-categories': apiErrors.CATEGORIES,
|
|
821
|
+
'x-endpoint-count': model.length,
|
|
822
|
+
'x-schema-count': Object.keys(sortedSchemas).length,
|
|
823
|
+
'x-undocumented-bodies': stats.undocumentedBodies,
|
|
824
|
+
'x-response-schemas': stats.responseSchemas,
|
|
825
|
+
'x-generated-by': 'scripts/gds/gen-api-docs.js',
|
|
826
|
+
};
|
|
827
|
+
return spec;
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
// ---------------------------------------------------------------------------
|
|
831
|
+
// Markdown reference
|
|
832
|
+
// ---------------------------------------------------------------------------
|
|
833
|
+
|
|
834
|
+
const MD_BANNER = '<!-- GENERATED by scripts/gds/gen-api-docs.js — do not hand-edit. Run `node scripts/gds/gen-api-docs.js`. -->';
|
|
835
|
+
|
|
836
|
+
function shortDesc(d) {
|
|
837
|
+
if (!d) return '';
|
|
838
|
+
const first = d.split(/(?<=\.)\s/)[0];
|
|
839
|
+
return (first.length > 140 ? first.slice(0, 137) + '…' : first).replace(/\|/g, '\\|');
|
|
840
|
+
}
|
|
841
|
+
|
|
842
|
+
function buildReferenceMd(model, spec) {
|
|
843
|
+
const L = [];
|
|
844
|
+
L.push(MD_BANNER);
|
|
845
|
+
L.push('');
|
|
846
|
+
L.push('# GDS API reference');
|
|
847
|
+
L.push('');
|
|
848
|
+
L.push(`> **Generated from the live route files** — the route file is authoritative. ${model.length} endpoints across ${new Set(model.map((r) => r.file)).size} route files.`);
|
|
849
|
+
L.push('> Machine-readable spec: [`docs/api/openapi.json`](api/openapi.json) (OpenAPI 3.1). Rendered docs site: **`/docs`** (e.g. `cloudbongos.com/docs`).');
|
|
850
|
+
L.push('');
|
|
851
|
+
L.push(`Base path: \`${API_PREFIX}\`. Ranks (enforced server-side, [ADR 0016](adr/0016-trust-boundary-server-enforced-permissions.md)): \`public\` < \`any-builder\` < \`metic+archon\` < \`archon\`.`);
|
|
852
|
+
L.push('');
|
|
853
|
+
const byTag = new Map();
|
|
854
|
+
for (const r of model) {
|
|
855
|
+
if (!byTag.has(r.tag)) byTag.set(r.tag, []);
|
|
856
|
+
byTag.get(r.tag).push(r);
|
|
857
|
+
}
|
|
858
|
+
for (const tag of [...byTag.keys()].sort()) {
|
|
859
|
+
const rows = byTag.get(tag);
|
|
860
|
+
L.push(`## \`${tag}\` (${rows.length})`);
|
|
861
|
+
L.push('');
|
|
862
|
+
L.push('| Method | Path | Rank | Body | Description |');
|
|
863
|
+
L.push('|---|---|---|---|---|');
|
|
864
|
+
for (const r of rows) {
|
|
865
|
+
const body = r.body
|
|
866
|
+
? (r.body.validated
|
|
867
|
+
? (r.body.fields.length ? '`' + r.body.fields.join('`, `') + '`' : 'validated')
|
|
868
|
+
: '_undocumented_')
|
|
869
|
+
: '—';
|
|
870
|
+
L.push(`| ${r.method} | \`${API_PREFIX}${r.route}\` | \`${r.rank}\` | ${body} | ${shortDesc(r.description)} |`);
|
|
871
|
+
}
|
|
872
|
+
L.push('');
|
|
873
|
+
}
|
|
874
|
+
if (spec['x-undocumented-bodies'] > 0) {
|
|
875
|
+
L.push('---');
|
|
876
|
+
L.push('');
|
|
877
|
+
L.push(`> ${spec['x-undocumented-bodies']} write endpoint(s) take a JSON body with no declared validator schema — their fields are not yet machine-documented. Adding a \`validateOrRespond\` schema (\`src/bongos/routes/_helpers.js\`) surfaces them here automatically.`);
|
|
878
|
+
L.push('');
|
|
879
|
+
}
|
|
880
|
+
return L.join('\n') + '\n';
|
|
881
|
+
}
|
|
882
|
+
|
|
883
|
+
function buildPermissionsStub() {
|
|
884
|
+
return [
|
|
885
|
+
MD_BANNER,
|
|
886
|
+
'',
|
|
887
|
+
'# Routes & permissions — moved',
|
|
888
|
+
'',
|
|
889
|
+
'> This hand-maintained mirror has been **retired**. Every endpoint and its rank gate',
|
|
890
|
+
'> is now generated from the live route files.',
|
|
891
|
+
'',
|
|
892
|
+
'- **Full endpoint + permission reference:** [`docs/api-reference.md`](api-reference.md)',
|
|
893
|
+
'- **Machine-readable spec (OpenAPI 3.1):** [`docs/api/openapi.json`](api/openapi.json)',
|
|
894
|
+
'- **Rendered docs site:** **`/docs`** (e.g. `cloudbongos.com/docs`)',
|
|
895
|
+
'- **Rank-ladder model:** [`docs/canonical-permissions.md`](canonical-permissions.md) · [ADR 0016](adr/0016-trust-boundary-server-enforced-permissions.md)',
|
|
896
|
+
'',
|
|
897
|
+
'Regenerate with `node scripts/gds/gen-api-docs.js`.',
|
|
898
|
+
'',
|
|
899
|
+
].join('\n');
|
|
900
|
+
}
|
|
901
|
+
|
|
902
|
+
// ---------------------------------------------------------------------------
|
|
903
|
+
// Built-in structural OpenAPI 3.1 validator (no external dep)
|
|
904
|
+
// ---------------------------------------------------------------------------
|
|
905
|
+
|
|
906
|
+
function validateOpenapi(spec) {
|
|
907
|
+
const problems = [];
|
|
908
|
+
if (spec.openapi !== '3.1.0') problems.push(`openapi must be '3.1.0' (got ${JSON.stringify(spec.openapi)})`);
|
|
909
|
+
if (!spec.info || typeof spec.info.title !== 'string' || !spec.info.title) problems.push('info.title missing');
|
|
910
|
+
if (!spec.info || typeof spec.info.version !== 'string' || !spec.info.version) problems.push('info.version missing');
|
|
911
|
+
if (!spec.paths || typeof spec.paths !== 'object') problems.push('paths missing');
|
|
912
|
+
const schemes = (spec.components && spec.components.securitySchemes) || {};
|
|
913
|
+
for (const [p, item] of Object.entries(spec.paths || {})) {
|
|
914
|
+
if (!p.startsWith('/')) problems.push(`path ${p} must start with '/'`);
|
|
915
|
+
for (const [method, op] of Object.entries(item)) {
|
|
916
|
+
if (!METHOD_ORDER.includes(method)) problems.push(`${p}: unexpected method ${method}`);
|
|
917
|
+
if (!op.responses || Object.keys(op.responses).length === 0) problems.push(`${method.toUpperCase()} ${p}: no responses`);
|
|
918
|
+
for (const param of op.parameters || []) {
|
|
919
|
+
if (!param.name || !param.in) problems.push(`${method.toUpperCase()} ${p}: malformed parameter`);
|
|
920
|
+
}
|
|
921
|
+
for (const req of op.security || []) {
|
|
922
|
+
for (const name of Object.keys(req)) {
|
|
923
|
+
if (!schemes[name]) problems.push(`${method.toUpperCase()} ${p}: security scheme '${name}' not defined in components`);
|
|
924
|
+
}
|
|
925
|
+
}
|
|
926
|
+
}
|
|
927
|
+
}
|
|
928
|
+
// Every local $ref under paths/components must resolve to a defined component
|
|
929
|
+
// (schema or response) — a dangling $ref is a broken contract (task 2050).
|
|
930
|
+
const definedSchemas = new Set(Object.keys((spec.components && spec.components.schemas) || {}));
|
|
931
|
+
const definedResponses = new Set(Object.keys((spec.components && spec.components.responses) || {}));
|
|
932
|
+
const seen = [];
|
|
933
|
+
(function collectRefs(node) {
|
|
934
|
+
if (!node || typeof node !== 'object') return;
|
|
935
|
+
if (typeof node.$ref === 'string') seen.push(node.$ref);
|
|
936
|
+
for (const v of Object.values(node)) collectRefs(v);
|
|
937
|
+
})({ paths: spec.paths, components: spec.components });
|
|
938
|
+
for (const ref of seen) {
|
|
939
|
+
const sm = /^#\/components\/schemas\/(.+)$/.exec(ref);
|
|
940
|
+
const rm = /^#\/components\/responses\/(.+)$/.exec(ref);
|
|
941
|
+
if (sm) { if (!definedSchemas.has(sm[1])) problems.push(`$ref '${ref}' does not resolve to a defined component schema`); }
|
|
942
|
+
else if (rm) { if (!definedResponses.has(rm[1])) problems.push(`$ref '${ref}' does not resolve to a defined component response`); }
|
|
943
|
+
else problems.push(`unsupported $ref (only #/components/{schemas,responses}/* is emitted): ${ref}`);
|
|
944
|
+
}
|
|
945
|
+
return problems;
|
|
946
|
+
}
|
|
947
|
+
|
|
948
|
+
// ---------------------------------------------------------------------------
|
|
949
|
+
// Main
|
|
950
|
+
// ---------------------------------------------------------------------------
|
|
951
|
+
|
|
952
|
+
function build() {
|
|
953
|
+
const model = extractModel();
|
|
954
|
+
const spec = buildOpenapi(model);
|
|
955
|
+
return {
|
|
956
|
+
model,
|
|
957
|
+
spec,
|
|
958
|
+
files: [
|
|
959
|
+
{ full: OPENAPI_PATH, rel: 'docs/api/openapi.json', content: JSON.stringify(spec, null, 2) + '\n' },
|
|
960
|
+
{ full: REFERENCE_PATH, rel: 'docs/api-reference.md', content: buildReferenceMd(model, spec) },
|
|
961
|
+
{ full: PERMISSIONS_PATH, rel: 'docs/routes-permissions.md', content: buildPermissionsStub() },
|
|
962
|
+
],
|
|
963
|
+
};
|
|
964
|
+
}
|
|
965
|
+
|
|
966
|
+
function main() {
|
|
967
|
+
const args = process.argv.slice(2);
|
|
968
|
+
const checkOnly = args.includes('--check');
|
|
969
|
+
const jsonOnly = args.includes('--json');
|
|
970
|
+
|
|
971
|
+
const { model, spec, files } = build();
|
|
972
|
+
|
|
973
|
+
if (jsonOnly) {
|
|
974
|
+
console.log(JSON.stringify({ endpoints: model.length, undocumentedBodies: spec['x-undocumented-bodies'], model }, null, 2));
|
|
975
|
+
return;
|
|
976
|
+
}
|
|
977
|
+
|
|
978
|
+
// Always structurally validate — a malformed spec is a hard failure in both modes.
|
|
979
|
+
const problems = validateOpenapi(spec);
|
|
980
|
+
if (problems.length) {
|
|
981
|
+
console.error('gen-api-docs: generated spec is NOT valid OpenAPI 3.1:');
|
|
982
|
+
for (const p of problems) console.error(' - ' + p);
|
|
983
|
+
process.exit(1);
|
|
984
|
+
}
|
|
985
|
+
|
|
986
|
+
if (checkOnly) {
|
|
987
|
+
const stale = files.filter((f) => {
|
|
988
|
+
let cur = null;
|
|
989
|
+
try { cur = fs.readFileSync(f.full, 'utf8'); } catch { /* missing → stale */ }
|
|
990
|
+
return cur !== f.content;
|
|
991
|
+
});
|
|
992
|
+
if (stale.length) {
|
|
993
|
+
console.error(`gen-api-docs --check: STALE — would update ${stale.map((f) => f.rel).join(', ')}. Run \`node scripts/gds/gen-api-docs.js\` and commit.`);
|
|
994
|
+
process.exit(2);
|
|
995
|
+
}
|
|
996
|
+
console.log(`gen-api-docs --check: up to date (${model.length} endpoints, ${spec['x-undocumented-bodies']} undocumented bodies).`);
|
|
997
|
+
return;
|
|
998
|
+
}
|
|
999
|
+
|
|
1000
|
+
fs.mkdirSync(path.dirname(OPENAPI_PATH), { recursive: true });
|
|
1001
|
+
for (const f of files) fs.writeFileSync(f.full, f.content);
|
|
1002
|
+
console.log(`gen-api-docs: wrote ${files.length} files — ${model.length} endpoints, ${spec['x-undocumented-bodies']} undocumented bodies.`);
|
|
1003
|
+
}
|
|
1004
|
+
|
|
1005
|
+
if (require.main === module) main();
|
|
1006
|
+
|
|
1007
|
+
module.exports = {
|
|
1008
|
+
extractModel,
|
|
1009
|
+
buildOpenapi,
|
|
1010
|
+
buildReferenceMd,
|
|
1011
|
+
buildPermissionsStub,
|
|
1012
|
+
validateOpenapi,
|
|
1013
|
+
commentBlockAbove,
|
|
1014
|
+
pathParams,
|
|
1015
|
+
captureBalanced,
|
|
1016
|
+
topLevelKeys,
|
|
1017
|
+
topLevelEntries,
|
|
1018
|
+
splitTopLevel,
|
|
1019
|
+
splitKeyValue,
|
|
1020
|
+
resolveScalar,
|
|
1021
|
+
resolveArray,
|
|
1022
|
+
resolveConst,
|
|
1023
|
+
parseRule,
|
|
1024
|
+
scanFileConstants,
|
|
1025
|
+
ruleToSchema,
|
|
1026
|
+
requestSchemaFor,
|
|
1027
|
+
isIdField,
|
|
1028
|
+
pascalCase,
|
|
1029
|
+
extractBody,
|
|
1030
|
+
routeEndLine,
|
|
1031
|
+
responseValueSchema,
|
|
1032
|
+
parseResponseObject,
|
|
1033
|
+
mergeResponseShapes,
|
|
1034
|
+
extractResponse,
|
|
1035
|
+
build,
|
|
1036
|
+
API_PREFIX,
|
|
1037
|
+
};
|