@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
|
@@ -326,6 +326,57 @@ try {
|
|
|
326
326
|
assert.match(html, /no longer match the page/);
|
|
327
327
|
assert.match(html, /hero: “<b>new<\/b> line” \(added\)/);
|
|
328
328
|
assert.equal(b.el('tw-notice').querySelectorAll('b').length, 0);
|
|
329
|
+
// The panel names the same lines, escaped too.
|
|
330
|
+
assert.equal(b.el('tw-refused').hidden, false);
|
|
331
|
+
assert.match(b.el('tw-refused-list').innerHTML, /hero: “<b>new<\/b> line” \(added\)/);
|
|
332
|
+
assert.equal(b.el('tw-refused-list').querySelectorAll('b').length, 0);
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
// Task 1004354: the owner's Google Docs upload was refused and nothing on
|
|
336
|
+
// the screen said so where they were looking.
|
|
337
|
+
await test('a refused upload opens a panel over the page: what happened, the next step, and a way on', async () => {
|
|
338
|
+
const b = bootEditor({ answers: {
|
|
339
|
+
'GET /copy-desk/pages/landing:index/draft': ok(editorRead()),
|
|
340
|
+
'FETCH POST /copy-desk/pages/landing:index/draft.docx': { ok: false, data: { error: { code: 'wrong_page', details: { page_details: false, mismatched: ['title', 'headings'], what_this_means: 'This file is not the download of this page: its title or its section headings are not this page\'s. Nothing was saved.' } } } },
|
|
341
|
+
} });
|
|
342
|
+
await b.settle();
|
|
343
|
+
assert.equal(b.el('tw-refused').hidden, true, 'closed until a refusal');
|
|
344
|
+
b.el('tw-file').fire('change', { target: { files: [{ type: '' }], value: 'x' } });
|
|
345
|
+
await b.settle();
|
|
346
|
+
assert.equal(b.el('tw-refused').hidden, false);
|
|
347
|
+
assert.equal(b.el('tw-refused-title').textContent, 'This file is a different page.');
|
|
348
|
+
assert.equal(boot.focused, b.el('tw-refused-title'), 'focus moves to the panel');
|
|
349
|
+
assert.match(b.el('tw-refused-what').textContent, /not the download of this page/);
|
|
350
|
+
assert.match(b.el('tw-refused-next').textContent, /Pick the file you downloaded from this page/);
|
|
351
|
+
assert.equal(b.el('tw-refused-list').hidden, true, 'no lines to name');
|
|
352
|
+
assert.equal(b.el('tw-refused-download').hidden, false);
|
|
353
|
+
assert.equal(b.el('tw-bar').inert, true);
|
|
354
|
+
assert.equal(b.el('tw-paper').inert, true);
|
|
355
|
+
assert.equal(b.el('tw-upload').textContent, 'upload .docx');
|
|
356
|
+
// Close: the page is back, and the paper still says what happened.
|
|
357
|
+
await b.click('tw-refused-close');
|
|
358
|
+
assert.equal(b.el('tw-refused').hidden, true);
|
|
359
|
+
assert.equal(b.el('tw-bar').inert, false);
|
|
360
|
+
assert.equal(b.el('tw-paper').inert, false);
|
|
361
|
+
assert.equal(boot.focused, b.el('tw-upload'));
|
|
362
|
+
assert.match(b.el('tw-notice').innerHTML, /not the download of this page/);
|
|
363
|
+
// Download again from the panel: it closes, and the page downloads.
|
|
364
|
+
b.el('tw-file').fire('change', { target: { files: [{ type: '' }], value: 'x' } });
|
|
365
|
+
await b.settle();
|
|
366
|
+
await b.click('tw-refused-download');
|
|
367
|
+
await b.settle();
|
|
368
|
+
assert.equal(b.el('tw-refused').hidden, true);
|
|
369
|
+
assert.equal(b.el('tw-download').textContent, 'downloaded .docx');
|
|
370
|
+
});
|
|
371
|
+
|
|
372
|
+
await test('each refusal the upload can answer has its own headline and next step', () => {
|
|
373
|
+
for (const code of ['wrong_page', 'reading_moved', 'structure_changed', 'not_a_docx', 'upload_too_large', 'page_not_held', 'page_in_flight']) {
|
|
374
|
+
const r = lib.uploadRefusal(code);
|
|
375
|
+
assert.ok(r.title && r.title !== 'Your file was not saved.', code);
|
|
376
|
+
assert.ok(r.next.length > 20, code);
|
|
377
|
+
}
|
|
378
|
+
assert.equal(lib.uploadRefusal('something_new').title, 'Your file was not saved.');
|
|
379
|
+
assert.match(lib.uploadRefusal('not_a_docx').next, /Google Docs/);
|
|
329
380
|
});
|
|
330
381
|
|
|
331
382
|
await test('submit: saves first, then "Home is in.", next up, and take next opens the next page', async () => {
|
|
@@ -409,7 +460,7 @@ try {
|
|
|
409
460
|
assert.ok(HTML.indexOf('tweak-editor-lib.js') < HTML.indexOf('tweak-editor.js?'), 'the lib loads first');
|
|
410
461
|
assert.match(HTML, /shell\.js\?v=2026-09-28-archive/);
|
|
411
462
|
assert.equal(/fonts\.googleapis\.com\/css/.test(HTML), false);
|
|
412
|
-
for (const id of ['tw-bar', 'tw-counter', 'tw-open', 'tw-download', 'tw-upload', 'tw-file', 'tw-submit', 'tw-paper', 'tw-doc', 'tw-notice', 'tw-done', 'tw-take', 'tw-pick', 'tw-picks']) {
|
|
463
|
+
for (const id of ['tw-bar', 'tw-counter', 'tw-open', 'tw-download', 'tw-upload', 'tw-file', 'tw-submit', 'tw-paper', 'tw-doc', 'tw-notice', 'tw-done', 'tw-take', 'tw-pick', 'tw-picks', 'tw-refused', 'tw-refused-title', 'tw-refused-list', 'tw-refused-next', 'tw-refused-download', 'tw-refused-close']) {
|
|
413
464
|
assert.ok(new RegExp(`id="${id}"`).test(HTML), `#${id} is in the markup`);
|
|
414
465
|
}
|
|
415
466
|
assert.match(HTML, />← studio</);
|
|
@@ -33,7 +33,6 @@ const ALLOWED = {
|
|
|
33
33
|
allow_rule_guard: '.claude/hooks/allow-rule-guard.js (package-excluded)',
|
|
34
34
|
art_stage: 'scripts/gds/art-stage.js (art-pipeline tooling, not in the core)',
|
|
35
35
|
bongos_downloads: 'config/bongos-cli.json (instance content)',
|
|
36
|
-
box_dns_repoint: 'infra/box-dns-cloudflare.sh (host topology)',
|
|
37
36
|
box_ship_permissions: '.claude/settings.json (package-excluded)',
|
|
38
37
|
conductor: '.claude/hooks/conductor.js (package-excluded)',
|
|
39
38
|
deploy_instance: 'scripts/deploy/deploy-instance.sh (host topology)',
|
|
@@ -94,5 +93,5 @@ test('the ratchet is non-vacuous and actually detects a skip', () => {
|
|
|
94
93
|
assert.ok(actual.length >= 15, `expected the known skip set, found ${actual.length}`);
|
|
95
94
|
assert.ok(actual.includes('stop_refcheck'), 'the hand-rolled precedent must still be detected');
|
|
96
95
|
assert.ok(actual.includes('conductor'), 'a requireHostModule caller must be detected');
|
|
97
|
-
assert.ok(actual.includes('
|
|
96
|
+
assert.ok(actual.includes('deploy_instance'), 'a skipUnlessPresent caller must be detected');
|
|
98
97
|
});
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
// tests/html_comment_nesting.mjs — no served page's comment may quote comment
|
|
2
|
+
// syntax (task 1004359, BV2.TW24).
|
|
3
|
+
//
|
|
4
|
+
// An HTML comment ends at the FIRST closer, whatever its author meant. The head
|
|
5
|
+
// note of contact, privacy and terms quoted the apex-only sentinel verbatim, so
|
|
6
|
+
// the sentinel's closer ended the note early and the rest of the developer
|
|
7
|
+
// prose rendered as text at the top of all three pages — and the page reader
|
|
8
|
+
// filed it as page copy, so it reached Tweak Mode's editor too.
|
|
9
|
+
//
|
|
10
|
+
// The guard: in every served .html (each modules/<key>/public tree, plus
|
|
11
|
+
// public-docs), no comment's body holds another opener, and no closer stands
|
|
12
|
+
// outside a comment. Raw-text elements (script, style, textarea) are skipped:
|
|
13
|
+
// the browser never parses comments inside them.
|
|
14
|
+
//
|
|
15
|
+
// The file list is walked from disk, not git, so a new untracked page is
|
|
16
|
+
// scanned too, and a floor on the count keeps an emptied list from passing.
|
|
17
|
+
//
|
|
18
|
+
// Run: node --test tests/html_comment_nesting.mjs
|
|
19
|
+
|
|
20
|
+
import assert from 'node:assert/strict';
|
|
21
|
+
import { test } from 'node:test';
|
|
22
|
+
import fs from 'node:fs';
|
|
23
|
+
import path from 'node:path';
|
|
24
|
+
import { fileURLToPath } from 'node:url';
|
|
25
|
+
|
|
26
|
+
const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
|
27
|
+
const OPEN = '<!' + '--';
|
|
28
|
+
const CLOSE = '--' + '>';
|
|
29
|
+
|
|
30
|
+
/** Every .html under a served tree: modules/<key>/public/** and public-docs/**. */
|
|
31
|
+
function servedHtml() {
|
|
32
|
+
const roots = [path.join(ROOT, 'public-docs')];
|
|
33
|
+
for (const key of fs.readdirSync(path.join(ROOT, 'modules'))) {
|
|
34
|
+
roots.push(path.join(ROOT, 'modules', key, 'public'));
|
|
35
|
+
}
|
|
36
|
+
const out = [];
|
|
37
|
+
const walk = (dir) => {
|
|
38
|
+
if (!fs.existsSync(dir)) return;
|
|
39
|
+
for (const e of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
40
|
+
const p = path.join(dir, e.name);
|
|
41
|
+
if (e.isDirectory()) { if (e.name !== 'node_modules') walk(p); }
|
|
42
|
+
else if (e.name.endsWith('.html')) out.push(p);
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
roots.forEach(walk);
|
|
46
|
+
return out;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** Blank raw-text elements, keeping newlines so line numbers still point true. */
|
|
50
|
+
function blankRawText(html) {
|
|
51
|
+
return html.replace(/<(script|style|textarea)\b[^>]*>[\s\S]*?<\/\1\s*>/gi,
|
|
52
|
+
(m) => m.replace(/[^\n]/g, ' '));
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const lineAt = (s, i) => s.slice(0, i).split('\n').length;
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* The comment traps in one page, as "line N: why" strings (empty when clean).
|
|
59
|
+
* A comment runs from an opener to the FIRST closer after it, as the parser reads it.
|
|
60
|
+
*/
|
|
61
|
+
function commentTraps(html) {
|
|
62
|
+
const s = blankRawText(html);
|
|
63
|
+
const traps = [];
|
|
64
|
+
let i = 0;
|
|
65
|
+
for (;;) {
|
|
66
|
+
const o = s.indexOf(OPEN, i);
|
|
67
|
+
const c = s.indexOf(CLOSE, i);
|
|
68
|
+
if (c !== -1 && (o === -1 || c < o)) {
|
|
69
|
+
traps.push(`line ${lineAt(s, c)}: a comment closer outside any comment (text before it has leaked onto the page)`);
|
|
70
|
+
i = c + CLOSE.length;
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
if (o === -1) break;
|
|
74
|
+
const end = s.indexOf(CLOSE, o + OPEN.length);
|
|
75
|
+
if (end === -1) { traps.push(`line ${lineAt(s, o)}: a comment that never closes`); break; }
|
|
76
|
+
const inner = s.slice(o + OPEN.length, end).indexOf(OPEN);
|
|
77
|
+
if (inner !== -1) {
|
|
78
|
+
traps.push(`line ${lineAt(s, o + OPEN.length + inner)}: a comment that quotes a comment opener (its own closer ends the outer comment early)`);
|
|
79
|
+
}
|
|
80
|
+
i = end + CLOSE.length;
|
|
81
|
+
}
|
|
82
|
+
return traps;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// ── the detector, at unit altitude ──────────────────────────────────────────
|
|
86
|
+
|
|
87
|
+
test('detector: the exact TW24 head note is caught', () => {
|
|
88
|
+
const html = `<head>\n${OPEN} Server-stamped: every ${OPEN}apex-only${CLOSE} region is REMOVED off\n the hub. ${CLOSE}\n<title>x</title>`;
|
|
89
|
+
const traps = commentTraps(html);
|
|
90
|
+
assert.equal(traps.length, 2, traps.join('\n'));
|
|
91
|
+
assert.match(traps[0], /^line 2: .*quotes a comment opener/);
|
|
92
|
+
assert.match(traps[1], /^line 3: .*closer outside any comment/);
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
test('detector: the fixed wording and the real sentinels are clean', () => {
|
|
96
|
+
const html = `${OPEN} every apex-only:start/end region is REMOVED off the hub ${CLOSE}\n`
|
|
97
|
+
+ `<p>a</p>${OPEN}apex-only:start${CLOSE}<p>b</p>${OPEN}apex-only:end${CLOSE}`;
|
|
98
|
+
assert.deepEqual(commentTraps(html), []);
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
test('detector: a lone opener inside a comment is caught even with no stray closer', () => {
|
|
102
|
+
assert.equal(commentTraps(`${OPEN} see ${OPEN} here ${CLOSE}`).length, 1);
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
test('detector: an unclosed comment is caught', () => {
|
|
106
|
+
assert.match(commentTraps(`<p>a</p>${OPEN} never ends`)[0], /never closes/);
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
test('detector: comment syntax inside script, style and textarea is ignored', () => {
|
|
110
|
+
const html = `<script>const re = /${OPEN}x${CLOSE}/; if (a ${CLOSE} b) {}</script>`
|
|
111
|
+
+ `<style>/* ${CLOSE} */</style><textarea>${OPEN}</textarea>`;
|
|
112
|
+
assert.deepEqual(commentTraps(html), []);
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
test('detector: line numbers survive a blanked script above the trap', () => {
|
|
116
|
+
const html = `<script>\n1\n2\n</script>\n${OPEN} a ${OPEN} b ${CLOSE}`;
|
|
117
|
+
assert.match(commentTraps(html)[0], /^line 5: /);
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
// ── every served page ───────────────────────────────────────────────────────
|
|
121
|
+
|
|
122
|
+
test('every served .html page is free of comment traps', () => {
|
|
123
|
+
const files = servedHtml();
|
|
124
|
+
// 39 pages on 2026-09-28 (hall 31, landing 5, status 2, public-docs 1); a
|
|
125
|
+
// floor well under that keeps a broken walk from passing on an empty list.
|
|
126
|
+
assert.ok(files.length >= 30, `expected at least 30 served pages, found ${files.length}`);
|
|
127
|
+
const names = files.map((f) => path.relative(ROOT, f).split(path.sep).join('/'));
|
|
128
|
+
for (const must of ['modules/public-landing/public/contact.html',
|
|
129
|
+
'modules/public-landing/public/privacy.html', 'modules/public-landing/public/terms.html']) {
|
|
130
|
+
assert.ok(names.includes(must), `${must} is in the scanned set`);
|
|
131
|
+
}
|
|
132
|
+
const bad = [];
|
|
133
|
+
files.forEach((f, k) => {
|
|
134
|
+
for (const t of commentTraps(fs.readFileSync(f, 'utf8'))) bad.push(`${names[k]} ${t}`);
|
|
135
|
+
});
|
|
136
|
+
assert.deepEqual(bad, [], `comment traps:\n${bad.join('\n')}`);
|
|
137
|
+
});
|
|
@@ -31,11 +31,10 @@ function test(name, fn) {
|
|
|
31
31
|
catch (err) { console.error(` FAIL ${name}`); console.error(' ', err.message); failed++; }
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
-
// Host topology means systemd UNITS
|
|
35
|
-
//
|
|
36
|
-
//
|
|
37
|
-
//
|
|
38
|
-
// and the launcher checks on core-node.sh — the synthetic counterfactuals always run.
|
|
34
|
+
// Host topology means systemd UNITS: the neutral core carries no units, no launcher
|
|
35
|
+
// and no infra/ directory (task 1002470), so the real-tree unit lint is gated on
|
|
36
|
+
// *.service files being present, and the launcher checks on core-node.sh — the
|
|
37
|
+
// synthetic counterfactuals always run.
|
|
39
38
|
const INFRA_DIR = path.join(REPO, 'infra');
|
|
40
39
|
const HAS_UNITS = fs.existsSync(INFRA_DIR) &&
|
|
41
40
|
fs.readdirSync(INFRA_DIR).some((f) => f.endsWith('.service'));
|
|
@@ -50,10 +49,10 @@ hostTest('lint-infra-exec: every core-script unit resolves in a consumer layout
|
|
|
50
49
|
const r = lintInfraExec({ instanceRoot: REPO, coreRoot: REPO });
|
|
51
50
|
assert.deepEqual(r.violations, [], `expected no violations, got: ${JSON.stringify(r.violations)}`);
|
|
52
51
|
assert.ok(r.ok, 'lint should pass');
|
|
53
|
-
assert.
|
|
54
|
-
// The
|
|
52
|
+
assert.ok(r.checked.length >= 2, `expected the core-script units repointed + resolving, got ${r.checked.length}`);
|
|
53
|
+
// The core CLIs the units invoke are all covered.
|
|
55
54
|
const rels = new Set(r.checked.map((c) => c.rel));
|
|
56
|
-
for (const rel of ['scripts/gds/
|
|
55
|
+
for (const rel of ['scripts/gds/provision.js', 'scripts/gds/search-ingest-db.js']) {
|
|
57
56
|
assert.ok(rels.has(rel), `expected a unit routing through ${rel}`);
|
|
58
57
|
}
|
|
59
58
|
});
|
|
@@ -64,9 +63,9 @@ test('lint-infra-exec: a bare repo-relative core ExecStart is flagged (counterfa
|
|
|
64
63
|
try {
|
|
65
64
|
fs.mkdirSync(path.join(tmp, 'infra'), { recursive: true });
|
|
66
65
|
fs.mkdirSync(path.join(tmp, 'scripts', 'gds'), { recursive: true });
|
|
67
|
-
fs.writeFileSync(path.join(tmp, 'scripts', 'gds', '
|
|
66
|
+
fs.writeFileSync(path.join(tmp, 'scripts', 'gds', 'provision.js'), '// probe\n');
|
|
68
67
|
fs.writeFileSync(path.join(tmp, 'infra', 'legacy.service'),
|
|
69
|
-
'[Service]\nExecStart=/usr/bin/node scripts/gds/
|
|
68
|
+
'[Service]\nExecStart=/usr/bin/node scripts/gds/provision.js run-intents --apply\n');
|
|
70
69
|
const r = lintInfraExec({ instanceRoot: tmp, coreRoot: tmp });
|
|
71
70
|
assert.ok(!r.ok, 'a bare repo-relative core ExecStart must fail the lint');
|
|
72
71
|
assert.equal(r.violations.length, 1);
|
package/tests/init.mjs
CHANGED
|
@@ -88,7 +88,6 @@ test('buildModulesConfig: every known module present, enabled ones true', () =>
|
|
|
88
88
|
assert.equal(m['art-pipeline'], true);
|
|
89
89
|
assert.equal(m.game, false);
|
|
90
90
|
assert.equal(m.discord, false);
|
|
91
|
-
assert.equal(m['dev-box'], false);
|
|
92
91
|
});
|
|
93
92
|
|
|
94
93
|
test('buildModulesConfig: vanilla (no modules) = all off', () => {
|
|
@@ -122,8 +121,8 @@ test('kickoffTasks: 7 base steps + 1 per enabled module', () => {
|
|
|
122
121
|
const withArt = init.kickoffTasks(fullSpec());
|
|
123
122
|
assert.equal(withArt.length, 8); // +1 Gemini key task
|
|
124
123
|
assert.ok(withArt.some((t) => /Gemini/i.test(t.title)));
|
|
125
|
-
const all = init.kickoffTasks(init.normalizeSpec({ modules: ['art-pipeline', 'discord'
|
|
126
|
-
assert.equal(all.length,
|
|
124
|
+
const all = init.kickoffTasks(init.normalizeSpec({ modules: ['art-pipeline', 'discord'], firstAdmin: 'x', repo: { owner: 'o', name: 'n' } }));
|
|
125
|
+
assert.equal(all.length, 9);
|
|
127
126
|
});
|
|
128
127
|
|
|
129
128
|
test('kickoffTasks: includes the load-bearing owner-manual steps', () => {
|
|
@@ -141,7 +140,7 @@ test('kickoffTasks: includes the load-bearing owner-manual steps', () => {
|
|
|
141
140
|
test('kickoffTasks: every kind/discipline satisfies the DB CHECK vocabulary', () => {
|
|
142
141
|
const { VALID_KINDS, VALID_DISCIPLINES } = require('../modules/lifecycle/task-classifier.js');
|
|
143
142
|
const all = [
|
|
144
|
-
...init.kickoffTasks(init.normalizeSpec({ modules: ['art-pipeline', 'discord', '
|
|
143
|
+
...init.kickoffTasks(init.normalizeSpec({ modules: ['art-pipeline', 'discord', 'game'],
|
|
145
144
|
firstAdmin: 'x', repo: { owner: 'o', name: 'n' } })),
|
|
146
145
|
init.productionTopologyKickoff(),
|
|
147
146
|
];
|
|
@@ -429,7 +428,7 @@ test('enabledModuleDeps: merges only the ENABLED modules’ declared deps', () =
|
|
|
429
428
|
'art-pipeline': { sharp: '^0.34.5' },
|
|
430
429
|
}[k] || {});
|
|
431
430
|
const merged = init.enabledModuleDeps(
|
|
432
|
-
{ modules: { discord: true, game: false, 'art-pipeline': true
|
|
431
|
+
{ modules: { discord: true, game: false, 'art-pipeline': true } },
|
|
433
432
|
{ readDeps }
|
|
434
433
|
);
|
|
435
434
|
assert.deepEqual(merged, { 'discord.js': '^14.26.4', sharp: '^0.34.5' }); // game excluded (off)
|
|
@@ -30,17 +30,17 @@ const BRAND_VANILLA = {
|
|
|
30
30
|
rankLabels: {},
|
|
31
31
|
};
|
|
32
32
|
const MODULES_OTB = {
|
|
33
|
-
enabled: { game: true, 'art-pipeline': true, discord: true
|
|
33
|
+
enabled: { game: true, 'art-pipeline': true, discord: true },
|
|
34
34
|
disciplines: ['engineer', 'artist'],
|
|
35
35
|
};
|
|
36
36
|
const MODULES_VANILLA = {
|
|
37
|
-
enabled: { game: false, 'art-pipeline': false, discord: false
|
|
37
|
+
enabled: { game: false, 'art-pipeline': false, discord: false },
|
|
38
38
|
disciplines: ['engineer'],
|
|
39
39
|
};
|
|
40
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
|
-
const m = buildInstanceManifest({ branding: BRAND_OTB, modules: MODULES_OTB, rankTier: RANK_TIER, repo: REPO, authConfigured: true, openEnrollment: false
|
|
43
|
+
const m = buildInstanceManifest({ branding: BRAND_OTB, modules: MODULES_OTB, rankTier: RANK_TIER, repo: REPO, authConfigured: true, openEnrollment: false });
|
|
44
44
|
assert.equal(m.schema, MANIFEST_SCHEMA);
|
|
45
45
|
assert.equal(m.name, 'Example World');
|
|
46
46
|
assert.equal(m.brand, BRAND_OTB);
|
|
@@ -48,13 +48,13 @@ test('manifest: schema id + headline name + embedded brand', () => {
|
|
|
48
48
|
});
|
|
49
49
|
|
|
50
50
|
test('manifest: brand.project rides the manifest — the settings read-back a manage page needs (task 1003140 / ADR 0190)', () => {
|
|
51
|
-
const m = buildInstanceManifest({ branding: BRAND_OTB, modules: MODULES_OTB, rankTier: RANK_TIER, repo: REPO, authConfigured: true, openEnrollment: false
|
|
51
|
+
const m = buildInstanceManifest({ branding: BRAND_OTB, modules: MODULES_OTB, rankTier: RANK_TIER, repo: REPO, authConfigured: true, openEnrollment: false });
|
|
52
52
|
assert.deepEqual(m.brand.project, { platformVisibility: 'gated', joinability: 'invite_only' },
|
|
53
53
|
'a client reads what the instance RESOLVED from here, never from the platform PATCH response');
|
|
54
54
|
});
|
|
55
55
|
|
|
56
56
|
test('manifest: rank ladder ordered entry→top, labelled from brand', () => {
|
|
57
|
-
const m = buildInstanceManifest({ branding: BRAND_OTB, modules: MODULES_OTB, rankTier: RANK_TIER, repo: REPO, authConfigured: true, openEnrollment: false
|
|
57
|
+
const m = buildInstanceManifest({ branding: BRAND_OTB, modules: MODULES_OTB, rankTier: RANK_TIER, repo: REPO, authConfigured: true, openEnrollment: false });
|
|
58
58
|
assert.deepEqual(m.ranks.map((r) => r.key), ['xenos', 'thetes', 'metic', 'archon']);
|
|
59
59
|
assert.deepEqual(m.ranks.map((r) => r.tier), [1, 2, 3, 4]);
|
|
60
60
|
assert.equal(m.ranks[0].label, 'Xenos');
|
|
@@ -62,46 +62,39 @@ test('manifest: rank ladder ordered entry→top, labelled from brand', () => {
|
|
|
62
62
|
});
|
|
63
63
|
|
|
64
64
|
test('manifest: rank label falls back to the key when unlabelled', () => {
|
|
65
|
-
const m = buildInstanceManifest({ branding: BRAND_VANILLA, modules: MODULES_VANILLA, rankTier: RANK_TIER, repo: { error: 'no_repo_info' }, authConfigured: false, openEnrollment: false
|
|
65
|
+
const m = buildInstanceManifest({ branding: BRAND_VANILLA, modules: MODULES_VANILLA, rankTier: RANK_TIER, repo: { error: 'no_repo_info' }, authConfigured: false, openEnrollment: false });
|
|
66
66
|
assert.equal(m.ranks[0].label, 'xenos');
|
|
67
67
|
assert.equal(m.ranks[2].label, 'metic');
|
|
68
68
|
});
|
|
69
69
|
|
|
70
70
|
test('manifest: repo projects to public clone coords; null on error', () => {
|
|
71
|
-
const ok = buildInstanceManifest({ branding: BRAND_OTB, modules: MODULES_OTB, rankTier: RANK_TIER, repo: REPO, authConfigured: true, openEnrollment: false
|
|
71
|
+
const ok = buildInstanceManifest({ branding: BRAND_OTB, modules: MODULES_OTB, rankTier: RANK_TIER, repo: REPO, authConfigured: true, openEnrollment: false });
|
|
72
72
|
assert.deepEqual(ok.repo, { owner: 'example-owner', repo: 'example', branch: 'main', url: 'https://github.com/example-owner/example' });
|
|
73
|
-
const none = buildInstanceManifest({ branding: BRAND_VANILLA, modules: MODULES_VANILLA, rankTier: RANK_TIER, repo: { error: 'no_repo_info' }, authConfigured: false, openEnrollment: false
|
|
73
|
+
const none = buildInstanceManifest({ branding: BRAND_VANILLA, modules: MODULES_VANILLA, rankTier: RANK_TIER, repo: { error: 'no_repo_info' }, authConfigured: false, openEnrollment: false });
|
|
74
74
|
assert.equal(none.repo, null);
|
|
75
75
|
});
|
|
76
76
|
|
|
77
77
|
test('manifest: auth methods gated on configured', () => {
|
|
78
|
-
const on = buildInstanceManifest({ branding: BRAND_OTB, modules: MODULES_OTB, rankTier: RANK_TIER, repo: REPO, authConfigured: true, openEnrollment: false
|
|
78
|
+
const on = buildInstanceManifest({ branding: BRAND_OTB, modules: MODULES_OTB, rankTier: RANK_TIER, repo: REPO, authConfigured: true, openEnrollment: false });
|
|
79
79
|
assert.deepEqual(on.auth.methods, AUTH_METHODS);
|
|
80
80
|
assert.equal(on.auth.configured, true);
|
|
81
|
-
const off = buildInstanceManifest({ branding: BRAND_VANILLA, modules: MODULES_VANILLA, rankTier: RANK_TIER, repo: { error: 'x' }, authConfigured: false, openEnrollment: false
|
|
81
|
+
const off = buildInstanceManifest({ branding: BRAND_VANILLA, modules: MODULES_VANILLA, rankTier: RANK_TIER, repo: { error: 'x' }, authConfigured: false, openEnrollment: false });
|
|
82
82
|
assert.deepEqual(off.auth.methods, []);
|
|
83
83
|
assert.equal(off.auth.configured, false);
|
|
84
84
|
});
|
|
85
85
|
|
|
86
86
|
test('manifest: admission policy reflects open-enrollment flag', () => {
|
|
87
|
-
const invite = buildInstanceManifest({ branding: BRAND_OTB, modules: MODULES_OTB, rankTier: RANK_TIER, repo: REPO, authConfigured: true, openEnrollment: false
|
|
87
|
+
const invite = buildInstanceManifest({ branding: BRAND_OTB, modules: MODULES_OTB, rankTier: RANK_TIER, repo: REPO, authConfigured: true, openEnrollment: false });
|
|
88
88
|
assert.equal(invite.admission.policy, 'invite-only');
|
|
89
|
-
const open = buildInstanceManifest({ branding: BRAND_OTB, modules: MODULES_OTB, rankTier: RANK_TIER, repo: REPO, authConfigured: true, openEnrollment: true
|
|
89
|
+
const open = buildInstanceManifest({ branding: BRAND_OTB, modules: MODULES_OTB, rankTier: RANK_TIER, repo: REPO, authConfigured: true, openEnrollment: true });
|
|
90
90
|
assert.equal(open.admission.policy, 'open');
|
|
91
91
|
});
|
|
92
92
|
|
|
93
|
-
test('manifest: dev-box availability mirrors the module flag', () => {
|
|
94
|
-
const on = buildInstanceManifest({ branding: BRAND_OTB, modules: MODULES_OTB, rankTier: RANK_TIER, repo: REPO, authConfigured: true, openEnrollment: false, devBoxEnabled: true });
|
|
95
|
-
assert.equal(on.devBox.available, true);
|
|
96
|
-
const off = buildInstanceManifest({ branding: BRAND_VANILLA, modules: MODULES_VANILLA, rankTier: RANK_TIER, repo: { error: 'x' }, authConfigured: false, openEnrollment: false, devBoxEnabled: false });
|
|
97
|
-
assert.equal(off.devBox.available, false);
|
|
98
|
-
});
|
|
99
|
-
|
|
100
93
|
test('manifest: no server-only secrets/topology leak into the body', () => {
|
|
101
94
|
// The assembler only ever reads the client-safe projections it is handed, so
|
|
102
95
|
// even if a caller passed a fuller object, these server-only keys must not
|
|
103
96
|
// appear. We assert on the serialized body (deep, key-name based).
|
|
104
|
-
const m = buildInstanceManifest({ branding: BRAND_OTB, modules: MODULES_OTB, rankTier: RANK_TIER, repo: REPO, authConfigured: true, openEnrollment: false
|
|
97
|
+
const m = buildInstanceManifest({ branding: BRAND_OTB, modules: MODULES_OTB, rankTier: RANK_TIER, repo: REPO, authConfigured: true, openEnrollment: false });
|
|
105
98
|
const json = JSON.stringify(m);
|
|
106
99
|
for (const forbidden of ['cookieDomain', 'oauthOrigin', 'publicOrigin', 'envPrefix', 'client_secret', 'dbName', 'instanceDbName', 'token']) {
|
|
107
100
|
assert.ok(!json.includes(forbidden), `manifest leaked "${forbidden}"`);
|
|
@@ -109,8 +102,8 @@ test('manifest: no server-only secrets/topology leak into the body', () => {
|
|
|
109
102
|
});
|
|
110
103
|
|
|
111
104
|
test('manifest: vanilla instance is fully populated (every field present)', () => {
|
|
112
|
-
const m = buildInstanceManifest({ branding: BRAND_VANILLA, modules: MODULES_VANILLA, rankTier: RANK_TIER, repo: { error: 'x' }, authConfigured: false, openEnrollment: false
|
|
113
|
-
for (const k of ['schema', 'name', 'brand', 'modules', 'ranks', 'repo', 'auth', 'admission'
|
|
105
|
+
const m = buildInstanceManifest({ branding: BRAND_VANILLA, modules: MODULES_VANILLA, rankTier: RANK_TIER, repo: { error: 'x' }, authConfigured: false, openEnrollment: false });
|
|
106
|
+
for (const k of ['schema', 'name', 'brand', 'modules', 'ranks', 'repo', 'auth', 'admission']) {
|
|
114
107
|
assert.ok(k in m, `missing field ${k}`);
|
|
115
108
|
}
|
|
116
109
|
assert.equal(m.name, 'Cloud Bongos');
|
|
@@ -124,7 +117,7 @@ const buildInstanceRouter = instanceMod;
|
|
|
124
117
|
import { strangerManifest } from '../src/bongos/routes/instance.js';
|
|
125
118
|
|
|
126
119
|
const IDP = { origin: 'https://hub.example.com', client_id: 'proj-1' };
|
|
127
|
-
const FULL = () => buildInstanceManifest({ branding: BRAND_OTB, modules: MODULES_OTB, rankTier: RANK_TIER, repo: REPO, authConfigured: false, openEnrollment: false,
|
|
120
|
+
const FULL = () => buildInstanceManifest({ branding: BRAND_OTB, modules: MODULES_OTB, rankTier: RANK_TIER, repo: REPO, authConfigured: false, openEnrollment: false, idp: IDP });
|
|
128
121
|
|
|
129
122
|
test('stranger manifest: schema, name, brand.project, auth (idp kept), admission — and nothing else', () => {
|
|
130
123
|
const s = strangerManifest(FULL());
|
|
@@ -136,13 +129,13 @@ test('stranger manifest: schema, name, brand.project, auth (idp kept), admission
|
|
|
136
129
|
assert.deepEqual(s.auth, { methods: AUTH_METHODS, configured: true, idp: IDP });
|
|
137
130
|
assert.deepEqual(s.admission, { policy: 'invite-only' });
|
|
138
131
|
const json = JSON.stringify(s);
|
|
139
|
-
for (const gone of ['example-owner', 'github.com', 'Straight example world', 'Hermeslines', 'drachmae', 'art-pipeline', 'Archon'
|
|
132
|
+
for (const gone of ['example-owner', 'github.com', 'Straight example world', 'Hermeslines', 'drachmae', 'art-pipeline', 'Archon']) {
|
|
140
133
|
assert.ok(!json.includes(gone), `a stranger must not read "${gone}"`);
|
|
141
134
|
}
|
|
142
135
|
});
|
|
143
136
|
|
|
144
137
|
test('stranger manifest: a brand with no project policy projects none', () => {
|
|
145
|
-
const s = strangerManifest(buildInstanceManifest({ branding: BRAND_VANILLA, modules: MODULES_VANILLA, rankTier: RANK_TIER, repo: { error: 'x' }, authConfigured: false, openEnrollment: true
|
|
138
|
+
const s = strangerManifest(buildInstanceManifest({ branding: BRAND_VANILLA, modules: MODULES_VANILLA, rankTier: RANK_TIER, repo: { error: 'x' }, authConfigured: false, openEnrollment: true }));
|
|
146
139
|
assert.deepEqual(JSON.parse(JSON.stringify(s.brand)), { identity: { productName: 'Cloud Bongos' } });
|
|
147
140
|
assert.equal(s.admission.policy, 'open');
|
|
148
141
|
});
|
|
@@ -76,7 +76,7 @@ test('firstSignIn: never reads spec.repo or spec.firstAdmin (the steps that need
|
|
|
76
76
|
});
|
|
77
77
|
|
|
78
78
|
test('every variant satisfies the DB CHECK vocabulary (tasks_kind_check / tasks_discipline_check)', () => {
|
|
79
|
-
const full = { ...spec(), modules: ['art-pipeline', 'discord'
|
|
79
|
+
const full = { ...spec(), modules: ['art-pipeline', 'discord'] };
|
|
80
80
|
const all = [
|
|
81
81
|
...kickoffChecklist(full),
|
|
82
82
|
...kickoffChecklist(full, { provisioned: true }),
|