@bongos/core 1.19.726 → 1.19.727
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 +176 -491
- package/.devcontainer/Dockerfile +1 -1
- package/.devcontainer/devcontainer.json +2 -2
- package/.devcontainer/healthcheck.sh +1 -1
- package/.devcontainer/init-firewall.sh +1 -1
- package/.devcontainer/post-create.sh +2 -2
- package/.env.local.example +1 -1
- package/Dockerfile +1 -1
- package/docs/copy-inventory.md +1 -1
- package/docs/copy-registry.json +9 -9
- package/docs/module-api-changelog.md +2 -0
- package/docs/recipes/devbox-app-release.md +4 -4
- package/modules/dev-box/app/scripts/dmgbuild-settings.py +5 -5
- package/modules/dev-box/app/scripts/gen-brand.js +1 -1
- package/modules/dev-box/app/src/brand.js +1 -1
- package/modules/dev-box/app/test/unit.mjs +16 -16
- package/modules/dev-box/box-access.js +1 -1
- package/modules/dev-box/routes/box.js +1 -1
- package/modules/discord/discord-approvals.js +1 -1
- package/modules/grading/grader-model.js +1 -1
- package/modules/grading/grader-prompt.js +1 -1
- package/modules/grading/grader-workers/quality.js +1 -1
- package/modules/hall-ui/module.json +1 -1
- package/modules/hall-ui/public/brand-apply.js +1 -1
- package/modules/hall-ui/public/shell.js +1 -1
- package/modules/hall-ui/public/work.js +1 -1
- package/modules/lifecycle/conflict-resolve.js +1 -1
- package/modules/lifecycle/db-analytics.js +1 -1
- package/modules/lifecycle/routes/analytics.js +1 -1
- package/modules/onboarding/approval-broadcast.js +1 -1
- package/modules/onboarding/onboarding.js +1 -1
- package/modules/provisioning/tests/liveness.mjs +1 -1
- package/modules/public-landing/public/index.html +1 -1
- package/modules/public-landing/public/projects-floor.states.json +2 -2
- package/modules/public-landing/public/projects.probes.json +4 -4
- package/modules/public-landing/public/projects.states.json +2 -2
- package/modules/status-ui/module.json +1 -1
- package/modules/status-ui/public/status.js +4 -4
- package/modules/status-ui/public/style.css +1 -1
- package/modules/ui-design/kit/fixtures/cost-summary.json +1 -1
- package/modules/ui-design/kit/fixtures/featured-hub.json +1 -1
- package/modules/ui-design/kit/fixtures/featured.json +1 -1
- package/modules/ui-design/kit/fixtures/leaderboard.json +1 -1
- package/modules/ui-design/kit/fixtures/my-projects.json +1 -1
- package/modules/ui-design/kit/fixtures/progress.json +1 -1
- package/modules/ui-design/kit/fixtures/recent-shipped.json +1 -1
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/scripts/cron/style_guide_audit.sh +1 -1
- package/scripts/discord/lib.js +1 -1
- package/scripts/discord/post.js +1 -1
- package/scripts/gds/audit-authorship.sh +1 -1
- package/scripts/gds/audit-replay.sh +29 -2
- package/scripts/gds/audit-ships.js +1 -1
- package/scripts/gds/backfill-sessions.js +2 -2
- package/scripts/gds/cf-tunnel.js +4 -4
- package/scripts/gds/claude-materialize.js +2 -2
- package/scripts/gds/cli-lib.js +6 -6
- package/scripts/gds/cutover-codemod.js +1 -1
- package/scripts/gds/dev-lib.js +2 -2
- package/scripts/gds/diagnose-achievements.js +2 -2
- package/scripts/gds/fitness-checks-identity.js +6 -4
- package/scripts/gds/gen-diagrams.js +1 -1
- package/scripts/gds/leak-scan-allowlist.js +293 -0
- package/scripts/gds/learning-capture.js +1 -1
- package/scripts/gds/linkify-refs.js +2 -2
- package/scripts/gds/local-preview-lib.js +1 -1
- package/scripts/gds/mirror-lineage-check.js +1 -1
- package/scripts/gds/mirror-redact.js +17 -2
- package/scripts/gds/newcomer-floor.templates.json +1 -1
- package/scripts/gds/package-core.js +18 -1
- package/scripts/gds/paste-token.js +1 -1
- package/scripts/gds/provision-units.js +2 -2
- package/scripts/gds/publish-manifest.js +15 -0
- package/scripts/gds/repo-metrics.js +1 -1
- package/scripts/gds/sandbox-stage.js +1 -1
- package/scripts/gds/seed-bongos-coreB-tranche1-tasks.js +1 -1
- package/scripts/gds/seed-bongos-coreB-tranche2-tasks.js +1 -1
- package/scripts/gds/seed-bongos-goal-hierarchy-tasks.js +1 -1
- package/scripts/gds/seed-bongos-tasks.js +2 -2
- package/scripts/gds/seed-gds-v4-tasks.js +1 -1
- package/scripts/gds/seed-medusa-tasks.js +10 -10
- package/scripts/gds/seed-module-upstreaming.js +1 -1
- package/scripts/gds/seed-productization-tasks.js +6 -6
- package/scripts/gds/seed-provisioning-tasks.js +2 -2
- package/scripts/gds/seed-staging.sh +19 -6
- package/scripts/gds/ship-deploy-target.js +1 -1
- package/scripts/gds/ship-flow.js +1 -1
- package/scripts/gds/smoke-gds.js +1 -1
- package/scripts/status-probe.mjs +2 -2
- package/src/bongos/auth-discord.js +2 -2
- package/src/bongos/live-channel.js +1 -1
- package/src/bongos/pool.js +1 -1
- package/src/bongos/routes/auth.js +11 -11
- package/src/bongos/routes/security.js +1 -1
- package/src/bongos/routes.js +1 -1
- package/src/branding.js +1 -1
- package/src/devbox-downloads.js +1 -1
- package/src/instance-config.js +2 -2
- package/src/module-api.js +1 -1
- package/src/module-loader/provenance.js +2 -2
- package/tests/art_stage.mjs +2 -2
- package/tests/auth_anthropic_identity.mjs +1 -1
- package/tests/auth_duplicate_cookie.mjs +1 -1
- package/tests/auth_error_brand.mjs +1 -1
- package/tests/auth_inactive_builder.mjs +1 -1
- package/tests/backup_cli.mjs +4 -4
- package/tests/bongos_box_connect.mjs +2 -2
- package/tests/bongos_downloads.mjs +2 -2
- package/tests/bongos_login.mjs +5 -5
- package/tests/box_connect_e2e.mjs +2 -2
- package/tests/box_dns_repoint.mjs +10 -10
- package/tests/box_host_keys.mjs +1 -1
- package/tests/box_onboard.mjs +9 -9
- package/tests/box_scope_session.mjs +1 -1
- package/tests/box_terminal_ssrf.mjs +7 -7
- package/tests/boxes.mjs +9 -9
- package/tests/branding.mjs +2 -2
- package/tests/bump_version.mjs +2 -2
- package/tests/cf_tunnel.mjs +40 -40
- package/tests/cli_token_reissue.mjs +7 -7
- package/tests/cloud_bongos_pack.mjs +1 -1
- package/tests/currency_label.mjs +8 -8
- package/tests/dev_lib.mjs +4 -4
- package/tests/devbox_dmg_backdrop.mjs +1 -1
- package/tests/devbox_downloads.mjs +2 -2
- package/tests/devbox_icons.mjs +1 -1
- package/tests/discord_approvals.mjs +1 -1
- package/tests/discord_oauth.mjs +2 -2
- package/tests/executed_closure.mjs +6 -1
- package/tests/first_admin_bootstrap.mjs +1 -1
- package/tests/gate_approvals.mjs +5 -5
- package/tests/grade_aggregates_unavailable.mjs +1 -1
- package/tests/hook_user_agents.mjs +2 -2
- package/tests/init.mjs +6 -6
- package/tests/instance_config.mjs +1 -1
- package/tests/instance_manifest.mjs +5 -5
- package/tests/instance_mismatch_guard.mjs +13 -13
- package/tests/leak_scan_allowlist.mjs +94 -0
- package/tests/migrate_root_resolution.mjs +4 -4
- package/tests/mirror_redact.mjs +28 -4
- package/tests/module_catalog.mjs +5 -5
- package/tests/module_submissions.mjs +3 -3
- package/tests/package_core.mjs +1 -1
- package/tests/parallel-load.mjs +6 -6
- package/tests/projects_hub.mjs +2 -2
- package/tests/provision.mjs +50 -50
- package/tests/provisioning_github_app_routes.mjs +1 -1
- package/tests/provisioning_onboard_mode.mjs +2 -2
- package/tests/publish_reconciler.mjs +4 -4
- package/tests/sandbox_stage.mjs +1 -1
- package/tests/scrubber_corpus.mjs +3 -3
- package/tests/search_isolation.mjs +1 -1
- package/tests/session_records.mjs +1 -1
- package/tests/ship_card.mjs +6 -6
- package/tests/ship_ci_deploy.mjs +1 -1
- package/tests/single_host_hall.mjs +9 -9
- package/tests/smoke_preflight.mjs +1 -1
- package/tests/smoke_target.mjs +1 -1
- package/tests/status-probe.mjs +1 -1
- package/tests/status_mirror_sync.mjs +1 -1
- package/docs/adr/0004-amazonprimea-name-and-trademark-acceptance.md +0 -45
- package/docs/design/mocks/_schemes.css +0 -604
- package/docs/design/mocks/component-sheet.html +0 -428
- package/docs/design/mocks/hall/_tokens.css +0 -370
- package/docs/design/mocks/hall/board/v1.html +0 -846
- package/docs/design/mocks/hall/board/v2.html +0 -858
- package/docs/design/mocks/hall/board/v3.html +0 -884
- package/docs/design/mocks/hall/goals/v1.html +0 -1212
- package/docs/design/mocks/hall/goals/v2.html +0 -1192
- package/docs/design/mocks/hall/goals/v3.html +0 -1205
- package/docs/design/mocks/hall/hall-sheet.html +0 -244
- package/docs/design/mocks/hall/reading/v1.html +0 -1125
- package/docs/design/mocks/hall/reading/v2.html +0 -1177
- package/docs/design/mocks/hall/reading/v3.html +0 -1150
- package/docs/design/mocks/hall/settings/v1.html +0 -1238
- package/docs/design/mocks/hall/settings/v2.html +0 -1235
- package/docs/design/mocks/hall/settings/v3.html +0 -1299
- package/docs/design/mocks/hall/task/v1.html +0 -633
- package/docs/design/mocks/hall/task/v2.html +0 -646
- package/docs/design/mocks/hall/task/v3.html +0 -647
- package/docs/design/mocks/hub/assets/r3-space-deepfield-half.jpg +0 -0
- package/docs/design/mocks/hub/assets/r3-space-deepfield-half.jpg.json +0 -13
- package/docs/design/mocks/hub/assets/r3-space-dust-half.jpg +0 -0
- package/docs/design/mocks/hub/assets/r3-space-dust-half.jpg.json +0 -13
- package/docs/design/mocks/hub/assets/r3-space-nebula-half.jpg +0 -0
- package/docs/design/mocks/hub/assets/r3-space-nebula-half.jpg.json +0 -13
- package/docs/design/mocks/hub/assets/r3-space-starfield-half.jpg +0 -0
- package/docs/design/mocks/hub/assets/r3-space-starfield-half.jpg.json +0 -13
- package/docs/design/mocks/hub/hub-sheet.html +0 -192
- package/docs/design/mocks/hub/mine.html +0 -465
- package/docs/design/mocks/hub/picker.html +0 -421
- package/docs/design/mocks/hub/planets.html +0 -356
- package/docs/design/mocks/hub/v1.html +0 -1209
- package/docs/design/mocks/hub/v2.html +0 -1257
- package/docs/design/mocks/hub/v3.html +0 -1496
- package/docs/design/mocks/hub/wizard.html +0 -421
- package/docs/design/mocks/landing/landing-sheet.html +0 -131
- package/docs/design/mocks/landing/v1.html +0 -956
- package/docs/design/mocks/landing/v2.html +0 -708
- package/docs/design/mocks/landing/v3.html +0 -911
- package/docs/design/mocks/landing-page-mock.html +0 -687
- package/docs/design/mocks/landing-palettes.html +0 -103
- package/docs/design/mocks/projects-hub-mock.html +0 -991
- package/docs/design/mocks/projects-hub-variants.html +0 -146
- package/docs/design/mocks/projects-page-mock.html +0 -167
- package/docs/design/mocks/world/blue-hud/_tokens.css +0 -246
- package/docs/design/mocks/world/blue-hud/hall.html +0 -595
- package/docs/design/mocks/world/blue-hud/landing.html +0 -424
- package/docs/design/mocks/world/blue-hud/status.html +0 -410
- package/docs/design/mocks/world/chrome-blobs/_tokens.css +0 -479
- package/docs/design/mocks/world/chrome-blobs/hall.html +0 -553
- package/docs/design/mocks/world/chrome-blobs/landing-b.html +0 -581
- package/docs/design/mocks/world/chrome-blobs/landing.html +0 -328
- package/docs/design/mocks/world/chrome-blobs/status.html +0 -416
- package/docs/design/mocks/world/natgeo-editorial/_tokens.css +0 -377
- package/docs/design/mocks/world/natgeo-editorial/hall.html +0 -649
- package/docs/design/mocks/world/natgeo-editorial/landing-b.html +0 -743
- package/docs/design/mocks/world/natgeo-editorial/landing.html +0 -475
- package/docs/design/mocks/world/natgeo-editorial/status.html +0 -437
- package/docs/design/mocks/world/organic-cards/_tokens.css +0 -462
- package/docs/design/mocks/world/organic-cards/hall.html +0 -625
- package/docs/design/mocks/world/organic-cards/landing-b.html +0 -736
- package/docs/design/mocks/world/organic-cards/landing.html +0 -420
- package/docs/design/mocks/world/organic-cards/status.html +0 -458
- package/docs/design/mocks/world/world-sheet.html +0 -156
|
@@ -14,10 +14,10 @@ const RANK_TIER = { xenos: 1, thetes: 2, metic: 3, archon: 4 };
|
|
|
14
14
|
|
|
15
15
|
// A client-safe branding projection (the shape clientBranding() returns).
|
|
16
16
|
const BRAND_OTB = {
|
|
17
|
-
identity: { productName: '
|
|
17
|
+
identity: { productName: 'Example World', worldName: 'Example', company: { name: 'Hermeslines' } },
|
|
18
18
|
currency: { label: 'drachmae', symbol: '' },
|
|
19
19
|
theme: { colors: {}, fonts: { display: '', body: '', links: [] }, favicon: '', backdrop: '' },
|
|
20
|
-
copy: { tagline: 'Straight
|
|
20
|
+
copy: { tagline: 'Straight example world', hallName: 'Builders’ Hall', notFound: 'Page not found.' },
|
|
21
21
|
rankLabels: { xenos: 'Xenos', thetes: 'Thetes', metic: 'Metic', archon: 'Archon' },
|
|
22
22
|
// the per-project policy the platform pushes (task 1003140) — the settings read-back
|
|
23
23
|
project: { platformVisibility: 'gated', joinability: 'invite_only' },
|
|
@@ -37,12 +37,12 @@ const MODULES_VANILLA = {
|
|
|
37
37
|
enabled: { game: false, 'art-pipeline': false, discord: false, 'dev-box': false },
|
|
38
38
|
disciplines: ['engineer'],
|
|
39
39
|
};
|
|
40
|
-
const REPO = { owner: '
|
|
40
|
+
const REPO = { owner: 'example-owner', repo: 'example', branch: 'main', blob_base: 'x', tree_base: 'y' };
|
|
41
41
|
|
|
42
42
|
test('manifest: schema id + headline name + embedded brand', () => {
|
|
43
43
|
const m = buildInstanceManifest({ branding: BRAND_OTB, modules: MODULES_OTB, rankTier: RANK_TIER, repo: REPO, authConfigured: true, openEnrollment: false, devBoxEnabled: true });
|
|
44
44
|
assert.equal(m.schema, MANIFEST_SCHEMA);
|
|
45
|
-
assert.equal(m.name, '
|
|
45
|
+
assert.equal(m.name, 'Example World');
|
|
46
46
|
assert.equal(m.brand, BRAND_OTB);
|
|
47
47
|
assert.deepEqual(m.modules, MODULES_OTB);
|
|
48
48
|
});
|
|
@@ -69,7 +69,7 @@ test('manifest: rank label falls back to the key when unlabelled', () => {
|
|
|
69
69
|
|
|
70
70
|
test('manifest: repo projects to public clone coords; null on error', () => {
|
|
71
71
|
const ok = buildInstanceManifest({ branding: BRAND_OTB, modules: MODULES_OTB, rankTier: RANK_TIER, repo: REPO, authConfigured: true, openEnrollment: false, devBoxEnabled: true });
|
|
72
|
-
assert.deepEqual(ok.repo, { owner: '
|
|
72
|
+
assert.deepEqual(ok.repo, { owner: 'example-owner', repo: 'example', branch: 'main', url: 'https://github.com/example-owner/example' });
|
|
73
73
|
const none = buildInstanceManifest({ branding: BRAND_VANILLA, modules: MODULES_VANILLA, rankTier: RANK_TIER, repo: { error: 'no_repo_info' }, authConfigured: false, openEnrollment: false, devBoxEnabled: false });
|
|
74
74
|
assert.equal(none.repo, null);
|
|
75
75
|
});
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
//
|
|
8
8
|
// The guard stops the CLI from silently borrowing a DIFFERENT instance's session
|
|
9
9
|
// (the 2026-07-08 demo-session failure, where a fresh demo clone used the
|
|
10
|
-
// founder's
|
|
10
|
+
// founder's example.com session and offered to write into the wrong live
|
|
11
11
|
// backlog). Both helpers are deterministic with no DB/network/fs, so they
|
|
12
12
|
// unit-test standalone. Run: node tests/instance_mismatch_guard.mjs
|
|
13
13
|
|
|
@@ -25,14 +25,14 @@ function test(name, fn) {
|
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
const DEMO = { domains: { publicOrigin: 'https://demo.cloudbongos.com' } };
|
|
28
|
-
const OTB = { domains: { publicOrigin: 'https://
|
|
28
|
+
const OTB = { domains: { publicOrigin: 'https://example.com' } };
|
|
29
29
|
const noEnv = {}; // no <PREFIX>_API_BASE override in scope
|
|
30
30
|
const branded = () => true; // simulate a checkout that HAS config/branding.json (a declared instance)
|
|
31
31
|
const neutral = () => false; // simulate a NEUTRAL checkout (portable core — no config/branding.json)
|
|
32
32
|
|
|
33
33
|
test('instanceOriginHost: extracts host, tolerates trailing slash', () => {
|
|
34
|
-
assert.equal(cli.instanceOriginHost('https://
|
|
35
|
-
assert.equal(cli.instanceOriginHost('https://
|
|
34
|
+
assert.equal(cli.instanceOriginHost('https://example.com'), 'example.com');
|
|
35
|
+
assert.equal(cli.instanceOriginHost('https://example.com/'), 'example.com');
|
|
36
36
|
assert.equal(cli.instanceOriginHost('https://demo.cloudbongos.com/api/gds'), 'demo.cloudbongos.com');
|
|
37
37
|
});
|
|
38
38
|
|
|
@@ -43,20 +43,20 @@ test('instanceOriginHost: unparseable input → null (edge)', () => {
|
|
|
43
43
|
});
|
|
44
44
|
|
|
45
45
|
test('match: same-host session + branding → no mismatch (the OTB/normal case)', () => {
|
|
46
|
-
const r = cli.detectInstanceMismatch({ session: { api_base: 'https://
|
|
46
|
+
const r = cli.detectInstanceMismatch({ session: { api_base: 'https://example.com' }, brand: OTB, env: noEnv, hasInstanceConfig: branded });
|
|
47
47
|
assert.equal(r.mismatch, false);
|
|
48
48
|
});
|
|
49
49
|
|
|
50
50
|
test('match: scheme/trailing-slash differences still match on host', () => {
|
|
51
|
-
const r = cli.detectInstanceMismatch({ session: { api_base: 'https://
|
|
51
|
+
const r = cli.detectInstanceMismatch({ session: { api_base: 'https://example.com/' }, brand: OTB, env: noEnv, hasInstanceConfig: branded });
|
|
52
52
|
assert.equal(r.mismatch, false);
|
|
53
53
|
});
|
|
54
54
|
|
|
55
|
-
test('mismatch: demo repo + founder
|
|
56
|
-
const r = cli.detectInstanceMismatch({ session: { api_base: 'https://
|
|
55
|
+
test('mismatch: demo repo + founder example session → REFUSE (the reported failure)', () => {
|
|
56
|
+
const r = cli.detectInstanceMismatch({ session: { api_base: 'https://example.com' }, brand: DEMO, env: noEnv, hasInstanceConfig: branded });
|
|
57
57
|
assert.equal(r.mismatch, true);
|
|
58
58
|
assert.equal(r.expected, 'https://demo.cloudbongos.com');
|
|
59
|
-
assert.equal(r.actual, 'https://
|
|
59
|
+
assert.equal(r.actual, 'https://example.com');
|
|
60
60
|
});
|
|
61
61
|
|
|
62
62
|
test('mismatch: demo repo + cloudbongos.com session (sibling instance) → REFUSE', () => {
|
|
@@ -66,15 +66,15 @@ test('mismatch: demo repo + cloudbongos.com session (sibling instance) → REFUS
|
|
|
66
66
|
|
|
67
67
|
test('env-override: an explicit *_API_BASE override wins (dev/staging) → no mismatch', () => {
|
|
68
68
|
const r = cli.detectInstanceMismatch({
|
|
69
|
-
session: { api_base: 'https://
|
|
69
|
+
session: { api_base: 'https://example.com' }, brand: DEMO,
|
|
70
70
|
env: { GDS_API_BASE: 'http://localhost:3000' },
|
|
71
71
|
});
|
|
72
72
|
assert.equal(r.mismatch, false);
|
|
73
73
|
});
|
|
74
74
|
|
|
75
75
|
test('no-branding: repo declares no publicOrigin → cannot assert a mismatch → no mismatch', () => {
|
|
76
|
-
assert.equal(cli.detectInstanceMismatch({ session: { api_base: 'https://
|
|
77
|
-
assert.equal(cli.detectInstanceMismatch({ session: { api_base: 'https://
|
|
76
|
+
assert.equal(cli.detectInstanceMismatch({ session: { api_base: 'https://example.com' }, brand: {}, env: noEnv, hasInstanceConfig: branded }).mismatch, false);
|
|
77
|
+
assert.equal(cli.detectInstanceMismatch({ session: { api_base: 'https://example.com' }, brand: { domains: {} }, env: noEnv, hasInstanceConfig: branded }).mismatch, false);
|
|
78
78
|
});
|
|
79
79
|
|
|
80
80
|
test('NEUTRAL checkout (no config/branding.json) → trust the session, NO mismatch (task 1002399)', () => {
|
|
@@ -88,7 +88,7 @@ test('NEUTRAL checkout (no config/branding.json) → trust the session, NO misma
|
|
|
88
88
|
|
|
89
89
|
test('neutral guard is precedence-safe: env override still wins even when neutral', () => {
|
|
90
90
|
// env override short-circuits before the neutral check — order-independent.
|
|
91
|
-
const r = cli.detectInstanceMismatch({ session: { api_base: 'https://
|
|
91
|
+
const r = cli.detectInstanceMismatch({ session: { api_base: 'https://example.com' }, brand: DEMO, env: { GDS_API_BASE: 'http://localhost:3000' }, hasInstanceConfig: neutral });
|
|
92
92
|
assert.equal(r.mismatch, false);
|
|
93
93
|
});
|
|
94
94
|
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
// tests/leak_scan_allowlist.mjs
|
|
2
|
+
//
|
|
3
|
+
// Task 1003869 — the named exceptions to the prior-instance leak scan. The allowlist is
|
|
4
|
+
// a security carve-out, so what matters is not that it permits the right things but that
|
|
5
|
+
// it CANNOT permit the wrong ones: scoping is per (path, literal), a listed file does not
|
|
6
|
+
// become a free pass, and an entry that stops matching is reported rather than forgotten.
|
|
7
|
+
//
|
|
8
|
+
// Run: node tests/leak_scan_allowlist.mjs
|
|
9
|
+
|
|
10
|
+
import { strict as assert } from 'node:assert';
|
|
11
|
+
import { createRequire } from 'node:module';
|
|
12
|
+
|
|
13
|
+
const require = createRequire(import.meta.url);
|
|
14
|
+
const A = require('../scripts/gds/leak-scan-allowlist.js');
|
|
15
|
+
const { allowReason, partitionHits, unusedEntries, FILE_ALLOWLIST, WELL_KNOWN_PUBLIC_IPS } = A;
|
|
16
|
+
const { PRIOR_INSTANCE_FLOOR } = require('../scripts/gds/package-core.js');
|
|
17
|
+
|
|
18
|
+
// This test file is itself published, so it derives the literals it exercises rather
|
|
19
|
+
// than spelling them out — the same self-leak invariant the allowlist module holds.
|
|
20
|
+
const OWNER = PRIOR_INSTANCE_FLOOR.owners[0];
|
|
21
|
+
const TOKEN = PRIOR_INSTANCE_FLOOR.tokens[0];
|
|
22
|
+
|
|
23
|
+
let passed = 0, failed = 0;
|
|
24
|
+
function t(name, fn) {
|
|
25
|
+
try { fn(); passed++; console.log(` PASS ${name}`); }
|
|
26
|
+
catch (e) { failed++; console.log(` FAIL ${name}\n ${e.message}`); }
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// ── every entry carries its reason ───────────────────────────────────────────────────
|
|
30
|
+
t('every entry names a path, at least one literal, and a substantive reason', () => {
|
|
31
|
+
for (const e of FILE_ALLOWLIST) {
|
|
32
|
+
assert.ok(e.path && typeof e.path === 'string', 'entry needs a path');
|
|
33
|
+
assert.ok(Array.isArray(e.classes) && e.classes.length > 0, `${e.path} needs classes`);
|
|
34
|
+
assert.ok(typeof e.reason === 'string' && e.reason.length > 40,
|
|
35
|
+
`${e.path} needs a reason that explains itself, not a label`);
|
|
36
|
+
}
|
|
37
|
+
for (const [ip, why] of Object.entries(WELL_KNOWN_PUBLIC_IPS)) {
|
|
38
|
+
assert.ok(typeof why === 'string' && why.length > 10, `${ip} needs a reason`);
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
// ── scoping: a listed file is NOT a free pass ────────────────────────────────────────
|
|
43
|
+
t('an allowlisted file still fails on a literal it was not listed for', () => {
|
|
44
|
+
// mirror-redact.js is listed for public-ip only. An owner login appearing there would
|
|
45
|
+
// be a real leak — the SELF-LEAK INVARIANT that file documents about itself.
|
|
46
|
+
assert.ok(allowReason('scripts/gds/mirror-redact.js', 'public-ip', '8.8.8.8'));
|
|
47
|
+
assert.equal(allowReason('scripts/gds/mirror-redact.js', `literal:${OWNER}`, OWNER), null,
|
|
48
|
+
'a literal whose class is not named for this path must still fail');
|
|
49
|
+
});
|
|
50
|
+
t('an entry does not leak across to another path', () => {
|
|
51
|
+
// audit-authorship.sh may carry the owner login; an arbitrary script may not.
|
|
52
|
+
assert.ok(allowReason('scripts/gds/audit-authorship.sh', `literal:${OWNER}`, OWNER));
|
|
53
|
+
assert.equal(allowReason('scripts/gds/some-other-script.js', `literal:${OWNER}`, OWNER), null);
|
|
54
|
+
});
|
|
55
|
+
t('an unlisted file gets no exception at all', () => {
|
|
56
|
+
assert.equal(allowReason('src/bongos/routes/auth.js', `literal:${TOKEN}`, TOKEN), null);
|
|
57
|
+
assert.equal(allowReason('src/anything.js', 'public-ip', '203.0.113.9'), null,
|
|
58
|
+
'an address that is not well-known is not covered anywhere');
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
// ── well-known addresses are global, and exact ───────────────────────────────────────
|
|
62
|
+
t('a well-known public address is covered in any file, but only by exact match', () => {
|
|
63
|
+
assert.ok(allowReason('tests/anything.mjs', 'public-ip', '8.8.8.8'));
|
|
64
|
+
assert.ok(allowReason('modules/whatever/x.js', 'public-ip', '1.1.1.1'));
|
|
65
|
+
// An address that is not on the well-known list is covered NOWHERE — the whole point of
|
|
66
|
+
// the sweep. Asserted with RFC 5737 documentation addresses rather than this instance's
|
|
67
|
+
// real ones: a test that pins "our droplet must not be allowlisted" by NAMING the droplet
|
|
68
|
+
// puts it straight back into the published trunk, which is the leak it is guarding against.
|
|
69
|
+
assert.equal(allowReason('tests/anything.mjs', 'public-ip', '198.51.100.7'), null);
|
|
70
|
+
assert.equal(allowReason('tests/anything.mjs', 'public-ip', '192.0.2.99'), null);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
// ── partition + staleness ────────────────────────────────────────────────────────────
|
|
74
|
+
t('partitionHits separates covered hits from real ones and attaches the reason', () => {
|
|
75
|
+
const hits = [
|
|
76
|
+
{ path: 'tests/mirror_redact.mjs', label: 'public-ip', sample: '8.8.8.8' },
|
|
77
|
+
{ path: 'src/bongos/routes/auth.js', label: `literal:${TOKEN}`, sample: TOKEN },
|
|
78
|
+
];
|
|
79
|
+
const { allowed, remaining } = partitionHits(hits);
|
|
80
|
+
assert.equal(allowed.length, 1);
|
|
81
|
+
assert.ok(allowed[0].reason.length > 10, 'a covered hit carries the reason it was covered');
|
|
82
|
+
assert.equal(remaining.length, 1);
|
|
83
|
+
assert.equal(remaining[0].path, 'src/bongos/routes/auth.js');
|
|
84
|
+
});
|
|
85
|
+
t('unusedEntries reports an entry that has stopped matching', () => {
|
|
86
|
+
// No hits at all → every file entry is stale, which is the signal to prune.
|
|
87
|
+
assert.ok(unusedEntries([]).length > 0);
|
|
88
|
+
// A hit that covers one entry removes exactly that one from the stale list.
|
|
89
|
+
const stale = unusedEntries([{ path: 'scripts/gds/mirror-redact.js', label: 'public-ip', sample: '8.8.8.8' }]);
|
|
90
|
+
assert.ok(!stale.some((s) => s.path === 'scripts/gds/mirror-redact.js' && s.literal === 'public-ip'));
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
console.log(`\n${passed} passed, ${failed} failed`);
|
|
94
|
+
process.exit(failed ? 1 : 0);
|
|
@@ -72,7 +72,7 @@ function printRoots({ cwd, initCwd } = {}) {
|
|
|
72
72
|
}
|
|
73
73
|
|
|
74
74
|
// A synthetic consumer: a dir with a bongos.config.json pointing at itself, exactly
|
|
75
|
-
// as the post-split
|
|
75
|
+
// as the post-split example repo carries (bongos.config.json { instanceRoot: "." }).
|
|
76
76
|
function makeConsumer() {
|
|
77
77
|
const dir = mkFixture('r89-consumer-');
|
|
78
78
|
writeFileSync(path.join(dir, 'bongos.config.json'), JSON.stringify({ instanceRoot: '.' }));
|
|
@@ -249,7 +249,7 @@ t('the pre-fix unquoted loop now fails loudly instead of silently skipping', ()
|
|
|
249
249
|
// ==========================================================================
|
|
250
250
|
// WHICH DATABASE does migrate target? (task 1002755)
|
|
251
251
|
//
|
|
252
|
-
// scripts/migrate.sh:52 used to read `DB="${PGDATABASE:-
|
|
252
|
+
// scripts/migrate.sh:52 used to read `DB="${PGDATABASE:-example}"` — one host instance's
|
|
253
253
|
// database name, baked into a script the core ships to EVERY instance. With no PGDATABASE in the
|
|
254
254
|
// environment, a co-hosted box silently aimed at a stranger's database and everywhere else died
|
|
255
255
|
// with a confusing "database ... does not exist" — after the caller had already moved its version
|
|
@@ -314,7 +314,7 @@ t('an instance that names no database is REFUSED, never defaulted', () => {
|
|
|
314
314
|
assert.match(r.stderr, /cannot determine which database/i);
|
|
315
315
|
assert.match(r.stderr, /Refusing to guess/i);
|
|
316
316
|
// the regression itself: it must not reach for another instance's database
|
|
317
|
-
assert.doesNotMatch(`${r.stdout}${r.stderr}`, /
|
|
317
|
+
assert.doesNotMatch(`${r.stdout}${r.stderr}`, /example/i);
|
|
318
318
|
});
|
|
319
319
|
|
|
320
320
|
t('--print-migrations stays DB-free — a dry run must not require a database name', () => {
|
|
@@ -333,7 +333,7 @@ t('migrate.sh hardcodes no instance database name', () => {
|
|
|
333
333
|
const code = src.split(/\r?\n/).filter((l) => !/^\s*#/.test(l)).join('\n');
|
|
334
334
|
assert.doesNotMatch(code, /PGDATABASE:-\s*[A-Za-z]/,
|
|
335
335
|
'PGDATABASE must have no literal fallback — resolve from the branding pack instead');
|
|
336
|
-
assert.doesNotMatch(code, /
|
|
336
|
+
assert.doesNotMatch(code, /example/i, 'no instance database name in executable lines');
|
|
337
337
|
});
|
|
338
338
|
|
|
339
339
|
console.log(`\nmigrate_root_resolution: ${passed} passed, ${failed} failed`);
|
package/tests/mirror_redact.mjs
CHANGED
|
@@ -104,12 +104,12 @@ t('isPublicRoutableIPv4 classifier boundaries', () => {
|
|
|
104
104
|
|
|
105
105
|
// ── IPv6 floor: global unicast redacted, loopback/link-local/ULA kept ─────────────────
|
|
106
106
|
t('public IPv6 (the droplet) redacted; loopback/link-local/ULA kept', () => {
|
|
107
|
-
const out = redactText('v6
|
|
107
|
+
const out = redactText('v6 2606:4700:4700::1111 keep fe80::1 fc00::5 ::1', rules);
|
|
108
108
|
assert.ok(!out.includes('2604:a880'), `public IPv6 leaked: ${out}`);
|
|
109
109
|
assert.ok(out.includes('fe80::1') && out.includes('fc00::5') && out.includes('::1'), out);
|
|
110
110
|
});
|
|
111
111
|
t('isPublicRoutableIPv6 + a "12:34:56" timestamp is NOT treated as an IP', () => {
|
|
112
|
-
assert.equal(isPublicRoutableIPv6('
|
|
112
|
+
assert.equal(isPublicRoutableIPv6('2606:4700:4700::1111'), true);
|
|
113
113
|
assert.equal(isPublicRoutableIPv6('fe80::1'), false);
|
|
114
114
|
assert.equal(isPublicRoutableIPv6('fc00::5'), false);
|
|
115
115
|
assert.equal(isPublicRoutableIPv6('::1'), false);
|
|
@@ -127,14 +127,38 @@ t('reserved ::/8 is NOT a public IP — degenerate shorthand in binary is not a
|
|
|
127
127
|
}
|
|
128
128
|
// ...and the narrowing did NOT blunt the gate: real global unicast still fires, in
|
|
129
129
|
// both the detector and the redactor.
|
|
130
|
-
for (const s of ['2000::1', '
|
|
130
|
+
for (const s of ['2000::1', '2606:4700:4700::1111', '3fff::1']) {
|
|
131
131
|
assert.equal(isPublicRoutableIPv6(s), true, `${s} is a real public address and must still be flagged`);
|
|
132
132
|
}
|
|
133
|
+
// 2001:db8::/32 is RESERVED FOR DOCUMENTATION (RFC 3849) — the IPv6 analogue of the
|
|
134
|
+
// RFC 5737 TEST-NET ranges v4 already exempts, and for the same reason: it is the
|
|
135
|
+
// address an example is SUPPOSED to use, nothing is reachable there, and flagging it
|
|
136
|
+
// only punishes docs for being written correctly (task 1003869). It was listed above
|
|
137
|
+
// as "a real public address" until then, which RFC 3849 says it is not.
|
|
138
|
+
for (const s of ['2001:db8::1', '2001:0db8:85a3:0000:0000:8a2e:0370:7334']) {
|
|
139
|
+
assert.equal(isPublicRoutableIPv6(s), false, `${s} is the RFC 3849 doc range and must not be flagged`);
|
|
140
|
+
}
|
|
133
141
|
assert.equal(firstPublicIP('sprite bytes :: more bytes'), null, 'binary-ish shorthand is not reported');
|
|
134
142
|
assert.equal(firstPublicIP('origin 2606:4700:4700::1111'), '2606:4700:4700::1111');
|
|
135
143
|
assert.ok(!redactText('host 2606:4700:4700::1111', []).includes('2606:4700'), 'a real public IPv6 is still redacted');
|
|
136
144
|
});
|
|
137
145
|
|
|
146
|
+
// ── candidate guards: things that LOOK like an address but are not one ───────────────
|
|
147
|
+
t('a leading-zero octet is packed numeric data, not an address (task 1003869)', () => {
|
|
148
|
+
// The committed GitHub logo path contains "…5.47 7.59.4.07.5…". The candidate regex
|
|
149
|
+
// reads 7.59.4.07 as a dotted quad, and it flagged ten phantom public-ip leaks across
|
|
150
|
+
// the design mocks and the landing page. No notation writes a host with a leading-zero
|
|
151
|
+
// octet ("07" is octal), so the guard costs nothing on a genuinely leaked address.
|
|
152
|
+
assert.equal(firstPublicIP('M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.5-.17'), null);
|
|
153
|
+
assert.equal(firstPublicIP('host 7.59.4.07'), null);
|
|
154
|
+
assert.equal(firstPublicIP('host 7.59.4.7'), '7.59.4.7', 'the same quad without the zero IS an address');
|
|
155
|
+
});
|
|
156
|
+
t('RFC 2544 benchmarking space 198.18/15 is not instance topology', () => {
|
|
157
|
+
assert.equal(isPublicRoutableIPv4(198, 18, 0, 1), false);
|
|
158
|
+
assert.equal(isPublicRoutableIPv4(198, 19, 255, 254), false);
|
|
159
|
+
assert.equal(isPublicRoutableIPv4(198, 20, 0, 1), true, 'the neighbouring /15 is ordinary public space');
|
|
160
|
+
});
|
|
161
|
+
|
|
138
162
|
// ── the DETECTOR is stricter than redaction ───────────────────────────────────────────
|
|
139
163
|
t('findLeaks catches a raw literal substring a bounded rule would miss', () => {
|
|
140
164
|
// "azoltanb" has no word boundary around zoltan; a \bzoltan\b rule misses it, the
|
|
@@ -143,7 +167,7 @@ t('findLeaks catches a raw literal substring a bounded rule would miss', () => {
|
|
|
143
167
|
assert.ok(findLeaks('inline widgetco reference', literals).length > 0);
|
|
144
168
|
});
|
|
145
169
|
t('findLeaks is clean on fully-redacted output (fixed point)', () => {
|
|
146
|
-
const dirty = 'https://widgetco.com by zoltan at 8.8.8.8 and
|
|
170
|
+
const dirty = 'https://widgetco.com by zoltan at 8.8.8.8 and 2606:4700:4700::1111';
|
|
147
171
|
const clean = redactText(dirty, rules);
|
|
148
172
|
assert.deepEqual(findLeaks(clean, literals), [], `residual leaks: ${JSON.stringify(findLeaks(clean, literals))}`);
|
|
149
173
|
});
|
package/tests/module_catalog.mjs
CHANGED
|
@@ -121,7 +121,7 @@ test('buildCatalog: keeps the DECLARED manifest fields beside the display credit
|
|
|
121
121
|
|
|
122
122
|
test('buildCatalog: source and provenance are independent axes', () => {
|
|
123
123
|
const rows = catalog.buildCatalog({
|
|
124
|
-
loaded: [loadedIn(CORE, 'provisioning', { origin: '
|
|
124
|
+
loaded: [loadedIn(CORE, 'provisioning', { origin: 'Example World', author: 'Example World' })],
|
|
125
125
|
coreRoot: CORE, instanceRoot: INST,
|
|
126
126
|
ownProjectName: 'Mercury',
|
|
127
127
|
});
|
|
@@ -156,7 +156,7 @@ test('buildCatalog: survives a manifest with nothing but the required keys', ()
|
|
|
156
156
|
const sample = () => catalog.buildCatalog({
|
|
157
157
|
loaded: [
|
|
158
158
|
loadedIn(CORE, 'economy', { description: 'drachmae and credits' }),
|
|
159
|
-
loadedIn(CORE, 'provisioning', { author: '
|
|
159
|
+
loadedIn(CORE, 'provisioning', { author: 'Example World', origin: 'Example World', license: 'AGPL-3.0' }),
|
|
160
160
|
loadedIn(INST, 'game', { description: 'the world' }),
|
|
161
161
|
],
|
|
162
162
|
enabled: { economy: true, game: true },
|
|
@@ -174,7 +174,7 @@ test('searchCatalog: matches key, description, author, origin and licence; empty
|
|
|
174
174
|
assert.equal(catalog.searchCatalog(rows, '').length, 3);
|
|
175
175
|
assert.equal(catalog.searchCatalog(rows, ' ').length, 3);
|
|
176
176
|
assert.deepEqual(catalog.searchCatalog(rows, 'DRACHMAE').map((r) => r.key), ['economy'], 'case-insensitive, over description');
|
|
177
|
-
assert.deepEqual(catalog.searchCatalog(rows, '
|
|
177
|
+
assert.deepEqual(catalog.searchCatalog(rows, 'example world').map((r) => r.key), ['provisioning'], 'over the credit');
|
|
178
178
|
assert.deepEqual(catalog.searchCatalog(rows, 'agpl').map((r) => r.key), ['provisioning'], 'over the licence');
|
|
179
179
|
assert.deepEqual(catalog.searchCatalog(rows, 'nothing-matches-this'), []);
|
|
180
180
|
});
|
|
@@ -202,7 +202,7 @@ test('formatCatalog: renders every row with its credit, and marks the inferred o
|
|
|
202
202
|
assert.match(out, /2 in the core catalog \(1 enabled\) · 1 built here · 3 total/);
|
|
203
203
|
assert.match(out, /\[x\]\s+economy/, 'an enabled module is marked [x]');
|
|
204
204
|
assert.match(out, /\[ \]\s+provisioning/, 'a disabled module is marked [ ]');
|
|
205
|
-
assert.match(out, /
|
|
205
|
+
assert.match(out, /Example World/, 'a declared credit is shown');
|
|
206
206
|
assert.match(out, /credit not declared in module\.json/, 'the inferred-credit footnote appears when one is shown');
|
|
207
207
|
});
|
|
208
208
|
|
|
@@ -227,7 +227,7 @@ test('formatCatalog: a search with no hits says so instead of printing an empty
|
|
|
227
227
|
|
|
228
228
|
test('cmdList: --json emits the rows + summary, and honours --catalog/--search', () => {
|
|
229
229
|
const lines = [];
|
|
230
|
-
const code = mod.cmdList(['--json', '--catalog', '--search', '
|
|
230
|
+
const code = mod.cmdList(['--json', '--catalog', '--search', 'example world'], {
|
|
231
231
|
log: (s) => lines.push(s),
|
|
232
232
|
gather: () => ({ rows: sample(), skipped: [], errors: [], core: '1.19.0' }),
|
|
233
233
|
});
|
|
@@ -59,14 +59,14 @@ await test('createModuleSubmission() INSERTs, jsonb-casts manifest/files/sign_of
|
|
|
59
59
|
const precheckPassedAt = new Date('2026-07-08T00:00:00.000Z');
|
|
60
60
|
nextRows = [{
|
|
61
61
|
id: 1, module_key: 'widget', submitted_by: 42, manifest, sign_off: signOff,
|
|
62
|
-
precheck_passed_at: precheckPassedAt, source_repo: '
|
|
62
|
+
precheck_passed_at: precheckPassedAt, source_repo: 'example-owner/example',
|
|
63
63
|
status: 'pending', reviewed_by: null, reviewed_at: null, review_notes: null,
|
|
64
64
|
submitted_at: '2026-07-08T00:00:01.000Z',
|
|
65
65
|
}];
|
|
66
66
|
|
|
67
67
|
const row = await moduleSubmissions.createModuleSubmission({
|
|
68
68
|
moduleKey: 'widget', submittedBy: 42, manifest, files, signOff, precheckPassedAt,
|
|
69
|
-
sourceRepo: '
|
|
69
|
+
sourceRepo: 'example-owner/example',
|
|
70
70
|
});
|
|
71
71
|
|
|
72
72
|
const { text, params } = lastCall();
|
|
@@ -83,7 +83,7 @@ await test('createModuleSubmission() INSERTs, jsonb-casts manifest/files/sign_of
|
|
|
83
83
|
assert.equal(params[3], JSON.stringify(files));
|
|
84
84
|
assert.equal(params[4], JSON.stringify(signOff));
|
|
85
85
|
assert.equal(params[5], precheckPassedAt);
|
|
86
|
-
assert.equal(params[6], '
|
|
86
|
+
assert.equal(params[6], 'example-owner/example');
|
|
87
87
|
|
|
88
88
|
assert.equal(row.id, 1);
|
|
89
89
|
assert.equal(row.status, 'pending');
|
package/tests/package_core.mjs
CHANGED
|
@@ -585,7 +585,7 @@ t('scanArtifactForLeaks: flags a prior-instance literal in any shipped file', ()
|
|
|
585
585
|
|
|
586
586
|
t('scanArtifactForLeaks: flags a public IP but not a private/localhost one', () => {
|
|
587
587
|
const r = scanArtifactForLeaks([
|
|
588
|
-
{ path: 'a.js', content: 'host =
|
|
588
|
+
{ path: 'a.js', content: 'host = 1.2.3.4\n' }, // public → flagged
|
|
589
589
|
{ path: 'b.js', content: 'host = 127.0.0.1; lan = 10.0.0.5\n' }, // localhost + private → not
|
|
590
590
|
]);
|
|
591
591
|
const byPath = Object.fromEntries(r.literalHits.map((h) => [h.path, h.label]));
|
package/tests/parallel-load.mjs
CHANGED
|
@@ -17,12 +17,12 @@
|
|
|
17
17
|
//
|
|
18
18
|
// SAFETY: never point the live mode at production. It mutates real claim/task
|
|
19
19
|
// state. Target staging (once #262 seeds it) or a local instance. The harness
|
|
20
|
-
// refuses a BASE_URL containing "
|
|
20
|
+
// refuses a BASE_URL containing "example.com" without --i-know (the prod
|
|
21
21
|
// host), so a fat-fingered run can't churn the live game's GDS.
|
|
22
22
|
//
|
|
23
23
|
// Usage:
|
|
24
24
|
// node tests/parallel-load.mjs # self-test (CI-safe)
|
|
25
|
-
// BASE_URL=https://staging.
|
|
25
|
+
// BASE_URL=https://staging.example.com TOKEN=… \
|
|
26
26
|
// node tests/parallel-load.mjs --workers 5 --duration 1800 # live (staging)
|
|
27
27
|
//
|
|
28
28
|
// Exit 0 = no anomalies (no 5xx, no unexpected outcome class). Exit 1 = a
|
|
@@ -324,9 +324,9 @@ function report(stats, wallMs) {
|
|
|
324
324
|
|
|
325
325
|
(async () => {
|
|
326
326
|
if (!SELF_TEST && !KNOW_PROD) {
|
|
327
|
-
// Parse the actual HOSTNAME — a substring match on "
|
|
327
|
+
// Parse the actual HOSTNAME — a substring match on "example.com" /
|
|
328
328
|
// "staging." can be fooled by query strings or paths (e.g.
|
|
329
|
-
// https://
|
|
329
|
+
// https://example.com/x?ref=staging.foo). Refuse the prod apex/www
|
|
330
330
|
// host; allow the staging subdomain and anything else (localhost, etc.).
|
|
331
331
|
let host = '';
|
|
332
332
|
try {
|
|
@@ -335,10 +335,10 @@ function report(stats, wallMs) {
|
|
|
335
335
|
console.error(`REFUSING: BASE_URL "${BASE_URL}" is not a valid URL.`);
|
|
336
336
|
process.exit(2);
|
|
337
337
|
}
|
|
338
|
-
const isProd = host === '
|
|
338
|
+
const isProd = host === 'example.com' || host === 'www.example.com';
|
|
339
339
|
if (isProd) {
|
|
340
340
|
console.error(`REFUSING: BASE_URL host "${host}" is PRODUCTION. This harness mutates claim/task state.`);
|
|
341
|
-
console.error('Point it at staging (staging.
|
|
341
|
+
console.error('Point it at staging (staging.example.com, once #262 seeds it) or a local instance.');
|
|
342
342
|
console.error('If you really mean it, pass --i-know (you almost certainly do not).');
|
|
343
343
|
process.exit(2);
|
|
344
344
|
}
|
package/tests/projects_hub.mjs
CHANGED
|
@@ -1821,7 +1821,7 @@ test("the field is the void: no drawn grid, no stars, no twinkle; the labels, th
|
|
|
1821
1821
|
|
|
1822
1822
|
test('the kit proves the field path: the fixture carries a curated template and the states file reads it back', () => {
|
|
1823
1823
|
const fixture = JSON.parse(fs.readFileSync(path.join(ROOT, 'modules', 'ui-design', 'kit', 'fixtures', 'featured.json'), 'utf8'));
|
|
1824
|
-
const otb = fixture.projects.find((p) => p.origin === 'https://
|
|
1824
|
+
const otb = fixture.projects.find((p) => p.origin === 'https://example.com');
|
|
1825
1825
|
assert.equal(otb.template, 'bead-wash');
|
|
1826
1826
|
const demo = fixture.projects.find((p) => p.origin === 'https://demo.cloudbongos.com');
|
|
1827
1827
|
assert.equal(demo.template, 'pair-gold-wash', 'a material template rides the wire (task 1003337)');
|
|
@@ -1831,7 +1831,7 @@ test('the kit proves the field path: the fixture carries a curated template and
|
|
|
1831
1831
|
const states = JSON.parse(fs.readFileSync(path.join(LANDING, 'projects.states.json'), 'utf8'));
|
|
1832
1832
|
const attrs = states.states['in-map'].expect.attr.map((a) => a.join(' '));
|
|
1833
1833
|
assert.ok(attrs.includes('#selfOrb data-template pair-chrome'));
|
|
1834
|
-
assert.ok(attrs.some((a) => a.includes('
|
|
1834
|
+
assert.ok(attrs.some((a) => a.includes('example.com') && a.endsWith('data-template bead-wash')), 'the curated template reaches the orb');
|
|
1835
1835
|
assert.ok(attrs.some((a) => a.includes('demo.cloudbongos.com') && a.endsWith('data-material obsidian')), 'the material reaches the orb as its own attribute');
|
|
1836
1836
|
assert.ok(attrs.some((a) => a.includes('demo.cloudbongos.com') && a.endsWith('data-template pair-obsidian-tint')), 'the owner\'s choice outranks the curated template on the sky (task 1003347)');
|
|
1837
1837
|
});
|