@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,286 @@
|
|
|
1
|
+
// src/bongos/metrics.js — zero-dependency Prometheus exporter (task #1680, V4.R28a).
|
|
2
|
+
//
|
|
3
|
+
// The foundation slice of the observability dashboard (R28 / task 263): a
|
|
4
|
+
// /metrics endpoint the droplet's Prometheus scrapes, feeding the Grafana
|
|
5
|
+
// dashboards that land in task 263. We hand-roll the Prometheus text exposition
|
|
6
|
+
// format rather than add `prom-client` — the project's bootstrapped posture
|
|
7
|
+
// keeps package.json deps minimal, and the format is small + easy to test.
|
|
8
|
+
//
|
|
9
|
+
// CORE-SAFE: lives under src/bongos/ so it's held to the core↔host import boundary
|
|
10
|
+
// (scripts/gds/fitness.js §1) — it imports only ./pool + ../build-info, nothing
|
|
11
|
+
// from the game runtime, so a non-game Cloud Bongos instance runs it unchanged.
|
|
12
|
+
//
|
|
13
|
+
// WHAT IT EXPOSES
|
|
14
|
+
// - process/runtime: uptime, RSS, heap, CPU (counter), event-loop lag, build_info
|
|
15
|
+
// - HTTP (from httpMiddleware): gds_http_requests_total{method,route,status} +
|
|
16
|
+
// gds_http_request_duration_seconds histogram — route normalized to a
|
|
17
|
+
// low-cardinality template so /api/gds/tasks/1380 and /…/1381 share a series.
|
|
18
|
+
// Gives error-rate + 4xx/5xx per route. 401/403 also bump gds_auth_failures_total.
|
|
19
|
+
// - DB gauges (collected at scrape, read-only — never instruments the gate-floor
|
|
20
|
+
// ship pipeline): tasks-by-status, claims (all-time + active), builders-by-rank,
|
|
21
|
+
// per-active-builder shipped-tasks + credits.
|
|
22
|
+
//
|
|
23
|
+
// ACCESS: /metrics is NOT public. authorizeScrape() allows (a) a local Prometheus
|
|
24
|
+
// scrape — loopback remote address with no proxy header — and (b) a Bearer
|
|
25
|
+
// METRICS_TOKEN for a remote scrape. Everything else gets a 404.
|
|
26
|
+
|
|
27
|
+
'use strict';
|
|
28
|
+
|
|
29
|
+
const { pool } = require('./pool');
|
|
30
|
+
|
|
31
|
+
const HIST_BUCKETS = [0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10];
|
|
32
|
+
|
|
33
|
+
// --- registry -------------------------------------------------------------
|
|
34
|
+
// counters: name -> { help, series: Map(labelKey -> { labels, value }) }
|
|
35
|
+
// histograms: name -> { help, series: Map(labelKey -> { labels, buckets:int[], sum, count }) }
|
|
36
|
+
const counters = new Map();
|
|
37
|
+
const histograms = new Map();
|
|
38
|
+
|
|
39
|
+
function labelKey(labels) {
|
|
40
|
+
const keys = Object.keys(labels).sort();
|
|
41
|
+
return keys.map((k) => `${k}=${labels[k]}`).join(',');
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function escapeLabelValue(v) {
|
|
45
|
+
return String(v).replace(/\\/g, '\\\\').replace(/\n/g, '\\n').replace(/"/g, '\\"');
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function renderLabels(labels) {
|
|
49
|
+
const keys = Object.keys(labels);
|
|
50
|
+
if (keys.length === 0) return '';
|
|
51
|
+
return '{' + keys.map((k) => `${k}="${escapeLabelValue(labels[k])}"`).join(',') + '}';
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function incCounter(name, help, labels = {}, by = 1) {
|
|
55
|
+
let c = counters.get(name);
|
|
56
|
+
if (!c) { c = { help, series: new Map() }; counters.set(name, c); }
|
|
57
|
+
const key = labelKey(labels);
|
|
58
|
+
const s = c.series.get(key);
|
|
59
|
+
if (s) s.value += by;
|
|
60
|
+
else c.series.set(key, { labels, value: by });
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function observeHistogram(name, help, labels, value) {
|
|
64
|
+
let h = histograms.get(name);
|
|
65
|
+
if (!h) { h = { help, series: new Map() }; histograms.set(name, h); }
|
|
66
|
+
const key = labelKey(labels);
|
|
67
|
+
let s = h.series.get(key);
|
|
68
|
+
if (!s) { s = { labels, buckets: new Array(HIST_BUCKETS.length).fill(0), sum: 0, count: 0 }; h.series.set(key, s); }
|
|
69
|
+
s.count += 1;
|
|
70
|
+
s.sum += value;
|
|
71
|
+
for (let i = 0; i < HIST_BUCKETS.length; i++) {
|
|
72
|
+
if (value <= HIST_BUCKETS[i]) s.buckets[i] += 1;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// --- event-loop lag sampler (unref'd so it never holds the process open) ---
|
|
77
|
+
let _lagSeconds = 0;
|
|
78
|
+
const _LAG_INTERVAL_MS = 1000;
|
|
79
|
+
const _lagTimer = setInterval(() => {
|
|
80
|
+
const start = process.hrtime.bigint();
|
|
81
|
+
setImmediate(() => {
|
|
82
|
+
const drift = Number(process.hrtime.bigint() - start) / 1e9;
|
|
83
|
+
_lagSeconds = drift > 0 ? drift : 0;
|
|
84
|
+
});
|
|
85
|
+
}, _LAG_INTERVAL_MS);
|
|
86
|
+
if (_lagTimer.unref) _lagTimer.unref();
|
|
87
|
+
|
|
88
|
+
// --- route normalization (bounds label cardinality) -----------------------
|
|
89
|
+
function normalizeRoute(p) {
|
|
90
|
+
if (!p || p === '/') return '/';
|
|
91
|
+
const parts = p.split('/').filter(Boolean).slice(0, 6).map((seg) => {
|
|
92
|
+
if (/^\d+$/.test(seg)) return ':id';
|
|
93
|
+
if (/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(seg)) return ':uuid';
|
|
94
|
+
// Any embedded 8+ hex run = a content-hashed asset (atlas-89d47b50.png) or
|
|
95
|
+
// an opaque token — collapse so per-deploy asset names don't grow cardinality.
|
|
96
|
+
// Route words in this namespace (tasks/claims/resolve/…) have no 8-hex run.
|
|
97
|
+
if (/[0-9a-f]{8,}/i.test(seg)) return ':hash';
|
|
98
|
+
return seg;
|
|
99
|
+
});
|
|
100
|
+
return '/' + parts.join('/');
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// --- HTTP middleware ------------------------------------------------------
|
|
104
|
+
function httpMiddleware() {
|
|
105
|
+
return (req, res, next) => {
|
|
106
|
+
// Don't count the scrape itself or the liveness ping — pure noise.
|
|
107
|
+
if (req.path === '/metrics' || req.path === '/healthz') return next();
|
|
108
|
+
const start = process.hrtime.bigint();
|
|
109
|
+
res.on('finish', () => {
|
|
110
|
+
const route = normalizeRoute(req.path);
|
|
111
|
+
const method = req.method;
|
|
112
|
+
const status = res.statusCode;
|
|
113
|
+
incCounter('gds_http_requests_total', 'Total HTTP requests by method, normalized route, and status code.',
|
|
114
|
+
{ method, route, status: String(status) });
|
|
115
|
+
const dur = Number(process.hrtime.bigint() - start) / 1e9;
|
|
116
|
+
observeHistogram('gds_http_request_duration_seconds', 'HTTP request duration in seconds.',
|
|
117
|
+
{ method, route }, dur);
|
|
118
|
+
if (status === 401 || status === 403) {
|
|
119
|
+
incCounter('gds_auth_failures_total', 'HTTP responses denied for auth reasons (401/403).',
|
|
120
|
+
{ code: String(status) });
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
next();
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// --- access gate for the scrape -------------------------------------------
|
|
128
|
+
function authorizeScrape(req) {
|
|
129
|
+
const get = (h) => (typeof req.get === 'function' ? req.get(h) : (req.headers || {})[h]);
|
|
130
|
+
const token = process.env.METRICS_TOKEN;
|
|
131
|
+
if (token && get('authorization') === `Bearer ${token}`) return true;
|
|
132
|
+
// A local Prometheus scrapes 127.0.0.1:3000 directly (no proxy hop), so a
|
|
133
|
+
// loopback remote address with NO forwarding header is the local agent.
|
|
134
|
+
// Public traffic arrives via Caddy, which always sets X-Forwarded-For.
|
|
135
|
+
const xff = get('x-forwarded-for') || get('x-real-ip');
|
|
136
|
+
const ra = (req.socket && req.socket.remoteAddress) || req.ip || '';
|
|
137
|
+
const loopback = ra === '127.0.0.1' || ra === '::1' || ra === '::ffff:127.0.0.1';
|
|
138
|
+
return !!loopback && !xff;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// --- text rendering -------------------------------------------------------
|
|
142
|
+
function renderCounterText(name, help, series) {
|
|
143
|
+
const out = [`# HELP ${name} ${help}`, `# TYPE ${name} counter`];
|
|
144
|
+
for (const s of series) out.push(`${name}${renderLabels(s.labels)} ${s.value}`);
|
|
145
|
+
return out.join('\n');
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function renderGaugeText(name, help, series) {
|
|
149
|
+
const out = [`# HELP ${name} ${help}`, `# TYPE ${name} gauge`];
|
|
150
|
+
for (const s of series) out.push(`${name}${renderLabels(s.labels)} ${s.value}`);
|
|
151
|
+
return out.join('\n');
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function renderHistogram(name, h) {
|
|
155
|
+
const out = [`# HELP ${name} ${h.help}`, `# TYPE ${name} histogram`];
|
|
156
|
+
for (const s of h.series.values()) {
|
|
157
|
+
let cumulative = 0;
|
|
158
|
+
for (let i = 0; i < HIST_BUCKETS.length; i++) {
|
|
159
|
+
cumulative += s.buckets[i];
|
|
160
|
+
const le = HIST_BUCKETS[i];
|
|
161
|
+
out.push(`${name}_bucket${renderLabels({ ...s.labels, le: String(le) })} ${cumulative}`);
|
|
162
|
+
}
|
|
163
|
+
out.push(`${name}_bucket${renderLabels({ ...s.labels, le: '+Inf' })} ${s.count}`);
|
|
164
|
+
out.push(`${name}_sum${renderLabels(s.labels)} ${s.sum}`);
|
|
165
|
+
out.push(`${name}_count${renderLabels(s.labels)} ${s.count}`);
|
|
166
|
+
}
|
|
167
|
+
return out.join('\n');
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function buildInfoSafe() {
|
|
171
|
+
try {
|
|
172
|
+
const { buildInfo } = require('../build-info');
|
|
173
|
+
const bi = buildInfo() || {};
|
|
174
|
+
return { version: String(bi.commit || bi.version || 'unknown').slice(0, 40) };
|
|
175
|
+
} catch {
|
|
176
|
+
return { version: 'unknown' };
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// DB-derived gauges. Read-only; pass a client for tests. Resilient: a failure
|
|
181
|
+
// here must not 500 the scrape (the caller wraps it), so the in-process metrics
|
|
182
|
+
// always render even if Postgres is briefly unreachable.
|
|
183
|
+
async function collectDbGauges(p = pool) {
|
|
184
|
+
const blocks = [];
|
|
185
|
+
|
|
186
|
+
const tasks = await p.query('SELECT status, count(*)::int AS n FROM tasks GROUP BY status');
|
|
187
|
+
blocks.push(renderGaugeText('gds_tasks', 'Tasks by lifecycle status.',
|
|
188
|
+
tasks.rows.map((r) => ({ labels: { status: r.status }, value: r.n }))));
|
|
189
|
+
|
|
190
|
+
const claims = await p.query(
|
|
191
|
+
"SELECT count(*)::int AS total, count(*) FILTER (WHERE released_at IS NULL)::int AS active FROM claims");
|
|
192
|
+
blocks.push(renderGaugeText('gds_claims_total', 'All-time claim count (monotonic — use for claim rate).',
|
|
193
|
+
[{ labels: {}, value: claims.rows[0].total }]));
|
|
194
|
+
blocks.push(renderGaugeText('gds_claims_active', 'Currently active (unreleased) claims.',
|
|
195
|
+
[{ labels: {}, value: claims.rows[0].active }]));
|
|
196
|
+
|
|
197
|
+
const builders = await p.query('SELECT rank, count(*)::int AS n FROM builders GROUP BY rank');
|
|
198
|
+
blocks.push(renderGaugeText('gds_builders', 'Builders by rank.',
|
|
199
|
+
builders.rows.map((r) => ({ labels: { rank: r.rank }, value: r.n }))));
|
|
200
|
+
|
|
201
|
+
const perBuilder = await p.query(
|
|
202
|
+
`SELECT b.github_login,
|
|
203
|
+
b.total_credits::int AS credits,
|
|
204
|
+
(SELECT count(*) FROM tasks t WHERE t.shipped_by = b.id AND t.status = 'shipped')::int AS shipped
|
|
205
|
+
FROM builders b
|
|
206
|
+
WHERE b.status = 'active'`);
|
|
207
|
+
blocks.push(renderGaugeText('gds_builder_shipped_tasks', 'Shipped tasks per active builder.',
|
|
208
|
+
perBuilder.rows.map((r) => ({ labels: { builder: r.github_login }, value: r.shipped }))));
|
|
209
|
+
blocks.push(renderGaugeText('gds_builder_credits', 'Total credits per active builder.',
|
|
210
|
+
perBuilder.rows.map((r) => ({ labels: { builder: r.github_login }, value: r.credits }))));
|
|
211
|
+
|
|
212
|
+
return blocks.join('\n');
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// Full exposition document. `p` injectable for tests.
|
|
216
|
+
async function render(p = pool) {
|
|
217
|
+
const out = [];
|
|
218
|
+
const mem = process.memoryUsage();
|
|
219
|
+
const cpu = process.cpuUsage(); // microseconds, cumulative since start
|
|
220
|
+
|
|
221
|
+
out.push(renderGaugeText('gds_process_uptime_seconds', 'Process uptime in seconds.',
|
|
222
|
+
[{ labels: {}, value: process.uptime() }]));
|
|
223
|
+
out.push(renderGaugeText('gds_process_resident_memory_bytes', 'Resident set size in bytes.',
|
|
224
|
+
[{ labels: {}, value: mem.rss }]));
|
|
225
|
+
out.push(renderGaugeText('gds_process_heap_used_bytes', 'V8 heap used in bytes.',
|
|
226
|
+
[{ labels: {}, value: mem.heapUsed }]));
|
|
227
|
+
out.push(renderGaugeText('gds_process_heap_total_bytes', 'V8 heap total in bytes.',
|
|
228
|
+
[{ labels: {}, value: mem.heapTotal }]));
|
|
229
|
+
out.push(renderCounterText('gds_process_cpu_user_seconds_total', 'Total user CPU seconds.',
|
|
230
|
+
[{ labels: {}, value: cpu.user / 1e6 }]));
|
|
231
|
+
out.push(renderCounterText('gds_process_cpu_system_seconds_total', 'Total system CPU seconds.',
|
|
232
|
+
[{ labels: {}, value: cpu.system / 1e6 }]));
|
|
233
|
+
out.push(renderGaugeText('gds_nodejs_eventloop_lag_seconds', 'Sampled event-loop lag in seconds.',
|
|
234
|
+
[{ labels: {}, value: _lagSeconds }]));
|
|
235
|
+
out.push(renderGaugeText('gds_build_info', 'Deployed build info (constant 1; data in labels).',
|
|
236
|
+
[{ labels: { version: buildInfoSafe().version, node: process.version }, value: 1 }]));
|
|
237
|
+
|
|
238
|
+
for (const [name, c] of counters) out.push(renderCounterText(name, c.help, [...c.series.values()]));
|
|
239
|
+
for (const [name, h] of histograms) out.push(renderHistogram(name, h));
|
|
240
|
+
|
|
241
|
+
try {
|
|
242
|
+
out.push(await collectDbGauges(p));
|
|
243
|
+
out.push(renderGaugeText('gds_db_scrape_ok', '1 if the last DB-gauge scrape succeeded, 0 otherwise.',
|
|
244
|
+
[{ labels: {}, value: 1 }]));
|
|
245
|
+
} catch {
|
|
246
|
+
out.push(renderGaugeText('gds_db_scrape_ok', '1 if the last DB-gauge scrape succeeded, 0 otherwise.',
|
|
247
|
+
[{ labels: {}, value: 0 }]));
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
return out.filter(Boolean).join('\n') + '\n';
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
function metricsHandler() {
|
|
254
|
+
return async (req, res) => {
|
|
255
|
+
if (!authorizeScrape(req)) {
|
|
256
|
+
res.status(404).type('text/plain').send('not found');
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
259
|
+
try {
|
|
260
|
+
const body = await render();
|
|
261
|
+
res.set('Content-Type', 'text/plain; version=0.0.4; charset=utf-8').send(body);
|
|
262
|
+
} catch {
|
|
263
|
+
res.status(500).type('text/plain').send('# metrics render error\n');
|
|
264
|
+
}
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// Test seam — wipe the in-process registry between cases.
|
|
269
|
+
function __resetForTests() {
|
|
270
|
+
counters.clear();
|
|
271
|
+
histograms.clear();
|
|
272
|
+
_lagSeconds = 0;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
module.exports = {
|
|
276
|
+
incCounter,
|
|
277
|
+
observeHistogram,
|
|
278
|
+
normalizeRoute,
|
|
279
|
+
httpMiddleware,
|
|
280
|
+
authorizeScrape,
|
|
281
|
+
collectDbGauges,
|
|
282
|
+
render,
|
|
283
|
+
metricsHandler,
|
|
284
|
+
HIST_BUCKETS,
|
|
285
|
+
__resetForTests,
|
|
286
|
+
};
|
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
// src/bongos/middleware/audit.js — append-only audit log for authenticated writes
|
|
2
|
+
// (V3.R35 / #256).
|
|
3
|
+
//
|
|
4
|
+
// Wire-up: mounted INSIDE the /api/gds sub-router by routes.js, AFTER the JSON
|
|
5
|
+
// body parser and AFTER each route's auth middleware has run (the middleware
|
|
6
|
+
// fires on res 'finish', so by then auth either populated req.builder or sent
|
|
7
|
+
// a 401 — both cases are handled below).
|
|
8
|
+
//
|
|
9
|
+
// What it captures:
|
|
10
|
+
// - Every authenticated POST/PATCH/PUT/DELETE that the /api/gds router
|
|
11
|
+
// served. Reads (GET) are explicitly skipped — they're high-volume and
|
|
12
|
+
// low-signal for forensics.
|
|
13
|
+
// - The redacted request body, the response status the client actually saw,
|
|
14
|
+
// IP, User-Agent.
|
|
15
|
+
//
|
|
16
|
+
// Redaction:
|
|
17
|
+
// - Walks the parsed JSON body recursively. Any KEY whose lower-cased name
|
|
18
|
+
// contains 'token', 'password', 'key', or 'secret' has its value replaced
|
|
19
|
+
// with the literal string '<redacted>'. Recurses into nested objects AND
|
|
20
|
+
// arrays. Non-plain values (numbers, booleans, strings) are not deepened.
|
|
21
|
+
// - Top-level primitives (the rare API that does `app.use(express.text())`
|
|
22
|
+
// and POSTs a raw string) just get the body stringified into
|
|
23
|
+
// { _value: <body> } so the column always carries an object.
|
|
24
|
+
//
|
|
25
|
+
// CRITICAL fail-safety properties (verified by tests/audit_middleware.mjs):
|
|
26
|
+
// 1. The middleware NEVER throws upward. The only way it can affect the
|
|
27
|
+
// request path is by populating req.* — and it does not. We attach to
|
|
28
|
+
// res.on('finish'), which runs after the response has been sent.
|
|
29
|
+
// 2. If logging fails (DB down, schema mismatch, jsonb shape error, anything)
|
|
30
|
+
// the error is caught, logged to console.error, and swallowed. The user
|
|
31
|
+
// already got their response; the audit miss is a known acceptable
|
|
32
|
+
// degradation per the design.
|
|
33
|
+
// 3. The 'finish' handler does its DB work in a fire-and-forget Promise so
|
|
34
|
+
// the response is never delayed by audit I/O.
|
|
35
|
+
//
|
|
36
|
+
// Why not a Postgres trigger? — Two reasons. (a) Several writable surfaces
|
|
37
|
+
// (e.g. session cookies, OAuth callbacks) don't touch a single canonical
|
|
38
|
+
// table, so per-table triggers would miss them. (b) The route-shaped log
|
|
39
|
+
// ('/tasks/:id', 'POST', the redacted body) is what makes the R67 security
|
|
40
|
+
// view useful; that information is lost at the SQL layer.
|
|
41
|
+
|
|
42
|
+
const db = require('../db');
|
|
43
|
+
|
|
44
|
+
const REDACTION_TOKENS = ['token', 'password', 'key', 'secret'];
|
|
45
|
+
const REDACTED = '<redacted>';
|
|
46
|
+
|
|
47
|
+
// Audit-body byte cap (SR-17 / task 997). The middleware stores a copy of every
|
|
48
|
+
// authenticated write body in audit_log. Routes that mount their own large body
|
|
49
|
+
// parser (POST /sessions, /memory/sync — well above the 64 KB global cap) could
|
|
50
|
+
// therefore write an UNBOUNDED second copy of a multi-MB payload here, doubling
|
|
51
|
+
// the disk-fill pressure of the primary write. We cap the SERIALIZED stored body
|
|
52
|
+
// to MAX_AUDIT_BODY_BYTES and replace an over-cap body with a small marker object
|
|
53
|
+
// that preserves the redacted prefix + the original size — enough for forensics,
|
|
54
|
+
// bounded on disk. 8 KB is generous for ordinary writes (task patches, claims,
|
|
55
|
+
// cost rows are all far under) while ensuring a fat upload can't bloat audit_log.
|
|
56
|
+
const MAX_AUDIT_BODY_BYTES = 8 * 1024;
|
|
57
|
+
|
|
58
|
+
// shouldRedactKey — case-insensitive substring match against the redaction
|
|
59
|
+
// vocabulary. Exported for the test suite.
|
|
60
|
+
function shouldRedactKey(key) {
|
|
61
|
+
if (typeof key !== 'string') return false;
|
|
62
|
+
const lower = key.toLowerCase();
|
|
63
|
+
return REDACTION_TOKENS.some((tok) => lower.includes(tok));
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// redactBody — returns a NEW value with sensitive fields replaced. Does not
|
|
67
|
+
// mutate the input. Pure + exported for unit tests.
|
|
68
|
+
//
|
|
69
|
+
// Object → new object, recursing into each value; sensitive keys → '<redacted>'.
|
|
70
|
+
// Array → new array, recursing into each element. (Array indices aren't keys
|
|
71
|
+
// in the redaction sense — we redact only by KEY name, not by
|
|
72
|
+
// position.)
|
|
73
|
+
// Other → returned unchanged.
|
|
74
|
+
//
|
|
75
|
+
// Guards against:
|
|
76
|
+
// - Cyclic structures (would otherwise infinitely recurse — we limit depth).
|
|
77
|
+
// - Non-plain objects (Buffer, Date, etc.) — we copy them by reference; the
|
|
78
|
+
// jsonb serializer will stringify or skip as appropriate. We don't try to
|
|
79
|
+
// deeply normalize because the request body in Express is always a plain
|
|
80
|
+
// object (express.json output).
|
|
81
|
+
function redactBody(value, depth = 0) {
|
|
82
|
+
if (depth > 32) return REDACTED; // pathological nesting → redact wholesale
|
|
83
|
+
if (value === null || value === undefined) return value;
|
|
84
|
+
if (Array.isArray(value)) {
|
|
85
|
+
return value.map((v) => redactBody(v, depth + 1));
|
|
86
|
+
}
|
|
87
|
+
if (typeof value === 'object') {
|
|
88
|
+
// Skip non-plain objects (we never see Date/Buffer in express.json output,
|
|
89
|
+
// but be defensive).
|
|
90
|
+
if (value.constructor && value.constructor !== Object) {
|
|
91
|
+
// For non-plain objects we can't safely walk: stringify shape.
|
|
92
|
+
try {
|
|
93
|
+
return JSON.parse(JSON.stringify(value));
|
|
94
|
+
} catch (_) {
|
|
95
|
+
return REDACTED;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
const out = {};
|
|
99
|
+
for (const k of Object.keys(value)) {
|
|
100
|
+
if (shouldRedactKey(k)) {
|
|
101
|
+
out[k] = REDACTED;
|
|
102
|
+
} else {
|
|
103
|
+
out[k] = redactBody(value[k], depth + 1);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
return out;
|
|
107
|
+
}
|
|
108
|
+
// Primitive — return as-is.
|
|
109
|
+
return value;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// normalizeBody — coerce whatever req.body is into a plain object suitable for
|
|
113
|
+
// jsonb storage. Wraps non-object payloads in { _value }.
|
|
114
|
+
function normalizeBody(body) {
|
|
115
|
+
if (body === undefined || body === null) return {};
|
|
116
|
+
if (Array.isArray(body)) return { _value: body };
|
|
117
|
+
if (typeof body === 'object') return body;
|
|
118
|
+
return { _value: body };
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// truncateForAudit — bound the SERIALIZED size of the (already-redacted) body
|
|
122
|
+
// we persist (SR-17). If the body serializes within MAX_AUDIT_BODY_BYTES it's
|
|
123
|
+
// stored verbatim. If it's over, we replace it with a small marker object that
|
|
124
|
+
// keeps a redacted prefix + the original byte count, so the row is still
|
|
125
|
+
// forensically useful but the on-disk footprint is bounded. Pure + exported for
|
|
126
|
+
// tests. Never throws — a serialization failure falls back to the marker.
|
|
127
|
+
function truncateForAudit(body, maxBytes = MAX_AUDIT_BODY_BYTES) {
|
|
128
|
+
let serialized;
|
|
129
|
+
try {
|
|
130
|
+
serialized = JSON.stringify(body);
|
|
131
|
+
} catch (_) {
|
|
132
|
+
return { _audit_truncated: true, _reason: 'unserializable' };
|
|
133
|
+
}
|
|
134
|
+
if (serialized === undefined) return body; // undefined → nothing to store
|
|
135
|
+
const bytes = Buffer.byteLength(serialized, 'utf8');
|
|
136
|
+
if (bytes <= maxBytes) return body;
|
|
137
|
+
// Keep a head slice for context. Slice on the serialized string (not the
|
|
138
|
+
// object) so the cap is a hard byte bound regardless of body shape; the
|
|
139
|
+
// prefix is already redacted because we serialized the redacted body.
|
|
140
|
+
const prefix = serialized.slice(0, maxBytes);
|
|
141
|
+
return {
|
|
142
|
+
_audit_truncated: true,
|
|
143
|
+
_original_bytes: bytes,
|
|
144
|
+
_marker: `…[truncated ${bytes - maxBytes} bytes of ${bytes}]`,
|
|
145
|
+
_prefix: prefix,
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// clientIp — prefer Express's resolved req.ip (which honors 'trust proxy', set
|
|
150
|
+
// in server.js so x-forwarded-for from Cloudflare → Caddy is respected) and
|
|
151
|
+
// fall back to the raw socket address.
|
|
152
|
+
function clientIp(req) {
|
|
153
|
+
if (req.ip) return req.ip;
|
|
154
|
+
const sock = req.socket || req.connection;
|
|
155
|
+
return sock && sock.remoteAddress ? sock.remoteAddress : null;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// routePattern — return the matched route pattern (e.g. '/tasks/:id') rather
|
|
159
|
+
// than the concrete URL ('/tasks/256'). When the request didn't hit a route
|
|
160
|
+
// (e.g. 404 from the router), fall back to req.path.
|
|
161
|
+
function routePattern(req) {
|
|
162
|
+
if (req.route && req.route.path) {
|
|
163
|
+
// baseUrl carries '/api/gds' plus any sub-router prefix; combining them
|
|
164
|
+
// yields the full pattern. trim trailing slash for tidiness.
|
|
165
|
+
const base = (req.baseUrl || '').replace(/\/+$/, '');
|
|
166
|
+
const tail = req.route.path === '/' ? '' : req.route.path;
|
|
167
|
+
const combined = `${base}${tail}` || '/';
|
|
168
|
+
return combined;
|
|
169
|
+
}
|
|
170
|
+
if (req.originalUrl) {
|
|
171
|
+
// Strip query string.
|
|
172
|
+
return String(req.originalUrl).split('?')[0];
|
|
173
|
+
}
|
|
174
|
+
return req.path || '/';
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// isAuditableMethod — POST/PATCH/PUT/DELETE only. Per spec: reads are NOT
|
|
178
|
+
// logged.
|
|
179
|
+
function isAuditableMethod(method) {
|
|
180
|
+
return method === 'POST' || method === 'PATCH' || method === 'PUT' || method === 'DELETE';
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// auditMiddleware — Express middleware factory. The factory pattern matches
|
|
184
|
+
// what other middleware factories in the GDS do.
|
|
185
|
+
//
|
|
186
|
+
// Optional opts:
|
|
187
|
+
// insertFn — alternative insert function (used in tests to inject a
|
|
188
|
+
// throwing implementation and prove fail-safety).
|
|
189
|
+
function auditMiddleware(opts = {}) {
|
|
190
|
+
const insert = typeof opts.insertFn === 'function' ? opts.insertFn : db.insertAuditLog;
|
|
191
|
+
|
|
192
|
+
return function audit(req, res, next) {
|
|
193
|
+
// Fast-path: never instrument reads. (Express normalizes method to upper.)
|
|
194
|
+
if (!isAuditableMethod(req.method)) return next();
|
|
195
|
+
|
|
196
|
+
// Snapshot the inputs NOW. By the time 'finish' fires, downstream handlers
|
|
197
|
+
// could have mutated req.body (e.g. an upsert helper that pops fields off
|
|
198
|
+
// the payload). We want the audit row to reflect what the CLIENT sent.
|
|
199
|
+
let snapshotBody;
|
|
200
|
+
try {
|
|
201
|
+
// Redact FIRST (so the truncation prefix is already scrubbed), then cap
|
|
202
|
+
// the serialized size so a large upload can't write an unbounded second
|
|
203
|
+
// copy into audit_log (SR-17).
|
|
204
|
+
snapshotBody = truncateForAudit(redactBody(normalizeBody(req.body)));
|
|
205
|
+
} catch (err) {
|
|
206
|
+
// redaction failures are themselves audit-failures: log + continue.
|
|
207
|
+
// We still attach the finish handler so a row goes in WITH an empty body.
|
|
208
|
+
console.error('[audit] redact_failed', err);
|
|
209
|
+
snapshotBody = { _redact_failed: true };
|
|
210
|
+
}
|
|
211
|
+
const ip = clientIp(req);
|
|
212
|
+
const userAgent = req.headers['user-agent'] || null;
|
|
213
|
+
const method = req.method;
|
|
214
|
+
|
|
215
|
+
// Attach the finish hook. 'finish' fires AFTER the response headers + body
|
|
216
|
+
// have been flushed to the kernel, so we cannot block the user response by
|
|
217
|
+
// doing work here. 'close' also fires on aborted requests; we do NOT log
|
|
218
|
+
// on close to keep the stream interpretable as "completed responses only".
|
|
219
|
+
let alreadyLogged = false;
|
|
220
|
+
res.on('finish', () => {
|
|
221
|
+
if (alreadyLogged) return; // belt-and-braces; 'finish' fires once
|
|
222
|
+
alreadyLogged = true;
|
|
223
|
+
|
|
224
|
+
// Skip rows where auth didn't pass — by spec we log AUTHENTICATED writes
|
|
225
|
+
// only. requireBuilder sends 401 + populates nothing; in that case
|
|
226
|
+
// req.builder is undefined.
|
|
227
|
+
const builder = req.builder;
|
|
228
|
+
if (!builder || builder.id === undefined || builder.id === null) return;
|
|
229
|
+
|
|
230
|
+
// Snapshot route AFTER routing has finished — the matched route is only
|
|
231
|
+
// populated after Express finds the handler. This is the reason we
|
|
232
|
+
// can't compute the route up front.
|
|
233
|
+
const route = routePattern(req);
|
|
234
|
+
const status = res.statusCode;
|
|
235
|
+
|
|
236
|
+
// Fire-and-forget. Wrapping in Promise.resolve().then(...).catch(...)
|
|
237
|
+
// gives us the swallow guarantee for both sync throws and async
|
|
238
|
+
// rejections.
|
|
239
|
+
Promise.resolve()
|
|
240
|
+
.then(() =>
|
|
241
|
+
insert({
|
|
242
|
+
builderId: builder.id,
|
|
243
|
+
route,
|
|
244
|
+
method,
|
|
245
|
+
requestBodyRedacted: snapshotBody,
|
|
246
|
+
responseStatus: status,
|
|
247
|
+
ip,
|
|
248
|
+
userAgent,
|
|
249
|
+
})
|
|
250
|
+
)
|
|
251
|
+
.catch((err) => {
|
|
252
|
+
// SWALLOW. Audit failures must not surface to the caller and must
|
|
253
|
+
// not affect future requests. Console-error is the only effect.
|
|
254
|
+
console.error('[audit] insert_failed', {
|
|
255
|
+
route,
|
|
256
|
+
method,
|
|
257
|
+
status,
|
|
258
|
+
err: err && err.message ? err.message : String(err),
|
|
259
|
+
});
|
|
260
|
+
});
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
next();
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
module.exports = {
|
|
268
|
+
auditMiddleware,
|
|
269
|
+
redactBody,
|
|
270
|
+
shouldRedactKey,
|
|
271
|
+
truncateForAudit,
|
|
272
|
+
// Exposed for testing only.
|
|
273
|
+
_internals: { normalizeBody, routePattern, isAuditableMethod, REDACTED, MAX_AUDIT_BODY_BYTES },
|
|
274
|
+
};
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
// src/bongos/middleware/error-envelope.js — the central error contract (task 1989, R02).
|
|
2
|
+
//
|
|
3
|
+
// Two pieces that make the R01 envelope (src/bongos/api-errors.js, ADR 0116) the
|
|
4
|
+
// guaranteed shape of every API failure:
|
|
5
|
+
//
|
|
6
|
+
// 1. attachFail — a tiny response decorator, mounted EARLY. It hangs a
|
|
7
|
+
// `res.fail(code, status?, details?)` helper on every response so a handler
|
|
8
|
+
// can emit the envelope in one call instead of hand-rolling
|
|
9
|
+
// `res.status(n).json({error:'x'})`. Status defaults from the registry; an
|
|
10
|
+
// explicit numeric status wins (so a site keeping its current status can).
|
|
11
|
+
//
|
|
12
|
+
// 2. errorEnvelopeHandler — the Express 4-arg error handler, mounted LAST
|
|
13
|
+
// (after every feature + module router). It is the safety net: any thrown
|
|
14
|
+
// ApiError, any `next(err)`, and body-parser failures (malformed JSON,
|
|
15
|
+
// oversized body) all come out as the envelope instead of Express's default
|
|
16
|
+
// HTML/text error page.
|
|
17
|
+
//
|
|
18
|
+
// BACKWARD-COMPATIBLE by design. It adds a helper and a trailing net; it changes
|
|
19
|
+
// no existing response. Handlers that already answer directly (the ~219 ad-hoc
|
|
20
|
+
// sites, and _helpers.asyncHandler which catches + responds itself) keep working
|
|
21
|
+
// untouched until the R04 sweep migrates them onto res.fail / thrown ApiError.
|
|
22
|
+
|
|
23
|
+
'use strict';
|
|
24
|
+
|
|
25
|
+
const { ApiError, isApiError, envelope, statusForCode } = require('../api-errors');
|
|
26
|
+
|
|
27
|
+
// attachFail — decorate res with fail(). Mounted once, before the routers.
|
|
28
|
+
function attachFail(req, res, next) {
|
|
29
|
+
// res.fail(code) → registry status, message = code
|
|
30
|
+
// res.fail(code, 409) → explicit status
|
|
31
|
+
// res.fail(code, 409, details) → with structured details
|
|
32
|
+
// res.fail(code, { status, message, details }) → object form
|
|
33
|
+
res.fail = function fail(code, statusOrOpts, details) {
|
|
34
|
+
let status;
|
|
35
|
+
let message;
|
|
36
|
+
let det = details;
|
|
37
|
+
if (statusOrOpts && typeof statusOrOpts === 'object') {
|
|
38
|
+
status = statusOrOpts.status;
|
|
39
|
+
message = statusOrOpts.message;
|
|
40
|
+
if (statusOrOpts.details !== undefined) det = statusOrOpts.details;
|
|
41
|
+
} else if (statusOrOpts !== undefined) {
|
|
42
|
+
status = statusOrOpts;
|
|
43
|
+
}
|
|
44
|
+
const httpStatus = statusForCode(code, status);
|
|
45
|
+
if (res.headersSent) return res;
|
|
46
|
+
return res.status(httpStatus).json(envelope(code, message, det));
|
|
47
|
+
};
|
|
48
|
+
next();
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Map an Express/body-parser/http-errors error to a canonical registry code.
|
|
52
|
+
// Body-parser tags parse failures with err.type; http-errors carries a numeric
|
|
53
|
+
// status/statusCode. We translate those to the envelope so a malformed request
|
|
54
|
+
// gets a machine-readable code, not a stack-trace HTML page.
|
|
55
|
+
function codeForFrameworkError(err) {
|
|
56
|
+
if (!err) return null;
|
|
57
|
+
if (err.type === 'entity.too.large') return { code: 'payload_too_large', status: 413 };
|
|
58
|
+
if (err.type === 'entity.parse.failed' || err instanceof SyntaxError) {
|
|
59
|
+
return { code: 'bad_json', status: 400 };
|
|
60
|
+
}
|
|
61
|
+
const status = err.status || err.statusCode;
|
|
62
|
+
if (Number.isInteger(status) && status >= 400 && status < 600) {
|
|
63
|
+
// A framework error already carrying a status (e.g. from http-errors). Give
|
|
64
|
+
// it a category-appropriate generic code; the message stays generic.
|
|
65
|
+
const code = status >= 500 ? 'internal_error' : 'bad_request';
|
|
66
|
+
return { code, status };
|
|
67
|
+
}
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// errorEnvelopeHandler — the trailing 4-arg net. MUST be mounted last.
|
|
72
|
+
function errorEnvelopeHandler(err, req, res, next) {
|
|
73
|
+
// Response already begun — hand back to Express's default finisher; we can't
|
|
74
|
+
// rewrite a partially-sent response.
|
|
75
|
+
if (res.headersSent) return next(err);
|
|
76
|
+
|
|
77
|
+
if (isApiError(err)) {
|
|
78
|
+
return res.status(err.status).json(envelope(err));
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const framework = codeForFrameworkError(err);
|
|
82
|
+
if (framework) {
|
|
83
|
+
return res.status(framework.status).json(envelope(framework.code));
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Anything else is an unexpected server fault. Log with a stable tag (so it's
|
|
87
|
+
// greppable in prod logs) and answer a leak-free internal envelope — the raw
|
|
88
|
+
// message/stack is never sent to the client.
|
|
89
|
+
console.error('[gds] unhandled', req && req.method, req && req.originalUrl, err);
|
|
90
|
+
return res.status(500).json(envelope('internal_error'));
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
module.exports = { attachFail, errorEnvelopeHandler, ApiError };
|