@bongos/core 1.19.1073 → 1.19.1075
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 +404 -549
- package/.claude/skills/feedback/SKILL.md +2 -2
- package/bin/bongos.js +1 -3
- package/clients/bongos-client/README.md +1 -1
- package/clients/bongos-client/bongos-client.global.js +4 -60
- package/clients/bongos-client/index.cjs +4 -60
- package/clients/bongos-client/index.d.ts +5 -90
- package/clients/bongos-client/index.mjs +4 -60
- package/config/branding.neutral.json +1 -5
- package/config/modules.neutral.json +2 -3
- package/config/scheduled-routines.json +0 -2
- package/docs/adr/0145-free-hosted-project-tier-isolation-and-domain-separation.md +1 -0
- package/docs/adr/0285-a-shared-box-holds-about-twelve-projects-per-gb-and-memory-is-the-wall.md +1 -0
- package/docs/adr/0323-hosting-is-three-shapes-and-we-are-not-the-landlord.md +1 -1
- package/docs/adr/0327-a-cloud-host-runs-on-the-owners-account-and-the-key-is-borrowed.md +1 -1
- package/docs/adr/0341-the-page-is-the-unit-of-tweak-mode.md +2 -0
- package/docs/adr/0345-we-host-every-projects-hall-and-its-app-deploys-where-the-owner-chooses.md +94 -0
- package/docs/adr/README.md +1 -0
- package/docs/api/openapi.json +175 -1665
- package/docs/api-reference.md +9 -41
- package/docs/architecture.md +16 -1
- package/docs/branding-contract.md +0 -3
- package/docs/copy-inventory.md +454 -479
- package/docs/copy-registry.json +746 -978
- package/docs/file-map.md +5 -34
- package/docs/module-api-changelog.md +4 -0
- package/docs/modules-contract.md +13 -1
- package/docs/onboarding/diagrams/04-architecture.mmd +11 -22
- package/docs/onboarding/diagrams/README.md +3 -3
- package/docs/onboarding/diagrams/assertions.json +2 -22
- package/docs/onboarding/primer.md +9 -28
- package/docs/page-inventory.json +4 -1
- package/docs/page-readings.json +1032 -1077
- package/docs/recipes/render-pilot.md +29 -10
- package/migrations/core_257_module_store_registry.sql +87 -0
- package/migrations/core_258_drop_dev_box_tables.sql +55 -0
- package/modules/agents/module.json +1 -0
- package/modules/autonomy/module.json +1 -0
- package/modules/builder-settings/module.json +1 -0
- package/modules/builder-settings/render-prefs.js +4 -5
- package/modules/copy-desk/docx.js +78 -5
- package/modules/copy-desk/module.json +1 -0
- package/modules/copy-desk/routes/copy-desk.js +10 -10
- package/modules/copy-desk/tests/copy_docx.mjs +107 -1
- package/modules/copy-desk/tests/fixtures/word-docx.mjs +30 -0
- package/modules/discord/module.json +1 -0
- package/modules/discord/ship-broadcast.js +3 -3
- package/modules/economy/credits.js +2 -2
- package/modules/economy/module.json +1 -0
- package/modules/economy/routes/credits.js +1 -2
- package/modules/government/catalog.js +13 -17
- package/modules/government/migrations/government_019_retire_box_permissions.sql +33 -0
- package/modules/government/module.json +1 -0
- package/modules/government/protected-surfaces.json +3 -11
- package/modules/government/resolver.js +3 -46
- package/modules/government/routes/government.js +0 -2
- package/modules/government/session-scopes.js +43 -83
- package/modules/government/session-scopes.json +8 -18
- package/modules/grading/module.json +1 -0
- package/modules/hall-ui/module.json +1 -0
- package/modules/hall-ui/public/board-lib.js +2 -2
- package/modules/hall-ui/public/builders.js +29 -40
- package/modules/hall-ui/public/collab.html +1 -1
- package/modules/hall-ui/public/collab.js +1 -1
- package/modules/hall-ui/public/diagrams.html +3 -3
- package/modules/hall-ui/public/dom-utils.js +1 -1
- package/modules/hall-ui/public/gate.js +2 -2
- package/modules/hall-ui/public/gate.states.json +1 -1
- package/modules/hall-ui/public/hall-render.js +43 -79
- package/modules/hall-ui/public/index.html +5 -1
- package/modules/hall-ui/public/modules.js +1 -0
- package/modules/hall-ui/public/oversight.css +5 -5
- package/modules/hall-ui/public/palette.js +0 -11
- package/modules/hall-ui/public/primer.js +1 -1
- package/modules/hall-ui/public/sessions.html +1 -1
- package/modules/hall-ui/public/settings-sessions.js +1 -1
- package/modules/hall-ui/public/settings.css +0 -1
- package/modules/hall-ui/public/settings.html +4 -33
- package/modules/hall-ui/public/settings.js +3 -33
- package/modules/hall-ui/public/settings.states.json +2 -2
- package/modules/hall-ui/public/studio.html +1 -1
- package/modules/hall-ui/public/style.css +10 -13
- package/modules/hall-ui/public/task.html +4 -1
- package/modules/hall-ui/public/task.js +36 -1
- package/modules/hall-ui/public/tweak-editor-lib.js +18 -1
- package/modules/hall-ui/public/tweak-editor.css +16 -0
- package/modules/hall-ui/public/tweak-editor.html +26 -3
- package/modules/hall-ui/public/tweak-editor.js +32 -1
- package/modules/hall-ui/public/work.js +1 -1
- package/modules/hall-ui/records/tweak-editor.md +1 -0
- package/modules/ideas/module.json +1 -0
- package/modules/ideas/projection.js +1 -1
- package/modules/lifecycle/db-claim-reads.js +1 -58
- package/modules/lifecycle/db-ship.js +4 -9
- package/modules/lifecycle/db.js +0 -4
- package/modules/lifecycle/github-push.js +12 -14
- package/modules/lifecycle/kickoff-checklist.js +0 -4
- package/modules/lifecycle/lifecycle.js +0 -8
- package/modules/lifecycle/module.json +1 -0
- package/modules/lifecycle/publish-reconciler.js +10 -10
- package/modules/lifecycle/routes/tasks.js +13 -15
- package/modules/lifecycle/ship-card.js +1 -1
- package/modules/lifecycle/task-visuals.js +2 -2
- package/modules/memory/module.json +1 -0
- package/modules/memory/routes/memory.js +2 -2
- package/modules/npm-release/module.json +1 -0
- package/modules/onboarding/module.json +1 -0
- package/modules/onboarding/onboarding-state.js +17 -36
- package/modules/onboarding/routes/access-requests.js +3 -3
- package/modules/platform-identity/module.json +1 -0
- package/modules/platform-identity/platform-identity.js +13 -5
- package/modules/platform-identity/routes/sso.js +5 -4
- package/modules/platform-identity/tests/platform-identity.mjs +17 -3
- package/modules/provisioning/capacity.js +1 -1
- package/modules/provisioning/module.json +1 -0
- package/modules/provisioning/provisioning.js +6 -5
- package/modules/provisioning/routes/provisioning.js +4 -4
- package/modules/provisioning/starter-bundles.js +5 -25
- package/modules/public-landing/module.json +1 -0
- package/modules/public-landing/public/assets/cosmos.css +1 -1
- package/modules/public-landing/public/contact.html +1 -1
- package/modules/public-landing/public/privacy.html +1 -1
- package/modules/public-landing/public/projects.html +101 -32
- package/modules/public-landing/public/projects.probes.json +2 -2
- package/modules/public-landing/public/projects.states.json +4 -3
- package/modules/public-landing/public/terms.html +1 -1
- package/modules/security/module.json +1 -0
- package/modules/sessions/db.js +1 -1
- package/modules/sessions/module.json +1 -0
- package/modules/specialities/module.json +1 -0
- package/modules/status-ui/module.json +1 -0
- package/modules/ui-design/module.json +1 -0
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/release-notes.json +84 -0
- package/scripts/gds/artifact-format.js +203 -0
- package/scripts/gds/artifact-staleness.js +2 -2
- package/scripts/gds/audit-authorship.sh +1 -13
- package/scripts/gds/backfill-task-rewards.js +5 -5
- package/scripts/gds/claim.js +0 -15
- package/scripts/gds/claude-materialize.js +2 -2
- package/scripts/gds/cli-lib.js +15 -15
- package/scripts/gds/codemod-rename-src-gds.js +1 -1
- package/scripts/gds/context-pack.js +5 -7
- package/scripts/gds/diagram-facts.js +12 -23
- package/scripts/gds/do-api.js +13 -80
- package/scripts/gds/doc-cli-guard.js +2 -0
- package/scripts/gds/doctor.js +4 -4
- package/scripts/gds/feedback-latest.js +139 -9
- package/scripts/gds/fitness-checks-identity.js +2 -7
- package/scripts/gds/fitness-checks-packaging.js +4 -4
- package/scripts/gds/fitness-checks-write-validation.js +4 -0
- package/scripts/gds/fitness-ratchets.js +5 -5
- package/scripts/gds/fitness.js +10 -10
- package/scripts/gds/gen-api-client.js +4 -4
- package/scripts/gds/gen-api-docs.js +1 -1
- package/scripts/gds/gen-diagrams.js +49 -71
- package/scripts/gds/gen-repo-map.js +13 -14
- package/scripts/gds/gen-session-index.js +8 -8
- package/scripts/gds/http-api-client.js +12 -19
- package/scripts/gds/init.js +1 -1
- package/scripts/gds/leak-scan-allowlist.js +1 -1
- package/scripts/gds/lint-infra-exec.js +1 -1
- package/scripts/gds/local-preview-lib.js +8 -39
- package/scripts/gds/local-preview.js +2 -15
- package/scripts/gds/migration-namespace.js +3 -3
- package/scripts/gds/module-artifact.js +212 -0
- package/scripts/gds/module.js +95 -6
- package/scripts/gds/package-core.js +5 -116
- package/scripts/gds/plain-cards.js +1 -1
- package/scripts/gds/provision-config.js +2 -2
- package/scripts/gds/provision-net.js +2 -3
- package/scripts/gds/provision.js +9 -9
- package/scripts/gds/publish-manifest.js +1 -2
- package/scripts/gds/push-path-brief.js +10 -10
- package/scripts/gds/regen-instance-docs.js +1 -1
- package/scripts/gds/rename-history-check.js +1 -1
- package/scripts/gds/run-unit-tests.js +2 -16
- package/scripts/gds/sandbox-stage.js +39 -341
- package/scripts/gds/session-digest-build.js +2 -2
- package/scripts/gds/ship-deploy-target.js +13 -13
- package/scripts/gds/ship-flow.js +7 -7
- package/scripts/gds/ship-land.js +9 -10
- package/scripts/gds/ship-merge.js +4 -4
- package/scripts/gds/ship-preflight-steps.js +4 -4
- package/scripts/gds/ship-regen.js +15 -49
- package/scripts/gds/ship.js +13 -33
- package/scripts/gds/skill-preflight.js +9 -8
- package/scripts/gds/start.js +0 -15
- package/scripts/hall-preview/README.md +5 -5
- package/scripts/hall-preview/server.js +6 -6
- package/scripts/render-diagrams.sh +1 -1
- package/src/bongos/api-errors.js +1 -1
- package/src/bongos/api-prefix.js +4 -4
- package/src/bongos/auth-github.js +2 -3
- package/src/bongos/auth.js +51 -126
- package/src/bongos/db-kernel.js +2 -3
- package/src/bongos/db.js +5 -28
- package/src/bongos/module-scope-map.js +14 -29
- package/src/bongos/module-store.js +141 -0
- package/src/bongos/platform-visibility-gate.js +1 -1
- package/src/bongos/route-rank-check.js +3 -16
- package/src/bongos/routes/auth.js +0 -116
- package/src/bongos/routes/builders.js +5 -15
- package/src/bongos/routes/instance.js +8 -14
- package/src/bongos/routes/me.js +7 -23
- package/src/bongos/routes/modules.js +83 -0
- package/src/bongos/routes/security.js +3 -3
- package/src/bongos/routes.js +7 -2
- package/src/bongos/serve-internal.js +5 -6
- package/src/bongos-downloads.js +1 -6
- package/src/branding.js +2 -2
- package/src/build-info.js +4 -6
- package/src/instance-config.js +2 -2
- package/src/module-api.js +13 -6
- package/src/module-loader/catalog.js +3 -0
- package/src/module-loader/loader.js +4 -4
- package/src/module-loader/manifest-schema.js +16 -8
- package/src/module-seams.js +2 -3
- package/src/modules.js +42 -14
- package/tests/agents_spend_guard.mjs +1 -1
- package/tests/api_alias_caller_ratchet.mjs +1 -1
- package/tests/api_alias_exceptions.mjs +1 -1
- package/tests/api_client.mjs +1 -16
- package/tests/api_path_404.mjs +1 -1
- package/tests/auth_page_gate.mjs +10 -10
- package/tests/backfill_task_rewards.mjs +1 -1
- package/tests/canonical_profile_url.mjs +1 -3
- package/tests/claim_action.mjs +4 -1
- package/tests/claim_from_task_and_home.mjs +127 -0
- package/tests/{box_ship_permissions.mjs → cli_allowlist_permissions.mjs} +5 -5
- package/tests/cli_sessions.mjs +1 -1
- package/tests/consumer_layout_boot.mjs +2 -0
- package/tests/context_pack.mjs +13 -13
- package/tests/copy_desk_page_docx.mjs +33 -2
- package/tests/copy_inventory.mjs +7 -7
- package/tests/core_upgrade_runner.mjs +6 -6
- package/tests/credit_grant.mjs +2 -2
- package/tests/criterion_suggest.mjs +1 -1
- package/tests/deploy_divergence_line.mjs +11 -11
- package/tests/design_tokens_sync.mjs +3 -2
- package/tests/diagram_facts_offset.mjs +1 -1
- package/tests/do_api.mjs +106 -0
- package/tests/feedback_latest.mjs +122 -0
- package/tests/fitness.mjs +44 -42
- package/tests/fitness_ratchets.mjs +2 -2
- package/tests/gate_approvals.mjs +0 -1
- package/tests/github_push_land_proof.mjs +1 -1
- package/tests/go_live.mjs +3 -3
- package/tests/goal_advisory.mjs +3 -3
- package/tests/goal_suggest.mjs +3 -3
- package/tests/government_abuse_matrix.mjs +21 -19
- package/tests/government_ownership_scope.mjs +33 -35
- package/tests/government_parity.mjs +2 -2
- package/tests/government_protected_surfaces.mjs +56 -37
- package/tests/government_require_permission.mjs +3 -3
- package/tests/government_seed.mjs +37 -4
- package/tests/government_session_scope.mjs +92 -366
- package/tests/hall_audit.mjs +1 -1
- package/tests/hall_error_envelope.mjs +4 -0
- package/tests/hall_landing_boot.mjs +4 -1
- package/tests/hall_palette.mjs +1 -16
- package/tests/hall_record_world.mjs +6 -0
- package/tests/hall_tweak_editor.mjs +52 -1
- package/tests/helpers.mjs +1 -1
- package/tests/host_topology_skips.mjs +3 -4
- package/tests/html_comment_nesting.mjs +137 -0
- package/tests/infra_exec_paths.mjs +9 -10
- package/tests/init.mjs +4 -5
- package/tests/instance_manifest.mjs +18 -25
- package/tests/kickoff_checklist.mjs +1 -1
- package/tests/lib_sh_resolution.mjs +0 -221
- package/tests/local_preview.mjs +0 -12
- package/tests/migration_namespace.mjs +6 -6
- package/tests/module-scope-map.mjs +9 -9
- package/tests/module_api.mjs +2 -2
- package/tests/module_catalog.mjs +1 -1
- package/tests/module_cli.mjs +20 -20
- package/tests/module_contributions.mjs +6 -6
- package/tests/module_loader.mjs +6 -6
- package/tests/module_manifest.mjs +26 -17
- package/tests/module_route_rank.mjs +2 -2
- package/tests/module_store_publish.mjs +307 -0
- package/tests/module_store_publish_route.mjs +121 -0
- package/tests/module_store_registry_migration.mjs +70 -0
- package/tests/modules.mjs +56 -26
- package/tests/modules_route_wiring.mjs +1 -1
- package/tests/onboarding_route_signals.mjs +21 -31
- package/tests/onboarding_state.mjs +35 -49
- package/tests/paste_token_session_store.mjs +1 -1
- package/tests/permission_path.mjs +0 -3
- package/tests/platform_boot.mjs +1 -2
- package/tests/platform_visibility_gate.mjs +1 -1
- package/tests/profile_route.mjs +1 -6
- package/tests/project_modules_ui.mjs +17 -17
- package/tests/projects_hub.mjs +5 -5
- package/tests/projects_hub_app_step.mjs +138 -0
- package/tests/projects_hub_module_picker.mjs +77 -76
- package/tests/projects_hub_pre_uat.mjs +4 -5
- package/tests/provision.mjs +7 -7
- package/tests/provisioning_capacity.mjs +1 -1
- package/tests/provisioning_recommendations.mjs +2 -1
- package/tests/provisioning_starter_bundles.mjs +0 -22
- package/tests/publish_branch_route.mjs +11 -19
- package/tests/publish_manifest.mjs +2 -3
- package/tests/publish_reconciler.mjs +8 -8
- package/tests/push_path_brief.mjs +10 -9
- package/tests/rank_tier_single_source.mjs +1 -13
- package/tests/regrade_eligibility.mjs +1 -1
- package/tests/rename_history_restraint.mjs +1 -1
- package/tests/repo_map.mjs +12 -13
- package/tests/runner_drift.mjs +3 -21
- package/tests/sandbox_stage.mjs +57 -390
- package/tests/seam_wiring_guard.mjs +11 -11
- package/tests/search_isolation.mjs +3 -3
- package/tests/session_records.mjs +1 -1
- package/tests/session_rename_fallback.mjs +1 -22
- package/tests/session_start_freshness.mjs +3 -3
- package/tests/session_token_hash_db.mjs +1 -1
- package/tests/ship_card.mjs +1 -1
- package/tests/ship_ci_deploy.mjs +17 -17
- package/tests/ship_error_shape.mjs +1 -1
- package/tests/ship_premerge.mjs +1 -1
- package/tests/ship_resume.mjs +1 -1
- package/tests/skill_preflight.mjs +2 -2
- package/tests/skip_is_not_pass.mjs +1 -1
- package/tests/task_visual_slots.mjs +31 -0
- package/tests/terms_acceptance.mjs +2 -2
- package/tests/upgrade.mjs +4 -4
- package/tests/watch_sealed_floor.mjs +0 -1
- package/tests/wizard_draft_resume.mjs +2 -2
- package/tests/wizard_intent_resume.mjs +21 -10
- package/tests/wizard_preselect_why.mjs +18 -18
- package/docs/onboarding/browser-terminal-guide.md +0 -73
- package/docs/recipes/managed-settings-remote-control.md +0 -416
- package/modules/dev-box/CLAUDE.md +0 -15
- package/modules/dev-box/box-access.js +0 -623
- package/modules/dev-box/box-credential.js +0 -94
- package/modules/dev-box/box-onboard.js +0 -486
- package/modules/dev-box/boxes.js +0 -1038
- package/modules/dev-box/db.js +0 -39
- package/modules/dev-box/module.json +0 -19
- package/modules/dev-box/routes/box.js +0 -1223
- package/scripts/gds/box-auth-check.js +0 -160
- package/scripts/gds/box-infra.js +0 -321
- package/scripts/gds/box-sync.js +0 -216
- package/scripts/gds/box.js +0 -1332
- package/scripts/gds/cf-tunnel.js +0 -558
- package/scripts/gds/smoke-box.sh +0 -150
- package/scripts/gds/tree-preflight.js +0 -241
- package/src/bongos/app-pair.js +0 -172
- package/tests/app_pair.mjs +0 -168
- package/tests/box_access.mjs +0 -578
- package/tests/box_auth_check.mjs +0 -114
- package/tests/box_code_staleness.mjs +0 -129
- package/tests/box_connect_e2e.mjs +0 -196
- package/tests/box_cost.mjs +0 -70
- package/tests/box_credential.mjs +0 -125
- package/tests/box_dns_repoint.mjs +0 -153
- package/tests/box_env.mjs +0 -64
- package/tests/box_host_keys.mjs +0 -126
- package/tests/box_infra_resolve.mjs +0 -246
- package/tests/box_onboard.mjs +0 -317
- package/tests/box_scope_session.mjs +0 -119
- package/tests/box_sweep_docs.mjs +0 -119
- package/tests/box_sync.mjs +0 -55
- package/tests/box_sync_scope_report.mjs +0 -126
- package/tests/box_task_scope.mjs +0 -169
- package/tests/box_task_scope_pause.mjs +0 -66
- package/tests/box_terminal_ssrf.mjs +0 -96
- package/tests/boxes.mjs +0 -1585
- package/tests/cf_ruleset_preflight.mjs +0 -142
- package/tests/cf_tunnel.mjs +0 -496
- package/tests/docs_scrubber_damage.mjs +0 -111
- package/tests/module_scope_active_claims.mjs +0 -94
- package/tests/ship_api_client_pathspec.mjs +0 -100
- package/tests/tree_preflight.mjs +0 -243
|
@@ -65,7 +65,7 @@ const API = `(?:${ALL_API_PREFIXES.map(escRe).join('|')})`;
|
|
|
65
65
|
// Caddy's on-demand-TLS `ask` and the provisioning probe —
|
|
66
66
|
// server-to-server, cookie-less, and on a control plane a
|
|
67
67
|
// refused ask means no project ever gets a certificate.
|
|
68
|
-
// /downloads/* the installer script and the
|
|
68
|
+
// /downloads/* the installer script and the release downloads — a
|
|
69
69
|
// cookie-less `curl … | sh`, and release redirects carry
|
|
70
70
|
// no project data.
|
|
71
71
|
// /__prelaunch the pre-launch gate's own submit, when both gates are on.
|
|
@@ -221,9 +221,7 @@ const EXPECTED_RANKS = {
|
|
|
221
221
|
// criterion confirmation, and gate-approvals — moved to MODULE_EXPECTED_RANKS
|
|
222
222
|
// ['lifecycle'] below in BV1.R86. They live in the lifecycle module now
|
|
223
223
|
// (modules/lifecycle/routes/…), so they're verified only when that module is in
|
|
224
|
-
// scope, not unconditionally from this core list — the pins follow the routes
|
|
225
|
-
// the dev-box precedent.)
|
|
226
|
-
// (dev-box fleet pins likewise live in MODULE_EXPECTED_RANKS below — BV1.R43.)
|
|
224
|
+
// scope, not unconditionally from this core list — the pins follow the routes.)
|
|
227
225
|
// security engagement docs — exploit-detail report store (task 991). Pinned
|
|
228
226
|
// because a silent downgrade here leaks the attacker cookbook below the
|
|
229
227
|
// trust line. (security-the-PROPERTY stays kernel — routes/security.js.)
|
|
@@ -277,10 +275,8 @@ const GOVERNMENT_PINS = {
|
|
|
277
275
|
// when that module is in scope (its routes were discovered in this scan). So an
|
|
278
276
|
// instance that doesn't ship the module — or a hermetic test isolating a
|
|
279
277
|
// different module — never trips a false "vanished" alarm, while a real rank
|
|
280
|
-
// downgrade or a removal IS still caught wherever the module is present
|
|
281
|
-
//
|
|
282
|
-
// follow the routes; the intent is unchanged: closing/blocking another builder's
|
|
283
|
-
// paid infra, and reading the fleet/cost roster, stay archon-only.)
|
|
278
|
+
// downgrade or a removal IS still caught wherever the module is present — the pins
|
|
279
|
+
// follow the routes (BV1.R43 / task 1412 moved the first set here).
|
|
284
280
|
//
|
|
285
281
|
// The `ideas` / `memory` / `security` blocks were added in BV1.R109b: their routes are
|
|
286
282
|
// declared multi-line and so were invisible to this auditor until it learned that form
|
|
@@ -293,15 +289,6 @@ const GOVERNMENT_PINS = {
|
|
|
293
289
|
// (metic), `security.report.adjudicate` / `idea.ratify` / `memory.read.any` (archon) —
|
|
294
290
|
// so tightening these to `perm:` pins is a clean follow-up once R104-R107 have landed.
|
|
295
291
|
const MODULE_EXPECTED_RANKS = {
|
|
296
|
-
'dev-box': {
|
|
297
|
-
// ADR 0157 lowered `box.fleet.manage` archon→metic (operating paid infra is admin
|
|
298
|
-
// work, not an authority grant). Re-pinned by ATOM so the guarded invariant is
|
|
299
|
-
// "these gate on the fleet permission", independent of its floor.
|
|
300
|
-
'GET /boxes': 'perm:box.fleet.manage',
|
|
301
|
-
'GET /boxes/cost-ledger': 'perm:box.fleet.manage',
|
|
302
|
-
'POST /boxes/:builderId/close': 'perm:box.fleet.manage',
|
|
303
|
-
'PATCH /boxes/:builderId/block': 'perm:box.fleet.manage',
|
|
304
|
-
},
|
|
305
292
|
// The lifecycle module's privileged routes (carved BV1.R86 — the pins follow the
|
|
306
293
|
// routes from EXPECTED_RANKS). Verified only when the lifecycle module is in scope.
|
|
307
294
|
lifecycle: {
|
|
@@ -13,7 +13,6 @@
|
|
|
13
13
|
// - /auth/discord/* LINKING an existing builder to a Discord id for role sync.
|
|
14
14
|
// It is NOT a way to sign in — Discord sits OUTSIDE the trust
|
|
15
15
|
// boundary (ADR 0032), so it can never mint a session.
|
|
16
|
-
// - /auth/app-pair/* the Dev Box desktop app pairing to its builder (ADR 0045).
|
|
17
16
|
//
|
|
18
17
|
// TWO INVARIANTS worth keeping in view before editing:
|
|
19
18
|
// - `/auth/cli-token/issue` mints a CLI bearer from a browser COOKIE, never from a
|
|
@@ -36,7 +35,6 @@ const { reportActivityRollupForShippedTask } = require('../auth-admission.js');
|
|
|
36
35
|
// endpoint refuses. Same composed answer the gate itself reads.
|
|
37
36
|
const { effectiveJoinDoor } = require('../project-door.js');
|
|
38
37
|
const db = require('../db');
|
|
39
|
-
const { pairStore } = require('../app-pair');
|
|
40
38
|
const { publicOrigin, parseCookie, validateOrRespond } = require('./_helpers');
|
|
41
39
|
const { accountExistenceReadRateLimit } = require('../middleware/rate-limit');
|
|
42
40
|
const { branding } = require('../../branding');
|
|
@@ -1162,120 +1160,6 @@ module.exports = function buildAuthRouter() {
|
|
|
1162
1160
|
}
|
|
1163
1161
|
});
|
|
1164
1162
|
|
|
1165
|
-
// /auth/app-pair — browser-clicks-only sign-in for the Dev Box desktop app
|
|
1166
|
-
// (#904, ADR 0045). The app starts a pairing, opens the browser at
|
|
1167
|
-
// /builders/pair?code=…, the signed-in builder clicks ONE Approve button,
|
|
1168
|
-
// and the app collects a freshly-minted source='cli' session over its own
|
|
1169
|
-
// secret poll channel. Design + threat model in src/bongos/app-pair.js.
|
|
1170
|
-
//
|
|
1171
|
-
// Route order note: the two PUBLIC routes here sit so no requireBuilder/
|
|
1172
|
-
// requireRank from a NEIGHBORING declaration falls inside route-rank-check's
|
|
1173
|
-
// 20-line look-ahead — the deterministic auditor must classify them from
|
|
1174
|
-
// their own `rank:` annotations, not a false middleware hit.
|
|
1175
|
-
|
|
1176
|
-
// rank: public — starts a pairing; returns opaque codes only, no identity.
|
|
1177
|
-
router.post('/auth/app-pair/start', (req, res) => {
|
|
1178
|
-
if (validateOrRespond(req, res, {
|
|
1179
|
-
device_name: { type: 'string', maxLength: 80 },
|
|
1180
|
-
platform: { type: 'string', maxLength: 20 },
|
|
1181
|
-
})) return;
|
|
1182
|
-
const body = req.body || {};
|
|
1183
|
-
const out = pairStore.start({ deviceName: body.device_name, platform: body.platform });
|
|
1184
|
-
if (out.error === 'busy') {
|
|
1185
|
-
return res.fail('pairing_busy', { status: 429, message: 'Too many pairings in flight. Try again in a few minutes.' });
|
|
1186
|
-
}
|
|
1187
|
-
// The approve-page URL is PINNED to the canonical public origin (same
|
|
1188
|
-
// posture as the OAuth redirect_uris above) — never derived from the
|
|
1189
|
-
// request host, so a spoofed Host header can't point the app's browser
|
|
1190
|
-
// hand-off at a look-alike approve page.
|
|
1191
|
-
const verifyPath = `/builders/pair?code=${out.pairCode}`;
|
|
1192
|
-
res.status(201).json({
|
|
1193
|
-
pair_code: out.pairCode,
|
|
1194
|
-
poll_token: out.pollToken,
|
|
1195
|
-
verify_path: verifyPath,
|
|
1196
|
-
verify_url: `${WEB_REDIRECT_ORIGIN}${verifyPath}`,
|
|
1197
|
-
expires_in: out.expiresIn,
|
|
1198
|
-
interval: out.interval,
|
|
1199
|
-
});
|
|
1200
|
-
});
|
|
1201
|
-
|
|
1202
|
-
// rank: any-builder — read-only: what the approve page shows BEFORE the
|
|
1203
|
-
// click (device name/platform for the given code). Unknown and expired are
|
|
1204
|
-
// indistinguishable (404) so the code space can't be probed for liveness.
|
|
1205
|
-
router.get('/auth/app-pair/info', auth.requireBuilder, (req, res) => {
|
|
1206
|
-
const info = pairStore.info(req.query.code);
|
|
1207
|
-
if (!info) return res.fail('unknown_or_expired', 404);
|
|
1208
|
-
res.json({
|
|
1209
|
-
pair_code: info.pairCode,
|
|
1210
|
-
device_name: info.deviceName,
|
|
1211
|
-
platform: info.platform,
|
|
1212
|
-
status: info.status,
|
|
1213
|
-
});
|
|
1214
|
-
});
|
|
1215
|
-
|
|
1216
|
-
// rank: any-builder (own identity only) — approving signs the app in AS THE
|
|
1217
|
-
// APPROVER. Cookie-gated exactly like /auth/cli-token/issue above: a leaked
|
|
1218
|
-
// CLI bearer must not be able to approve pairings and mint itself siblings;
|
|
1219
|
-
// only the httpOnly browser cookie — the click in the hall — can.
|
|
1220
|
-
router.post('/auth/app-pair/approve', auth.requireBuilder, async (req, res) => {
|
|
1221
|
-
const cookieToken = sessionCookieToken(req);
|
|
1222
|
-
if (!cookieToken) {
|
|
1223
|
-
return res.fail('cookie_required', { status: 403, message: 'Pairings are approved from an authenticated browser session in the builders’ hall, not from a bearer token.' });
|
|
1224
|
-
}
|
|
1225
|
-
if (validateOrRespond(req, res, {
|
|
1226
|
-
pair_code: { required: true, type: 'string', maxLength: 32 },
|
|
1227
|
-
})) return;
|
|
1228
|
-
try {
|
|
1229
|
-
const token = crypto.randomBytes(32).toString('hex');
|
|
1230
|
-
const row = await db.createSession({
|
|
1231
|
-
builderId: req.builder.id,
|
|
1232
|
-
token,
|
|
1233
|
-
source: 'cli', // 003_pms.sql CHECK; the app is a CLI-class bearer client
|
|
1234
|
-
userAgent: `devbox-app pair (${req.headers['user-agent'] ?? 'unknown'})`,
|
|
1235
|
-
expiresAt: null, // no expiry — a CLI bearer lasts until revoked (ADR 0294)
|
|
1236
|
-
});
|
|
1237
|
-
const out = pairStore.approve((req.body || {}).pair_code, {
|
|
1238
|
-
token,
|
|
1239
|
-
expires_at: row.expires_at,
|
|
1240
|
-
builder: {
|
|
1241
|
-
id: req.builder.id,
|
|
1242
|
-
github_login: req.builder.github_login,
|
|
1243
|
-
display_name: req.builder.display_name,
|
|
1244
|
-
rank: req.builder.rank,
|
|
1245
|
-
},
|
|
1246
|
-
});
|
|
1247
|
-
if (!out.ok) {
|
|
1248
|
-
// The pairing was gone or already approved — revoke the session we
|
|
1249
|
-
// just minted so an orphaned token never outlives this branch.
|
|
1250
|
-
await db.revokeSession(token);
|
|
1251
|
-
return out.error === 'already_approved'
|
|
1252
|
-
? res.fail('already_approved', 409)
|
|
1253
|
-
: res.fail('unknown_or_expired', 404);
|
|
1254
|
-
}
|
|
1255
|
-
res.json({ ok: true });
|
|
1256
|
-
} catch (err) {
|
|
1257
|
-
log.error('[gds] POST /auth/app-pair/approve', err);
|
|
1258
|
-
res.fail('approve_failed', { status: 500, message: 'internal error' });
|
|
1259
|
-
}
|
|
1260
|
-
});
|
|
1261
|
-
|
|
1262
|
-
// rank: public — poll with the secret poll_token; the token IS the bearer
|
|
1263
|
-
// (mirrors /auth/device/poll, where the device_code is the bearer). Kept
|
|
1264
|
-
// LAST in this router so route-rank-check's look-ahead window stays clear
|
|
1265
|
-
// of any neighboring gated declaration (see the block comment above).
|
|
1266
|
-
router.post('/auth/app-pair/poll', (req, res) => {
|
|
1267
|
-
if (validateOrRespond(req, res, {
|
|
1268
|
-
poll_token: { required: true, type: 'string', maxLength: 128 },
|
|
1269
|
-
})) return;
|
|
1270
|
-
const out = pairStore.poll((req.body || {}).poll_token);
|
|
1271
|
-
if (out.status === 'approved') {
|
|
1272
|
-
return res.json({ status: 'approved', ...out.grant });
|
|
1273
|
-
}
|
|
1274
|
-
res.json(out.status === 'pending'
|
|
1275
|
-
? { status: 'pending', interval: out.interval }
|
|
1276
|
-
: { status: 'expired' });
|
|
1277
|
-
});
|
|
1278
|
-
|
|
1279
1163
|
return router;
|
|
1280
1164
|
};
|
|
1281
1165
|
|
|
@@ -82,14 +82,6 @@ function staleHoursFor(req, res) {
|
|
|
82
82
|
// kernel PORT (lc()). The rank/identity mutations (setBuilderRank/deactivate/
|
|
83
83
|
// reactivate) stay core db (this domain is builder identity, not the lifecycle).
|
|
84
84
|
const lc = () => seams.resolveOptional('lifecycle');
|
|
85
|
-
// Dev-box "has this builder ever connected a box?" read through the kernel seam
|
|
86
|
-
// instead of a direct `require('../boxes')` (a forbidden core→module edge once
|
|
87
|
-
// dev-box moved to modules/dev-box/, ADR 0083 / BV1.R43). The dev-box module
|
|
88
|
-
// provides the `box.hasEverConnected` port when enabled; degrades to false on a
|
|
89
|
-
// box-less instance (the box_connect onboarding step just never auto-clears).
|
|
90
|
-
const boxHasEverConnected = (builderId) =>
|
|
91
|
-
seams.resolveOptional('box.hasEverConnected', async () => false)(builderId);
|
|
92
|
-
|
|
93
85
|
// The discord "has this builder linked their Discord account?" signal, read
|
|
94
86
|
// through the kernel seam instead of a direct `db.getDiscordLink` (a forbidden
|
|
95
87
|
// core→module edge once discord moved to modules/discord/, ADR 0083 / BV1.R47).
|
|
@@ -470,22 +462,20 @@ module.exports = function buildBuildersRouter() {
|
|
|
470
462
|
// never a direct require of a module. Default-on (present in prod); degrades
|
|
471
463
|
// to a null state row (shapeForApi synthesizes the "nothing cleared" view).
|
|
472
464
|
const onboarding = seams.resolveOptional('onboarding');
|
|
473
|
-
const [state, shipCount, discordLink,
|
|
465
|
+
const [state, shipCount, discordLink, cliAuthed] = await Promise.all([
|
|
474
466
|
onboarding ? onboarding.getState(pool, targetId) : null,
|
|
475
467
|
lc().countShippedTasksForBuilder(targetId),
|
|
476
468
|
discordIsLinked(targetId),
|
|
477
|
-
boxHasEverConnected(targetId),
|
|
478
469
|
db.hasCliSession(targetId),
|
|
479
470
|
]);
|
|
480
471
|
// shipCount drives the terminal 'ship_three' stage's "N of 3" counter.
|
|
481
|
-
// The
|
|
472
|
+
// The discord/cli steps clear from LIVE signals (not the onboarding_state
|
|
482
473
|
// row), so they must be passed in exactly as GET /me does — otherwise they
|
|
483
|
-
// default to false and this route renders
|
|
474
|
+
// default to false and this route renders join_discord/cli_setup
|
|
484
475
|
// perpetually unchecked even after the builder cleared them (task 1444).
|
|
485
476
|
const shaped = onboarding ? onboarding.shapeForApi(state, {
|
|
486
477
|
shipCount,
|
|
487
478
|
discordLinked: !!discordLink,
|
|
488
|
-
boxConnected: !!boxConnected,
|
|
489
479
|
cliAuthed: !!cliAuthed,
|
|
490
480
|
}) : null;
|
|
491
481
|
// Light cache — onboarding state changes a few times per builder, ever.
|
|
@@ -510,7 +500,7 @@ module.exports = function buildBuildersRouter() {
|
|
|
510
500
|
// shareable without a sign-in wall (that IS the feature). It deliberately
|
|
511
501
|
// WHITELISTS the public column subset off the full builder row — the private
|
|
512
502
|
// columns getBuilderById also returns (anthropic_email / anthropic_user_id /
|
|
513
|
-
// monthly_budget_usd / system_role
|
|
503
|
+
// monthly_budget_usd / system_role) are NEVER spread into the
|
|
514
504
|
// JSON. A leak-prevention unit test (tests/profile_route.mjs) asserts their
|
|
515
505
|
// absence + the 404-on-unknown-id behaviour.
|
|
516
506
|
// THROTTLED since task 1002804. The global limiter meters WRITES only
|
|
@@ -554,7 +544,7 @@ module.exports = function buildBuildersRouter() {
|
|
|
554
544
|
]);
|
|
555
545
|
// WHITELIST — only the leaderboard-equivalent public field set. Never spread
|
|
556
546
|
// the raw row: it carries anthropic_email / anthropic_user_id /
|
|
557
|
-
// monthly_budget_usd / system_role /
|
|
547
|
+
// monthly_budget_usd / system_role / deactivated_reason,
|
|
558
548
|
// none of which are public.
|
|
559
549
|
const builder = {
|
|
560
550
|
id: b.id,
|
|
@@ -9,14 +9,12 @@
|
|
|
9
9
|
// * modules + disciplines — the R54 feature-module flags
|
|
10
10
|
// * rank ladder — the canonical live tiers + the instance's labels
|
|
11
11
|
// * repo binding — the GitHub owner/repo for `--local` clones
|
|
12
|
-
// * auth methods — how a stranger signs in (device-flow / web
|
|
12
|
+
// * auth methods — how a stranger signs in (device-flow / web)
|
|
13
13
|
// * admission policy — invite-only vs open self-enrollment
|
|
14
|
-
// * dev-box availability — whether the managed cloud-box backend is on
|
|
15
14
|
//
|
|
16
15
|
// WHY THIS EXISTS: GET /healthz returns only { ok, auth_configured } — too thin
|
|
17
|
-
// for a CLI to brand + feature-gate itself
|
|
18
|
-
//
|
|
19
|
-
// that gap calls for.
|
|
16
|
+
// for a CLI to brand + feature-gate itself against an instance's modules. This is
|
|
17
|
+
// the richer public descriptor that gap calls for.
|
|
20
18
|
//
|
|
21
19
|
// SECURITY (no secrets / no topology): the manifest is assembled ONLY from
|
|
22
20
|
// already-client-safe projections — clientBranding() (the documented allow-list
|
|
@@ -35,7 +33,7 @@
|
|
|
35
33
|
const express = require('express');
|
|
36
34
|
const { corsPublicGet, asyncHandler } = require('./_helpers');
|
|
37
35
|
const { clientBranding } = require('../../branding');
|
|
38
|
-
const { clientModules
|
|
36
|
+
const { clientModules } = require('../../modules');
|
|
39
37
|
const { loadRepoInfo } = require('../repo-info');
|
|
40
38
|
const { authConfigured, openEnrollmentEnabled, loadIdpConfig, resolveSession } = require('../auth');
|
|
41
39
|
const { RANK_TIER } = require('../permission-path-check');
|
|
@@ -49,15 +47,14 @@ const MANIFEST_SCHEMA = 'instance-manifest/v1';
|
|
|
49
47
|
|
|
50
48
|
// Supported sign-in methods. All require GitHub auth to be configured (the
|
|
51
49
|
// server holds the OAuth client creds): device-flow = the CLI Device Flow;
|
|
52
|
-
// web = the browsers' hall sign-in (completeWebFlow)
|
|
53
|
-
//
|
|
54
|
-
|
|
55
|
-
const AUTH_METHODS = ['device-flow', 'web', 'app-pair'];
|
|
50
|
+
// web = the browsers' hall sign-in (completeWebFlow). Both mount unconditionally
|
|
51
|
+
// in routes/auth.js, so configured-ness is the only gate.
|
|
52
|
+
const AUTH_METHODS = ['device-flow', 'web'];
|
|
56
53
|
|
|
57
54
|
// buildInstanceManifest — PURE assembler. Every input is injected so this is
|
|
58
55
|
// unit-testable without disk, DB, or git. Inputs are the CLIENT-SAFE projections
|
|
59
56
|
// (branding = clientBranding() output, modules = clientModules() output), the
|
|
60
|
-
// canonical rank-tier map, loadRepoInfo()'s result, and
|
|
57
|
+
// canonical rank-tier map, loadRepoInfo()'s result, and two booleans. Returns
|
|
61
58
|
// the JSON body verbatim.
|
|
62
59
|
function buildInstanceManifest({
|
|
63
60
|
branding,
|
|
@@ -66,7 +63,6 @@ function buildInstanceManifest({
|
|
|
66
63
|
repo,
|
|
67
64
|
authConfigured: authOk,
|
|
68
65
|
openEnrollment,
|
|
69
|
-
devBoxEnabled,
|
|
70
66
|
idp = null,
|
|
71
67
|
} = {}) {
|
|
72
68
|
const b = branding || {};
|
|
@@ -109,7 +105,6 @@ function buildInstanceManifest({
|
|
|
109
105
|
idp: idp || null,
|
|
110
106
|
},
|
|
111
107
|
admission: { policy: openEnrollment ? 'open' : 'invite-only' },
|
|
112
|
-
devBox: { available: !!devBoxEnabled },
|
|
113
108
|
};
|
|
114
109
|
}
|
|
115
110
|
|
|
@@ -144,7 +139,6 @@ function currentManifest() {
|
|
|
144
139
|
// what the gate will do — and 'invite-only' already covers every non-open
|
|
145
140
|
// door, so narrowing it names no state (ADR 0247 D3).
|
|
146
141
|
openEnrollment: openEnrollmentEnabled(),
|
|
147
|
-
devBoxEnabled: isModuleEnabled('dev-box'),
|
|
148
142
|
// Project OUT the client secret — the manifest is public; only origin +
|
|
149
143
|
// client_id (both public) advertise federation.
|
|
150
144
|
idp: idpCfg ? { origin: idpCfg.origin, client_id: idpCfg.clientId } : null,
|
package/src/bongos/routes/me.js
CHANGED
|
@@ -74,15 +74,6 @@ function boundShipDaysToYear(rows, todayStr) {
|
|
|
74
74
|
return o != null && o >= cutoff && o <= todayOrd;
|
|
75
75
|
});
|
|
76
76
|
}
|
|
77
|
-
// The dev-box "has this builder ever connected a box?" signal, read through the
|
|
78
|
-
// kernel seam instead of a direct `require('../boxes')` (a forbidden core→module
|
|
79
|
-
// edge once dev-box moved to modules/dev-box/, ADR 0083 / BV1.R43). The dev-box
|
|
80
|
-
// module provides the `box.hasEverConnected` port when enabled; resolveOptional
|
|
81
|
-
// degrades to false on a box-less instance, so onboarding's box_connect step
|
|
82
|
-
// simply never auto-clears there. Resolved per call (the provider registers at
|
|
83
|
-
// boot, before any request).
|
|
84
|
-
const boxHasEverConnected = (builderId) =>
|
|
85
|
-
seams.resolveOptional('box.hasEverConnected', async () => false)(builderId);
|
|
86
77
|
// The art-pipeline "is the shared newcomer key configured?" signal, read through
|
|
87
78
|
// the kernel seam (ADR 0083 / BV1.R48). The art-pipeline module provides
|
|
88
79
|
// 'art.sharedKeyConfigured' when enabled; resolveOptional degrades to false on a
|
|
@@ -192,7 +183,7 @@ module.exports = function buildMeRouter() {
|
|
|
192
183
|
const specialities = seams.resolveOptional('specialities');
|
|
193
184
|
const [
|
|
194
185
|
builder, activeClaims, estVsActual, unlocked, lockedWithProgress, streakInfo,
|
|
195
|
-
recentGrades, builderAvg, onboardingRow, shipCount, recentKarma, karma30d, discordLink,
|
|
186
|
+
recentGrades, builderAvg, onboardingRow, shipCount, recentKarma, karma30d, discordLink, cliAuthed,
|
|
196
187
|
behaviorPrefs, geminiKeyRow, needsRebaseTasks, foundingBuilder, helpRequestCount,
|
|
197
188
|
taskRecommendationCount, boardVotesPending, socialMode, mingleIntroductionCount,
|
|
198
189
|
navPermissions,
|
|
@@ -227,14 +218,8 @@ module.exports = function buildMeRouter() {
|
|
|
227
218
|
// follow-up call. Boolean only — the discord_user_id itself is never sent
|
|
228
219
|
// to the client (it's PII the UI has no need for).
|
|
229
220
|
discordIsLinked(req.builder.id),
|
|
230
|
-
// #
|
|
231
|
-
//
|
|
232
|
-
// step — the monotonic "properly connected to the dev box" signal.
|
|
233
|
-
boxHasEverConnected(req.builder.id),
|
|
234
|
-
// #777: whether the builder has ever authenticated the GDS CLI (ran
|
|
235
|
-
// /builder-setup, or the box-connect bootstrap). Auto-clears the
|
|
236
|
-
// onboarding 'cli_setup' step — distinct from box_connect (#823: both
|
|
237
|
-
// steps have a place — connecting vs. signing the GDS in).
|
|
221
|
+
// #777: whether the builder has ever authenticated the CLI (ran
|
|
222
|
+
// /builder-setup). Auto-clears the onboarding 'cli_setup' step.
|
|
238
223
|
db.hasCliSession(req.builder.id),
|
|
239
224
|
// task 1268: the builder's behavior_prefs blob (the 'wandering' + 'render'
|
|
240
225
|
// knobs), resolved against builder.rank below so /me carries the effective
|
|
@@ -431,7 +416,7 @@ module.exports = function buildMeRouter() {
|
|
|
431
416
|
// returns a stage-1 "nothing cleared" view so the client renders the
|
|
432
417
|
// checklist either way. Through the `onboarding` port (BV1.R81); null when
|
|
433
418
|
// the module is off.
|
|
434
|
-
onboarding: onboarding ? onboarding.shapeForApi(onboardingRow, { shipCount, discordLinked: !!discordLink,
|
|
419
|
+
onboarding: onboarding ? onboarding.shapeForApi(onboardingRow, { shipCount, discordLinked: !!discordLink, cliAuthed: !!cliAuthed }) : null,
|
|
435
420
|
// F7 (ADR 0033): Discord link state for the hall's Connect/Unlink UI.
|
|
436
421
|
// `configured` tells the client whether the server even has Discord OAuth
|
|
437
422
|
// creds — when false the hall renders nothing (the start route 503s).
|
|
@@ -525,7 +510,7 @@ module.exports = function buildMeRouter() {
|
|
|
525
510
|
};
|
|
526
511
|
// Module→core contribution seam (BV1.R42, ADR 0083). Let each enabled module
|
|
527
512
|
// add its own profile slice instead of core hardcoding it — the strangler
|
|
528
|
-
// prerequisite that lets
|
|
513
|
+
// prerequisite that lets discord/art leave this route. ADD-ONLY (core
|
|
529
514
|
// keys win); a collision means a move task took over a core key without
|
|
530
515
|
// dropping the core copy. Empty until a module registers → byte-identical today.
|
|
531
516
|
const slices = await seams.collectContributions('profile.fields', {
|
|
@@ -570,14 +555,13 @@ module.exports = function buildMeRouter() {
|
|
|
570
555
|
}
|
|
571
556
|
// Echo the onboarding state back so the hall's path checklist can tick
|
|
572
557
|
// this step off without a follow-up /me round-trip.
|
|
573
|
-
const [state, shipCount, discordLink,
|
|
558
|
+
const [state, shipCount, discordLink, cliAuthed] = await Promise.all([
|
|
574
559
|
onboardingPort ? onboardingPort.getState(pool, req.builder.id) : null,
|
|
575
560
|
lc().countShippedTasksForBuilder(req.builder.id),
|
|
576
561
|
discordIsLinked(req.builder.id),
|
|
577
|
-
boxHasEverConnected(req.builder.id),
|
|
578
562
|
db.hasCliSession(req.builder.id),
|
|
579
563
|
]);
|
|
580
|
-
const onboarding = onboardingPort ? onboardingPort.shapeForApi(state, { shipCount, discordLinked: !!discordLink,
|
|
564
|
+
const onboarding = onboardingPort ? onboardingPort.shapeForApi(state, { shipCount, discordLinked: !!discordLink, cliAuthed: !!cliAuthed }) : null;
|
|
581
565
|
res.json({ builder, onboarding });
|
|
582
566
|
} catch (err) {
|
|
583
567
|
if (err.code === 'invalid_discipline') {
|
|
@@ -17,6 +17,12 @@
|
|
|
17
17
|
// authoritative check — `bongos module submit`
|
|
18
18
|
// is a thin CLI client of this same route).
|
|
19
19
|
// GET /modules/submissions list queued submissions (task 1771 reads this).
|
|
20
|
+
// POST /store/modules/:key/versions
|
|
21
|
+
// publish one module version to the Bongos-hosted
|
|
22
|
+
// module store (task 1004271, ADR 0338 D1) — the
|
|
23
|
+
// server end of `bongos module publish`. Not the
|
|
24
|
+
// upstreaming door above: submit proposes a module
|
|
25
|
+
// INTO core; publish puts a version in the store.
|
|
20
26
|
//
|
|
21
27
|
// Read is open to any signed-in builder (same gate as the atlas/primer pages);
|
|
22
28
|
// every write (enable/disable, submit) is metic+archon, same rank as
|
|
@@ -58,6 +64,9 @@ const modulesLib = require('../../modules');
|
|
|
58
64
|
// server process.
|
|
59
65
|
const moduleCli = require('../../../scripts/gds/module');
|
|
60
66
|
const moduleSubmissions = require('../module-submissions');
|
|
67
|
+
const moduleArtifact = require('../../../scripts/gds/module-artifact');
|
|
68
|
+
const { stageArtifact, commitArtifact, removeArtifact, relativeArtifactPath, publishVersion } = require('../module-store');
|
|
69
|
+
const { KEY_RE } = require('../../module-loader/manifest-schema');
|
|
61
70
|
// Named imports, not a namespace object (task 1004052 strand walk): knip cannot follow
|
|
62
71
|
// property access on a CommonJS namespace, so reaching these through one made them read
|
|
63
72
|
// as unused exports and tripped the dead-code ratchet that held this branch out of main.
|
|
@@ -70,6 +79,21 @@ const { listOverrides, setOverride } = require('../module-overrides');
|
|
|
70
79
|
// cap (SECURITY_DOC_CONTENT_MAX) in routes/security.js.
|
|
71
80
|
const SUBMISSION_MAX_BYTES = 5_000_000;
|
|
72
81
|
|
|
82
|
+
// A published version's body is the tarball itself, never JSON (the global parser
|
|
83
|
+
// only reads application/json, so it passes this through). Capped at the parser and
|
|
84
|
+
// never inflated from a Content-Encoding, so the cap is the bytes on the wire.
|
|
85
|
+
const TARBALL_BODY_TYPES = ['application/gzip', 'application/x-gzip', 'application/octet-stream'];
|
|
86
|
+
const rawTarball = express.raw({ type: TARBALL_BODY_TYPES, limit: moduleArtifact.MAX_TARBALL_BYTES, inflate: false });
|
|
87
|
+
function tarballBody(req, res, next) {
|
|
88
|
+
rawTarball(req, res, (err) => {
|
|
89
|
+
if (!err) return next();
|
|
90
|
+
if (err.type === 'entity.too.large') {
|
|
91
|
+
return res.fail('artifact_too_large', { status: 413, message: `The tarball is over the ${moduleArtifact.MAX_TARBALL_BYTES}-byte store cap.` });
|
|
92
|
+
}
|
|
93
|
+
return res.fail('bad_tarball', { status: 400, message: 'The upload could not be read.' });
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
|
|
73
97
|
function ownProjectName() {
|
|
74
98
|
try { return require('../../branding').branding().identity.productName; } catch { return null; }
|
|
75
99
|
}
|
|
@@ -264,6 +288,65 @@ module.exports = function buildModulesRouter() {
|
|
|
264
288
|
res.json({ submissions });
|
|
265
289
|
}));
|
|
266
290
|
|
|
291
|
+
// POST /api/bongos/store/modules/:key/versions — publish one module version to
|
|
292
|
+
// the store (task 1004271, ADR 0338 D1). Body: the gzip tarball
|
|
293
|
+
// `bongos module publish` builds (scripts/gds/module-artifact.js), sent as
|
|
294
|
+
// application/gzip. The route trusts nothing but the bytes: it recomputes every
|
|
295
|
+
// hash, re-runs the publish denylist and validates module.json itself
|
|
296
|
+
// (verifyModuleArtifact), then keeps the tarball on the control plane's disk and
|
|
297
|
+
// INSERTs the version row in one transaction (module-store.js). A key's first
|
|
298
|
+
// publish makes the caller its author; after that only the author may publish,
|
|
299
|
+
// a delisted key takes nothing new, and a version is published once and never
|
|
300
|
+
// changed.
|
|
301
|
+
//
|
|
302
|
+
// rank: metic+archon — it puts code in a shared store other instances install
|
|
303
|
+
// from. Gate: requirePermission('module.submit') — the atom that already guards
|
|
304
|
+
// filing a module into a shared queue; a dedicated `module.publish` atom is a
|
|
305
|
+
// follow-up once who-may-sell is decided.
|
|
306
|
+
router.post('/store/modules/:key/versions', auth.requireBuilder, auth.requirePermission('module.submit'), tarballBody,
|
|
307
|
+
asyncHandler('POST /store/modules/:key/versions', async (req, res) => {
|
|
308
|
+
const { key } = req.params;
|
|
309
|
+
if (!KEY_RE.test(key)) {
|
|
310
|
+
return res.fail('bad_module_key', { status: 400, message: 'A module key is lowercase kebab-case.' });
|
|
311
|
+
}
|
|
312
|
+
const tgz = Buffer.isBuffer(req.body) ? req.body : null;
|
|
313
|
+
// The audit log serializes req.body; a tarball there would be megabytes of
|
|
314
|
+
// byte array. Leave it a one-line marker instead.
|
|
315
|
+
req.body = { tarball_bytes: tgz ? tgz.length : 0 };
|
|
316
|
+
if (!tgz || !tgz.length) {
|
|
317
|
+
return res.fail('empty_artifact', { status: 400, message: 'Send the tarball as the body, Content-Type: application/gzip.' });
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
const v = await moduleArtifact.verifyModuleArtifact(tgz, { key });
|
|
321
|
+
if (!v.ok) {
|
|
322
|
+
return res.fail(v.code, { status: v.code === 'artifact_too_large' ? 413 : 422, message: v.message });
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
const staged = await stageArtifact(tgz, { key, version: v.version });
|
|
326
|
+
let result;
|
|
327
|
+
try {
|
|
328
|
+
result = await publishVersion({
|
|
329
|
+
key,
|
|
330
|
+
title: v.moduleJson.title,
|
|
331
|
+
description: v.moduleJson.description,
|
|
332
|
+
version: v.version,
|
|
333
|
+
coreVersion: v.coreVersion,
|
|
334
|
+
manifest: v.manifest,
|
|
335
|
+
treeSha256: v.treeSha256,
|
|
336
|
+
tarballSha256: v.tarballSha256,
|
|
337
|
+
bytes: v.bytes,
|
|
338
|
+
artifactPath: relativeArtifactPath(staged.final),
|
|
339
|
+
builderId: req.builder.id,
|
|
340
|
+
}, { beforeCommit: () => commitArtifact(staged) });
|
|
341
|
+
} finally {
|
|
342
|
+
await removeArtifact(staged.tmp);
|
|
343
|
+
}
|
|
344
|
+
if (!result.ok) return res.fail(result.code, { status: result.status, message: result.message });
|
|
345
|
+
|
|
346
|
+
console.log(`[gds] module "${key}" ${v.version} published to the store by builder ${req.builder.id}`);
|
|
347
|
+
res.status(201).json({ ok: true, created_module: result.created, version: result.version });
|
|
348
|
+
}));
|
|
349
|
+
|
|
267
350
|
return router;
|
|
268
351
|
};
|
|
269
352
|
|
|
@@ -113,9 +113,9 @@ module.exports = function buildSecurityRouter() {
|
|
|
113
113
|
//
|
|
114
114
|
// The DATED security engagement reports (exploit detail — full repro,
|
|
115
115
|
// evidence, the attacker cookbook) are served from the DB, NOT committed to
|
|
116
|
-
// the git clone: the repo is cloned in full onto every Metic+
|
|
117
|
-
// (threat-model §6.3 / ADR 0020), so a committed exploit report is an
|
|
118
|
-
// attacker cookbook on every
|
|
116
|
+
// the git clone: the repo is cloned in full onto every Metic+ builder's
|
|
117
|
+
// machine (threat-model §6.3 / ADR 0020), so a committed exploit report is an
|
|
118
|
+
// attacker cookbook on every machine. The SAFE conceptual docs (threat-model.md,
|
|
119
119
|
// the README) stay in docs/security/; only the dated reports live here, and
|
|
120
120
|
// ALL three routes gate on requirePermission('security.report.adjudicate')
|
|
121
121
|
// — an archon-floored atom (BV1.R104; was requireRank('archon') and admits
|
package/src/bongos/routes.js
CHANGED
|
@@ -96,6 +96,11 @@ function buildGdsRouter() {
|
|
|
96
96
|
// encoded (4 MB decoded). Same dynamic-:id pattern as publish-branch above;
|
|
97
97
|
// modules/lifecycle/routes/task-visuals.js mounts its own 8 MB parser.
|
|
98
98
|
/^\/tasks\/\d+\/visual$/.test(req.path) ||
|
|
99
|
+
// task 1004358: the named-slot sibling (TW11 — the eight page-tweak renders).
|
|
100
|
+
// Same 8 MB parser in modules/lifecycle/routes/visuals.js; a full-page render
|
|
101
|
+
// is ~90-130 KB, so without this every attach 413'd here first. The route
|
|
102
|
+
// validates the slot name itself, so the pattern only needs to be one segment.
|
|
103
|
+
/^\/tasks\/\d+\/visuals\/[^/]+$/.test(req.path) ||
|
|
99
104
|
// task 1003850: the grade body carries committed_files — EVERY changed path, tens of
|
|
100
105
|
// KB on its own for a mass change — plus the panel's findings. A 1,141-file rename
|
|
101
106
|
// (task 1003705) produced a body over the 64 KB cap, so the grade could not be
|
|
@@ -162,8 +167,8 @@ function buildGdsRouter() {
|
|
|
162
167
|
// core to every instance, not an OTB feature.
|
|
163
168
|
router.use(buildLiveRouter());
|
|
164
169
|
// Feature-module-gated mounts (R54, task 1193, ADR 0062 §4). Off on a vanilla
|
|
165
|
-
// instance; ON for OTB via config/modules.json so prod is unchanged.
|
|
166
|
-
//
|
|
170
|
+
// instance; ON for OTB via config/modules.json so prod is unchanged. art-pipeline
|
|
171
|
+
// and discord (with its bug-attachments route) have both graduated
|
|
167
172
|
// into real modules (modules/<key>/) — they are discovered + mounted by
|
|
168
173
|
// moduleLoader.mountModuleRoutes below, not here (BV1.R43 / R48 / R47 / R72).
|
|
169
174
|
// gate-approvals likewise moved to the lifecycle module (BV1.R86).
|
|
@@ -622,8 +622,8 @@ function mountInternalSurfaces(app) {
|
|
|
622
622
|
//
|
|
623
623
|
// Dual-mount (task 1919): the router + this discovery index are mounted at the
|
|
624
624
|
// canonical API_PREFIX (/api/bongos) AND every LEGACY_API_PREFIXES entry
|
|
625
|
-
// (/api/gds) — the legacy alias stays FOREVER so
|
|
626
|
-
// cached status mirror
|
|
625
|
+
// (/api/gds) — the legacy alias stays FOREVER so older CLI releases and the
|
|
626
|
+
// cached status mirror keep working (src/bongos/api-prefix.js). The
|
|
627
627
|
// SAME router instance is mounted at each path. Each discovery GET is registered
|
|
628
628
|
// BEFORE its router mount so the exact base-path GET wins; every other
|
|
629
629
|
// method/subpath falls through to the router.
|
|
@@ -670,7 +670,7 @@ function mountInternalSurfaces(app) {
|
|
|
670
670
|
// below only prefixes non-/api paths, so without this placement /downloads/*
|
|
671
671
|
// would be rewritten to /builders/downloads/* and 404). The installer scripts are served with the
|
|
672
672
|
// server's PINNED origin substituted for __GDS_API_BASE__ — never a request
|
|
673
|
-
// header, so a spoofed Host can't redirect a `curl | sh
|
|
673
|
+
// header, so a spoofed Host can't redirect a `curl | sh`.
|
|
674
674
|
const bongosCfg = tryLoadInstanceConfig('bongos-cli.json');
|
|
675
675
|
if (bongosCfg) {
|
|
676
676
|
const bongosDownloads = buildBongosDownloads(bongosCfg);
|
|
@@ -894,8 +894,7 @@ function mountInternalSurfaces(app) {
|
|
|
894
894
|
// them to the hall, and an unauthenticated visitor into sign-in). The subdomain
|
|
895
895
|
// shim above has already rewritten the bare /watch + /gate onto /builders/*.
|
|
896
896
|
// (Defense in depth: every data endpoint these pages call carries its own
|
|
897
|
-
// server-side gate.)
|
|
898
|
-
// box: its page is gone, and a gate in front of nothing is a dead guard.
|
|
897
|
+
// server-side gate.)
|
|
899
898
|
const WATCH_PAGE_RE = /^\/builders\/watch(?:\.html)?\/?$/;
|
|
900
899
|
const GATE_PAGE_RE = /^\/builders\/gate(?:\.html)?\/?$/;
|
|
901
900
|
// /sessions (the session-log ledger) belongs to that same Governance group and
|
|
@@ -1144,7 +1143,7 @@ function mountInternalSurfaces(app) {
|
|
|
1144
1143
|
// the landing card + GitHub button render for a stranger.
|
|
1145
1144
|
//
|
|
1146
1145
|
// ONE tier of gate: ANY SIGNED-IN BUILDER — the Work Board, Roadmap, the Goals
|
|
1147
|
-
// page, the task-detail page, the Repo Atlas,
|
|
1146
|
+
// page, the task-detail page, the Repo Atlas, Settings
|
|
1148
1147
|
// (#748, #904, task 1293, task 1757, task 751) and, since task 1002710, the
|
|
1149
1148
|
// orientation reading rooms (primer, diagrams). A Xenos IS allowed; an
|
|
1150
1149
|
// unauthenticated visitor is bounced into sign-in rather than served the shell.
|
package/src/bongos-downloads.js
CHANGED
|
@@ -4,8 +4,7 @@
|
|
|
4
4
|
// The compiled per-platform binaries can't ride the git-based deploy (private
|
|
5
5
|
// repo, no LFS, GitHub's 100MB cap), so CI publishes them as release assets on a
|
|
6
6
|
// PUBLIC releases repo (config/bongos-cli.json — the same off-repo pattern as the
|
|
7
|
-
//
|
|
8
|
-
// module turns that committed config into:
|
|
7
|
+
// ADR 0029 status mirror). This module turns that committed config into:
|
|
9
8
|
//
|
|
10
9
|
// GET /downloads/bongos/latest.json → { version, assets: { target: {file,url} } }
|
|
11
10
|
// GET /downloads/bongos/version → { version, assets: { target: url } }
|
|
@@ -14,10 +13,6 @@
|
|
|
14
13
|
// so every URL a user sees or the installer fetches stays on the instance apex,
|
|
15
14
|
// and a release bump is a one-file config change that rides the normal ship flow.
|
|
16
15
|
// Pure (config in, urls out) so tests need no server.
|
|
17
|
-
//
|
|
18
|
-
// (A near-twin of src/devbox-downloads.js by design — same off-repo release
|
|
19
|
-
// pattern, different artifact. Kept as its own module for a clear name + its own
|
|
20
|
-
// config pointer; the two are independent instance features.)
|
|
21
16
|
|
|
22
17
|
function buildBongosDownloads(cfg) {
|
|
23
18
|
if (!cfg || typeof cfg !== 'object') throw new Error('bongos-downloads: missing config');
|
package/src/branding.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
// src/branding.js — the branding contract (ADR 0062 §3, task 1192 / V4.R53).
|
|
2
2
|
//
|
|
3
3
|
// The single source for every instance-identity string the platform renders:
|
|
4
|
-
// product/world name, company, domains/origins, cookie + OAuth
|
|
4
|
+
// product/world name, company, domains/origins, cookie + OAuth
|
|
5
5
|
// hosts, currency label, the track set, theme, and the project name injected
|
|
6
|
-
// into LLM prompts.
|
|
6
|
+
// into LLM prompts.
|
|
7
7
|
//
|
|
8
8
|
// Resolution precedence (highest wins):
|
|
9
9
|
// env override -> instance config (config/branding.json) -> neutral
|
package/src/build-info.js
CHANGED
|
@@ -41,12 +41,10 @@ function readGitSha() {
|
|
|
41
41
|
}
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
-
// The deployed commit's date (ISO 8601),
|
|
45
|
-
//
|
|
46
|
-
//
|
|
47
|
-
//
|
|
48
|
-
// committer date is the right reference. Null when git is unavailable (then the
|
|
49
|
-
// "behind" check is skipped and only never-reported boxes flag).
|
|
44
|
+
// The deployed commit's date (ISO 8601), served on /version beside the commit.
|
|
45
|
+
// Best-effort: after a `git reset --hard origin/main` deploy, HEAD is the
|
|
46
|
+
// deployed commit, so its committer date is the right reference. Null when git
|
|
47
|
+
// is unavailable.
|
|
50
48
|
function readGitCommitDate() {
|
|
51
49
|
try {
|
|
52
50
|
return execSync('git show -s --format=%cI HEAD', {
|