@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/tests/modules.mjs
CHANGED
|
@@ -14,12 +14,11 @@ const {
|
|
|
14
14
|
} = modulesMod;
|
|
15
15
|
|
|
16
16
|
const VANILLA = {}; // no config at all
|
|
17
|
-
const OTB = { modules: { game: true, 'art-pipeline': true, discord: true, '
|
|
18
|
-
// The post-extraction registry:
|
|
19
|
-
//
|
|
20
|
-
//
|
|
21
|
-
//
|
|
22
|
-
// loadModules() does in production. Membership across the move is preserved here.
|
|
17
|
+
const OTB = { modules: { game: true, 'art-pipeline': true, discord: true, 'npm-release': true } };
|
|
18
|
+
// The post-extraction registry: every discovered module (npm-release lives in
|
|
19
|
+
// modules/npm-release/, so it is a DISCOVERED key, not a built-in MODULE_REGISTRY
|
|
20
|
+
// one). The pure resolver tests below pass this effective registry so 'npm-release'
|
|
21
|
+
// is a known key — exactly what loadModules() does in production.
|
|
23
22
|
const EFFECTIVE = modulesMod.effectiveRegistry();
|
|
24
23
|
|
|
25
24
|
// Sample feature-module keys for the PURE resolver tests below. `game` and
|
|
@@ -43,7 +42,7 @@ test('every registry module has an explicit default of false', () => {
|
|
|
43
42
|
test('instance config turns modules on', () => {
|
|
44
43
|
const m = resolveModules({ neutral: VANILLA, instance: OTB, registry: SAMPLE_REG });
|
|
45
44
|
assert.equal(m.game, true);
|
|
46
|
-
assert.equal(m['
|
|
45
|
+
assert.equal(m['npm-release'], true);
|
|
47
46
|
assert.equal(isModuleEnabled('discord', m), true);
|
|
48
47
|
// memory (BV1.R72), grading (BV1.R73), economy (BV1.R77), ideas (BV1.R78),
|
|
49
48
|
// security (BV1.R79), builder-settings (BV1.R80), onboarding (BV1.R81),
|
|
@@ -70,7 +69,7 @@ test('instance config turns modules on', () => {
|
|
|
70
69
|
});
|
|
71
70
|
|
|
72
71
|
test('instance config overrides the neutral starter per-flag (deep merge of the modules map)', () => {
|
|
73
|
-
const neutral = { modules: { game: false, discord: false, '
|
|
72
|
+
const neutral = { modules: { game: false, discord: false, 'npm-release': false, 'art-pipeline': false } };
|
|
74
73
|
const instance = { modules: { discord: true } }; // only flip one
|
|
75
74
|
const m = resolveModules({ neutral, instance, registry: SAMPLE_REG });
|
|
76
75
|
assert.equal(m.discord, true);
|
|
@@ -78,8 +77,8 @@ test('instance config overrides the neutral starter per-flag (deep merge of the
|
|
|
78
77
|
});
|
|
79
78
|
|
|
80
79
|
test('env override beats instance config (precedence), with prefix + snake-casing', () => {
|
|
81
|
-
const m = resolveModules({ instance: OTB, env: {
|
|
82
|
-
assert.equal(m['
|
|
80
|
+
const m = resolveModules({ instance: OTB, env: { OTB_MODULE_NPM_RELEASE: '0' }, envPrefix: 'OTB', registry: SAMPLE_REG });
|
|
81
|
+
assert.equal(m['npm-release'], false); // env off beat instance on
|
|
83
82
|
assert.equal(m.game, true); // others untouched
|
|
84
83
|
});
|
|
85
84
|
|
|
@@ -106,14 +105,14 @@ test('an unknown module key in config is rejected when ENABLED (typo guard)', ()
|
|
|
106
105
|
assert.throws(() => resolveModules({ instance: { modules: { gaem: true } } }), /unknown module "gaem"/);
|
|
107
106
|
});
|
|
108
107
|
|
|
109
|
-
// task 1824 — a stripped CORE has no game/art-pipeline/discord
|
|
108
|
+
// task 1824 — a stripped CORE has no game/art-pipeline/discord, but the
|
|
110
109
|
// config/modules.neutral.json that SHIPS with it names them (so the same file is valid
|
|
111
110
|
// in the full repo). The loader must TOLERATE an unknown module when it is DISABLED, or
|
|
112
111
|
// a vanilla self-host crashes on `unknown module "game"`.
|
|
113
112
|
test('an unknown module key that is DISABLED is tolerated (a config may name a not-yet-installed module)', () => {
|
|
114
113
|
const reg = { memory: { default: true }, grading: { default: true } }; // core-only registry (no feature modules)
|
|
115
114
|
// The exact vanilla-core scenario: neutral names four absent feature modules, all off.
|
|
116
|
-
const neutral = { modules: { game: false, 'art-pipeline': false, discord: false, '
|
|
115
|
+
const neutral = { modules: { game: false, 'art-pipeline': false, discord: false, 'npm-release': false } };
|
|
117
116
|
let m;
|
|
118
117
|
assert.doesNotThrow(() => { m = resolveModules({ neutral, registry: reg }); });
|
|
119
118
|
assert.deepEqual(m, { memory: true, grading: true }); // core modules resolve; absent-disabled dropped
|
|
@@ -202,7 +201,38 @@ test('the committed config files resolve: OTB enables all four, on disk', () =>
|
|
|
202
201
|
assert.equal(m.game, true);
|
|
203
202
|
assert.equal(m['art-pipeline'], true);
|
|
204
203
|
assert.equal(m.discord, true);
|
|
205
|
-
assert.equal(m['
|
|
204
|
+
assert.equal(m['npm-release'], true);
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
// task 1003892 — a RETIRED key (the module was deleted from the core) that a
|
|
208
|
+
// committed instance pack still switches ON must NOT stop the process booting.
|
|
209
|
+
// cloudbongos.com carried `"dev-box": true` when the module went; the typo guard
|
|
210
|
+
// would have thrown `cannot enable unknown module` at the next restart.
|
|
211
|
+
test('1003892: a retired module enabled in the instance pack is dropped with a warning, not thrown on', () => {
|
|
212
|
+
const warned = [];
|
|
213
|
+
const m = resolveModules({
|
|
214
|
+
instance: { modules: { 'dev-box': true, discord: true } },
|
|
215
|
+
registry: SAMPLE_REG,
|
|
216
|
+
warnRetired: (key, source) => warned.push(`${key}@${source}`),
|
|
217
|
+
});
|
|
218
|
+
assert.equal('dev-box' in m, false, 'a retired key never enters the resolved set');
|
|
219
|
+
assert.equal(m.discord, true, 'the rest of the pack still resolves');
|
|
220
|
+
assert.deepEqual(warned, ['dev-box@instance'], 'the stale line is named once, with its layer');
|
|
221
|
+
// disabled is the quiet no-op it always was
|
|
222
|
+
const quiet = [];
|
|
223
|
+
resolveModules({ neutral: { modules: { 'dev-box': false } }, registry: SAMPLE_REG, warnRetired: (k) => quiet.push(k) });
|
|
224
|
+
assert.deepEqual(quiet, [], 'a retired key switched OFF says nothing');
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
test('1003892: the retired list does not weaken the typo guard for any other unknown key', () => {
|
|
228
|
+
assert.throws(
|
|
229
|
+
() => resolveModules({ instance: { modules: { 'dev-boxes': true } }, registry: SAMPLE_REG, warnRetired: () => {} }),
|
|
230
|
+
/cannot enable unknown module "dev-boxes"/,
|
|
231
|
+
);
|
|
232
|
+
for (const [key, why] of Object.entries(modulesMod.RETIRED_MODULE_KEYS)) {
|
|
233
|
+
assert.ok(!(key in modulesMod.effectiveRegistry()), `${key} is retired, so no installed module may carry it`);
|
|
234
|
+
assert.ok(why && why.length > 20, `${key} must say why it left`);
|
|
235
|
+
}
|
|
206
236
|
});
|
|
207
237
|
|
|
208
238
|
test('GDS_MODULES_FILE selects the instance modules pack on disk (mirrors GDS_BRANDING_FILE)', () => {
|
|
@@ -235,8 +265,8 @@ test('a branded sibling (GDS_BRANDING_FILE set, no GDS_MODULES_FILE) defaults to
|
|
|
235
265
|
test('1002857: an override outranks the committed instance pack', () => {
|
|
236
266
|
// The whole point: the pack is what a deploy restores, so an override that sat
|
|
237
267
|
// below it would be erased by the very event it exists to survive.
|
|
238
|
-
const m = resolveModules({ instance: OTB, override: { '
|
|
239
|
-
assert.equal(m['
|
|
268
|
+
const m = resolveModules({ instance: OTB, override: { 'npm-release': false }, registry: SAMPLE_REG });
|
|
269
|
+
assert.equal(m['npm-release'], false, 'the override wins over the pack');
|
|
240
270
|
assert.equal(m.discord, true, 'a module with no override still reads the pack');
|
|
241
271
|
});
|
|
242
272
|
|
|
@@ -248,12 +278,12 @@ test('1002857: an override turns a module ON as well as off', () => {
|
|
|
248
278
|
test('1002857: env still outranks an override — the incident knob needs no database', () => {
|
|
249
279
|
const m = resolveModules({
|
|
250
280
|
instance: VANILLA,
|
|
251
|
-
override: { '
|
|
252
|
-
env: {
|
|
281
|
+
override: { 'npm-release': true },
|
|
282
|
+
env: { OTB_MODULE_NPM_RELEASE: '0' },
|
|
253
283
|
envPrefix: 'OTB',
|
|
254
284
|
registry: SAMPLE_REG,
|
|
255
285
|
});
|
|
256
|
-
assert.equal(m['
|
|
286
|
+
assert.equal(m['npm-release'], false, 'env must be able to kill a module the DB turned on');
|
|
257
287
|
});
|
|
258
288
|
|
|
259
289
|
test('1002857: an override for a module this build does not carry is inert, not fatal', () => {
|
|
@@ -279,12 +309,12 @@ test('1002857: each module reports the layer that produced its state', () => {
|
|
|
279
309
|
const m = resolveModules({
|
|
280
310
|
neutral: { modules: { discord: false } },
|
|
281
311
|
instance: OTB,
|
|
282
|
-
override: { '
|
|
312
|
+
override: { 'npm-release': false },
|
|
283
313
|
env: { OTB_MODULE_GAME: '0' },
|
|
284
314
|
envPrefix: 'OTB',
|
|
285
315
|
registry: SAMPLE_REG,
|
|
286
316
|
});
|
|
287
|
-
assert.equal(m.sources['
|
|
317
|
+
assert.equal(m.sources['npm-release'], 'override');
|
|
288
318
|
assert.equal(m.sources.game, 'env');
|
|
289
319
|
assert.equal(m.sources.discord, 'instance', 'the instance pack outranks the neutral one');
|
|
290
320
|
assert.equal(m.sources['art-pipeline'], 'instance');
|
|
@@ -316,18 +346,18 @@ test('1002857: setModuleOverrides installs the snapshot AND invalidates the cach
|
|
|
316
346
|
// the exact class of bug this feature exists to remove.
|
|
317
347
|
try {
|
|
318
348
|
const before = modulesMod.modules();
|
|
319
|
-
assert.equal(before['
|
|
320
|
-
const after = modulesMod.setModuleOverrides({ '
|
|
321
|
-
assert.equal(after['
|
|
322
|
-
assert.equal(modulesMod.modules()['
|
|
323
|
-
assert.equal(moduleOverrides()['
|
|
349
|
+
assert.equal(before['npm-release'], false, 'precondition: off on a vanilla resolve');
|
|
350
|
+
const after = modulesMod.setModuleOverrides({ 'npm-release': true });
|
|
351
|
+
assert.equal(after['npm-release'], true, 'the returned set reflects the override');
|
|
352
|
+
assert.equal(modulesMod.modules()['npm-release'], true, 'and so does the live singleton');
|
|
353
|
+
assert.equal(moduleOverrides()['npm-release'], true);
|
|
324
354
|
} finally {
|
|
325
355
|
modulesMod._resetForTests();
|
|
326
356
|
}
|
|
327
357
|
});
|
|
328
358
|
|
|
329
359
|
test('1002857: _resetForTests clears the override snapshot too', () => {
|
|
330
|
-
modulesMod.setModuleOverrides({ '
|
|
360
|
+
modulesMod.setModuleOverrides({ 'npm-release': true });
|
|
331
361
|
modulesMod._resetForTests();
|
|
332
362
|
assert.deepEqual(moduleOverrides(), {}, 'a leaked snapshot would cross-contaminate tests');
|
|
333
363
|
});
|
|
@@ -3,16 +3,15 @@
|
|
|
3
3
|
// Regression for task 1444 (scootermack's bug).
|
|
4
4
|
//
|
|
5
5
|
// GET /builders/:id/onboarding shapes the SAME displayed checklist as GET /me,
|
|
6
|
-
// but
|
|
7
|
-
// row:
|
|
8
|
-
//
|
|
9
|
-
//
|
|
10
|
-
//
|
|
11
|
-
// the
|
|
12
|
-
// through, exactly as /me does.
|
|
6
|
+
// but two of its steps clear from LIVE signals rather than the onboarding_state
|
|
7
|
+
// row: join_discord (the discord.isLinked port) and cli_setup (db.hasCliSession).
|
|
8
|
+
// The route originally called shapeForApi(state, { shipCount }) and omitted those
|
|
9
|
+
// signals — so they defaulted to false and the route rendered the steps
|
|
10
|
+
// perpetually UNCHECKED even after the builder cleared them. This locks in that
|
|
11
|
+
// the route passes the live signals through, exactly as /me does.
|
|
13
12
|
//
|
|
14
|
-
// We stub auth + db and patch onboarding-state.getState
|
|
15
|
-
//
|
|
13
|
+
// We stub auth + db and patch onboarding-state.getState, but exercise the REAL
|
|
14
|
+
// shapeForApi. No network, no Postgres.
|
|
16
15
|
//
|
|
17
16
|
// Run: node tests/onboarding_route_signals.mjs
|
|
18
17
|
|
|
@@ -66,16 +65,12 @@ const onboardingState = require('../modules/onboarding/onboarding-state.js');
|
|
|
66
65
|
let fakeState = null;
|
|
67
66
|
onboardingState.getState = async () => fakeState;
|
|
68
67
|
|
|
69
|
-
//
|
|
70
|
-
// `
|
|
71
|
-
//
|
|
72
|
-
//
|
|
73
|
-
// So we register providers instead of monkey-patching; the route resolves them via
|
|
74
|
-
// resolveOptional exactly as in production.
|
|
68
|
+
// join_discord's live signal reaches builders.js through the kernel seam (the
|
|
69
|
+
// `discord.isLinked` port the discord module provides, BV1.R47) — not
|
|
70
|
+
// db.getDiscordLink. So we register a provider instead of monkey-patching; the
|
|
71
|
+
// route resolves it via resolveOptional exactly as in production.
|
|
75
72
|
const seams = require('../src/module-seams.js');
|
|
76
|
-
let fakeBoxConnected = false;
|
|
77
73
|
seams._reset();
|
|
78
|
-
seams.registerProvider('box.hasEverConnected', async () => fakeBoxConnected);
|
|
79
74
|
seams.registerProvider('discord.isLinked', async () => dbSignals.discordLinked);
|
|
80
75
|
|
|
81
76
|
// The onboarding state machine carved into modules/onboarding/ (BV1.R81): the
|
|
@@ -83,7 +78,7 @@ seams.registerProvider('discord.isLinked', async () => dbSignals.discordLinked);
|
|
|
83
78
|
// (resolveOptional('onboarding')), not a direct require. Register the real port
|
|
84
79
|
// provider — its getState delegates to the (patched-above) onboarding-state, so
|
|
85
80
|
// the genuine shapeForApi is still exercised. (Mirrors how this test already
|
|
86
|
-
// registers the
|
|
81
|
+
// registers the discord provider instead of monkey-patching.)
|
|
87
82
|
seams.registerProvider('onboarding', require('../modules/onboarding/port.js'));
|
|
88
83
|
|
|
89
84
|
// The lifecycle (tasks · claims · ship) carved to modules/lifecycle/ (BV1.R86):
|
|
@@ -129,45 +124,40 @@ const stepCleared = (body, id) => {
|
|
|
129
124
|
};
|
|
130
125
|
|
|
131
126
|
// A mid-onboarding row (mirrors a real newcomer who's signed in, picked a craft,
|
|
132
|
-
// and run /builder-start) — so the
|
|
127
|
+
// and run /builder-start) — so the discord/cli steps are decided purely by
|
|
133
128
|
// the LIVE signals, which is exactly what regressed.
|
|
134
129
|
const MID_STATE = { current_stage: 'first_claim', completed_stages: ['auth_complete', 'pick_disciplines', 'first_start'] };
|
|
135
130
|
|
|
136
131
|
const { server, port } = await listen(buildApp());
|
|
137
132
|
|
|
138
|
-
await t('no live signals →
|
|
133
|
+
await t('no live signals → discord/cli both uncleared, and no retired box step is rendered', async () => {
|
|
139
134
|
fakeState = MID_STATE;
|
|
140
|
-
fakeBoxConnected = false;
|
|
141
135
|
dbSignals.discordLinked = false; dbSignals.cliAuthed = false;
|
|
142
136
|
const r = await get(port, '/api/gds/builders/94/onboarding');
|
|
143
137
|
assert.equal(r.status, 200);
|
|
144
|
-
assert.equal(stepCleared(r.json, 'box_connect'), false);
|
|
145
138
|
assert.equal(stepCleared(r.json, 'join_discord'), false);
|
|
146
139
|
assert.equal(stepCleared(r.json, 'cli_setup'), false);
|
|
140
|
+
assert.equal(stepCleared(r.json, 'box_connect'), undefined, 'the dev-box step left the checklist (task 1004310)');
|
|
147
141
|
});
|
|
148
142
|
|
|
149
|
-
await t('
|
|
143
|
+
await t('discord linked → join_discord clears from ITS OWN signal [the scootermack regression]', async () => {
|
|
150
144
|
fakeState = MID_STATE;
|
|
151
|
-
|
|
152
|
-
dbSignals.discordLinked = false; dbSignals.cliAuthed = false;
|
|
145
|
+
dbSignals.discordLinked = true; dbSignals.cliAuthed = false;
|
|
153
146
|
const r = await get(port, '/api/gds/builders/94/onboarding');
|
|
154
147
|
assert.equal(r.status, 200);
|
|
155
|
-
assert.equal(stepCleared(r.json, '
|
|
156
|
-
//
|
|
148
|
+
assert.equal(stepCleared(r.json, 'join_discord'), true, 'join_discord must clear once the account is linked');
|
|
149
|
+
// cli_setup stays false — proving join_discord cleared from its own signal,
|
|
157
150
|
// not because every step got force-cleared.
|
|
158
|
-
assert.equal(stepCleared(r.json, 'join_discord'), false);
|
|
159
151
|
assert.equal(stepCleared(r.json, 'cli_setup'), false);
|
|
160
152
|
});
|
|
161
153
|
|
|
162
|
-
await t('discord linked + cli authed →
|
|
154
|
+
await t('discord linked + cli authed → both clear', async () => {
|
|
163
155
|
fakeState = MID_STATE;
|
|
164
|
-
fakeBoxConnected = false;
|
|
165
156
|
dbSignals.discordLinked = true; dbSignals.cliAuthed = true;
|
|
166
157
|
const r = await get(port, '/api/gds/builders/94/onboarding');
|
|
167
158
|
assert.equal(r.status, 200);
|
|
168
159
|
assert.equal(stepCleared(r.json, 'join_discord'), true);
|
|
169
160
|
assert.equal(stepCleared(r.json, 'cli_setup'), true);
|
|
170
|
-
assert.equal(stepCleared(r.json, 'box_connect'), false);
|
|
171
161
|
});
|
|
172
162
|
|
|
173
163
|
server.close();
|
|
@@ -77,29 +77,27 @@ test('isKnownStage: typo rejected', () => {
|
|
|
77
77
|
|
|
78
78
|
// ---- shapeForApi (displayed checklist, #777) ------------------------------
|
|
79
79
|
//
|
|
80
|
-
// shapeForApi returns the DISPLAYED
|
|
81
|
-
// (
|
|
82
|
-
//
|
|
83
|
-
//
|
|
84
|
-
//
|
|
85
|
-
// `cliAuthed` (no manual ticks, #823/#777).
|
|
80
|
+
// shapeForApi returns the DISPLAYED 4-step "Path into the City"
|
|
81
|
+
// (join_discord, pick_disciplines, cli_setup, ship_three) — split from the
|
|
82
|
+
// engine STAGES so NONE of the displayed steps affect graduation. `graduated`
|
|
83
|
+
// and `current_stage` still reflect the engine. join_discord/cli_setup are
|
|
84
|
+
// auto-derived from `discordLinked`/`cliAuthed` (no manual ticks, #777).
|
|
86
85
|
|
|
87
|
-
const DISPLAY_ORDER = ['
|
|
86
|
+
const DISPLAY_ORDER = ['join_discord', 'pick_disciplines', 'cli_setup', 'ship_three'];
|
|
88
87
|
|
|
89
|
-
test('shapeForApi: null row →
|
|
88
|
+
test('shapeForApi: null row → 4-step displayed shape in order', () => {
|
|
90
89
|
const out = onboarding.shapeForApi(null);
|
|
91
90
|
assert.equal(out.current_stage, 'auth_complete'); // engine stage still surfaced
|
|
92
91
|
assert.deepEqual(out.completed_stages, []);
|
|
93
92
|
assert.equal(out.graduated, false);
|
|
94
93
|
assert.equal(out.progress.cleared, 0);
|
|
95
|
-
assert.equal(out.progress.total,
|
|
94
|
+
assert.equal(out.progress.total, 4);
|
|
96
95
|
assert.deepEqual(out.stages.map((s) => s.id), DISPLAY_ORDER);
|
|
97
|
-
assert.equal(out.stages[0].is_current, true); //
|
|
96
|
+
assert.equal(out.stages[0].is_current, true); // join_discord, first gap
|
|
98
97
|
assert.equal(out.stages[0].cleared, false);
|
|
99
|
-
assert.equal(out.stages[0].source, '
|
|
100
|
-
assert.equal(out.stages[1].source, '
|
|
101
|
-
assert.equal(out.stages[2].source, '
|
|
102
|
-
assert.equal(out.stages[3].source, 'cli'); // cli_setup
|
|
98
|
+
assert.equal(out.stages[0].source, 'discord'); // join_discord
|
|
99
|
+
assert.equal(out.stages[1].source, 'stage'); // pick_disciplines is a real stage
|
|
100
|
+
assert.equal(out.stages[2].source, 'cli'); // cli_setup
|
|
103
101
|
});
|
|
104
102
|
|
|
105
103
|
test('shapeForApi: partial row → cleared flags + is_current points at first displayed gap', () => {
|
|
@@ -113,62 +111,50 @@ test('shapeForApi: partial row → cleared flags + is_current points at first di
|
|
|
113
111
|
const byId = Object.fromEntries(out.stages.map((s) => [s.id, s]));
|
|
114
112
|
// pick_disciplines is the only displayed step the engine has cleared.
|
|
115
113
|
assert.equal(byId.pick_disciplines.cleared, true);
|
|
116
|
-
assert.equal(byId.box_connect.cleared, false); // no box connection
|
|
117
114
|
assert.equal(byId.cli_setup.cleared, false); // no cli auth
|
|
118
115
|
assert.equal(byId.join_discord.cleared, false); // no link
|
|
119
116
|
assert.equal(byId.ship_three.cleared, false);
|
|
120
|
-
// First displayed gap is
|
|
121
|
-
assert.equal(byId.
|
|
117
|
+
// First displayed gap is join_discord (it sorts before the cleared craft step).
|
|
118
|
+
assert.equal(byId.join_discord.is_current, true);
|
|
122
119
|
assert.equal(byId.pick_disciplines.is_current, false);
|
|
123
120
|
assert.equal(out.progress.cleared, 1);
|
|
124
121
|
});
|
|
125
122
|
|
|
126
|
-
test('shapeForApi:
|
|
123
|
+
test('shapeForApi: a legacy caller passing boxConnected changes nothing (the step is gone)', () => {
|
|
124
|
+
// Removed with the dev box (task 1004310): an unknown option must be inert,
|
|
125
|
+
// never a fifth step or a thrown error.
|
|
127
126
|
const out = onboarding.shapeForApi(
|
|
128
127
|
{ current_stage: 'auth_complete', completed_stages: ['auth_complete'] },
|
|
129
128
|
{ boxConnected: true }
|
|
130
129
|
);
|
|
131
|
-
|
|
132
|
-
assert.equal(
|
|
133
|
-
// Next gap is now join_discord (no link supplied).
|
|
134
|
-
assert.equal(byId.join_discord.is_current, true);
|
|
130
|
+
assert.deepEqual(out.stages.map((s) => s.id), DISPLAY_ORDER);
|
|
131
|
+
assert.equal(out.progress.cleared, 0);
|
|
135
132
|
});
|
|
136
133
|
|
|
137
|
-
test('shapeForApi: cli_setup (step
|
|
134
|
+
test('shapeForApi: cli_setup (step 3) clears from cliAuthed; join_discord stays first gap without a link', () => {
|
|
138
135
|
const out = onboarding.shapeForApi(
|
|
139
136
|
{ current_stage: 'auth_complete', completed_stages: ['auth_complete'] },
|
|
140
137
|
{ cliAuthed: true }
|
|
141
138
|
);
|
|
142
139
|
const byId = Object.fromEntries(out.stages.map((s) => [s.id, s]));
|
|
143
140
|
assert.equal(byId.cli_setup.cleared, true);
|
|
144
|
-
assert.equal(byId.
|
|
145
|
-
//
|
|
146
|
-
assert.equal(byId.
|
|
147
|
-
});
|
|
148
|
-
|
|
149
|
-
test('shapeForApi: box_connect and cli_setup are independent signals', () => {
|
|
150
|
-
// Connected to the box but CLI not yet authenticated — only box_connect ticks.
|
|
151
|
-
const out = onboarding.shapeForApi(
|
|
152
|
-
{ current_stage: 'auth_complete', completed_stages: ['auth_complete'] },
|
|
153
|
-
{ boxConnected: true, cliAuthed: false }
|
|
154
|
-
);
|
|
155
|
-
const byId = Object.fromEntries(out.stages.map((s) => [s.id, s]));
|
|
156
|
-
assert.equal(byId.box_connect.cleared, true);
|
|
157
|
-
assert.equal(byId.cli_setup.cleared, false);
|
|
158
|
-
// box_connect clears, but cli_setup (step 4) stays open on its own signal.
|
|
159
|
-
assert.equal(byId.box_connect.is_current, false);
|
|
141
|
+
assert.equal(byId.join_discord.cleared, false);
|
|
142
|
+
// join_discord is still the first gap (it precedes the cleared cli step).
|
|
143
|
+
assert.equal(byId.join_discord.is_current, true);
|
|
160
144
|
});
|
|
161
145
|
|
|
162
|
-
test('shapeForApi: join_discord
|
|
146
|
+
test('shapeForApi: join_discord and cli_setup are independent signals', () => {
|
|
147
|
+
// Discord linked but CLI not yet authenticated — only join_discord ticks.
|
|
163
148
|
const out = onboarding.shapeForApi(
|
|
164
149
|
{ current_stage: 'auth_complete', completed_stages: ['auth_complete'] },
|
|
165
|
-
{ discordLinked: true }
|
|
150
|
+
{ discordLinked: true, cliAuthed: false }
|
|
166
151
|
);
|
|
167
152
|
const byId = Object.fromEntries(out.stages.map((s) => [s.id, s]));
|
|
168
153
|
assert.equal(byId.join_discord.cleared, true);
|
|
169
|
-
assert.equal(byId.
|
|
170
|
-
//
|
|
171
|
-
assert.equal(byId.
|
|
154
|
+
assert.equal(byId.cli_setup.cleared, false);
|
|
155
|
+
// join_discord clears, and the next gap is the craft picker.
|
|
156
|
+
assert.equal(byId.join_discord.is_current, false);
|
|
157
|
+
assert.equal(byId.pick_disciplines.is_current, true);
|
|
172
158
|
});
|
|
173
159
|
|
|
174
160
|
test('shapeForApi: graduated row → all displayed steps cleared, none current (regardless of derived signals)', () => {
|
|
@@ -177,10 +163,10 @@ test('shapeForApi: graduated row → all displayed steps cleared, none current (
|
|
|
177
163
|
completed_stages: onboarding.STAGES,
|
|
178
164
|
started_at: '2026-05-01T00:00:00Z',
|
|
179
165
|
completed_at: '2026-05-05T00:00:00Z',
|
|
180
|
-
}, { discordLinked: false,
|
|
166
|
+
}, { discordLinked: false, cliAuthed: false }); // proves display steps don't gate graduation
|
|
181
167
|
assert.equal(out.graduated, true);
|
|
182
|
-
assert.equal(out.progress.cleared,
|
|
183
|
-
assert.equal(out.progress.total,
|
|
168
|
+
assert.equal(out.progress.cleared, 4);
|
|
169
|
+
assert.equal(out.progress.total, 4);
|
|
184
170
|
assert.ok(out.stages.every((s) => s.cleared === true));
|
|
185
171
|
assert.ok(out.stages.every((s) => s.is_current === false));
|
|
186
172
|
});
|
|
@@ -329,9 +315,9 @@ test('graduation is engine-only (display signals never gate it)', async () => {
|
|
|
329
315
|
}
|
|
330
316
|
const state = await onboarding.getState(c, 60);
|
|
331
317
|
assert.equal(state.current_stage, 'graduated');
|
|
332
|
-
// graduated:true even with discordLinked/
|
|
318
|
+
// graduated:true even with discordLinked/cliAuthed false — proves
|
|
333
319
|
// the display-only steps never gate the xenos→thetes promotion (#692).
|
|
334
|
-
const shaped = onboarding.shapeForApi(state, { discordLinked: false,
|
|
320
|
+
const shaped = onboarding.shapeForApi(state, { discordLinked: false, cliAuthed: false });
|
|
335
321
|
assert.equal(shaped.graduated, true);
|
|
336
322
|
assert.ok(shaped.stages.every((s) => s.cleared === true));
|
|
337
323
|
});
|
|
@@ -73,9 +73,6 @@ t('#863-followup: newly-added protected surfaces are covered (findings #3/#9/#10
|
|
|
73
73
|
'.husky/pre-push',
|
|
74
74
|
'.husky/pre-commit',
|
|
75
75
|
'modules/grading/grader-workers/hacker.js',
|
|
76
|
-
'modules/dev-box/boxes.js',
|
|
77
|
-
'modules/dev-box/box-access.js',
|
|
78
|
-
'modules/dev-box/box-credential.js',
|
|
79
76
|
];
|
|
80
77
|
for (const f of newlyProtected) {
|
|
81
78
|
const hits = ppc.matchProtected([f]);
|
package/tests/platform_boot.mjs
CHANGED
|
@@ -178,7 +178,7 @@ test('R56: the served hall injects window.__BRANDING__ synchronously', async ()
|
|
|
178
178
|
});
|
|
179
179
|
|
|
180
180
|
test('R60: the served hall injects window.__MODULES__ (feature-module flags) synchronously', async () => {
|
|
181
|
-
// The hall's
|
|
181
|
+
// The hall's discord UI gating (R60, task 1199) reads
|
|
182
182
|
// window.__MODULES__.enabled[...]; serve-internal must inject it the same
|
|
183
183
|
// synchronous way as __BRANDING__, or the frontend gate has nothing to read.
|
|
184
184
|
const res = await request('/', 'builders.test.local');
|
|
@@ -189,7 +189,6 @@ test('R60: the served hall injects window.__MODULES__ (feature-module flags) syn
|
|
|
189
189
|
assert.ok(m, 'a __MODULES__ assignment is present');
|
|
190
190
|
const parsed = JSON.parse(m[1]);
|
|
191
191
|
assert.equal(typeof parsed.enabled, 'object');
|
|
192
|
-
assert.equal(typeof parsed.enabled['dev-box'], 'boolean');
|
|
193
192
|
assert.equal(typeof parsed.enabled.discord, 'boolean');
|
|
194
193
|
});
|
|
195
194
|
|
package/tests/profile_route.mjs
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
// goal 25 "bx-identity"). The route is intentionally UNAUTHENTICATED, so its one
|
|
5
5
|
// load-bearing invariant is that it must NEVER leak the private columns that
|
|
6
6
|
// db.getBuilderById also returns (anthropic_email / anthropic_user_id /
|
|
7
|
-
// monthly_budget_usd / system_role /
|
|
7
|
+
// monthly_budget_usd / system_role / deactivated_reason). It
|
|
8
8
|
// whitelists a public subset off the row; this test proves the whitelist holds,
|
|
9
9
|
// that the shape is right, and that unknown/invalid ids answer 404/400.
|
|
10
10
|
//
|
|
@@ -74,17 +74,12 @@ const FULL_ROW = {
|
|
|
74
74
|
anthropic_email: SECRET_EMAIL,
|
|
75
75
|
anthropic_user_id: SECRET_USER,
|
|
76
76
|
monthly_budget_usd: 500,
|
|
77
|
-
box_blocked: true,
|
|
78
|
-
box_blocked_reason: SECRET_REASON,
|
|
79
|
-
box_blocked_at: '2026-01-01T00:00:00.000Z',
|
|
80
|
-
box_blocked_by: 7,
|
|
81
77
|
};
|
|
82
78
|
|
|
83
79
|
// The columns that must NEVER appear on the returned builder object.
|
|
84
80
|
const PRIVATE_KEYS = [
|
|
85
81
|
'github_id', 'status', 'deactivated_at', 'reactivated_at', 'deactivated_reason',
|
|
86
82
|
'system_role', 'anthropic_email', 'anthropic_user_id', 'monthly_budget_usd',
|
|
87
|
-
'box_blocked', 'box_blocked_reason', 'box_blocked_at', 'box_blocked_by',
|
|
88
83
|
];
|
|
89
84
|
// The public subset the route DOES return.
|
|
90
85
|
const PUBLIC_KEYS = [
|
package/tests/projects_hub.mjs
CHANGED
|
@@ -707,14 +707,14 @@ test('the About answers travel on the create request, and an empty one is OMITTE
|
|
|
707
707
|
|
|
708
708
|
test('a renumbering of the wizard invalidates old drafts — the key carries the version', () => {
|
|
709
709
|
// task 1002772 renumbered once and bumped to -v2; task 1002334 renumbered again;
|
|
710
|
-
// task 1002339 (the module picker) is the third. A draft replayed under a
|
|
710
|
+
// task 1002339 (the module picker) is the third; task 1004351 (the app step) the fourth. A draft replayed under a
|
|
711
711
|
// different numbering lands on the wrong panel with the rail already marking
|
|
712
712
|
// the skipped steps done.
|
|
713
713
|
const key = (SCRIPT.match(/var DRAFT_KEY = '([^']+)'/) || [, ''])[1];
|
|
714
714
|
assert.match(key, /-v\d+$/, 'the draft key is versioned');
|
|
715
715
|
const panels = (SCRIPT.match(/var panels = \{([^}]*)\}/) || [, ''])[1];
|
|
716
716
|
const count = [...panels.matchAll(/\d+:\s*'panel-/g)].length;
|
|
717
|
-
assert.equal(count,
|
|
717
|
+
assert.equal(count, 10, 'gate + eight numbered steps + done — bump DRAFT_KEY when this changes');
|
|
718
718
|
});
|
|
719
719
|
|
|
720
720
|
test('every row lays the same five parts — the ledger has no ragged row', () => {
|
|
@@ -2133,9 +2133,9 @@ test('the wizard takes the world by scope: no card ground, the six-step strip as
|
|
|
2133
2133
|
assert.ok(at > -1, 'the #view-new block exists in cosmos.css');
|
|
2134
2134
|
const block = COSMOS.slice(at);
|
|
2135
2135
|
assert.match(block, /#view-new \.card\{background:none;border-radius:0;box-shadow:none;/, 'no card ground');
|
|
2136
|
-
//
|
|
2136
|
+
// eight tracks since task 1004351 added the App step (seven since 1002339); the count is the
|
|
2137
2137
|
// rail's own, so it moves with the panels map rather than being a magic number
|
|
2138
|
-
assert.match(block, /#view-new \.steps\{display:grid;grid-template-columns:repeat\(
|
|
2138
|
+
assert.match(block, /#view-new \.steps\{display:grid;grid-template-columns:repeat\(8,1fr\);/, 'the strip lays a track per numbered step');
|
|
2139
2139
|
assert.match(block, /#view-new \.steps li::before\{content:"";position:absolute;top:-4px;left:0;right:auto;width:7px;height:7px;/, 'the connector becomes the 7px dot');
|
|
2140
2140
|
assert.match(block, /#view-new \.steps li\.now::before\{background:var\(--accent\);border-color:var\(--accent\);outline:4px solid/, 'the lit step carries the accent and an outline halo');
|
|
2141
2141
|
assert.match(block, /#view-new \.panel h1\{font-weight:300;[^}]*text-transform:lowercase;/, 'the step title in the display voice, lowercase');
|
|
@@ -2182,7 +2182,7 @@ test('the wizard\'s own structures wear the world: hairline fields, cells of one
|
|
|
2182
2182
|
assert.match(block, /\.addrBig code\{display:block;font-family:var\(--font-display\);/, 'the address stands in the display voice');
|
|
2183
2183
|
assert.doesNotMatch(block, /linear-gradient/, 'no tinted plates');
|
|
2184
2184
|
assert.doesNotMatch(block, /box-shadow:(?!none)/, 'no box shadow in the wizard block');
|
|
2185
|
-
assert.match(HUB, /<div class="nav">\s*<button type="button" class="btn ghost" data-back="
|
|
2185
|
+
assert.match(HUB, /<div class="nav">\s*<button type="button" class="btn ghost" data-back="7">← Back<\/button>\s*<button type="button" class="btn big" id="doCreate">Create my project<\/button>/, 'the review\'s doors sit on one row, back then the primary');
|
|
2186
2186
|
assert.match(HUB, /<h2 class="stepsHead" id="doneStepsHead">Your steps — the parts only you can play<\/h2>/, 'the steps heading is the label face by class, not an inline style (the id is where a skipped invite step hands focus on, task 1002346)');
|
|
2187
2187
|
// what is enqueued is unchanged (ADR 0111): the filing body the control plane reads
|
|
2188
2188
|
assert.match(SCRIPT, /body: JSON\.stringify\(\{\s*slug: state\.slug,/, 'the filing still carries the slug first');
|