@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,916 @@
|
|
|
1
|
+
// GDS builder-memory sync — HTTP surface over the #273/R54 storage helpers.
|
|
2
|
+
//
|
|
3
|
+
// Implements the push half of ADR 0024 (cloneable repo + local-first memory
|
|
4
|
+
// with server-canonical sync): /builder-ship enumerates the builder's local
|
|
5
|
+
// Claude Code memory dir and POSTs each file here; this route stores them via
|
|
6
|
+
// src/bongos/memory.js (storeMemoryFile), last-write-wins.
|
|
7
|
+
//
|
|
8
|
+
// Scope of THIS task (#274 / V3.R55): the POST /memory/sync endpoint + the
|
|
9
|
+
// ship.js wiring (in scripts/gds/ship.js). The READ side (GET /memory/files,
|
|
10
|
+
// GET /memory/file) + the isolation-hardening + Archon forensic read land in
|
|
11
|
+
// #276 (this task); the pull-on-start wiring lands in #275. Those build ON
|
|
12
|
+
// this.
|
|
13
|
+
//
|
|
14
|
+
// ISOLATION (ADR 0024 §4, same posture as ADR 0016) — the trust boundary of
|
|
15
|
+
// this entire module:
|
|
16
|
+
// * Every SELF endpoint (the common path: POST /memory/sync, GET
|
|
17
|
+
// /memory/files, GET /memory/file) takes its owner ALWAYS from
|
|
18
|
+
// req.builder.id — the authenticated session — NEVER from req.body,
|
|
19
|
+
// req.query, or req.params. There is deliberately no `builder_id` /
|
|
20
|
+
// `owner` input field on any self endpoint. A builder physically cannot
|
|
21
|
+
// read OR write another builder's keyspace through these routes: there is
|
|
22
|
+
// no parameter to even name another builder.
|
|
23
|
+
// * The ONLY endpoints that accept an owner id from input are the two
|
|
24
|
+
// Archon-only forensic reads (GET /memory/builders/:builderId/files and
|
|
25
|
+
// .../file). They are READ-ONLY, gated by requireRank('archon'), and EVERY
|
|
26
|
+
// call writes an audit_log row recording {archon id, target builder id,
|
|
27
|
+
// path|'list', action}. No write path ever accepts a cross-builder owner.
|
|
28
|
+
// This mirrors ADR 0016: enforce server-side, trust nothing a builder can edit
|
|
29
|
+
// locally. The grep-test for a reviewer is simple — search this file for
|
|
30
|
+
// `req.body`, `req.query.builder`, `req.params.builderId`; the only owner
|
|
31
|
+
// derived from input lives under the audited Archon path.
|
|
32
|
+
//
|
|
33
|
+
// RENDER / STORED-XSS (#575, audit 2026-06-19) — memory content is stored RAW
|
|
34
|
+
// and is NEVER emitted as HTML. Every endpoint here returns it as JSON
|
|
35
|
+
// (res.json — content as a string field), and the only consumers are the CLI /
|
|
36
|
+
// dev-box memory sync (it writes the strings to the local filesystem) and the
|
|
37
|
+
// Archon-only forensic reads (also JSON). An audit of every web surface
|
|
38
|
+
// (modules/hall-ui/public/, modules/status-ui/public/) found NO code path that interpolates
|
|
39
|
+
// memory content into the DOM: there is no markdown→HTML renderer in the client
|
|
40
|
+
// at all, and the one place that does render externally-authored markdown — the
|
|
41
|
+
// Archon "Watch" security-doc reader (modules/hall-ui/public/watch.js) — escapes via
|
|
42
|
+
// OTB.escapeHtml + a <pre>. So the stored-XSS vector that the threat model lists
|
|
43
|
+
// for "memory content" (docs/security/threat-model.md §5 external input) is NOT
|
|
44
|
+
// realized today; this is the "record why low-priority" outcome of #575.
|
|
45
|
+
// FORWARD RULE (load-bearing): if a memory-viewer UI is ever added to the hall,
|
|
46
|
+
// it MUST escape the content at the render boundary (OTB.escapeHtml, like
|
|
47
|
+
// watch.js) — memory is builder-controlled free text, never trusted markup.
|
|
48
|
+
//
|
|
49
|
+
// Body limit: the global GDS json parser is capped at 64 KB (routes.js), but a
|
|
50
|
+
// single memory file may be up to 1 MB (memory.js MAX_FILE_BYTES) and a sync
|
|
51
|
+
// batches several files. So this router mounts its OWN express.json with a
|
|
52
|
+
// larger limit, scoped to the /memory/sync path only — it does not relax the
|
|
53
|
+
// 64 KB cap that every other GDS write still runs under. The read endpoints
|
|
54
|
+
// take no body and rely on the global parser.
|
|
55
|
+
|
|
56
|
+
const express = require('express');
|
|
57
|
+
// Carved into modules/memory/ (BV1.R72 / ADR 0091 §2): reach core ONLY through
|
|
58
|
+
// the published doorway. Auth gates + the kernel db helpers this route uses
|
|
59
|
+
// (getBuilderById, insertAuditLog) come off `api`; the memory lib is an
|
|
60
|
+
// intra-module require.
|
|
61
|
+
const api = require('../../../src/module-api');
|
|
62
|
+
const auth = api;
|
|
63
|
+
const memory = require('../memory');
|
|
64
|
+
const db = api;
|
|
65
|
+
|
|
66
|
+
// Per-builder rate limit for POST /memory/sync, layered ON TOP of the global
|
|
67
|
+
// per-IP / per-session limiter (middleware/rate-limit.js, applied to every
|
|
68
|
+
// write). This endpoint is special: a single request can carry up to 50 MB
|
|
69
|
+
// across 2000 files, so even one request/minute is a lot of work — the global
|
|
70
|
+
// 30-writes/min cap is the wrong shape here. We add a tighter sliding-window
|
|
71
|
+
// cap keyed on the AUTHENTICATED builder id (never on input), reusing the same
|
|
72
|
+
// in-memory sliding-window technique as the global limiter rather than
|
|
73
|
+
// inventing a new system. A sync runs once per /builder-ship, so a few per
|
|
74
|
+
// minute is generous headroom; the cap exists to stop a runaway loop from
|
|
75
|
+
// hammering large bodies.
|
|
76
|
+
const SYNC_RATE_WINDOW_MS = 60 * 1000;
|
|
77
|
+
const SYNC_RATE_LIMIT = 6; // syncs per builder per minute
|
|
78
|
+
const syncBuckets = new Map(); // builderId -> timestamp[]
|
|
79
|
+
|
|
80
|
+
const syncSweepTimer = setInterval(() => {
|
|
81
|
+
const cutoff = Date.now() - SYNC_RATE_WINDOW_MS;
|
|
82
|
+
for (const [k, arr] of syncBuckets) {
|
|
83
|
+
while (arr.length && arr[0] < cutoff) arr.shift();
|
|
84
|
+
if (arr.length === 0) syncBuckets.delete(k);
|
|
85
|
+
}
|
|
86
|
+
}, 5 * 60 * 1000);
|
|
87
|
+
if (typeof syncSweepTimer.unref === 'function') syncSweepTimer.unref();
|
|
88
|
+
|
|
89
|
+
// Express middleware: enforce the per-builder sync cap. Runs AFTER
|
|
90
|
+
// auth.requireBuilder so req.builder.id is populated.
|
|
91
|
+
function syncRateLimit(req, res, next) {
|
|
92
|
+
const now = Date.now();
|
|
93
|
+
const key = String(req.builder.id);
|
|
94
|
+
let bucket = syncBuckets.get(key);
|
|
95
|
+
if (!bucket) { bucket = []; syncBuckets.set(key, bucket); }
|
|
96
|
+
const cutoff = now - SYNC_RATE_WINDOW_MS;
|
|
97
|
+
while (bucket.length && bucket[0] < cutoff) bucket.shift();
|
|
98
|
+
if (bucket.length >= SYNC_RATE_LIMIT) {
|
|
99
|
+
const retryAfter = Math.max(1, Math.ceil((bucket[0] + SYNC_RATE_WINDOW_MS - now) / 1000));
|
|
100
|
+
res.set('Retry-After', String(retryAfter));
|
|
101
|
+
res.set('X-RateLimit-Scope', 'per-builder-memory-sync');
|
|
102
|
+
res.set('X-RateLimit-Limit', String(SYNC_RATE_LIMIT));
|
|
103
|
+
res.set('X-RateLimit-Window-Seconds', String(SYNC_RATE_WINDOW_MS / 1000));
|
|
104
|
+
return res.fail('rate_limited', 429, { scope: 'per-builder-memory-sync', retry_after_seconds: retryAfter });
|
|
105
|
+
}
|
|
106
|
+
bucket.push(now);
|
|
107
|
+
return next();
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Per-builder rate limit for the READ endpoints (GET /memory/files,
|
|
111
|
+
// GET /memory/file, and the two Archon forensic reads). The global
|
|
112
|
+
// rate-limit middleware (middleware/rate-limit.js) only caps WRITE methods —
|
|
113
|
+
// GET requests bypass it entirely — so without this a pull loop (or a forensic
|
|
114
|
+
// sweep) could hammer reads uncapped. Reads are cheaper than a sync, so the cap
|
|
115
|
+
// is more generous, but it still exists: keyed on the AUTHENTICATED builder id
|
|
116
|
+
// (never on input), same sliding-window technique as the sync limiter and the
|
|
117
|
+
// global limiter. We reuse one bucket map for every read on this router.
|
|
118
|
+
const READ_RATE_WINDOW_MS = 60 * 1000;
|
|
119
|
+
const READ_RATE_LIMIT = 120; // memory reads per builder per minute
|
|
120
|
+
const readBuckets = new Map(); // builderId -> timestamp[]
|
|
121
|
+
|
|
122
|
+
const readSweepTimer = setInterval(() => {
|
|
123
|
+
const cutoff = Date.now() - READ_RATE_WINDOW_MS;
|
|
124
|
+
for (const [k, arr] of readBuckets) {
|
|
125
|
+
while (arr.length && arr[0] < cutoff) arr.shift();
|
|
126
|
+
if (arr.length === 0) readBuckets.delete(k);
|
|
127
|
+
}
|
|
128
|
+
}, 5 * 60 * 1000);
|
|
129
|
+
if (typeof readSweepTimer.unref === 'function') readSweepTimer.unref();
|
|
130
|
+
|
|
131
|
+
// Express middleware: enforce the per-builder read cap. Runs AFTER
|
|
132
|
+
// auth.requireBuilder so req.builder.id is populated. Keyed on the
|
|
133
|
+
// authenticated id only — never on any path/query input.
|
|
134
|
+
function readRateLimit(req, res, next) {
|
|
135
|
+
const now = Date.now();
|
|
136
|
+
const key = String(req.builder.id);
|
|
137
|
+
let bucket = readBuckets.get(key);
|
|
138
|
+
if (!bucket) { bucket = []; readBuckets.set(key, bucket); }
|
|
139
|
+
const cutoff = now - READ_RATE_WINDOW_MS;
|
|
140
|
+
while (bucket.length && bucket[0] < cutoff) bucket.shift();
|
|
141
|
+
if (bucket.length >= READ_RATE_LIMIT) {
|
|
142
|
+
const retryAfter = Math.max(1, Math.ceil((bucket[0] + READ_RATE_WINDOW_MS - now) / 1000));
|
|
143
|
+
res.set('Retry-After', String(retryAfter));
|
|
144
|
+
res.set('X-RateLimit-Scope', 'per-builder-memory-read');
|
|
145
|
+
res.set('X-RateLimit-Limit', String(READ_RATE_LIMIT));
|
|
146
|
+
res.set('X-RateLimit-Window-Seconds', String(READ_RATE_WINDOW_MS / 1000));
|
|
147
|
+
return res.fail('rate_limited', 429, { scope: 'per-builder-memory-read', retry_after_seconds: retryAfter });
|
|
148
|
+
}
|
|
149
|
+
bucket.push(now);
|
|
150
|
+
return next();
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// assertSelf — the explicit, testable statement of the isolation invariant for
|
|
154
|
+
// the SELF endpoints. It returns the ONLY id a self endpoint may ever use as
|
|
155
|
+
// the memory owner: req.builder.id (the authenticated session). It throws if a
|
|
156
|
+
// caller-supplied owner is present and DOESN'T match the session — defense in
|
|
157
|
+
// depth, since the self endpoints never read such a field in the first place,
|
|
158
|
+
// but it makes the invariant impossible to violate by a later careless edit
|
|
159
|
+
// (e.g. someone wiring a caller-supplied owner-id query param into a handler).
|
|
160
|
+
// The invariant: a self endpoint's owner is ALWAYS the authenticated session
|
|
161
|
+
// (req.builder.id). The ONLY owner-from-input path in this module is the Archon
|
|
162
|
+
// forensic route, which is rank-gated and audited. Reading these helpers and
|
|
163
|
+
// their call sites establishes that a self endpoint cannot return another
|
|
164
|
+
// builder's data, full stop.
|
|
165
|
+
function assertSelf(req, claimedOwnerId) {
|
|
166
|
+
const selfId = req.builder && req.builder.id;
|
|
167
|
+
if (selfId === undefined || selfId === null) {
|
|
168
|
+
// Should be unreachable behind requireBuilder, but fail closed.
|
|
169
|
+
throw { code: 'NOT_AUTHENTICATED' };
|
|
170
|
+
}
|
|
171
|
+
if (
|
|
172
|
+
claimedOwnerId !== undefined &&
|
|
173
|
+
claimedOwnerId !== null &&
|
|
174
|
+
String(claimedOwnerId) !== String(selfId)
|
|
175
|
+
) {
|
|
176
|
+
throw { code: 'CROSS_BUILDER_FORBIDDEN', self: selfId, claimed: claimedOwnerId };
|
|
177
|
+
}
|
|
178
|
+
return selfId;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// recordForensicRead — write an audit_log row for an Archon's cross-builder
|
|
182
|
+
// memory read. The global audit middleware only instruments mutating methods
|
|
183
|
+
// (POST/PATCH/PUT/DELETE), and these are GETs, so it does NOT fire for them —
|
|
184
|
+
// we record explicitly here so every forensic look is logged (the done-when
|
|
185
|
+
// requirement). The audit_log.method column is CHECK-constrained to a write
|
|
186
|
+
// verb, so we record the action as 'POST' (it IS a logged, privileged action)
|
|
187
|
+
// and carry the true semantics in route + the redacted body.
|
|
188
|
+
//
|
|
189
|
+
// FAIL-CLOSED (V3.R57): unlike the global audit middleware (which
|
|
190
|
+
// fires after the response and may swallow), this MUST be awaited BEFORE the
|
|
191
|
+
// Archon is shown any of the target builder's data, and a failed insert MUST
|
|
192
|
+
// surface to the caller as a rejected promise. The invariant is that no
|
|
193
|
+
// forensic cross-builder read is ever served without a durable audit row — so
|
|
194
|
+
// the caller awaits this, and on rejection returns 500 and withholds the data.
|
|
195
|
+
// We do NOT catch/swallow here: the catch belongs to the route, which turns a
|
|
196
|
+
// rejection into a 500 (and logs it) rather than leaking the target's content.
|
|
197
|
+
//
|
|
198
|
+
// `responseStatus` is the ACTUAL outcome of the look (200 when a file/list was
|
|
199
|
+
// returned, 404 when the target file was absent) so the forensic trail is
|
|
200
|
+
// accurate rather than always claiming 200.
|
|
201
|
+
function recordForensicRead(req, { route, targetBuilderId, target, responseStatus = 200, action = 'archon_forensic_memory_read' }, insertFn) {
|
|
202
|
+
const insert = typeof insertFn === 'function' ? insertFn : db.insertAuditLog;
|
|
203
|
+
// Return the insert promise directly (awaited by the caller). Any rejection
|
|
204
|
+
// propagates — the route's try/catch fails the read closed.
|
|
205
|
+
return Promise.resolve(
|
|
206
|
+
insert({
|
|
207
|
+
builderId: req.builder.id,
|
|
208
|
+
route,
|
|
209
|
+
method: 'POST', // CHECK constraint forbids 'GET'; semantics live in the body
|
|
210
|
+
requestBodyRedacted: {
|
|
211
|
+
// 'archon_forensic_memory_read' (Archon rank-gated) or 'bfg_memory_read'
|
|
212
|
+
// (BFG-principal-gated, 6B.1) — auditors query by this action.
|
|
213
|
+
action,
|
|
214
|
+
http_method: 'GET',
|
|
215
|
+
target_builder_id: targetBuilderId,
|
|
216
|
+
target, // a path string, or 'list'
|
|
217
|
+
},
|
|
218
|
+
responseStatus,
|
|
219
|
+
ip: req.ip || (req.socket && req.socket.remoteAddress) || null,
|
|
220
|
+
userAgent: (req.headers && req.headers['user-agent']) || null,
|
|
221
|
+
})
|
|
222
|
+
);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// Generous batch cap: MAX_BUILDER_BYTES (50 MB) + framing headroom. A push
|
|
226
|
+
// that would exceed the per-builder quota is still rejected per-file with a
|
|
227
|
+
// clean 413 below; this limit only stops a pathological request body from
|
|
228
|
+
// being buffered into memory. Files-per-request is capped separately.
|
|
229
|
+
const SYNC_BODY_LIMIT = '64mb';
|
|
230
|
+
const MAX_FILES_PER_SYNC = memory.MAX_BUILDER_FILES; // 2000 — same as the per-builder file cap
|
|
231
|
+
|
|
232
|
+
// Kill-switch (ADR 0026 §6C): the cross-builder WRITE path is DISABLED unless
|
|
233
|
+
// BFG_WRITE_ENABLED === 'true' in the environment. Fail-safe default — this is a
|
|
234
|
+
// deliberate, bounded puncture of the trust boundary (ADR 0016), so the capability
|
|
235
|
+
// stays OFF until an operator turns it on (set in the droplet EnvironmentFile,
|
|
236
|
+
// then restart). Unsetting it + restart instantly closes the path again. Checked
|
|
237
|
+
// per-request so the running process reflects the env at request time. Pure +
|
|
238
|
+
// exported so tests can drive it via process.env without standing up the server.
|
|
239
|
+
function bfgWriteEnabled() {
|
|
240
|
+
return process.env.BFG_WRITE_ENABLED === 'true';
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
module.exports = function buildMemoryRouter() {
|
|
244
|
+
const router = express.Router();
|
|
245
|
+
|
|
246
|
+
// Larger body parser, scoped to this path only. Mounted before the handler
|
|
247
|
+
// so the route sees the parsed array. routes.js explicitly SKIPS the global
|
|
248
|
+
// 64 KB parser for the /memory/sync path (its mounter checks req.path and
|
|
249
|
+
// calls next() without parsing), so this router's own express.json is the
|
|
250
|
+
// ONLY body parser that runs here — making SYNC_BODY_LIMIT the effective cap.
|
|
251
|
+
const syncJson = express.json({ limit: SYNC_BODY_LIMIT });
|
|
252
|
+
|
|
253
|
+
// POST /memory/sync — push a builder's local memory files to the server.
|
|
254
|
+
//
|
|
255
|
+
// Auth: any authenticated builder (requireBuilder). A builder can only ever
|
|
256
|
+
// write their OWN memory — the owner is req.builder.id, never from input.
|
|
257
|
+
//
|
|
258
|
+
// Request body (two accepted shapes):
|
|
259
|
+
// { files: [ { path, content, mtime? }, ... ] } (preferred)
|
|
260
|
+
// [ { path, content, mtime? }, ... ] (bare array, also ok)
|
|
261
|
+
// where:
|
|
262
|
+
// path — string, relative path under the memory dir (e.g. 'MEMORY.md').
|
|
263
|
+
// Validated by storeMemoryFile (no leading '/', no '..', no NUL).
|
|
264
|
+
// content — string, the file body (UTF-8).
|
|
265
|
+
// mtime — optional ISO-8601 string or epoch ms; the source file's mtime.
|
|
266
|
+
// Stored for the pull side's newer-than check. Server clock is the
|
|
267
|
+
// conflict authority (last-write-wins), so this is advisory.
|
|
268
|
+
//
|
|
269
|
+
// Response 200 (always 200 when the request itself was well-formed; per-file
|
|
270
|
+
// outcomes are in the body so one bad file doesn't sink the batch):
|
|
271
|
+
// {
|
|
272
|
+
// stored: <count of files upserted>,
|
|
273
|
+
// skipped: <count of files skipped — e.g. non-string content>,
|
|
274
|
+
// errors: [ { path, code, message, hint? }, ... ],
|
|
275
|
+
// results: [ { path, stored: true, sha256, byte_len, updated_at } | { path, error: {...} } ]
|
|
276
|
+
// }
|
|
277
|
+
// A quota/oversize failure on a file is reported in errors[] with code
|
|
278
|
+
// FILE_TOO_LARGE / MEMORY_QUOTA_EXCEEDED and an HTTP 413 IF ANY file tripped a
|
|
279
|
+
// quota (so the client knows to prune), otherwise HTTP 200. Malformed paths
|
|
280
|
+
// (BAD_PATH) are per-file errors and do NOT force a non-200 — they're a client
|
|
281
|
+
// bug, not a quota signal.
|
|
282
|
+
router.post('/memory/sync', auth.requireBuilder, syncRateLimit, syncJson, async (req, res) => {
|
|
283
|
+
const builderId = req.builder.id;
|
|
284
|
+
|
|
285
|
+
// strict:false — /memory/sync accepts a bare ARRAY or { files: [...] } (dual
|
|
286
|
+
// shape), and the block below does the authoritative shape/size validation;
|
|
287
|
+
// this declares `files` for the spec + type-checks the object form only.
|
|
288
|
+
if (api.validateOrRespond(req, res, { files: { type: 'array' } }, { strict: false })) return;
|
|
289
|
+
|
|
290
|
+
// Normalize the two accepted body shapes into an array of file entries.
|
|
291
|
+
const body = req.body;
|
|
292
|
+
let files;
|
|
293
|
+
if (Array.isArray(body)) {
|
|
294
|
+
files = body;
|
|
295
|
+
} else if (body && Array.isArray(body.files)) {
|
|
296
|
+
files = body.files;
|
|
297
|
+
} else {
|
|
298
|
+
return res.fail('bad_request', { status: 400, message: 'body must be an array of {path, content, mtime?} or { files: [...] }' });
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
if (files.length > MAX_FILES_PER_SYNC) {
|
|
302
|
+
return res.fail('too_many_files', { status: 413, message: `at most ${MAX_FILES_PER_SYNC} files per sync`, details: { count: files.length, max: MAX_FILES_PER_SYNC } });
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
const results = [];
|
|
306
|
+
const errors = [];
|
|
307
|
+
let stored = 0;
|
|
308
|
+
let skipped = 0;
|
|
309
|
+
let quotaTripped = false;
|
|
310
|
+
|
|
311
|
+
// First pass: filter out the files that fail input-shape validation
|
|
312
|
+
// (BAD_PATH / BAD_CONTENT) — those are the caller's bug and never reach the
|
|
313
|
+
// DB. The rest go to the batch helper, which does all upserts + the
|
|
314
|
+
// per-builder quota check inside ONE transaction (was N transactions —
|
|
315
|
+
// one per file — before this fix).
|
|
316
|
+
const toStore = []; // { path, content, mtime } entries for the batch
|
|
317
|
+
const placeholder = []; // results[] slots to fill from the batch, in order
|
|
318
|
+
for (const f of files) {
|
|
319
|
+
const path = f && typeof f.path === 'string' ? f.path : null;
|
|
320
|
+
if (!path) {
|
|
321
|
+
skipped += 1;
|
|
322
|
+
const err = { path: path ?? '<missing>', code: 'BAD_PATH', message: 'path must be a non-empty string' };
|
|
323
|
+
errors.push(err);
|
|
324
|
+
results.push({ path: err.path, error: { code: err.code, message: 'internal error' } });
|
|
325
|
+
continue;
|
|
326
|
+
}
|
|
327
|
+
if (typeof f.content !== 'string') {
|
|
328
|
+
skipped += 1;
|
|
329
|
+
const err = { path, code: 'BAD_CONTENT', message: 'content must be a string' };
|
|
330
|
+
errors.push(err);
|
|
331
|
+
results.push({ path, error: { code: err.code, message: 'internal error' } });
|
|
332
|
+
continue;
|
|
333
|
+
}
|
|
334
|
+
// Reserved-namespace guard (ADR 0026 §6C): a builder cannot SYNC into the
|
|
335
|
+
// BFG's keyspace. `bfg/` is exclusively BFG-written, so the BFG never
|
|
336
|
+
// collides with — nor overwrites — a builder's own file. The builder can
|
|
337
|
+
// still READ and DELETE files under bfg/ (their own keyspace); they just
|
|
338
|
+
// can't create/overwrite them via sync.
|
|
339
|
+
if (memory.isReservedNamespacePath(path)) {
|
|
340
|
+
skipped += 1;
|
|
341
|
+
const err = { path, code: 'RESERVED_NAMESPACE', message: `'${memory.BFG_NAMESPACE_PREFIX}' is reserved for BFG-authored notes and cannot be synced into` };
|
|
342
|
+
errors.push(err);
|
|
343
|
+
results.push({ path, error: { code: err.code, message: 'internal error' } });
|
|
344
|
+
continue;
|
|
345
|
+
}
|
|
346
|
+
// Reserve this file's slot in results[] now so order is preserved.
|
|
347
|
+
const slot = results.length;
|
|
348
|
+
results.push(null);
|
|
349
|
+
placeholder.push(slot);
|
|
350
|
+
toStore.push({ path, content: f.content, mtime: f.mtime ?? null });
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
// Single transaction for every well-formed file (N+1 fix).
|
|
354
|
+
let batch = [];
|
|
355
|
+
try {
|
|
356
|
+
batch = await memory.storeMemoryFilesBatch(builderId, toStore);
|
|
357
|
+
} catch (e) {
|
|
358
|
+
// A throw here means the transaction itself failed (BEGIN/COMMIT/connect),
|
|
359
|
+
// not a per-file rejection — those come back inside the results array.
|
|
360
|
+
console.error('[gds] POST /memory/sync batch transaction error', { builderId, err: e });
|
|
361
|
+
return res.fail('store_failed', { status: 500, message: 'memory sync failed; nothing was stored' });
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
// Fold the batch's per-file outcomes back into the response, preserving the
|
|
365
|
+
// exact shape (and the exact error mapping) the old per-file loop produced.
|
|
366
|
+
for (let i = 0; i < batch.length; i++) {
|
|
367
|
+
const slot = placeholder[i];
|
|
368
|
+
const r = batch[i];
|
|
369
|
+
const path = toStore[i].path;
|
|
370
|
+
if (r && r.ok) {
|
|
371
|
+
const row = r.row;
|
|
372
|
+
stored += 1;
|
|
373
|
+
results[slot] = {
|
|
374
|
+
path: row.path,
|
|
375
|
+
stored: true,
|
|
376
|
+
sha256: row.sha256,
|
|
377
|
+
byte_len: Number(row.byte_len),
|
|
378
|
+
updated_at: row.updated_at,
|
|
379
|
+
};
|
|
380
|
+
continue;
|
|
381
|
+
}
|
|
382
|
+
// Per-file rejection — same code/hint mapping as before.
|
|
383
|
+
const e = r && r.error ? r.error : null;
|
|
384
|
+
const code = e && e.code ? e.code : 'STORE_FAILED';
|
|
385
|
+
let message = (e && e.message) || 'store failed';
|
|
386
|
+
let hint;
|
|
387
|
+
if (code === 'FILE_TOO_LARGE') {
|
|
388
|
+
quotaTripped = true;
|
|
389
|
+
message = `file exceeds the ${memory.MAX_FILE_BYTES}-byte per-file limit`;
|
|
390
|
+
hint = 'Prune or split this file before syncing; the server stores files individually.';
|
|
391
|
+
} else if (code === 'MEMORY_QUOTA_EXCEEDED') {
|
|
392
|
+
quotaTripped = true;
|
|
393
|
+
message =
|
|
394
|
+
e.kind === 'file_count'
|
|
395
|
+
? `would exceed the ${memory.MAX_BUILDER_FILES}-file per-builder limit`
|
|
396
|
+
: `would exceed the ${memory.MAX_BUILDER_BYTES}-byte per-builder limit`;
|
|
397
|
+
hint = 'Prune old memory files (the limit is per-builder, summed across all files).';
|
|
398
|
+
} else if (code !== 'BAD_PATH' && code !== 'STORE_FAILED') {
|
|
399
|
+
// Unknown helper error code — log for forensics, still report per-file.
|
|
400
|
+
console.error('[gds] POST /memory/sync store error', { builderId, path, code, err: e });
|
|
401
|
+
} else if (code === 'STORE_FAILED') {
|
|
402
|
+
console.error('[gds] POST /memory/sync unexpected error', { builderId, path, err: e });
|
|
403
|
+
}
|
|
404
|
+
const err = { path, code, message, ...(hint ? { hint } : {}) };
|
|
405
|
+
errors.push(err);
|
|
406
|
+
results[slot] = { path, error: { code, message, ...(hint ? { hint } : {}) } };
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
const payload = { stored, skipped, errors, results };
|
|
410
|
+
// 413 only when a quota/size limit was actually hit, so the client knows to
|
|
411
|
+
// prune. Malformed-path / bad-content errors stay on a 200 — the batch was
|
|
412
|
+
// accepted, those individual files are just the caller's bug.
|
|
413
|
+
res.status(quotaTripped ? 413 : 200).json(payload);
|
|
414
|
+
});
|
|
415
|
+
|
|
416
|
+
// -----------------------------------------------------------------------
|
|
417
|
+
// READ side (#276 / V3.R57) — strictly self-scoped. The pull side (#275)
|
|
418
|
+
// and a builder inspecting their own memory call these. The owner is ALWAYS
|
|
419
|
+
// req.builder.id; there is no parameter to name another builder, so these
|
|
420
|
+
// are inherently isolated — assertSelf() states that invariant explicitly.
|
|
421
|
+
// -----------------------------------------------------------------------
|
|
422
|
+
|
|
423
|
+
// GET /memory/files — list the AUTHENTICATED builder's memory files
|
|
424
|
+
// (metadata only, no content). Single query (listMemoryFiles is one SELECT —
|
|
425
|
+
// no N+1). Returns newest-changed first.
|
|
426
|
+
//
|
|
427
|
+
// Auth: any authenticated builder (requireBuilder). Self-scoped: owner is
|
|
428
|
+
// req.builder.id. NO owner param is accepted.
|
|
429
|
+
//
|
|
430
|
+
// Response 200:
|
|
431
|
+
// { builder_id, count, files: [ { path, sha256, byte_len, mtime, updated_at, created_at }, ... ] }
|
|
432
|
+
router.get('/memory/files', auth.requireBuilder, readRateLimit, async (req, res) => {
|
|
433
|
+
let builderId;
|
|
434
|
+
try {
|
|
435
|
+
builderId = assertSelf(req); // owner is the session, never input
|
|
436
|
+
} catch (_e) {
|
|
437
|
+
return res.fail('unauthenticated', 401);
|
|
438
|
+
}
|
|
439
|
+
try {
|
|
440
|
+
const rows = await memory.listMemoryFiles(builderId);
|
|
441
|
+
res.set('Cache-Control', 'no-store');
|
|
442
|
+
return res.json({
|
|
443
|
+
builder_id: builderId,
|
|
444
|
+
count: rows.length,
|
|
445
|
+
files: rows.map((r) => ({
|
|
446
|
+
path: r.path,
|
|
447
|
+
sha256: r.sha256,
|
|
448
|
+
byte_len: Number(r.byte_len),
|
|
449
|
+
mtime: r.mtime,
|
|
450
|
+
updated_at: r.updated_at,
|
|
451
|
+
created_at: r.created_at,
|
|
452
|
+
})),
|
|
453
|
+
});
|
|
454
|
+
} catch (err) {
|
|
455
|
+
console.error('[gds] GET /memory/files', { builderId, err });
|
|
456
|
+
return res.fail('list_failed', { status: 500, message: 'could not list memory files' });
|
|
457
|
+
}
|
|
458
|
+
});
|
|
459
|
+
|
|
460
|
+
// GET /memory/file?path=... — fetch ONE of the AUTHENTICATED builder's files
|
|
461
|
+
// (content included). Single query (fetchMemoryFile is one SELECT). 404 if
|
|
462
|
+
// this builder has no file at that path. We use a ?path= query rather than a
|
|
463
|
+
// path-segment param because the stored keys are relative paths that may
|
|
464
|
+
// contain '/' (e.g. 'sub/dir/notes.md'); a single :path segment can't carry a
|
|
465
|
+
// slash, and a wildcard segment re-opens the traversal surface that
|
|
466
|
+
// assertValidPath exists to close. The query value is validated by
|
|
467
|
+
// assertValidPath (no leading '/', no '..', no NUL, no backslash) before it
|
|
468
|
+
// ever reaches the store.
|
|
469
|
+
//
|
|
470
|
+
// Auth: any authenticated builder. Self-scoped: owner is req.builder.id.
|
|
471
|
+
//
|
|
472
|
+
// Response 200:
|
|
473
|
+
// { builder_id, path, content, sha256, byte_len, mtime, updated_at, created_at }
|
|
474
|
+
router.get('/memory/file', auth.requireBuilder, readRateLimit, async (req, res) => {
|
|
475
|
+
let builderId;
|
|
476
|
+
try {
|
|
477
|
+
builderId = assertSelf(req);
|
|
478
|
+
} catch (_e) {
|
|
479
|
+
return res.fail('unauthenticated', 401);
|
|
480
|
+
}
|
|
481
|
+
const path = typeof req.query.path === 'string' ? req.query.path : null;
|
|
482
|
+
if (path === null) {
|
|
483
|
+
return res.fail('bad_request', { status: 400, message: 'path query param is required', details: { code: 'BAD_PATH' } });
|
|
484
|
+
}
|
|
485
|
+
try {
|
|
486
|
+
memory.assertValidPath(path); // reuse the one canonical validator
|
|
487
|
+
} catch (e) {
|
|
488
|
+
return res.fail('bad_path', { status: 400, message: 'internal error' ? e.message : 'invalid path', details: { code: 'BAD_PATH' } });
|
|
489
|
+
}
|
|
490
|
+
try {
|
|
491
|
+
const row = await memory.fetchMemoryFile(builderId, path);
|
|
492
|
+
if (!row) {
|
|
493
|
+
return res.fail('not_found', 404, { path });
|
|
494
|
+
}
|
|
495
|
+
res.set('Cache-Control', 'no-store');
|
|
496
|
+
return res.json({
|
|
497
|
+
builder_id: builderId,
|
|
498
|
+
path: row.path,
|
|
499
|
+
content: row.content,
|
|
500
|
+
sha256: row.sha256,
|
|
501
|
+
byte_len: Number(row.byte_len),
|
|
502
|
+
mtime: row.mtime,
|
|
503
|
+
updated_at: row.updated_at,
|
|
504
|
+
created_at: row.created_at,
|
|
505
|
+
});
|
|
506
|
+
} catch (err) {
|
|
507
|
+
console.error('[gds] GET /memory/file', { builderId, path, err });
|
|
508
|
+
return res.fail('fetch_failed', { status: 500, message: 'could not fetch memory file' });
|
|
509
|
+
}
|
|
510
|
+
});
|
|
511
|
+
|
|
512
|
+
// DELETE /memory/file?path=... — the SELF delete affordance (ADR 0026 §6C).
|
|
513
|
+
// A builder removes ONE of their OWN files — INCLUDING a BFG-written
|
|
514
|
+
// dream/correction, which lives in this builder's keyspace. Owner is ALWAYS
|
|
515
|
+
// req.builder.id (assertSelf); there is no parameter to delete another
|
|
516
|
+
// builder's file, so this can't be a cross-builder delete. This is what makes
|
|
517
|
+
// a BFG write reversible by its recipient (the load-bearing consent property).
|
|
518
|
+
// Single-line opener so the route-rank auditor classifies it ('any-builder').
|
|
519
|
+
router.delete('/memory/file', auth.requireBuilder, readRateLimit, async (req, res) => {
|
|
520
|
+
// Query-param validation (R11/#1998): type-check `path`. strict:false tolerates
|
|
521
|
+
// incidental query params rather than reject; the handler enforces path
|
|
522
|
+
// presence/shape (bad_request/bad_path) below.
|
|
523
|
+
if (api.validateOrRespond(req, res, {}, { query: { path: { type: 'string' } }, strict: false })) return;
|
|
524
|
+
let builderId;
|
|
525
|
+
try { builderId = assertSelf(req); } catch (_e) { return res.fail('unauthenticated', 401); }
|
|
526
|
+
const path = typeof req.query.path === 'string' ? req.query.path : null;
|
|
527
|
+
if (path === null) {
|
|
528
|
+
return res.fail('bad_request', { status: 400, message: 'path query param is required', details: { code: 'BAD_PATH' } });
|
|
529
|
+
}
|
|
530
|
+
try {
|
|
531
|
+
memory.assertValidPath(path);
|
|
532
|
+
} catch (e) {
|
|
533
|
+
return res.fail('bad_path', { status: 400, message: 'internal error' ? e.message : 'invalid path', details: { code: 'BAD_PATH' } });
|
|
534
|
+
}
|
|
535
|
+
try {
|
|
536
|
+
const n = await memory.deleteMemoryFile(builderId, path);
|
|
537
|
+
if (n === 0) return res.fail('not_found', 404, { path });
|
|
538
|
+
return res.json({ deleted: true, builder_id: builderId, path });
|
|
539
|
+
} catch (err) {
|
|
540
|
+
console.error('[gds] DELETE /memory/file', { builderId, path, err });
|
|
541
|
+
return res.fail('delete_failed', { status: 500, message: 'could not delete memory file' });
|
|
542
|
+
}
|
|
543
|
+
});
|
|
544
|
+
|
|
545
|
+
// -----------------------------------------------------------------------
|
|
546
|
+
// ARCHON FORENSIC OVERRIDE (#276 / V3.R57) — the ONLY endpoints in this
|
|
547
|
+
// module that take an owner id from input. READ-ONLY, requireRank('archon'),
|
|
548
|
+
// and EVERY call is audit-logged (recordForensicRead) because the global
|
|
549
|
+
// audit middleware doesn't instrument GETs. This is the documented exception
|
|
550
|
+
// to the isolation invariant: an Archon can read another builder's memory for
|
|
551
|
+
// forensics, but the read leaves an immutable trail.
|
|
552
|
+
// -----------------------------------------------------------------------
|
|
553
|
+
|
|
554
|
+
// GET /memory/builders/:builderId/files — Archon lists ANOTHER builder's
|
|
555
|
+
// memory file metadata. Audit-logged.
|
|
556
|
+
router.get(
|
|
557
|
+
'/memory/builders/:builderId/files',
|
|
558
|
+
auth.requireBuilder,
|
|
559
|
+
auth.requireRank('archon'),
|
|
560
|
+
readRateLimit,
|
|
561
|
+
async (req, res) => {
|
|
562
|
+
const targetId = Number(req.params.builderId);
|
|
563
|
+
if (!Number.isInteger(targetId) || targetId <= 0) {
|
|
564
|
+
return res.fail('bad_builder_id', 400);
|
|
565
|
+
}
|
|
566
|
+
const route = '/api/bongos/memory/builders/:builderId/files';
|
|
567
|
+
let rows;
|
|
568
|
+
try {
|
|
569
|
+
rows = await memory.listMemoryFiles(targetId);
|
|
570
|
+
} catch (err) {
|
|
571
|
+
console.error('[gds] GET /memory/builders/:builderId/files', { targetId, err });
|
|
572
|
+
return res.fail('list_failed', { status: 500, message: 'could not list memory files' });
|
|
573
|
+
}
|
|
574
|
+
// FAIL CLOSED: the audit row must land durably BEFORE we return any of the
|
|
575
|
+
// target builder's data. A list always "succeeds" (empty list is a valid
|
|
576
|
+
// result), so the audited responseStatus is 200. If the audit insert
|
|
577
|
+
// throws, withhold the data and return 500 — no forensic read is ever
|
|
578
|
+
// served without a durable audit row.
|
|
579
|
+
try {
|
|
580
|
+
await recordForensicRead(req, { route, targetBuilderId: targetId, target: 'list', responseStatus: 200 });
|
|
581
|
+
} catch (auditErr) {
|
|
582
|
+
console.error('[gds] forensic-read audit insert failed (read withheld)', {
|
|
583
|
+
route, targetId, err: auditErr && auditErr.message ? auditErr.message : String(auditErr),
|
|
584
|
+
});
|
|
585
|
+
return res.fail('audit_failed', { status: 500, message: 'forensic read not served: audit could not be recorded' });
|
|
586
|
+
}
|
|
587
|
+
res.set('Cache-Control', 'no-store');
|
|
588
|
+
return res.json({
|
|
589
|
+
builder_id: targetId,
|
|
590
|
+
forensic: true,
|
|
591
|
+
count: rows.length,
|
|
592
|
+
files: rows.map((r) => ({
|
|
593
|
+
path: r.path,
|
|
594
|
+
sha256: r.sha256,
|
|
595
|
+
byte_len: Number(r.byte_len),
|
|
596
|
+
mtime: r.mtime,
|
|
597
|
+
updated_at: r.updated_at,
|
|
598
|
+
created_at: r.created_at,
|
|
599
|
+
})),
|
|
600
|
+
});
|
|
601
|
+
}
|
|
602
|
+
);
|
|
603
|
+
|
|
604
|
+
// GET /memory/builders/:builderId/file?path=... — Archon reads ONE of
|
|
605
|
+
// ANOTHER builder's files (content included). Audit-logged. 404 if the
|
|
606
|
+
// target has no file at that path.
|
|
607
|
+
router.get(
|
|
608
|
+
'/memory/builders/:builderId/file',
|
|
609
|
+
auth.requireBuilder,
|
|
610
|
+
auth.requireRank('archon'),
|
|
611
|
+
readRateLimit,
|
|
612
|
+
async (req, res) => {
|
|
613
|
+
const targetId = Number(req.params.builderId);
|
|
614
|
+
if (!Number.isInteger(targetId) || targetId <= 0) {
|
|
615
|
+
return res.fail('bad_builder_id', 400);
|
|
616
|
+
}
|
|
617
|
+
const path = typeof req.query.path === 'string' ? req.query.path : null;
|
|
618
|
+
if (path === null) {
|
|
619
|
+
return res.fail('bad_request', { status: 400, message: 'path query param is required', details: { code: 'BAD_PATH' } });
|
|
620
|
+
}
|
|
621
|
+
try {
|
|
622
|
+
memory.assertValidPath(path);
|
|
623
|
+
} catch (e) {
|
|
624
|
+
return res.fail('bad_path', { status: 400, message: 'internal error' ? e.message : 'invalid path', details: { code: 'BAD_PATH' } });
|
|
625
|
+
}
|
|
626
|
+
const route = '/api/bongos/memory/builders/:builderId/file';
|
|
627
|
+
let row;
|
|
628
|
+
try {
|
|
629
|
+
row = await memory.fetchMemoryFile(targetId, path);
|
|
630
|
+
} catch (err) {
|
|
631
|
+
console.error('[gds] GET /memory/builders/:builderId/file', { targetId, path, err });
|
|
632
|
+
return res.fail('fetch_failed', { status: 500, message: 'could not fetch memory file' });
|
|
633
|
+
}
|
|
634
|
+
// FAIL CLOSED: audit the look — logged even if the path is a 404, because
|
|
635
|
+
// "the Archon TRIED to read X" is the forensic fact we want — and the row
|
|
636
|
+
// must land durably BEFORE we return data. responseStatus reflects the
|
|
637
|
+
// ACTUAL outcome (200 if the file existed, 404 if absent), not a hardcoded
|
|
638
|
+
// 200. If the audit insert throws, withhold data and return 500.
|
|
639
|
+
const outcomeStatus = row ? 200 : 404;
|
|
640
|
+
try {
|
|
641
|
+
await recordForensicRead(req, { route, targetBuilderId: targetId, target: path, responseStatus: outcomeStatus });
|
|
642
|
+
} catch (auditErr) {
|
|
643
|
+
console.error('[gds] forensic-read audit insert failed (read withheld)', {
|
|
644
|
+
route, targetId, path, err: auditErr && auditErr.message ? auditErr.message : String(auditErr),
|
|
645
|
+
});
|
|
646
|
+
return res.fail('audit_failed', { status: 500, message: 'forensic read not served: audit could not be recorded' });
|
|
647
|
+
}
|
|
648
|
+
if (!row) {
|
|
649
|
+
return res.fail('not_found', 404, { builder_id: targetId, path });
|
|
650
|
+
}
|
|
651
|
+
res.set('Cache-Control', 'no-store');
|
|
652
|
+
return res.json({
|
|
653
|
+
builder_id: targetId,
|
|
654
|
+
forensic: true,
|
|
655
|
+
path: row.path,
|
|
656
|
+
content: row.content,
|
|
657
|
+
sha256: row.sha256,
|
|
658
|
+
byte_len: Number(row.byte_len),
|
|
659
|
+
mtime: row.mtime,
|
|
660
|
+
updated_at: row.updated_at,
|
|
661
|
+
created_at: row.created_at,
|
|
662
|
+
});
|
|
663
|
+
}
|
|
664
|
+
);
|
|
665
|
+
|
|
666
|
+
// -----------------------------------------------------------------------
|
|
667
|
+
// BFG CROSS-BUILDER READ (#444 / ADR 0026 §6B) — the "read the room"
|
|
668
|
+
// substrate. Reachable ONLY by the BFG service principal (requireBfgPrincipal,
|
|
669
|
+
// an IDENTITY gate — NOT Archon rank), same audited fail-closed posture as the
|
|
670
|
+
// Archon forensic reads above but with a distinct audit action
|
|
671
|
+
// ('bfg_memory_read'). Lets the BFG read any builder's memory to SCORE
|
|
672
|
+
// interaction quality and (in 6B.2) extract transferable practice. Openers are
|
|
673
|
+
// single-line so the route-rank auditor detects + classifies them.
|
|
674
|
+
// -----------------------------------------------------------------------
|
|
675
|
+
|
|
676
|
+
router.get('/memory/bfg/builders/:builderId/files',
|
|
677
|
+
auth.requireBuilder,
|
|
678
|
+
auth.requireBfgPrincipal,
|
|
679
|
+
readRateLimit,
|
|
680
|
+
async (req, res) => {
|
|
681
|
+
const targetId = Number(req.params.builderId);
|
|
682
|
+
if (!Number.isInteger(targetId) || targetId <= 0) {
|
|
683
|
+
return res.fail('bad_builder_id', 400);
|
|
684
|
+
}
|
|
685
|
+
const route = '/api/bongos/memory/bfg/builders/:builderId/files';
|
|
686
|
+
let rows;
|
|
687
|
+
try {
|
|
688
|
+
rows = await memory.listMemoryFiles(targetId);
|
|
689
|
+
} catch (err) {
|
|
690
|
+
console.error('[gds] GET /memory/bfg/builders/:builderId/files', { targetId, err });
|
|
691
|
+
return res.fail('list_failed', { status: 500, message: 'could not list memory files' });
|
|
692
|
+
}
|
|
693
|
+
// FAIL CLOSED: the audit row must land before any of the target's data is
|
|
694
|
+
// served (same invariant as the Archon forensic read).
|
|
695
|
+
try {
|
|
696
|
+
await recordForensicRead(req, { route, targetBuilderId: targetId, target: 'list', responseStatus: 200, action: 'bfg_memory_read' });
|
|
697
|
+
} catch (auditErr) {
|
|
698
|
+
console.error('[gds] bfg-read audit insert failed (read withheld)', { route, targetId, err: auditErr && auditErr.message ? auditErr.message : String(auditErr) });
|
|
699
|
+
return res.fail('audit_failed', { status: 500, message: 'read not served: audit could not be recorded' });
|
|
700
|
+
}
|
|
701
|
+
res.set('Cache-Control', 'no-store');
|
|
702
|
+
return res.json({
|
|
703
|
+
builder_id: targetId,
|
|
704
|
+
bfg_read: true,
|
|
705
|
+
count: rows.length,
|
|
706
|
+
files: rows.map((r) => ({ path: r.path, sha256: r.sha256, byte_len: Number(r.byte_len), mtime: r.mtime, updated_at: r.updated_at, created_at: r.created_at })),
|
|
707
|
+
});
|
|
708
|
+
}
|
|
709
|
+
);
|
|
710
|
+
|
|
711
|
+
router.get('/memory/bfg/builders/:builderId/file',
|
|
712
|
+
auth.requireBuilder,
|
|
713
|
+
auth.requireBfgPrincipal,
|
|
714
|
+
readRateLimit,
|
|
715
|
+
async (req, res) => {
|
|
716
|
+
const targetId = Number(req.params.builderId);
|
|
717
|
+
if (!Number.isInteger(targetId) || targetId <= 0) {
|
|
718
|
+
return res.fail('bad_builder_id', 400);
|
|
719
|
+
}
|
|
720
|
+
const path = typeof req.query.path === 'string' ? req.query.path : null;
|
|
721
|
+
if (path === null) {
|
|
722
|
+
return res.fail('bad_request', { status: 400, message: 'path query param is required', details: { code: 'BAD_PATH' } });
|
|
723
|
+
}
|
|
724
|
+
try {
|
|
725
|
+
memory.assertValidPath(path);
|
|
726
|
+
} catch (e) {
|
|
727
|
+
return res.fail('bad_path', { status: 400, message: 'internal error' ? e.message : 'invalid path', details: { code: 'BAD_PATH' } });
|
|
728
|
+
}
|
|
729
|
+
const route = '/api/bongos/memory/bfg/builders/:builderId/file';
|
|
730
|
+
let row;
|
|
731
|
+
try {
|
|
732
|
+
row = await memory.fetchMemoryFile(targetId, path);
|
|
733
|
+
} catch (err) {
|
|
734
|
+
console.error('[gds] GET /memory/bfg/builders/:builderId/file', { targetId, path, err });
|
|
735
|
+
return res.fail('fetch_failed', { status: 500, message: 'could not fetch memory file' });
|
|
736
|
+
}
|
|
737
|
+
const outcomeStatus = row ? 200 : 404;
|
|
738
|
+
try {
|
|
739
|
+
await recordForensicRead(req, { route, targetBuilderId: targetId, target: path, responseStatus: outcomeStatus, action: 'bfg_memory_read' });
|
|
740
|
+
} catch (auditErr) {
|
|
741
|
+
console.error('[gds] bfg-read audit insert failed (read withheld)', { route, targetId, path, err: auditErr && auditErr.message ? auditErr.message : String(auditErr) });
|
|
742
|
+
return res.fail('audit_failed', { status: 500, message: 'read not served: audit could not be recorded' });
|
|
743
|
+
}
|
|
744
|
+
if (!row) return res.fail('not_found', 404, { builder_id: targetId, path });
|
|
745
|
+
res.set('Cache-Control', 'no-store');
|
|
746
|
+
return res.json({
|
|
747
|
+
builder_id: targetId,
|
|
748
|
+
bfg_read: true,
|
|
749
|
+
path: row.path,
|
|
750
|
+
content: row.content,
|
|
751
|
+
sha256: row.sha256,
|
|
752
|
+
byte_len: Number(row.byte_len),
|
|
753
|
+
mtime: row.mtime,
|
|
754
|
+
updated_at: row.updated_at,
|
|
755
|
+
created_at: row.created_at,
|
|
756
|
+
});
|
|
757
|
+
}
|
|
758
|
+
);
|
|
759
|
+
|
|
760
|
+
// -----------------------------------------------------------------------
|
|
761
|
+
// BFG CROSS-BUILDER WRITE (#446 / ADR 0026 §6C) — the FIRST cross-builder
|
|
762
|
+
// memory write path, the deliberate bounded exception to ADR 0016. Reachable
|
|
763
|
+
// ONLY by the BFG service principal (requireBfgPrincipal — an IDENTITY gate on
|
|
764
|
+
// system_role='bfg', NOT a rank gate, so an Archon/stolen token is rejected),
|
|
765
|
+
// ONLY when the kill-switch BFG_WRITE_ENABLED is on, and EVERY write lands a
|
|
766
|
+
// fail-closed audit row in the SAME transaction as the write (memory.bfgWrite-
|
|
767
|
+
// MemoryFile). Writes are namespaced (bfg/<kind>-<slug>.md) + self-announcing
|
|
768
|
+
// (frontmatter author: BFG) so a BFG note can never masquerade as the
|
|
769
|
+
// builder's own. The builder can always see and delete it.
|
|
770
|
+
//
|
|
771
|
+
// Rate limiting: this is a small write (one file), so the GLOBAL per-session/
|
|
772
|
+
// per-IP write limiter (middleware/rate-limit.js, applied to every GDS write)
|
|
773
|
+
// already throttles a runaway loop. Unlike /memory/sync — which carries up to
|
|
774
|
+
// 50 MB and needed a tighter dedicated cap — this endpoint doesn't warrant its
|
|
775
|
+
// own limiter, especially as it's principal-only + kill-switched.
|
|
776
|
+
//
|
|
777
|
+
// POST /memory/builders/:builderId/bfg-write
|
|
778
|
+
// body: { kind: 'dream'|'correction', slug, title?, body_md, rationale }
|
|
779
|
+
// 201 → { ok, builder_id, path, kind, author:'BFG', audit_id, sha256, byte_len, updated_at }
|
|
780
|
+
// Declaration opener kept on ONE line (route path beside router.post) so the
|
|
781
|
+
// deterministic route-rank auditor (src/bongos/route-rank-check.js) detects +
|
|
782
|
+
// classifies it; it reads requireBuilder in the chain below → 'any-builder',
|
|
783
|
+
// and requireBfgPrincipal narrows that to the principal alone.
|
|
784
|
+
router.post('/memory/builders/:builderId/bfg-write',
|
|
785
|
+
auth.requireBuilder,
|
|
786
|
+
auth.requireBfgPrincipal,
|
|
787
|
+
async (req, res) => {
|
|
788
|
+
// Kill-switch first: a disabled write path must answer the same to the
|
|
789
|
+
// principal as to anyone else — the capability simply isn't on.
|
|
790
|
+
if (!bfgWriteEnabled()) {
|
|
791
|
+
return res.fail('bfg_write_disabled', { status: 503, message: 'the BFG cross-builder write path is disabled (BFG_WRITE_ENABLED is not set)' });
|
|
792
|
+
}
|
|
793
|
+
const targetId = Number(req.params.builderId);
|
|
794
|
+
if (!Number.isInteger(targetId) || targetId <= 0) {
|
|
795
|
+
return res.fail('bad_builder_id', 400);
|
|
796
|
+
}
|
|
797
|
+
if (String(targetId) === String(req.builder.id)) {
|
|
798
|
+
return res.fail('bfg_cannot_write_self', 400);
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
if (api.validateOrRespond(req, res, {
|
|
802
|
+
kind: { type: 'string' },
|
|
803
|
+
slug: { type: 'string' },
|
|
804
|
+
title: { type: 'string' },
|
|
805
|
+
body_md: { type: 'string' },
|
|
806
|
+
rationale: { type: 'string' },
|
|
807
|
+
karma_delta: {}, // non-positive integer or null; validated below (correction-only)
|
|
808
|
+
})) return;
|
|
809
|
+
const { kind, slug, title, body_md, rationale } = req.body || {};
|
|
810
|
+
|
|
811
|
+
// Construct the attributed file (pure) — throws BAD_BFG_* on bad input, so
|
|
812
|
+
// the mandatory frontmatter can never be omitted.
|
|
813
|
+
let file;
|
|
814
|
+
try {
|
|
815
|
+
file = memory.buildBfgMemoryFile({
|
|
816
|
+
kind,
|
|
817
|
+
slug,
|
|
818
|
+
title,
|
|
819
|
+
body_md,
|
|
820
|
+
rationale,
|
|
821
|
+
sourceBuilder: req.builder.github_login,
|
|
822
|
+
targetBuilderId: targetId,
|
|
823
|
+
});
|
|
824
|
+
} catch (e) {
|
|
825
|
+
return res.fail('bad_bfg_input', { status: 400, message: 'internal error', details: { code: e && e.code } });
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
// Target must be a real, non-system builder.
|
|
829
|
+
let target;
|
|
830
|
+
try {
|
|
831
|
+
target = await db.getBuilderById(targetId);
|
|
832
|
+
} catch (err) {
|
|
833
|
+
console.error('[gds] bfg-write target lookup', { targetId, err });
|
|
834
|
+
return res.fail('lookup_failed', 500);
|
|
835
|
+
}
|
|
836
|
+
if (!target) return res.fail('target_not_found', 404);
|
|
837
|
+
if (target.system_role) return res.fail('target_is_system_principal', 400);
|
|
838
|
+
|
|
839
|
+
// 6C.2 — a CORRECTION may carry a negative karma delta alongside its note
|
|
840
|
+
// (ADR 0026 §6C). It is only valid on a correction, and MUST be
|
|
841
|
+
// non-positive: the BFG corrects by docking, never by rewarding (a reward
|
|
842
|
+
// is a different, non-BFG mechanism). A dream never carries karma.
|
|
843
|
+
let karma = null;
|
|
844
|
+
if (req.body.karma_delta != null) {
|
|
845
|
+
if (kind !== 'correction') {
|
|
846
|
+
return res.fail('karma_delta_not_allowed', { status: 400, message: 'karma_delta is only valid for kind=correction' });
|
|
847
|
+
}
|
|
848
|
+
const kd = Number(req.body.karma_delta);
|
|
849
|
+
if (!Number.isInteger(kd) || kd > 0) {
|
|
850
|
+
return res.fail('bad_karma_delta', { status: 400, message: 'karma_delta must be a non-positive integer (corrections dock, never reward)' });
|
|
851
|
+
}
|
|
852
|
+
if (kd !== 0) karma = { delta: kd, reason: 'bfg_correction', grantedBy: req.builder.id };
|
|
853
|
+
}
|
|
854
|
+
|
|
855
|
+
const action = kind === 'correction' ? 'bfg_correction_write' : 'bfg_dream_write';
|
|
856
|
+
try {
|
|
857
|
+
const result = await memory.bfgWriteMemoryFile({
|
|
858
|
+
targetBuilderId: targetId,
|
|
859
|
+
path: file.path,
|
|
860
|
+
content: file.content,
|
|
861
|
+
audit: {
|
|
862
|
+
principalId: req.builder.id,
|
|
863
|
+
route: '/api/bongos/memory/builders/:builderId/bfg-write',
|
|
864
|
+
action,
|
|
865
|
+
kind,
|
|
866
|
+
rationale: String(rationale).slice(0, 500),
|
|
867
|
+
responseStatus: 201,
|
|
868
|
+
ip: req.ip || (req.socket && req.socket.remoteAddress) || null,
|
|
869
|
+
userAgent: (req.headers && req.headers['user-agent']) || null,
|
|
870
|
+
},
|
|
871
|
+
karma,
|
|
872
|
+
});
|
|
873
|
+
return res.status(201).json({
|
|
874
|
+
ok: true,
|
|
875
|
+
builder_id: targetId,
|
|
876
|
+
path: result.row.path,
|
|
877
|
+
kind,
|
|
878
|
+
author: 'BFG',
|
|
879
|
+
audit_id: result.audit_id,
|
|
880
|
+
karma_id: result.karma_id ?? null,
|
|
881
|
+
sha256: result.row.sha256,
|
|
882
|
+
byte_len: Number(result.row.byte_len),
|
|
883
|
+
updated_at: result.row.updated_at,
|
|
884
|
+
});
|
|
885
|
+
} catch (e) {
|
|
886
|
+
const code = e && e.code;
|
|
887
|
+
if (code === 'FILE_TOO_LARGE' || code === 'MEMORY_QUOTA_EXCEEDED') {
|
|
888
|
+
return res.fail('quota_exceeded', 413, { code });
|
|
889
|
+
}
|
|
890
|
+
if (code === 'NOT_BFG_NAMESPACE') {
|
|
891
|
+
// Unreachable via THIS route (buildBfgMemoryFile always yields a bfg/
|
|
892
|
+
// path), but bfgWriteMemoryFile is exported — this keeps the helper's
|
|
893
|
+
// own assertBfgPath guard mapped to a clean 400 for any direct caller.
|
|
894
|
+
return res.fail('bad_path', 400, { code });
|
|
895
|
+
}
|
|
896
|
+
console.error('[gds] POST /memory/builders/:builderId/bfg-write', e);
|
|
897
|
+
return res.fail('bfg_write_failed', 500);
|
|
898
|
+
}
|
|
899
|
+
}
|
|
900
|
+
);
|
|
901
|
+
|
|
902
|
+
return router;
|
|
903
|
+
};
|
|
904
|
+
|
|
905
|
+
// Exposed for the test suite — pure helpers + the middleware factories, no
|
|
906
|
+
// router state. Tests exercise the isolation invariant (assertSelf) and the
|
|
907
|
+
// forensic audit shape directly, without standing up Express or a DB.
|
|
908
|
+
module.exports._internals = {
|
|
909
|
+
assertSelf,
|
|
910
|
+
recordForensicRead,
|
|
911
|
+
readRateLimit,
|
|
912
|
+
syncRateLimit,
|
|
913
|
+
READ_RATE_LIMIT,
|
|
914
|
+
SYNC_RATE_LIMIT,
|
|
915
|
+
bfgWriteEnabled,
|
|
916
|
+
};
|