@bongos/core 1.19.1073 → 1.19.1074
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 +320 -465
- package/.claude/skills/feedback/SKILL.md +2 -2
- 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 +171 -1661
- 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 +2 -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 +9 -9
- 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/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/lifecycle/db-claim-reads.js +1 -58
- package/modules/lifecycle/db-ship.js +0 -5
- package/modules/lifecycle/db.js +0 -4
- 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/routes/tasks.js +5 -7
- package/modules/memory/module.json +1 -0
- 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 +2 -2
- package/modules/platform-identity/tests/platform-identity.mjs +17 -3
- package/modules/provisioning/module.json +1 -0
- package/modules/provisioning/provisioning.js +2 -2
- 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/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 +78 -0
- package/scripts/gds/artifact-format.js +203 -0
- package/scripts/gds/audit-authorship.sh +1 -13
- package/scripts/gds/claim.js +0 -15
- package/scripts/gds/cli-lib.js +8 -8
- 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 +1 -1
- package/scripts/gds/feedback-latest.js +139 -9
- package/scripts/gds/fitness-checks-identity.js +2 -7
- package/scripts/gds/fitness-checks-write-validation.js +4 -0
- package/scripts/gds/fitness-ratchets.js +4 -4
- package/scripts/gds/fitness.js +10 -10
- package/scripts/gds/gen-diagrams.js +49 -71
- package/scripts/gds/http-api-client.js +12 -19
- package/scripts/gds/init.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/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/regen-instance-docs.js +1 -1
- package/scripts/gds/run-unit-tests.js +2 -16
- package/scripts/gds/sandbox-stage.js +39 -341
- package/scripts/gds/ship-flow.js +6 -6
- package/scripts/gds/ship-land.js +3 -4
- package/scripts/gds/ship-regen.js +10 -41
- package/scripts/gds/ship.js +4 -24
- 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/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/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 +6 -22
- package/src/bongos/routes/modules.js +83 -0
- package/src/bongos/routes.js +5 -0
- 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 +15 -7
- package/src/module-seams.js +2 -3
- package/src/modules.js +42 -14
- package/tests/agents_spend_guard.mjs +1 -1
- package/tests/api_client.mjs +0 -15
- package/tests/auth_page_gate.mjs +10 -10
- 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/consumer_layout_boot.mjs +2 -0
- package/tests/copy_desk_page_docx.mjs +33 -2
- package/tests/core_upgrade_runner.mjs +6 -6
- package/tests/deploy_divergence_line.mjs +11 -11
- 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/gate_approvals.mjs +0 -1
- package/tests/go_live.mjs +3 -3
- package/tests/government_abuse_matrix.mjs +20 -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_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/host_topology_skips.mjs +1 -2
- 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/module-scope-map.mjs +9 -9
- package/tests/module_catalog.mjs +1 -1
- package/tests/module_cli.mjs +15 -15
- package/tests/module_contributions.mjs +6 -6
- package/tests/module_loader.mjs +6 -6
- package/tests/module_manifest.mjs +13 -4
- 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/onboarding_route_signals.mjs +21 -31
- package/tests/onboarding_state.mjs +35 -49
- package/tests/permission_path.mjs +0 -3
- package/tests/platform_boot.mjs +1 -2
- package/tests/profile_route.mjs +1 -6
- package/tests/projects_hub.mjs +5 -5
- package/tests/projects_hub_app_step.mjs +138 -0
- package/tests/projects_hub_module_picker.mjs +2 -2
- package/tests/projects_hub_pre_uat.mjs +4 -5
- package/tests/provision.mjs +7 -7
- package/tests/provisioning_starter_bundles.mjs +0 -22
- package/tests/publish_branch_route.mjs +10 -18
- package/tests/publish_manifest.mjs +1 -2
- package/tests/rank_tier_single_source.mjs +1 -13
- package/tests/repo_map.mjs +3 -4
- package/tests/runner_drift.mjs +3 -21
- package/tests/sandbox_stage.mjs +57 -390
- package/tests/session_rename_fallback.mjs +0 -21
- package/tests/ship_error_shape.mjs +1 -1
- package/tests/task_visual_slots.mjs +31 -0
- package/tests/terms_acceptance.mjs +1 -1
- 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/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).
|
|
@@ -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
|
|
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
|
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.1074'; // 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 box-scoped session; that scope and its tokens
|
|
141
|
+
// were retired (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": "
|
|
12
|
+
// "key": "art-pipeline", // REQUIRED. ^[a-z][a-z0-9-]*$ ; must match the module's dir name.
|
|
13
13
|
// "title": "Dev boxes", // 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');
|
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 } }) });
|
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();
|
|
@@ -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
|
}
|