@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
package/src/instance-config.js
CHANGED
|
@@ -40,7 +40,7 @@ const LEGACY_DIRS = ['otb'];
|
|
|
40
40
|
//
|
|
41
41
|
// WHY THESE MUST OUTLIVE THIS TASK: these names are set OUTSIDE the repo —
|
|
42
42
|
// /etc/cloudbongos/web.env on the droplet, systemd units, CI secrets, shipped
|
|
43
|
-
//
|
|
43
|
+
// CLI binaries, builders' own machines. Deleting a spelling here is an outage the
|
|
44
44
|
// next time one of those restarts. Task 1003706 (C3d) retires them deliberately,
|
|
45
45
|
// after evidence — and asserts their ABSENCE, which is why the fallback needs a
|
|
46
46
|
// test that it FIRES rather than one that merely tolerates it.
|
|
@@ -140,7 +140,7 @@ function configPath(name, b = safeBrand()) {
|
|
|
140
140
|
// Lives here, not in cli-lib.js which owns the session STORE, because this module
|
|
141
141
|
// is the lowest one that has to read the file (resolveApiBase below) and cli-lib
|
|
142
142
|
// imports this — so this is the only place both can share. A rename APPENDS a
|
|
143
|
-
// name; dropping one signs out every builder machine
|
|
143
|
+
// name; dropping one signs out every builder machine at
|
|
144
144
|
// once. Part 4 (task 1003706) retires the tail. (task 1003704)
|
|
145
145
|
const SESSION_FILENAMES = Object.freeze([
|
|
146
146
|
'bongos-session.json',
|
package/src/module-api.js
CHANGED
|
@@ -71,10 +71,14 @@ const { responsibilityFor, ROLE_RESPONSIBILITIES } = require('./role-responsibil
|
|
|
71
71
|
// there. scripts/gds/bump-version.js still rewrites the literal below; it appends
|
|
72
72
|
// the entry to that file. Look for a version's history there, not here.
|
|
73
73
|
// ---------------------------------------------------------------------------
|
|
74
|
-
|
|
74
|
+
// The body behind a retired doorway export that must stay callable until the next
|
|
75
|
+
// MAJOR (see allowBoxScope below): passes the request through untouched.
|
|
76
|
+
function deprecatedNoopMiddleware(_req, _res, next) { next(); }
|
|
77
|
+
|
|
78
|
+
const CORE_VERSION = '1.19.1075'; // CI auto-patch carrier (ADR 0161); changelog: docs/module-api-changelog.md
|
|
75
79
|
|
|
76
80
|
// A namespaced logger so a module's log lines are attributable + consistent.
|
|
77
|
-
// Usage: const log = api.logger('
|
|
81
|
+
// Usage: const log = api.logger('discord'); log.info('mounted');
|
|
78
82
|
// task 1003208 (goal 1000079): this USED TO BE `console.log(tag, ...a)`. Every
|
|
79
83
|
// module that had "adopted the logger" was therefore still writing unstructured
|
|
80
84
|
// text to stdout with no request correlation and no redaction — the criterion's
|
|
@@ -131,10 +135,13 @@ module.exports = {
|
|
|
131
135
|
get requirePermission() { return require('./bongos/auth').requirePermission; },
|
|
132
136
|
get rankMeetsThreshold() { return require('./bongos/auth').rankMeetsThreshold; },
|
|
133
137
|
get requireBfgPrincipal() { return require('./bongos/auth').requireBfgPrincipal; },
|
|
134
|
-
// allowBoxScope —
|
|
135
|
-
//
|
|
136
|
-
//
|
|
137
|
-
|
|
138
|
+
// allowBoxScope — DEPRECATED no-op, kept so the doorway loses no export inside
|
|
139
|
+
// 1.x (removing one is a MAJOR, and every module declares ^1). It used to opt
|
|
140
|
+
// a route in to the narrowing machine-baked session scope; that scope and its
|
|
141
|
+
// tokens were retired (goal 1000120, task 1003894), so an undeclared session source is now denied
|
|
142
|
+
// on every route whatever a route mounts. Passing it changes nothing. Drop it
|
|
143
|
+
// at the next MAJOR.
|
|
144
|
+
get allowBoxScope() { return deprecatedNoopMiddleware; },
|
|
138
145
|
// resolveSession(req) — resolve the request to a live session row (or null)
|
|
139
146
|
// WITHOUT the 401-or-pass behaviour of requireBuilder. A module route that
|
|
140
147
|
// must make its own auth decision — e.g. a browser page that REDIRECTS to
|
|
@@ -125,6 +125,9 @@ function buildCatalog({
|
|
|
125
125
|
provenance: deriveProvenance(manifest.origin, ownProjectName),
|
|
126
126
|
enabled: enabled[key] === true,
|
|
127
127
|
default: manifest.default === true,
|
|
128
|
+
// The module's own version (task 1003781) — null only for a manifest
|
|
129
|
+
// that predates the field (the validator now requires it).
|
|
130
|
+
version: manifest.version || null,
|
|
128
131
|
coreVersion: manifest.coreVersion || null,
|
|
129
132
|
contributes: manifest.contributes || {},
|
|
130
133
|
// DECLARED — exactly what module.json says (null when it says nothing).
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
// routes.js mounts discovered module routers behind the kernel's auth/audit.
|
|
8
8
|
//
|
|
9
9
|
// SAFE-BY-CONSTRUCTION during the strangler migration: with no modules/ dir
|
|
10
|
-
// (the state
|
|
10
|
+
// (the state before BV1.R43 moved the first module), discovery returns EMPTY and every
|
|
11
11
|
// function below is a no-op — OTB behaves byte-identically. As built-ins move
|
|
12
12
|
// out, they become discovered manifests and the closed registry shrinks.
|
|
13
13
|
//
|
|
@@ -173,7 +173,7 @@ function verifyEnabledPorts(enabled) {
|
|
|
173
173
|
}
|
|
174
174
|
|
|
175
175
|
// Mount each discovered + ENABLED module's route factories onto `router`. A
|
|
176
|
-
// module declares contributes.routes:["
|
|
176
|
+
// module declares contributes.routes:["copy-desk"] → factory at modules/<key>/routes/copy-desk.js
|
|
177
177
|
// exporting a function returning an express Router. Auth model: audit is composed
|
|
178
178
|
// at the kernel level in routes.js before this is called; each route self-gates at
|
|
179
179
|
// its required rank via api.requireBuilder / api.requireRank (public read routes
|
|
@@ -309,8 +309,8 @@ function startModulePollers(deps = {}, { isEnabled } = {}, opts) {
|
|
|
309
309
|
poller = require(pollerPath);
|
|
310
310
|
} catch (e) {
|
|
311
311
|
// No modules/<key>/pollers/<pk>.js → a DECLARATIVE-ONLY / non-web poller
|
|
312
|
-
// (e.g.
|
|
313
|
-
//
|
|
312
|
+
// (e.g. a sweep that runs on the control plane, not in this process).
|
|
313
|
+
// Skip silently — only a web-process poller ships a file
|
|
314
314
|
// here. A file that EXISTS but throws is a real error (the catch below).
|
|
315
315
|
if (e && e.code === 'MODULE_NOT_FOUND' && String(e.message).includes(pollerPath)) continue;
|
|
316
316
|
console.error(`[module-loader] poller ${key}/${pk} failed to load: ${e && e.message ? e.message : e}`);
|
|
@@ -9,9 +9,11 @@
|
|
|
9
9
|
//
|
|
10
10
|
// THE SCHEMA (module.json):
|
|
11
11
|
// {
|
|
12
|
-
// "key": "
|
|
13
|
-
// "title": "
|
|
12
|
+
// "key": "art-pipeline", // REQUIRED. ^[a-z][a-z0-9-]*$ ; must match the module's dir name.
|
|
13
|
+
// "title": "Art pipeline", // REQUIRED. Human label.
|
|
14
14
|
// "description": "…", // REQUIRED. One sentence.
|
|
15
|
+
// "version": "1.4.2", // REQUIRED. The module's OWN exact version "X.Y.Z" (task 1003781) —
|
|
16
|
+
// // what update, rollout channels and re-assessment name.
|
|
15
17
|
// "coreVersion": "^1.0.0", // REQUIRED. semver range of src/module-api.js CORE_VERSION it supports.
|
|
16
18
|
// "default": false, // OPTIONAL (default false). Enablement fail-safe when no config names it.
|
|
17
19
|
// "author": "…", // OPTIONAL. Attribution (ADR 0107 §2) — portable, not a cross-GDS FK.
|
|
@@ -28,12 +30,12 @@
|
|
|
28
30
|
// // nobody does. `maintained` needs a named `maintainer`;
|
|
29
31
|
// // `deprecated`/`orphaned` need a `successor` or a `note`.
|
|
30
32
|
// "contributes": { // REQUIRED object (may be empty). What the module mounts:
|
|
31
|
-
// "routes": ["
|
|
33
|
+
// "routes": ["stage"], // route-factory keys (mounted behind kernel-composed auth)
|
|
32
34
|
// "rooms": ["WorldRoom"], // Colyseus room classes (realtime seam)
|
|
33
|
-
// "pollers": ["
|
|
35
|
+
// "pollers": ["nightly-sweep"],// background pollers to start/stop
|
|
34
36
|
// "disciplines": ["artist"], // disciplines this module adds
|
|
35
37
|
// "skills": ["otb-…"], // slash-command skills it ships
|
|
36
|
-
// "uiSections": ["
|
|
38
|
+
// "uiSections": ["gallery"], // hall/status UI sections it owns
|
|
37
39
|
// "webSurfaces": [{ "host": "status.", "dir": "public" }], // static web surfaces. Each names a Host
|
|
38
40
|
// // PREFIX (BV1.R84) or, as { "apexRoot": true, "dir": "public" },
|
|
39
41
|
// // the APEX ROOT — never both (ADR 0218).
|
|
@@ -46,7 +48,7 @@
|
|
|
46
48
|
// // refuses the enablement when the join door is `open` and no
|
|
47
49
|
// // payer is configured.
|
|
48
50
|
// "declarativeSeams": { // OPTIONAL. Per-entry exemptions for the seam-wiring
|
|
49
|
-
// "pollers": ["
|
|
51
|
+
// "pollers": ["nightly-sweep"] // guard (task 1003819): "this contributes entry ships no
|
|
50
52
|
// }, // artifact ON PURPOSE". Every key must be a key the module
|
|
51
53
|
// // actually declares in `contributes`, and every entry must be
|
|
52
54
|
// // one it actually lists there — a typo'd exemption is an error,
|
|
@@ -162,6 +164,12 @@ function validateManifest(obj) {
|
|
|
162
164
|
if (!isStr(obj.title)) E('title: required non-empty string');
|
|
163
165
|
if (!isStr(obj.description)) E('description: required non-empty string');
|
|
164
166
|
|
|
167
|
+
// The module's own version (task 1003781) — EXACT, unlike coreVersion's range:
|
|
168
|
+
// it names one published build, so update parity, rollout channels and
|
|
169
|
+
// re-assessment on publish (ADR 0338/0344) all have something to point at.
|
|
170
|
+
if (!isStr(obj.version)) E('version: required exact semver string "X.Y.Z" — the module\'s own version, e.g. "1.0.0"');
|
|
171
|
+
else if (!parseVersion(obj.version)) E(`version: "${obj.version}" must be an exact version "X.Y.Z" (not a range)`);
|
|
172
|
+
|
|
165
173
|
if (!isStr(obj.coreVersion)) E('coreVersion: required non-empty semver range string (e.g. "^1.0.0")');
|
|
166
174
|
else if (!isValidRange(obj.coreVersion)) E(`coreVersion: "${obj.coreVersion}" is not a valid semver range`);
|
|
167
175
|
|
|
@@ -239,8 +247,8 @@ function validateManifest(obj) {
|
|
|
239
247
|
// --- optional declarativeSeams (task 1003819) ---
|
|
240
248
|
// The exemption list for the seam-wiring guard: "this contributes entry ships no
|
|
241
249
|
// artifact on purpose". Some declarations are legitimately declarative-only —
|
|
242
|
-
//
|
|
243
|
-
//
|
|
250
|
+
// a poller that runs on the control plane, not in this process, or a uiSection
|
|
251
|
+
// that ships no hall widget — so the guard needs a way to say
|
|
244
252
|
// so without a blanket skip that would hide the real cases it exists to catch.
|
|
245
253
|
//
|
|
246
254
|
// Validated HERE, against `contributes`, because this is the one place that holds
|
package/src/module-seams.js
CHANGED
|
@@ -179,9 +179,8 @@ function listEvents() {
|
|
|
179
179
|
// fire-and-forget reaction; a CONTRIBUTION accumulates — zero-or-more modules
|
|
180
180
|
// each add a piece to a named extension point, and the kernel hands the whole
|
|
181
181
|
// set back to a core consumer that renders them together. This is how a module
|
|
182
|
-
// adds its own slice to a core surface without core hardcoding it:
|
|
183
|
-
//
|
|
184
|
-
// key surface — each `contribute()`s; core merges. The strangler prerequisite
|
|
182
|
+
// adds its own slice to a core surface without core hardcoding it: discord adds
|
|
183
|
+
// its link state, art adds its key surface — each `contribute()`s; core merges. The strangler prerequisite
|
|
185
184
|
// that lets those modules leave core (the surfaces in src/bongos/routes/me.js +
|
|
186
185
|
// modules/hall-ui/public/settings.js stop being hardcoded). With no module enabled the
|
|
187
186
|
// registry is empty and every consumer renders exactly what it does today.
|
package/src/modules.js
CHANGED
|
@@ -18,8 +18,8 @@
|
|
|
18
18
|
//
|
|
19
19
|
// This is a config-flag exclusion mechanism, NOT a full plugin registry — the
|
|
20
20
|
// cheaper near-term shape (ADR 0062 §4). R59 (task 1198) moves game + art-pipeline
|
|
21
|
-
// in; R60 (task 1199) moves discord
|
|
22
|
-
//
|
|
21
|
+
// in; R60 (task 1199) moves discord in (UI sections / skills / pollers); they
|
|
22
|
+
// consume the registry declared here.
|
|
23
23
|
|
|
24
24
|
const fs = require('node:fs');
|
|
25
25
|
const path = require('node:path');
|
|
@@ -45,9 +45,9 @@ const INSTANCE_PATH = path.join(resolveInstanceRoot(), 'config', 'modules.json')
|
|
|
45
45
|
// CORE (always on, never a module): auth, builders, tasks, claims, versions,
|
|
46
46
|
// done-when, credits/cost, inbox, blockers, leaderboard, analytics,
|
|
47
47
|
// audit-log, security, access-requests, sessions, gate-approvals, healthz,
|
|
48
|
-
// public. These mount unconditionally. The
|
|
49
|
-
// first TWO carved CORE-domain modules have graduated to modules/<key>/ (
|
|
50
|
-
// BV1.
|
|
48
|
+
// public. These mount unconditionally. The optional feature modules AND the
|
|
49
|
+
// first TWO carved CORE-domain modules have graduated to modules/<key>/ (discord
|
|
50
|
+
// BV1.R47, art-pipeline BV1.R48, game BV1.R49, memory BV1.R72 —
|
|
51
51
|
// memory/recall/search/learnings, ADR 0091 §2 — and grading BV1.R73 — the
|
|
52
52
|
// adversarial grade panel + rubric, behind the `grade` kernel port, ADR 0091 §4).
|
|
53
53
|
// A core-domain module like memory or grading is `default: true` (it ships to
|
|
@@ -166,6 +166,27 @@ function spendRefusals(resolved, registry, { joinDoorOpen = false, payer = null
|
|
|
166
166
|
// something they explicitly asked for, so it has to say what to do about it.
|
|
167
167
|
// `log` is injectable for the same reason instance-config's legacy-env warning
|
|
168
168
|
// injects it: a test asserts the warning fired, not only that the value resolved.
|
|
169
|
+
// RETIRED MODULE KEYS (goal 1000120, task 1003892). A module deleted from the core
|
|
170
|
+
// outright, which an instance's committed config/modules.json — written while the
|
|
171
|
+
// module still shipped — may still switch ON. fromConfig below throws on enabling
|
|
172
|
+
// an unknown module (the task-1824 typo guard), so without this a core upgrade
|
|
173
|
+
// would become a boot failure on every instance that ever enabled one:
|
|
174
|
+
// cloudbongos.com's own config still said `"dev-box": true` when the module went.
|
|
175
|
+
// A retired key is DROPPED with a one-line warning instead of thrown on. Keep the
|
|
176
|
+
// list short and say why each key left — an entry is a promise about someone
|
|
177
|
+
// else's config file, not a way to silence a typo.
|
|
178
|
+
const RETIRED_MODULE_KEYS = Object.freeze({
|
|
179
|
+
'dev-box': 'cloud dev boxes were removed from the core (goal 1000120)',
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
function warnRetiredModule(key, source, log = console) {
|
|
183
|
+
log.warn(
|
|
184
|
+
`[bongos] module "${key}" is switched on in the ${source} config, but it was retired: `
|
|
185
|
+
+ `${RETIRED_MODULE_KEYS[key]}. It is ignored — remove the key from that config to `
|
|
186
|
+
+ 'silence this line.',
|
|
187
|
+
);
|
|
188
|
+
}
|
|
189
|
+
|
|
169
190
|
function warnSpendRefusal(key, envPrefix, log = console) {
|
|
170
191
|
log.warn(
|
|
171
192
|
`[bongos] module "${key}" is enabled in config but was REFUSED: it spends money on behalf of `
|
|
@@ -185,7 +206,7 @@ function resolveModules({
|
|
|
185
206
|
// caller that does not know about spend resolves exactly as before; loadModules
|
|
186
207
|
// is the one that reads the real posture, and the one that fails closed when it
|
|
187
208
|
// cannot.
|
|
188
|
-
spendGuard = NO_SPEND_RISK, warn = warnSpendRefusal,
|
|
209
|
+
spendGuard = NO_SPEND_RISK, warn = warnSpendRefusal, warnRetired = warnRetiredModule,
|
|
189
210
|
// THE DB OVERRIDE LAYER (task 1002857) — a { <moduleKey>: boolean } snapshot of
|
|
190
211
|
// module_enablement_overrides for this scope, injected rather than read, so this
|
|
191
212
|
// resolver stays pure and synchronous. Defaulted to {} so every existing caller
|
|
@@ -207,13 +228,19 @@ function resolveModules({
|
|
|
207
228
|
for (const [k, v] of Object.entries(m)) {
|
|
208
229
|
if (known.includes(k)) { out[k] = v; continue; }
|
|
209
230
|
// Unknown key. A config may name a module this build hasn't installed: a stripped
|
|
210
|
-
// CORE has no game/art-pipeline/discord
|
|
211
|
-
//
|
|
212
|
-
//
|
|
213
|
-
// module is a no-op. ENABLING an absent module is a real error (the feature you
|
|
214
|
-
// for isn't here) and still throws, which also keeps the typo guard for the
|
|
215
|
-
// direction. (task 1824: this is what lets the vanilla core boot instead
|
|
216
|
-
// on `unknown module "game"`.)
|
|
231
|
+
// CORE has no game/art-pipeline/discord, yet config/modules.neutral.json (which
|
|
232
|
+
// ships WITH the core) names them so the SAME file is valid in the full repo too.
|
|
233
|
+
// Tolerate an unknown module ONLY when it is DISABLED — a disabled-and-absent
|
|
234
|
+
// module is a no-op. ENABLING an absent module is a real error (the feature you
|
|
235
|
+
// asked for isn't here) and still throws, which also keeps the typo guard for the
|
|
236
|
+
// dangerous direction. (task 1824: this is what lets the vanilla core boot instead
|
|
237
|
+
// of crashing on `unknown module "game"`.) The one exception is a RETIRED key
|
|
238
|
+
// (RETIRED_MODULE_KEYS above): it can never be installed again, so enabling it is
|
|
239
|
+
// a stale config line, not a request — warn and drop it.
|
|
240
|
+
if (coerceBool(v) === true && Object.prototype.hasOwnProperty.call(RETIRED_MODULE_KEYS, k)) {
|
|
241
|
+
warnRetired(k, source);
|
|
242
|
+
continue;
|
|
243
|
+
}
|
|
217
244
|
if (coerceBool(v) === true) {
|
|
218
245
|
throw new Error(`modules: cannot enable unknown module "${k}" in ${source} config — it is not installed (known: ${known.join(', ') || '(none)'}).`);
|
|
219
246
|
}
|
|
@@ -257,7 +284,7 @@ function resolveModules({
|
|
|
257
284
|
const merged = Object.assign({}, ...layers.map(([, m]) => m));
|
|
258
285
|
|
|
259
286
|
// Env overrides: <PREFIX>_MODULE_<KEY> where KEY is upper-snake of the module
|
|
260
|
-
// name (
|
|
287
|
+
// name (art-pipeline -> ART_PIPELINE). Highest precedence.
|
|
261
288
|
for (const key of known) {
|
|
262
289
|
const suffix = `MODULE_${key.toUpperCase().replace(/-/g, '_')}`;
|
|
263
290
|
const envName = `${envPrefix}_${suffix}`;
|
|
@@ -489,6 +516,7 @@ module.exports = {
|
|
|
489
516
|
metersSpend,
|
|
490
517
|
spendRefusals,
|
|
491
518
|
warnSpendRefusal,
|
|
519
|
+
RETIRED_MODULE_KEYS,
|
|
492
520
|
resolveModules,
|
|
493
521
|
resolveInstancePackPath,
|
|
494
522
|
moduleScopeKey,
|
|
@@ -122,7 +122,7 @@ test('spendRefusals never names a module that was already off', () => {
|
|
|
122
122
|
});
|
|
123
123
|
|
|
124
124
|
test('the manifest declaration is a CLOSED key set — a typo must not read as "no spend"', () => {
|
|
125
|
-
const base = { key: 'x', title: 't', description: 'd', coreVersion: '^1.0.0', contributes: {} };
|
|
125
|
+
const base = { key: 'x', title: 't', description: 'd', version: '1.0.0', coreVersion: '^1.0.0', contributes: {} };
|
|
126
126
|
assert.equal(validateManifest({ ...base, spend: { requiresPayer: true } }).valid, true);
|
|
127
127
|
assert.equal(validateManifest({ ...base, spend: { requirespayer: true } }).valid, false,
|
|
128
128
|
'a misspelled key silently declaring NO spend is the direction that costs money');
|
|
@@ -66,7 +66,7 @@ test('the exclusions are real and each one is reachable', () => {
|
|
|
66
66
|
'scripts/gds/fitness-ratchets.js',
|
|
67
67
|
'scripts/gds/api-alias-caller-scan.js',
|
|
68
68
|
'tests/api_alias_exceptions.mjs', // 5 tests
|
|
69
|
-
'modules/
|
|
69
|
+
'modules/agents/app/test/thing.mjs',
|
|
70
70
|
'docs/adr/0016-permissions.md', // 6 the FROZEN RECORD under docs/ (narrowed, task 1004113)
|
|
71
71
|
'docs/session-logs/2026-09-16-x.md',
|
|
72
72
|
'docs/audits/2026-08-01-x.md',
|
|
@@ -27,7 +27,7 @@ const ALIAS = '/api/gds';
|
|
|
27
27
|
test('the alias is still mounted — everything below depends on it', () => {
|
|
28
28
|
const { API_PREFIX, LEGACY_API_PREFIXES, ALL_API_PREFIXES } = require(path.join(ROOT, 'src', 'bongos', 'api-prefix.js'));
|
|
29
29
|
assert.equal(API_PREFIX, '/api/bongos', 'the canonical prefix is what in-repo callers now ask for');
|
|
30
|
-
assert.ok(LEGACY_API_PREFIXES.includes(ALIAS), 'shipped
|
|
30
|
+
assert.ok(LEGACY_API_PREFIXES.includes(ALIAS), 'shipped binaries and live instances call the alias');
|
|
31
31
|
assert.ok(ALL_API_PREFIXES.includes(ALIAS), 'and the router still mounts it');
|
|
32
32
|
});
|
|
33
33
|
|
package/tests/api_client.mjs
CHANGED
|
@@ -128,21 +128,6 @@ test('every method the README example names actually exists on the client', () =
|
|
|
128
128
|
assert.ok(count >= 3, 'README example should exercise several methods');
|
|
129
129
|
});
|
|
130
130
|
|
|
131
|
-
test('devbox vendored client is byte-identical to the canonical CJS build (run devbox-app vendor if stale)', () => {
|
|
132
|
-
// The devbox Electron app can't require() clients/ at runtime (packaged
|
|
133
|
-
// separately, ADR 0045), so scripts/vendor-client.js copies index.cjs into
|
|
134
|
-
// devbox-app/src/vendor/. Guard that the vendored copy never drifts from the
|
|
135
|
-
// canonical build — CI catches a forgotten `npm run vendor` after a spec change.
|
|
136
|
-
// devbox-app/ is instance content the neutral core does not carry (task 1002470).
|
|
137
|
-
// Absent APP => nothing to vendor into; absent VENDORED FILE inside a present app
|
|
138
|
-
// is the real "forgot to run vendor" drift this guards, and still fails.
|
|
139
|
-
if (!fs.existsSync(path.join(ROOT, 'devbox-app'))) return;
|
|
140
|
-
const vendored = path.join(ROOT, 'devbox-app/src/vendor/bongos-client.cjs');
|
|
141
|
-
if (!fs.existsSync(vendored)) throw new assert.AssertionError({ message: 'devbox-app/src/vendor/bongos-client.cjs missing — run `npm run vendor` in devbox-app/' });
|
|
142
|
-
const canonical = fs.readFileSync(path.join(ROOT, 'clients/bongos-client/index.cjs'), 'utf8');
|
|
143
|
-
assert.equal(fs.readFileSync(vendored, 'utf8'), canonical, 'devbox vendored client is STALE — run `npm run vendor` in devbox-app/');
|
|
144
|
-
});
|
|
145
|
-
|
|
146
131
|
test('baseUrl:"" is honored (absolute-path passthrough for the hall fetch wrappers)', async () => {
|
|
147
132
|
const rec = {};
|
|
148
133
|
const api = createClient({ baseUrl: '', credentials: 'same-origin', throwOnError: false, fetch: fakeFetch(rec, { ok: true, status: 200, body: { x: 1 } }) });
|
|
@@ -223,7 +208,7 @@ test('generated .d.ts types success responses (task 2061)', () => {
|
|
|
223
208
|
});
|
|
224
209
|
|
|
225
210
|
// The CJS + browser-global builds are pure transforms of the ESM source (task
|
|
226
|
-
// 1996 / R09).
|
|
211
|
+
// 1996 / R09). CommonJS callers (require) and classic <script> web pages consume them, so
|
|
227
212
|
// guard that they load and shape requests IDENTICALLY to the ESM build.
|
|
228
213
|
test('CJS build behaves identically to the ESM build', async () => {
|
|
229
214
|
const cjs = require('../clients/bongos-client/index.cjs');
|
package/tests/api_path_404.mjs
CHANGED
|
@@ -38,7 +38,7 @@ t('API-shaped: /api/*, /gds/*, and bare GDS roots (/tasks, /me, …) match', ()
|
|
|
38
38
|
|
|
39
39
|
t('NOT API-shaped: game/hall/static paths fall through to HTML/static', () => {
|
|
40
40
|
for (const p of ['/', '/index.html', '/builders', '/builders/work', '/atlas-ab12.png',
|
|
41
|
-
'/vendor/phaser.min.js', '/downloads/
|
|
41
|
+
'/vendor/phaser.min.js', '/downloads/bongos/latest.json', '/game', '/healthz', '/version']) {
|
|
42
42
|
assert.equal(isApiShapedPath(p), false, `${p} should NOT be API-shaped`);
|
|
43
43
|
}
|
|
44
44
|
});
|
package/tests/auth_page_gate.mjs
CHANGED
|
@@ -99,18 +99,18 @@ await t('session lookup error → fails CLOSED (redirect, next() NOT called)', a
|
|
|
99
99
|
'must redirect to sign-in on a lookup error (fail closed)');
|
|
100
100
|
});
|
|
101
101
|
|
|
102
|
-
// ---- SESSION
|
|
102
|
+
// ---- SESSION SOURCE on the page path (task 1003197 (A)) ---------------------
|
|
103
103
|
//
|
|
104
104
|
// resolveSession accepts a Bearer token with no source filter, so before this the
|
|
105
|
-
// page gate never asked the question requireBuilder asks:
|
|
106
|
-
//
|
|
107
|
-
//
|
|
108
|
-
//
|
|
109
|
-
//
|
|
105
|
+
// page gate never asked the question requireBuilder asks: does the session-source
|
|
106
|
+
// registry declare this session's source at all? Without it, a token of an
|
|
107
|
+
// undeclared source could fetch a gated page SHELL whenever its builder's rank held
|
|
108
|
+
// the page permission. The stub above resolves the permission for EVERY builder,
|
|
109
|
+
// which is what makes these assertions about the source alone.
|
|
110
110
|
|
|
111
111
|
console.log('\nrequireBuilderPage — session scope:');
|
|
112
112
|
|
|
113
|
-
await t('box
|
|
113
|
+
await t('a leftover dev-box session (source box, now undeclared) → 302 to sign-in even though its builder holds the permission', async () => {
|
|
114
114
|
nextSession = { builder_id: 3, github_login: 'boxbot', rank: 'archon', status: 'active', source: 'box' };
|
|
115
115
|
lookupThrows = false;
|
|
116
116
|
const req = { headers: { authorization: 'Bearer boxtoken' } }; // a page route declares no scope
|
|
@@ -145,9 +145,9 @@ await t('cli and web sessions are untouched by the scope check', async () => {
|
|
|
145
145
|
});
|
|
146
146
|
|
|
147
147
|
await t('a legacy session with no source field still reaches the page', async () => {
|
|
148
|
-
// The same invariant tests/
|
|
149
|
-
// source
|
|
150
|
-
// browser session minted before `source` was recorded.
|
|
148
|
+
// The same invariant tests/government_session_scope.mjs pins for requireBuilder:
|
|
149
|
+
// an absent source is the declared legacy row, so this gate can never lock out a
|
|
150
|
+
// real browser session minted before `source` was recorded.
|
|
151
151
|
nextSession = { builder_id: 2, github_login: 'legacy', rank: 'metic', status: 'active' };
|
|
152
152
|
const req = { headers: { cookie: 'gds_session=validtoken' } };
|
|
153
153
|
const res = fakeRes();
|
|
@@ -77,7 +77,7 @@ await test('computeTaskReward: 0 raw → 0 effective (a 0-mapping pays nothing)'
|
|
|
77
77
|
|
|
78
78
|
await test('netForBuilder: would-be minus already-paid', () => {
|
|
79
79
|
assert.equal(netForBuilder(100, 30), 70);
|
|
80
|
-
assert.equal(netForBuilder(100, 0), 100); // cost-plus paid nothing (
|
|
80
|
+
assert.equal(netForBuilder(100, 0), 100); // cost-plus paid nothing (the CI case)
|
|
81
81
|
});
|
|
82
82
|
|
|
83
83
|
await test('netForBuilder: cost-plus over-paid → floored at 0 (never claws back)', () => {
|
|
@@ -288,8 +288,6 @@ const BUILDER = {
|
|
|
288
288
|
anthropic_user_id: 'SENTINEL-anthropic-user',
|
|
289
289
|
monthly_budget_usd: 999,
|
|
290
290
|
system_role: 'SENTINEL-system-role',
|
|
291
|
-
box_blocked: true,
|
|
292
|
-
box_blocked_reason: 'SENTINEL-box-blocked',
|
|
293
291
|
deactivated_reason: 'SENTINEL-deactivated',
|
|
294
292
|
};
|
|
295
293
|
|
|
@@ -380,7 +378,7 @@ test('widening the response did not widen the leak: no private column appears an
|
|
|
380
378
|
// The raw serialized text, not the parsed keys — a private value smuggled
|
|
381
379
|
// under a different key still fails.
|
|
382
380
|
assert.doesNotMatch(r.text, /SENTINEL-/, 'a private builder column reached the public profile body');
|
|
383
|
-
assert.doesNotMatch(r.text, /monthly_budget_usd|anthropic_|system_role|
|
|
381
|
+
assert.doesNotMatch(r.text, /monthly_budget_usd|anthropic_|system_role|deactivated_reason/);
|
|
384
382
|
assert.deepEqual(Object.keys(r.data.builder).sort(), [
|
|
385
383
|
'avatar_url', 'created_at', 'display_name', 'github_login', 'id', 'karma',
|
|
386
384
|
'preferred_disciplines', 'rank', 'total_credits',
|
package/tests/claim_action.mjs
CHANGED
|
@@ -242,7 +242,10 @@ await test('NOBODY posts to /claims except through the shared action', () => {
|
|
|
242
242
|
// own claim POST, this fails — which is what "a new claim surface needs no new
|
|
243
243
|
// refusal handling" has to mean in practice.
|
|
244
244
|
const offenders = [];
|
|
245
|
-
|
|
245
|
+
// task 1003459 added two more claim surfaces (task.js's Claim it button,
|
|
246
|
+
// builders.js/hall-render.js's Home Next-up row) — both belong in this scan
|
|
247
|
+
// or a future hand-rolled POST on either trips nothing.
|
|
248
|
+
for (const f of ['work.js', 'goals-page.js', 'goals-render.js', 'task.js', 'builders.js', 'hall-render.js']) {
|
|
246
249
|
const src = read('modules', 'hall-ui', 'public', f);
|
|
247
250
|
if (/postJSON\(\s*`\$\{API\}\/claims/.test(src)) offenders.push(f);
|
|
248
251
|
}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
// tests/claim_from_task_and_home.mjs
|
|
2
|
+
//
|
|
3
|
+
// Task 1003459 — the two surfaces that say "ready to claim" gain the ability to
|
|
4
|
+
// claim: the task's own /task/:id page, and Home's "Next up" rows. Both wire
|
|
5
|
+
// onto the extracted claim action (task 1003458, claim-action.js) rather than
|
|
6
|
+
// hand-rolling a third POST /claims + refusal switch — claim_action.mjs already
|
|
7
|
+
// proves the switch itself is complete; this file proves these two call sites
|
|
8
|
+
// actually use it.
|
|
9
|
+
//
|
|
10
|
+
// Static-source assertions, matching claim_action.mjs's own idiom for these
|
|
11
|
+
// files: task.js and builders.js are browser IIFEs that reach for `window` at
|
|
12
|
+
// eval time, so they are read as text rather than executed in Node.
|
|
13
|
+
//
|
|
14
|
+
// Run: node tests/claim_from_task_and_home.mjs
|
|
15
|
+
|
|
16
|
+
import { strict as assert } from 'node:assert';
|
|
17
|
+
import { readFileSync } from 'node:fs';
|
|
18
|
+
import { fileURLToPath } from 'node:url';
|
|
19
|
+
import { dirname, join } from 'node:path';
|
|
20
|
+
import { makeRunner } from './helpers.mjs';
|
|
21
|
+
|
|
22
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
23
|
+
const repoRoot = join(__dirname, '..');
|
|
24
|
+
const read = (...p) => readFileSync(join(repoRoot, ...p), 'utf8');
|
|
25
|
+
const { test, summary } = makeRunner();
|
|
26
|
+
|
|
27
|
+
const taskJs = read('modules', 'hall-ui', 'public', 'task.js');
|
|
28
|
+
const buildersJs = read('modules', 'hall-ui', 'public', 'builders.js');
|
|
29
|
+
const hallRenderJs = read('modules', 'hall-ui', 'public', 'hall-render.js');
|
|
30
|
+
const taskHtml = read('modules', 'hall-ui', 'public', 'task.html');
|
|
31
|
+
const indexHtml = read('modules', 'hall-ui', 'public', 'index.html');
|
|
32
|
+
|
|
33
|
+
// == /task/:id ================================================================
|
|
34
|
+
|
|
35
|
+
await test('task.js constructs the shared claim action', () => {
|
|
36
|
+
assert.match(taskJs, /makeClaimAction\(\{/, 'task.js must build the action, not hand-roll one');
|
|
37
|
+
assert.match(taskJs, /window\.OTBClaimAction/);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
await test('task.js renders a Claim button only when the task is ready', () => {
|
|
41
|
+
const m = taskJs.match(/if \(t\.status === 'ready'\) \{\s*lifecycle = `([^`]*)`/);
|
|
42
|
+
assert.ok(m, 'no ready-status branch found in the lifecycle chain');
|
|
43
|
+
assert.match(m[1], /id="claim-btn"/);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
await test('the Claim button click routes through claimAction.claim, not a raw POST', () => {
|
|
47
|
+
const m = taskJs.match(/const claimBtn = \$\('#claim-btn'\);[\s\S]*?\n {4}\}/);
|
|
48
|
+
assert.ok(m, 'claim-btn wiring block not found');
|
|
49
|
+
assert.match(m[0], /claimAction\.claim\(t\.id\)/);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
await test('task.js does not hand-roll a POST to /claims', () => {
|
|
53
|
+
assert.doesNotMatch(taskJs, /postJSON\(\s*`\$\{API\}\/claims/);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
await test('task.html loads claim-action.js before task.js', () => {
|
|
57
|
+
const shared = taskHtml.indexOf('claim-action.js');
|
|
58
|
+
const after = taskHtml.indexOf('task.js?v=');
|
|
59
|
+
assert.ok(shared > -1, 'task.html must load claim-action.js');
|
|
60
|
+
assert.ok(after > shared, 'task.js must load AFTER claim-action.js');
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
await test('task.html\'s cache-bust moved for task.js', () => {
|
|
64
|
+
assert.doesNotMatch(taskHtml, /task\.js\?v=2026-09-26-taskwhere/, 'the old bust is still there');
|
|
65
|
+
assert.match(taskHtml, /task\.js\?v=2026-09-28-taskclaim/);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
// == Home's "Next up" rows ====================================================
|
|
69
|
+
|
|
70
|
+
await test('hall-render.js constructs the shared claim action for Home\'s Next-up row', () => {
|
|
71
|
+
// Lives in hall-render.js, not builders.js: builders.js sits at the
|
|
72
|
+
// 1,500-line fitness ratchet (hall-ui/CLAUDE.md — growth goes in the
|
|
73
|
+
// lib/renderer, not the page).
|
|
74
|
+
assert.match(hallRenderJs, /function wireNextUpClaim\(\)/);
|
|
75
|
+
assert.match(hallRenderJs, /makeClaimAction\(\{/);
|
|
76
|
+
assert.match(hallRenderJs, /window\.OTBClaimAction/);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
await test('the shared postJSON carries { status, data } — the contract claim-action.js reads', () => {
|
|
80
|
+
const m = buildersJs.match(/async function postJSON\(path, body\) \{[\s\S]*?\n {2}\}/);
|
|
81
|
+
assert.ok(m, 'postJSON not found');
|
|
82
|
+
assert.match(m[0], /err\.data\s*=/, 'postJSON must set err.data or every refusal here falls through to the generic line');
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
await test('each Next-up row renders a Claim button carrying its task id', () => {
|
|
86
|
+
const m = buildersJs.match(/list\.innerHTML = rows\.map\(\(t\) => \{[\s\S]*?\n {6}\}\)\.join\(''\);/);
|
|
87
|
+
assert.ok(m, 'loadNextUp row template not found');
|
|
88
|
+
assert.match(m[0], /class="[^"]*next-up__claim[^"]*"/);
|
|
89
|
+
assert.match(m[0], /data-task-id="\$\{escapeHtml\(String\(t\.id\)\)\}"/);
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
await test('the Next-up Claim button routes through claimAction.claim, not a raw POST', () => {
|
|
93
|
+
const m = hallRenderJs.match(/function wireNextUpClaim\(\) \{[\s\S]*?\n {2}\}/);
|
|
94
|
+
assert.ok(m, 'wireNextUpClaim not found');
|
|
95
|
+
assert.match(m[0], /\$\('#home-next-list'\)\?\.addEventListener\('click'/);
|
|
96
|
+
assert.match(m[0], /claimAction\.claim\(taskId\)/);
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
await test('builders.js wires the Next-up claim action rather than hand-rolling it', () => {
|
|
100
|
+
assert.doesNotMatch(buildersJs, /postJSON\(\s*`\$\{API\}\/claims/);
|
|
101
|
+
assert.match(buildersJs, /wireNextUpClaim\(\)/, 'builders.js must call the renderer\'s wiring');
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
await test('the claim wiring landed in the renderer, not back over the 1,500-line ratchet', () => {
|
|
105
|
+
// Not a second copy of fitness.js's own gate (scripts/gds/fitness-ratchets.js)
|
|
106
|
+
// — this is the reason wireNextUpClaim moved to hall-render.js in the first
|
|
107
|
+
// place (see its own comment): a regression here explains WHY that file grew,
|
|
108
|
+
// not just THAT some file grew.
|
|
109
|
+
for (const [name, src] of [['builders.js', buildersJs], ['hall-render.js', hallRenderJs]]) {
|
|
110
|
+
const lines = src.split('\n').length;
|
|
111
|
+
assert.ok(lines <= 1500, `${name} is ${lines} lines — over the fitness ratchet`);
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
await test('index.html loads claim-action.js before builders.js', () => {
|
|
116
|
+
const shared = indexHtml.indexOf('claim-action.js');
|
|
117
|
+
const after = indexHtml.indexOf('builders.js?v=');
|
|
118
|
+
assert.ok(shared > -1, 'index.html must load claim-action.js');
|
|
119
|
+
assert.ok(after > shared, 'builders.js must load AFTER claim-action.js');
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
await test('index.html\'s cache-bust moved for builders.js', () => {
|
|
123
|
+
assert.doesNotMatch(indexHtml, /builders\.js\?v=2026-06-27-r75/, 'the old bust is still there');
|
|
124
|
+
assert.match(indexHtml, /builders\.js\?v=2026-09-28-nextupclaim/);
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
summary();
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
// tests/
|
|
1
|
+
// tests/cli_allowlist_permissions.mjs
|
|
2
2
|
//
|
|
3
3
|
// task 1051 (D): the blessed methodology CLI scripts must stay pre-allowed in the
|
|
4
|
-
// project .claude/settings.json so a
|
|
4
|
+
// project .claude/settings.json so a Claude session in auto permission mode
|
|
5
5
|
// does not hard-DENY the very command it exists to run. The #1027 nightmare
|
|
6
6
|
// dead-ended on `node scripts/gds/ship.js` at the auto classifier; this guards
|
|
7
7
|
// against that allow-rule being silently dropped.
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
// on every routine read. The list of tools is `.claude/hooks/shell-tools.js`, the same
|
|
13
13
|
// one the guards read: a rule may only exist for a tool git-guard and secret-gate see.
|
|
14
14
|
//
|
|
15
|
-
// Run: node tests/
|
|
15
|
+
// Run: node tests/cli_allowlist_permissions.mjs
|
|
16
16
|
|
|
17
17
|
import { strict as assert } from 'node:assert';
|
|
18
18
|
import fs from 'node:fs';
|
|
@@ -62,7 +62,7 @@ perTool('permissions.allow pre-allows the generic GDS API channel api.js (auto-m
|
|
|
62
62
|
// idea-triage promote, create-task, blocker-resolve, criterion ratify, goal
|
|
63
63
|
// create, cost-via-API, security reports — rides the generic caller
|
|
64
64
|
// `node scripts/gds/api.js <METHOD> /api/gds/...`. If it is not pre-allowed, a
|
|
65
|
-
//
|
|
65
|
+
// session in AUTO permission mode has the classifier
|
|
66
66
|
// hard-DENY those writes as "[External System Writes] ... beyond the user's
|
|
67
67
|
// request" — mistaking a sanctioned idea-triage session for scope creep. The
|
|
68
68
|
// GDS server enforces builders.rank on every request (ADR 0016), so this rule
|
|
@@ -309,5 +309,5 @@ test('the instance starter mirrors this list in verb form (claude-materialize.js
|
|
|
309
309
|
});
|
|
310
310
|
|
|
311
311
|
console.log('');
|
|
312
|
-
console.log(`
|
|
312
|
+
console.log(`cli_allowlist_permissions: ${passed} passed, ${failed} failed`);
|
|
313
313
|
process.exit(failed === 0 ? 0 : 1);
|
package/tests/cli_sessions.mjs
CHANGED
|
@@ -201,7 +201,7 @@ test('signing into a second instance PRESERVES the first — including one writt
|
|
|
201
201
|
});
|
|
202
202
|
|
|
203
203
|
test('the ACTIVE pointer still lands exactly where every existing reader looks', () => {
|
|
204
|
-
// In-repo skills
|
|
204
|
+
// In-repo skills and hooks all read the active session file in the configured dir. The
|
|
205
205
|
// store is additive; if this moved, every one of them would silently stop finding a session.
|
|
206
206
|
const { out } = inSandbox(`
|
|
207
207
|
await lib.saveSession({ token:'T', api_base:'https://a.example.com', builder:{github_login:'x'} });
|
|
@@ -351,6 +351,8 @@ async function main() {
|
|
|
351
351
|
key: 'consumer-probe',
|
|
352
352
|
title: 'Consumer Probe',
|
|
353
353
|
description: 'A host module that ships only in the instance repo, never in @bongos/core.',
|
|
354
|
+
// Required since task 1003781: a module names its own exact version.
|
|
355
|
+
version: '1.0.0',
|
|
354
356
|
coreVersion: CORE_VERSION_RANGE,
|
|
355
357
|
contributes: {},
|
|
356
358
|
}, null, 2)}\n`);
|