@bongos/core 1.19.1072 → 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 +6 -2
- 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 +2 -1
- 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 +4 -0
- package/docs/modules-contract.md +13 -1
- package/docs/onboarding/diagrams/04-architecture.mmd +11 -22
- package/docs/onboarding/diagrams/README.md +3 -3
- package/docs/onboarding/diagrams/assertions.json +2 -22
- package/docs/onboarding/primer.md +9 -28
- package/docs/page-inventory.json +4 -1
- package/docs/page-readings.json +1032 -1077
- package/docs/recipes/render-pilot.md +29 -10
- package/migrations/core_257_module_store_registry.sql +87 -0
- package/migrations/core_258_drop_dev_box_tables.sql +55 -0
- package/modules/agents/module.json +1 -0
- package/modules/autonomy/module.json +1 -0
- package/modules/builder-settings/module.json +1 -0
- package/modules/builder-settings/render-prefs.js +4 -5
- package/modules/copy-desk/docx.js +78 -5
- package/modules/copy-desk/module.json +1 -0
- package/modules/copy-desk/routes/copy-desk.js +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 +84 -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
|
@@ -1,126 +0,0 @@
|
|
|
1
|
-
// tests/box_sync_scope_report.mjs
|
|
2
|
-
//
|
|
3
|
-
// Task 1002731 — the scope read-out that makes a too-narrow box VISIBLE.
|
|
4
|
-
//
|
|
5
|
-
// The failure being guarded is a silent one: a box comes up holding only the
|
|
6
|
-
// base slice (no modules/ at all) and nothing tells the builder. Live, that was
|
|
7
|
-
// 247 of 248 source grants on the cloudbongos fleet — including an Archon
|
|
8
|
-
// holding 7 active claims — and the only way to see it was reading box_events
|
|
9
|
-
// on the control plane.
|
|
10
|
-
//
|
|
11
|
-
// formatScopeReport is PURE (takes the GET /box/scope body, returns lines), so
|
|
12
|
-
// this needs no box, no network and no DB.
|
|
13
|
-
//
|
|
14
|
-
// Run: node tests/box_sync_scope_report.mjs
|
|
15
|
-
|
|
16
|
-
import { strict as assert } from 'node:assert';
|
|
17
|
-
import { createRequire } from 'node:module';
|
|
18
|
-
|
|
19
|
-
const require = createRequire(import.meta.url);
|
|
20
|
-
const { formatScopeReport } = require('../scripts/gds/box-sync.js');
|
|
21
|
-
// task 1003087 — the widen read-out lives in the same module.
|
|
22
|
-
const sync = require('../scripts/gds/box-sync.js');
|
|
23
|
-
|
|
24
|
-
let passed = 0;
|
|
25
|
-
let failed = 0;
|
|
26
|
-
function t(name, fn) {
|
|
27
|
-
try { fn(); passed++; console.log(` PASS ${name}`); }
|
|
28
|
-
catch (e) { failed++; console.log(` FAIL ${name}\n ${e.message}`); }
|
|
29
|
-
}
|
|
30
|
-
const joined = (scope) => formatScopeReport(scope).join('\n');
|
|
31
|
-
|
|
32
|
-
t('base-only is stated in plain words, with a why and a fix', () => {
|
|
33
|
-
const out = joined({ scope: 'base', mode: 'sparse', modules: [], sparse_paths: ['docs', 'scripts'], task_scope: true });
|
|
34
|
-
assert.match(out, /BASE ONLY/, 'the degraded state is named, not buried');
|
|
35
|
-
assert.match(out, /no modules\/ code at all/, 'says what is actually missing');
|
|
36
|
-
assert.match(out, /Why:/, 'explains the cause');
|
|
37
|
-
assert.match(out, /builder-claim/, 'gives the builder the actual next step');
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
t('task scope names the modules the box holds', () => {
|
|
41
|
-
const out = joined({ scope: 'task', mode: 'sparse', modules: ['dev-box', 'hall-ui'], sparse_paths: ['a', 'b', 'c'], task_scope: true });
|
|
42
|
-
assert.match(out, /scope: task/);
|
|
43
|
-
assert.match(out, /dev-box, hall-ui/, 'names them rather than just counting');
|
|
44
|
-
assert.match(out, /3 sparse entries/);
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
t('full + EMPTY modules surfaces the 1002729 fallback rather than looking healthy', () => {
|
|
48
|
-
// The exact live shape: claims held, but their goals declare no scope_modules.
|
|
49
|
-
// The box works, so nothing complains — but task-scoping is not doing anything,
|
|
50
|
-
// and that is worth telling someone.
|
|
51
|
-
const out = joined({ scope: 'full', mode: 'full', modules: [], sparse_paths: null, task_scope: true });
|
|
52
|
-
assert.match(out, /scope: full/);
|
|
53
|
-
assert.match(out, /declares no module scope/, 'names why it fell back');
|
|
54
|
-
assert.match(out, /scope_modules/, 'points at the thing to fix');
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
t('full with real modules does NOT nag about the fallback', () => {
|
|
58
|
-
const out = joined({ scope: 'full', mode: 'full', modules: ['kernel'], sparse_paths: null, task_scope: false });
|
|
59
|
-
assert.match(out, /scope: full/);
|
|
60
|
-
assert.ok(!/declares no module scope/.test(out), 'no false alarm when nothing is wrong');
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
t('starter is distinguished from a claim-scoped box', () => {
|
|
64
|
-
const out = joined({ scope: 'starter', mode: 'sparse', modules: null, sparse_paths: ['x'], task_scope: false });
|
|
65
|
-
assert.match(out, /starter/);
|
|
66
|
-
assert.match(out, /not scoped to your claim/);
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
t('a missing or malformed body degrades to one honest line, never a throw', () => {
|
|
70
|
-
// This runs on an already-degraded path; it must not be the thing that fails.
|
|
71
|
-
for (const bad of [null, undefined, 'nope', 42, []]) {
|
|
72
|
-
const lines = formatScopeReport(bad);
|
|
73
|
-
assert.ok(Array.isArray(lines) && lines.length >= 1, `handled ${JSON.stringify(bad)}`);
|
|
74
|
-
}
|
|
75
|
-
assert.match(joined(null), /could not read/);
|
|
76
|
-
});
|
|
77
|
-
|
|
78
|
-
t('an unknown scope value is reported verbatim rather than swallowed', () => {
|
|
79
|
-
assert.match(joined({ scope: 'something-new', sparse_paths: [] }), /something-new/);
|
|
80
|
-
});
|
|
81
|
-
|
|
82
|
-
// ---------------------------------------------------------------------------
|
|
83
|
-
// task 1003087 — the --widen read-out
|
|
84
|
-
//
|
|
85
|
-
// The whole feature exists because a widen that silently stops applying is
|
|
86
|
-
// indistinguishable from one that works. So the report must say when the server
|
|
87
|
-
// stored a path it will NOT apply (outside the caller's rank scope) — otherwise
|
|
88
|
-
// this fix reintroduces its own bug one layer up.
|
|
89
|
-
// ---------------------------------------------------------------------------
|
|
90
|
-
|
|
91
|
-
const widenLines = (body) => sync.formatWidenReport(body).join('\n');
|
|
92
|
-
|
|
93
|
-
t('an empty widen set says so plainly rather than printing nothing', () => {
|
|
94
|
-
assert.match(widenLines({ widen_paths: [], ignored: [] }), /nothing kept/i);
|
|
95
|
-
});
|
|
96
|
-
|
|
97
|
-
t('a kept set names the paths and the count', () => {
|
|
98
|
-
const out = widenLines({ widen_paths: ['docs', 'modules/game'], ignored: [] });
|
|
99
|
-
assert.match(out, /2 path/);
|
|
100
|
-
assert.match(out, /docs/);
|
|
101
|
-
assert.match(out, /modules\/game/);
|
|
102
|
-
});
|
|
103
|
-
|
|
104
|
-
t('an IGNORED path is called out loudly, not folded into the kept list', () => {
|
|
105
|
-
const out = widenLines({ widen_paths: ['docs', 'modules/game'], ignored: ['modules/game'] });
|
|
106
|
-
assert.match(out, /IGNORED/);
|
|
107
|
-
assert.match(out, /rank scope/i,
|
|
108
|
-
'the reason must be named — "stored but not applied" with no why is the silent failure again');
|
|
109
|
-
});
|
|
110
|
-
|
|
111
|
-
t('a garbage / missing body degrades to one honest line rather than throwing', () => {
|
|
112
|
-
for (const bad of [null, undefined, 'nope', 42, []]) {
|
|
113
|
-
assert.match(widenLines(bad), /could not read/i);
|
|
114
|
-
}
|
|
115
|
-
});
|
|
116
|
-
|
|
117
|
-
t('valueAfter reads the argument after a flag, and refuses the next flag as a value', () => {
|
|
118
|
-
assert.equal(sync.valueAfter(['--widen', 'docs'], '--widen'), 'docs');
|
|
119
|
-
assert.equal(sync.valueAfter(['--widen', '--dry-run'], '--widen'), null,
|
|
120
|
-
'consuming the next FLAG as a path would silently widen to something nobody typed');
|
|
121
|
-
assert.equal(sync.valueAfter(['--widen'], '--widen'), null);
|
|
122
|
-
assert.equal(sync.valueAfter([], '--widen'), null);
|
|
123
|
-
});
|
|
124
|
-
|
|
125
|
-
console.log(`\nbox_sync_scope_report.mjs: ${passed} passed, ${failed} failed`);
|
|
126
|
-
process.exit(failed === 0 ? 0 : 1);
|
package/tests/box_task_scope.mjs
DELETED
|
@@ -1,169 +0,0 @@
|
|
|
1
|
-
// tests/box_task_scope.mjs
|
|
2
|
-
//
|
|
3
|
-
// R95 (goal 1000051, ADR 0148) — the end-to-end PURE decision for task-scoped
|
|
4
|
-
// source access, composing the two halves shipped in R92 + R93:
|
|
5
|
-
// lifecycle.moduleScopeForActiveClaims (claims → tasks → goals.scope_modules)
|
|
6
|
-
// → box-access.decideSourceAccess({ scopeKeys, moduleScopeMap })
|
|
7
|
-
// Proves R95's done-when as named behavior: 0 active claims → BASE-only; N active
|
|
8
|
-
// claims → BASE ∪ the union of ALL N goals' module scopes.
|
|
9
|
-
//
|
|
10
|
-
// DB-free: moduleScopeForActiveClaims honours the #538 deps.pool seam
|
|
11
|
-
// (NODE_ENV==='test'), so a SQL-aware fake pool stands in for Postgres — we hand it
|
|
12
|
-
// the rows the DISTINCT-union query would return (the union itself is Postgres's
|
|
13
|
-
// job, shape-proven in module_scope_active_claims.mjs) and assert the WHOLE chain
|
|
14
|
-
// down to the sparse-checkout paths. The real module-scope-map (a pure core module)
|
|
15
|
-
// is the map the route passes in.
|
|
16
|
-
//
|
|
17
|
-
// Run: node tests/box_task_scope.mjs
|
|
18
|
-
|
|
19
|
-
import { strict as assert } from 'node:assert';
|
|
20
|
-
import { createRequire } from 'node:module';
|
|
21
|
-
import { makeRunner, makeSqlAwareClient } from './helpers.mjs';
|
|
22
|
-
|
|
23
|
-
process.env.NODE_ENV = 'test';
|
|
24
|
-
|
|
25
|
-
const require = createRequire(import.meta.url);
|
|
26
|
-
const db = require('../modules/lifecycle/db.js');
|
|
27
|
-
const access = require('../modules/dev-box/box-access.js');
|
|
28
|
-
const scopeMap = require('../src/bongos/module-scope-map.js');
|
|
29
|
-
|
|
30
|
-
const { test, summary } = makeRunner();
|
|
31
|
-
const BASE = access.BASE_SPARSE_PATHS;
|
|
32
|
-
|
|
33
|
-
// Fake pool: the resolver issues ONE query (claims→tasks→goals, unnest
|
|
34
|
-
// scope_modules); return the module-key rows a builder's active claims would
|
|
35
|
-
// produce (already DISTINCT-unioned + ORDER BY m, as Postgres would).
|
|
36
|
-
function poolReturning(moduleKeyRows) {
|
|
37
|
-
const c = makeSqlAwareClient((s) =>
|
|
38
|
-
(/FROM claims c/.test(s) && /unnest\(g\.scope_modules\)/.test(s)) ? { rows: moduleKeyRows } : null);
|
|
39
|
-
return { query: (sql, params) => c._client.query(sql, params), connect: c.connect, queries: c._client.queries };
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
// The full chain a box-fetch runs: resolve the active-claim scope, then decide the
|
|
43
|
-
// source spec from it (rank still gates ALLOW/DENY).
|
|
44
|
-
async function specForClaims({ builderId = 3, rank = 'metic', status = 'active', moduleKeyRows }) {
|
|
45
|
-
const pool = poolReturning(moduleKeyRows);
|
|
46
|
-
const scopeKeys = await db.moduleScopeForActiveClaims(builderId, { pool });
|
|
47
|
-
return { scopeKeys, decision: access.decideSourceAccess({ rank, status, scopeKeys, moduleScopeMap: scopeMap }) };
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
// =========================================================================
|
|
51
|
-
// 0 active claims → BASE-only (the pre-existing Bongos instance, nothing extra)
|
|
52
|
-
// =========================================================================
|
|
53
|
-
await test('0 active claims → base-only spec', async () => {
|
|
54
|
-
const { scopeKeys, decision } = await specForClaims({ moduleKeyRows: [] });
|
|
55
|
-
assert.deepEqual(scopeKeys, [], 'no active claim → no scope keys');
|
|
56
|
-
assert.equal(decision.allowed, true);
|
|
57
|
-
assert.equal(decision.scope, 'base');
|
|
58
|
-
assert.deepEqual(decision.spec.sparsePaths, [...BASE], 'the box carries only BASE');
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
// =========================================================================
|
|
62
|
-
// N active claims across goals → BASE ∪ the UNION of all N goals' scopes
|
|
63
|
-
// =========================================================================
|
|
64
|
-
await test('N active claims → base ∪ the union of all their goals’ module scopes', async () => {
|
|
65
|
-
// Two claims: goal A scoped {dev-box, status-ui}, goal B scoped {hall-ui}.
|
|
66
|
-
// Postgres DISTINCT-unions + ORDER BY m to this key set:
|
|
67
|
-
const { scopeKeys, decision } = await specForClaims({
|
|
68
|
-
moduleKeyRows: [{ module_key: 'dev-box' }, { module_key: 'hall-ui' }, { module_key: 'status-ui' }],
|
|
69
|
-
});
|
|
70
|
-
assert.deepEqual(scopeKeys, ['dev-box', 'hall-ui', 'status-ui'], 'union across the active claims');
|
|
71
|
-
assert.equal(decision.scope, 'task');
|
|
72
|
-
// BASE ∪ each module's DIRECTORY globs (dev-box's box-CLI FILE globs cone-filtered out).
|
|
73
|
-
assert.deepEqual(
|
|
74
|
-
decision.spec.sparsePaths,
|
|
75
|
-
[...BASE, 'devbox-app', 'modules/dev-box', 'modules/hall-ui', 'modules/status-ui'],
|
|
76
|
-
'every claimed goal’s module dirs are materialized, deduped + sorted',
|
|
77
|
-
);
|
|
78
|
-
assert.ok(!decision.spec.sparsePaths.includes('scripts/gds/box.js'), 'file globs never enter the sparse set');
|
|
79
|
-
});
|
|
80
|
-
|
|
81
|
-
// =========================================================================
|
|
82
|
-
// One claim, one goal → BASE ∪ that goal's scope (the single-claim base case)
|
|
83
|
-
// =========================================================================
|
|
84
|
-
await test('a single claim → base ∪ that one goal’s module scope', async () => {
|
|
85
|
-
const { decision } = await specForClaims({ moduleKeyRows: [{ module_key: 'status-ui' }] });
|
|
86
|
-
assert.equal(decision.scope, 'task');
|
|
87
|
-
assert.deepEqual(decision.spec.sparsePaths, [...BASE, 'modules/status-ui']);
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
// =========================================================================
|
|
91
|
-
// Rank/status STILL gate ALLOW/DENY ahead of any task scope (ADR 0148 C4 posture)
|
|
92
|
-
// =========================================================================
|
|
93
|
-
await test('an inactive builder is denied even with claims in scope', async () => {
|
|
94
|
-
const inactive = await specForClaims({ status: 'inactive', moduleKeyRows: [{ module_key: 'dev-box' }] });
|
|
95
|
-
assert.equal(inactive.decision.allowed, false);
|
|
96
|
-
assert.equal(inactive.decision.reason, 'BUILDER_INACTIVE');
|
|
97
|
-
});
|
|
98
|
-
|
|
99
|
-
// =========================================================================
|
|
100
|
-
// Task 1002729 — an EMPTY scope is two different situations, not one
|
|
101
|
-
// =========================================================================
|
|
102
|
-
// The live defect: moduleScopeForActiveClaims returns [] both for "holds no
|
|
103
|
-
// claim" and for "holds claims whose goals declare no scope_modules". Reading
|
|
104
|
-
// both as BASE handed working builders a box with no modules/ at all — 247 of
|
|
105
|
-
// 248 grants on the live fleet, including an Archon with 7 active claims.
|
|
106
|
-
|
|
107
|
-
// A pool that answers BOTH queries the resolver pair now issues: the
|
|
108
|
-
// claims→goals→scope_modules unnest, and the plain active-claim count.
|
|
109
|
-
function poolReturningWithClaims(moduleKeyRows, claimCount) {
|
|
110
|
-
const c = makeSqlAwareClient((s) => {
|
|
111
|
-
if (/FROM claims c/.test(s) && /unnest\(g\.scope_modules\)/.test(s)) return { rows: moduleKeyRows };
|
|
112
|
-
if (/count\(\*\)/.test(s) && /FROM claims/.test(s)) return { rows: [{ n: claimCount }] };
|
|
113
|
-
return null;
|
|
114
|
-
});
|
|
115
|
-
return { query: (sql, params) => c._client.query(sql, params), connect: c.connect, queries: c._client.queries };
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
async function specForClaimsWithCount({ builderId = 3, rank = 'metic', status = 'active', moduleKeyRows, claimCount }) {
|
|
119
|
-
const pool = poolReturningWithClaims(moduleKeyRows, claimCount);
|
|
120
|
-
const scopeKeys = await db.moduleScopeForActiveClaims(builderId, { pool });
|
|
121
|
-
const hasActiveClaims = (await db.activeClaimCountForBuilder(builderId, { pool })) > 0;
|
|
122
|
-
return { scopeKeys, hasActiveClaims, decision: access.decideSourceAccess({ rank, status, scopeKeys, hasActiveClaims, moduleScopeMap: scopeMap }) };
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
await test('claims held but every goal wall empty → RANK scope, never a stripped box', async () => {
|
|
126
|
-
// Exactly Lars's live shape: 7 active claims, all on goals with scope_modules={}.
|
|
127
|
-
const { scopeKeys, hasActiveClaims, decision } = await specForClaimsWithCount({
|
|
128
|
-
rank: 'archon', moduleKeyRows: [], claimCount: 7,
|
|
129
|
-
});
|
|
130
|
-
assert.deepEqual(scopeKeys, [], 'the union query still yields nothing — the goals declare no wall');
|
|
131
|
-
assert.equal(hasActiveClaims, true, 'but the builder IS working');
|
|
132
|
-
assert.equal(decision.scope, 'full', 'an Archon mid-claim gets the full surface, not base');
|
|
133
|
-
assert.equal(decision.spec.mode, 'full');
|
|
134
|
-
assert.notEqual(decision.scope, 'base', 'the regression this test exists to prevent');
|
|
135
|
-
});
|
|
136
|
-
|
|
137
|
-
await test('no claims at all → BASE-only is preserved (least privilege holds)', async () => {
|
|
138
|
-
const { hasActiveClaims, decision } = await specForClaimsWithCount({
|
|
139
|
-
rank: 'archon', moduleKeyRows: [], claimCount: 0,
|
|
140
|
-
});
|
|
141
|
-
assert.equal(hasActiveClaims, false);
|
|
142
|
-
assert.equal(decision.scope, 'base', 'an idle box is still not handed the repo');
|
|
143
|
-
assert.deepEqual(decision.spec.sparsePaths, [...BASE]);
|
|
144
|
-
});
|
|
145
|
-
|
|
146
|
-
await test('a declared goal wall still wins over the rank fallback', async () => {
|
|
147
|
-
// Claims AND a wall → task scope, exactly as before; the fix must not widen this.
|
|
148
|
-
const { decision } = await specForClaimsWithCount({
|
|
149
|
-
rank: 'archon', moduleKeyRows: [{ module_key: 'status-ui' }], claimCount: 2,
|
|
150
|
-
});
|
|
151
|
-
assert.equal(decision.scope, 'task', 'a real wall is still honoured, not overridden by rank');
|
|
152
|
-
assert.deepEqual(decision.spec.sparsePaths, [...BASE, 'modules/status-ui']);
|
|
153
|
-
});
|
|
154
|
-
|
|
155
|
-
await test('a Thetes mid-claim with no wall gets STARTER, not full — rank still bounds it', async () => {
|
|
156
|
-
const { decision } = await specForClaimsWithCount({
|
|
157
|
-
rank: 'thetes', moduleKeyRows: [], claimCount: 1,
|
|
158
|
-
});
|
|
159
|
-
assert.equal(decision.scope, 'starter', 'the fallback is rank-bounded; it is not a blanket widen');
|
|
160
|
-
});
|
|
161
|
-
|
|
162
|
-
await test('activeClaimCountForBuilder rejects a non-numeric builder id', async () => {
|
|
163
|
-
await assert.rejects(
|
|
164
|
-
() => db.activeClaimCountForBuilder('not-a-number', { pool: poolReturningWithClaims([], 0) }),
|
|
165
|
-
/must be numeric/,
|
|
166
|
-
);
|
|
167
|
-
});
|
|
168
|
-
|
|
169
|
-
summary();
|
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
// tests/box_task_scope_pause.mjs
|
|
2
|
-
//
|
|
3
|
-
// Regression guard for task 1003285 / ADR 0193 — the task-scoping PAUSE.
|
|
4
|
-
// The owner decision: a box serves its RANK scope (full repo for Metic+),
|
|
5
|
-
// not the claim-derived slice, unless BOX_TASK_SCOPE=on re-enables ADR 0148.
|
|
6
|
-
//
|
|
7
|
-
// Two layers, per the goal-1000077 lesson (a guard must assert the wiring,
|
|
8
|
-
// not just the helper): (1) taskScopingPaused()'s env semantics — paused by
|
|
9
|
-
// DEFAULT, only an explicit "on" re-enables; (2) the route source keeps BOTH
|
|
10
|
-
// lifecycle scope-resolution sites (source-access + /box/scope) behind the
|
|
11
|
-
// helper, and /box/scope keeps reporting task_scoping_paused — so deleting a
|
|
12
|
-
// gate or the report fails here instead of silently reviving the slice.
|
|
13
|
-
//
|
|
14
|
-
// Run: node --test tests/box_task_scope_pause.mjs
|
|
15
|
-
|
|
16
|
-
import { strict as assert } from 'node:assert';
|
|
17
|
-
import { test } from 'node:test';
|
|
18
|
-
import { createRequire } from 'node:module';
|
|
19
|
-
import fs from 'node:fs';
|
|
20
|
-
import path from 'node:path';
|
|
21
|
-
import { fileURLToPath } from 'node:url';
|
|
22
|
-
|
|
23
|
-
const require = createRequire(import.meta.url);
|
|
24
|
-
const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
|
25
|
-
const { taskScopingPaused } = require('../modules/dev-box/routes/box.js');
|
|
26
|
-
|
|
27
|
-
const withEnv = (value, fn) => {
|
|
28
|
-
const prev = process.env.BOX_TASK_SCOPE;
|
|
29
|
-
if (value === undefined) delete process.env.BOX_TASK_SCOPE;
|
|
30
|
-
else process.env.BOX_TASK_SCOPE = value;
|
|
31
|
-
try { return fn(); }
|
|
32
|
-
finally {
|
|
33
|
-
if (prev === undefined) delete process.env.BOX_TASK_SCOPE;
|
|
34
|
-
else process.env.BOX_TASK_SCOPE = prev;
|
|
35
|
-
}
|
|
36
|
-
};
|
|
37
|
-
|
|
38
|
-
test('paused by DEFAULT — an instance that sets nothing serves the rank scope', () => {
|
|
39
|
-
withEnv(undefined, () => assert.equal(taskScopingPaused(), true));
|
|
40
|
-
withEnv('', () => assert.equal(taskScopingPaused(), true));
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
test('only an explicit "on" re-enables the ADR 0148 slice (trim + case-insensitive)', () => {
|
|
44
|
-
withEnv('on', () => assert.equal(taskScopingPaused(), false));
|
|
45
|
-
withEnv(' ON ', () => assert.equal(taskScopingPaused(), false));
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
test('anything that is not "on" stays paused — no accidental re-enable', () => {
|
|
49
|
-
for (const v of ['off', 'true', '1', 'yes', 'paused', 'On!']) {
|
|
50
|
-
withEnv(v, () => assert.equal(taskScopingPaused(), true, `value ${JSON.stringify(v)} must stay paused`));
|
|
51
|
-
}
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
test('both lifecycle scope-resolution sites stay behind the pause gate', () => {
|
|
55
|
-
const src = fs.readFileSync(path.join(ROOT, 'modules', 'dev-box', 'routes', 'box.js'), 'utf8');
|
|
56
|
-
const gated = src.match(/!taskScopingPaused\(\) && lifecycle && typeof lifecycle\.moduleScopeForActiveClaims === 'function'/g) || [];
|
|
57
|
-
const resolutionSites = src.match(/typeof lifecycle\.moduleScopeForActiveClaims === 'function'/g) || [];
|
|
58
|
-
assert.equal(resolutionSites.length, 2, 'expected exactly the two known resolution sites (source-access + /box/scope); a new one must take the gate too');
|
|
59
|
-
assert.equal(gated.length, 2, 'every moduleScopeForActiveClaims resolution must be gated by !taskScopingPaused()');
|
|
60
|
-
});
|
|
61
|
-
|
|
62
|
-
test('/box/scope keeps telling the truth about the pause', () => {
|
|
63
|
-
const src = fs.readFileSync(path.join(ROOT, 'modules', 'dev-box', 'routes', 'box.js'), 'utf8');
|
|
64
|
-
assert.ok(src.includes('task_scoping_paused: taskScopingPaused()'),
|
|
65
|
-
'/box/scope must report task_scoping_paused so the hall/app can explain a false task_scope');
|
|
66
|
-
});
|
|
@@ -1,96 +0,0 @@
|
|
|
1
|
-
// tests/box_terminal_ssrf.mjs
|
|
2
|
-
//
|
|
3
|
-
// SECURITY regression guard for #871 — the box web-terminal URL allow-list.
|
|
4
|
-
// POST /box/terminal stores a builder-supplied URL and GET /box/terminal makes
|
|
5
|
-
// the WEB TIER fetch it. The old guard (/^https:\/\/[^\s]+$/) accepted ANY https
|
|
6
|
-
// host, so a builder could make the prod server probe internal/metadata
|
|
7
|
-
// endpoints (SSRF). isSafeTerminalUrl(url, login) must allow ONLY the two real
|
|
8
|
-
// tunnel shapes — `term-<login>.<apex>` (bound to the caller's login)
|
|
9
|
-
// and `*.trycloudflare.com` — and reject everything else.
|
|
10
|
-
//
|
|
11
|
-
// Pure function, no server/DB/network.
|
|
12
|
-
//
|
|
13
|
-
// Run: node tests/box_terminal_ssrf.mjs
|
|
14
|
-
|
|
15
|
-
import { strict as assert } from 'node:assert';
|
|
16
|
-
import { createRequire } from 'node:module';
|
|
17
|
-
|
|
18
|
-
const require = createRequire(import.meta.url);
|
|
19
|
-
const { isSafeTerminalUrl } = require('../modules/dev-box/routes/box.js');
|
|
20
|
-
|
|
21
|
-
let pass = 0, fail = 0;
|
|
22
|
-
const test = (name, fn) => {
|
|
23
|
-
try { fn(); pass++; console.log(`PASS ${name}`); }
|
|
24
|
-
catch (e) { fail++; console.log(`FAIL ${name}\n ${e.message}`); }
|
|
25
|
-
};
|
|
26
|
-
|
|
27
|
-
const LOGIN = 'example-owner';
|
|
28
|
-
// The dev-box apex is INJECTED rather than taken from the live instance branding
|
|
29
|
-
// (task 1002470). Two reasons: the neutral core has no example.com apex, so the
|
|
30
|
-
// three allow-cases could not pass; and every reject-case below was passing for the
|
|
31
|
-
// WRONG reason — an apex mismatch, not the rule it names. With a pinned apex,
|
|
32
|
-
// 'rejects another builder's term- host' really does prove the login binding.
|
|
33
|
-
const APEX = 'apex.example';
|
|
34
|
-
|
|
35
|
-
// ---- allowed: the two legitimate tunnel shapes -------------------------------
|
|
36
|
-
test('allows the named-tunnel apex host bound to the caller login', () => {
|
|
37
|
-
assert.equal(isSafeTerminalUrl(`https://term-example-owner.${APEX}`, LOGIN, APEX), true);
|
|
38
|
-
});
|
|
39
|
-
test(`allows the named-tunnel dev-zone host (term-<login>.dev.${APEX})`, () => {
|
|
40
|
-
assert.equal(isSafeTerminalUrl(`https://term-example-owner.dev.${APEX}/`, LOGIN, APEX), true);
|
|
41
|
-
});
|
|
42
|
-
test('allows a Cloudflare quick-tunnel host (*.trycloudflare.com)', () => {
|
|
43
|
-
assert.equal(isSafeTerminalUrl('https://random-words-1234.trycloudflare.com', LOGIN, APEX), true);
|
|
44
|
-
});
|
|
45
|
-
test('login match is case-insensitive (DNS is case-insensitive)', () => {
|
|
46
|
-
assert.equal(isSafeTerminalUrl(`https://term-LarsCode.${APEX}`, 'larscode', APEX), true);
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
// ---- rejected: SSRF targets --------------------------------------------------
|
|
50
|
-
for (const bad of [
|
|
51
|
-
'https://127.0.0.1/admin',
|
|
52
|
-
'https://127.0.0.1:9443/',
|
|
53
|
-
'https://10.0.0.5:8443/',
|
|
54
|
-
'https://192.168.1.10/',
|
|
55
|
-
'https://172.16.0.9/',
|
|
56
|
-
'https://169.254.169.254/metadata/v1/', // cloud metadata
|
|
57
|
-
'https://[::1]:443/', // ipv6 loopback literal
|
|
58
|
-
'https://localhost/',
|
|
59
|
-
'https://vault.internal:8200/',
|
|
60
|
-
'https://printer.local/',
|
|
61
|
-
]) {
|
|
62
|
-
test(`rejects SSRF target ${bad}`, () => {
|
|
63
|
-
assert.equal(isSafeTerminalUrl(bad, LOGIN, APEX), false);
|
|
64
|
-
});
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
// ---- rejected: wrong scheme / shape / cross-login ----------------------------
|
|
68
|
-
test('rejects http (must be https)', () => {
|
|
69
|
-
assert.equal(isSafeTerminalUrl(`http://term-example-owner.${APEX}`, LOGIN, APEX), false);
|
|
70
|
-
});
|
|
71
|
-
test('rejects embedded credentials (user:pass@host)', () => {
|
|
72
|
-
assert.equal(isSafeTerminalUrl(`https://user:pass@term-example-owner.${APEX}`, LOGIN, APEX), false);
|
|
73
|
-
});
|
|
74
|
-
test('rejects another builder\'s term- host (login binding)', () => {
|
|
75
|
-
assert.equal(isSafeTerminalUrl(`https://term-someoneelse.${APEX}`, LOGIN, APEX), false);
|
|
76
|
-
});
|
|
77
|
-
test('rejects a non-tunnel host on the project domain', () => {
|
|
78
|
-
assert.equal(isSafeTerminalUrl(`https://api.${APEX}`, LOGIN, APEX), false);
|
|
79
|
-
});
|
|
80
|
-
test('rejects an arbitrary external host', () => {
|
|
81
|
-
assert.equal(isSafeTerminalUrl('https://evil.example.com', LOGIN, APEX), false);
|
|
82
|
-
});
|
|
83
|
-
test('rejects a lookalike suffix (…trycloudflare.com.evil.com)', () => {
|
|
84
|
-
assert.equal(isSafeTerminalUrl('https://x.trycloudflare.com.evil.com', LOGIN, APEX), false);
|
|
85
|
-
});
|
|
86
|
-
test('rejects garbage / unparseable', () => {
|
|
87
|
-
assert.equal(isSafeTerminalUrl('not a url', LOGIN, APEX), false);
|
|
88
|
-
assert.equal(isSafeTerminalUrl('', LOGIN, APEX), false);
|
|
89
|
-
assert.equal(isSafeTerminalUrl(null, LOGIN, APEX), false);
|
|
90
|
-
});
|
|
91
|
-
test('rejects when login is empty (cannot bind the term- form)', () => {
|
|
92
|
-
assert.equal(isSafeTerminalUrl(`https://term-example-owner.${APEX}`, '', APEX), false);
|
|
93
|
-
});
|
|
94
|
-
|
|
95
|
-
console.log(`\n${pass} passed, ${fail} failed`);
|
|
96
|
-
process.exit(fail === 0 ? 0 : 1);
|