@bongos/core 1.19.1073 → 1.19.1075
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 +404 -549
- package/.claude/skills/feedback/SKILL.md +2 -2
- package/bin/bongos.js +1 -3
- package/clients/bongos-client/README.md +1 -1
- package/clients/bongos-client/bongos-client.global.js +4 -60
- package/clients/bongos-client/index.cjs +4 -60
- package/clients/bongos-client/index.d.ts +5 -90
- package/clients/bongos-client/index.mjs +4 -60
- package/config/branding.neutral.json +1 -5
- package/config/modules.neutral.json +2 -3
- package/config/scheduled-routines.json +0 -2
- package/docs/adr/0145-free-hosted-project-tier-isolation-and-domain-separation.md +1 -0
- package/docs/adr/0285-a-shared-box-holds-about-twelve-projects-per-gb-and-memory-is-the-wall.md +1 -0
- package/docs/adr/0323-hosting-is-three-shapes-and-we-are-not-the-landlord.md +1 -1
- package/docs/adr/0327-a-cloud-host-runs-on-the-owners-account-and-the-key-is-borrowed.md +1 -1
- package/docs/adr/0341-the-page-is-the-unit-of-tweak-mode.md +2 -0
- package/docs/adr/0345-we-host-every-projects-hall-and-its-app-deploys-where-the-owner-chooses.md +94 -0
- package/docs/adr/README.md +1 -0
- package/docs/api/openapi.json +175 -1665
- 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 +10 -10
- 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/discord/ship-broadcast.js +3 -3
- package/modules/economy/credits.js +2 -2
- 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/ideas/projection.js +1 -1
- package/modules/lifecycle/db-claim-reads.js +1 -58
- package/modules/lifecycle/db-ship.js +4 -9
- package/modules/lifecycle/db.js +0 -4
- package/modules/lifecycle/github-push.js +12 -14
- 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/publish-reconciler.js +10 -10
- package/modules/lifecycle/routes/tasks.js +13 -15
- package/modules/lifecycle/ship-card.js +1 -1
- package/modules/lifecycle/task-visuals.js +2 -2
- package/modules/memory/module.json +1 -0
- package/modules/memory/routes/memory.js +2 -2
- 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 +5 -4
- package/modules/platform-identity/tests/platform-identity.mjs +17 -3
- package/modules/provisioning/capacity.js +1 -1
- package/modules/provisioning/module.json +1 -0
- package/modules/provisioning/provisioning.js +6 -5
- package/modules/provisioning/routes/provisioning.js +4 -4
- 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/db.js +1 -1
- 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/artifact-staleness.js +2 -2
- package/scripts/gds/audit-authorship.sh +1 -13
- package/scripts/gds/backfill-task-rewards.js +5 -5
- package/scripts/gds/claim.js +0 -15
- package/scripts/gds/claude-materialize.js +2 -2
- package/scripts/gds/cli-lib.js +15 -15
- package/scripts/gds/codemod-rename-src-gds.js +1 -1
- package/scripts/gds/context-pack.js +5 -7
- 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 +4 -4
- package/scripts/gds/feedback-latest.js +139 -9
- package/scripts/gds/fitness-checks-identity.js +2 -7
- package/scripts/gds/fitness-checks-packaging.js +4 -4
- package/scripts/gds/fitness-checks-write-validation.js +4 -0
- package/scripts/gds/fitness-ratchets.js +5 -5
- package/scripts/gds/fitness.js +10 -10
- package/scripts/gds/gen-api-client.js +4 -4
- package/scripts/gds/gen-api-docs.js +1 -1
- package/scripts/gds/gen-diagrams.js +49 -71
- package/scripts/gds/gen-repo-map.js +13 -14
- package/scripts/gds/gen-session-index.js +8 -8
- package/scripts/gds/http-api-client.js +12 -19
- package/scripts/gds/init.js +1 -1
- package/scripts/gds/leak-scan-allowlist.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/plain-cards.js +1 -1
- 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/push-path-brief.js +10 -10
- package/scripts/gds/regen-instance-docs.js +1 -1
- package/scripts/gds/rename-history-check.js +1 -1
- package/scripts/gds/run-unit-tests.js +2 -16
- package/scripts/gds/sandbox-stage.js +39 -341
- package/scripts/gds/session-digest-build.js +2 -2
- package/scripts/gds/ship-deploy-target.js +13 -13
- package/scripts/gds/ship-flow.js +7 -7
- package/scripts/gds/ship-land.js +9 -10
- package/scripts/gds/ship-merge.js +4 -4
- package/scripts/gds/ship-preflight-steps.js +4 -4
- package/scripts/gds/ship-regen.js +15 -49
- package/scripts/gds/ship.js +13 -33
- package/scripts/gds/skill-preflight.js +9 -8
- 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/api-prefix.js +4 -4
- 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/platform-visibility-gate.js +1 -1
- 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 +7 -23
- package/src/bongos/routes/modules.js +83 -0
- package/src/bongos/routes/security.js +3 -3
- package/src/bongos/routes.js +7 -2
- package/src/bongos/serve-internal.js +5 -6
- package/src/bongos-downloads.js +1 -6
- package/src/branding.js +2 -2
- package/src/build-info.js +4 -6
- package/src/instance-config.js +2 -2
- 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 +16 -8
- package/src/module-seams.js +2 -3
- package/src/modules.js +42 -14
- package/tests/agents_spend_guard.mjs +1 -1
- package/tests/api_alias_caller_ratchet.mjs +1 -1
- package/tests/api_alias_exceptions.mjs +1 -1
- package/tests/api_client.mjs +1 -16
- package/tests/api_path_404.mjs +1 -1
- package/tests/auth_page_gate.mjs +10 -10
- package/tests/backfill_task_rewards.mjs +1 -1
- 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/{box_ship_permissions.mjs → cli_allowlist_permissions.mjs} +5 -5
- package/tests/cli_sessions.mjs +1 -1
- package/tests/consumer_layout_boot.mjs +2 -0
- package/tests/context_pack.mjs +13 -13
- package/tests/copy_desk_page_docx.mjs +33 -2
- package/tests/copy_inventory.mjs +7 -7
- package/tests/core_upgrade_runner.mjs +6 -6
- package/tests/credit_grant.mjs +2 -2
- package/tests/criterion_suggest.mjs +1 -1
- package/tests/deploy_divergence_line.mjs +11 -11
- package/tests/design_tokens_sync.mjs +3 -2
- 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/fitness_ratchets.mjs +2 -2
- package/tests/gate_approvals.mjs +0 -1
- package/tests/github_push_land_proof.mjs +1 -1
- package/tests/go_live.mjs +3 -3
- package/tests/goal_advisory.mjs +3 -3
- package/tests/goal_suggest.mjs +3 -3
- package/tests/government_abuse_matrix.mjs +21 -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_audit.mjs +1 -1
- 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/helpers.mjs +1 -1
- package/tests/host_topology_skips.mjs +3 -4
- 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/migration_namespace.mjs +6 -6
- package/tests/module-scope-map.mjs +9 -9
- package/tests/module_api.mjs +2 -2
- package/tests/module_catalog.mjs +1 -1
- package/tests/module_cli.mjs +20 -20
- package/tests/module_contributions.mjs +6 -6
- package/tests/module_loader.mjs +6 -6
- package/tests/module_manifest.mjs +26 -17
- package/tests/module_route_rank.mjs +2 -2
- 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/modules_route_wiring.mjs +1 -1
- package/tests/onboarding_route_signals.mjs +21 -31
- package/tests/onboarding_state.mjs +35 -49
- package/tests/paste_token_session_store.mjs +1 -1
- package/tests/permission_path.mjs +0 -3
- package/tests/platform_boot.mjs +1 -2
- package/tests/platform_visibility_gate.mjs +1 -1
- package/tests/profile_route.mjs +1 -6
- package/tests/project_modules_ui.mjs +17 -17
- package/tests/projects_hub.mjs +5 -5
- package/tests/projects_hub_app_step.mjs +138 -0
- package/tests/projects_hub_module_picker.mjs +77 -76
- package/tests/projects_hub_pre_uat.mjs +4 -5
- package/tests/provision.mjs +7 -7
- package/tests/provisioning_capacity.mjs +1 -1
- package/tests/provisioning_recommendations.mjs +2 -1
- package/tests/provisioning_starter_bundles.mjs +0 -22
- package/tests/publish_branch_route.mjs +11 -19
- package/tests/publish_manifest.mjs +2 -3
- package/tests/publish_reconciler.mjs +8 -8
- package/tests/push_path_brief.mjs +10 -9
- package/tests/rank_tier_single_source.mjs +1 -13
- package/tests/regrade_eligibility.mjs +1 -1
- package/tests/rename_history_restraint.mjs +1 -1
- package/tests/repo_map.mjs +12 -13
- package/tests/runner_drift.mjs +3 -21
- package/tests/sandbox_stage.mjs +57 -390
- package/tests/seam_wiring_guard.mjs +11 -11
- package/tests/search_isolation.mjs +3 -3
- package/tests/session_records.mjs +1 -1
- package/tests/session_rename_fallback.mjs +1 -22
- package/tests/session_start_freshness.mjs +3 -3
- package/tests/session_token_hash_db.mjs +1 -1
- package/tests/ship_card.mjs +1 -1
- package/tests/ship_ci_deploy.mjs +17 -17
- package/tests/ship_error_shape.mjs +1 -1
- package/tests/ship_premerge.mjs +1 -1
- package/tests/ship_resume.mjs +1 -1
- package/tests/skill_preflight.mjs +2 -2
- package/tests/skip_is_not_pass.mjs +1 -1
- package/tests/task_visual_slots.mjs +31 -0
- package/tests/terms_acceptance.mjs +2 -2
- 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
|
@@ -59,21 +59,22 @@ const PAYLOAD = {
|
|
|
59
59
|
// `summary` is part of every real bundle row and is the why-line's FLOOR since task
|
|
60
60
|
// 1003684 — the sentence shown when no rule moved anything. The fixture carried none
|
|
61
61
|
// before that, which quietly made every "no adjustment, no sentence" case pass for the
|
|
62
|
-
// wrong reason.
|
|
62
|
+
// wrong reason. Shaped like the real strings in starter-bundles.js (the live payload is pinned
|
|
63
|
+
// separately, at the bottom of the team-shape section).
|
|
63
64
|
bundles: [
|
|
64
|
-
{ type: 'game', bundle: ['
|
|
65
|
+
{ type: 'game', bundle: ['npm-release', 'discord'], summary: 'Somewhere for players to gather, and a way to publish each release of the game.' },
|
|
65
66
|
{ type: 'research', bundle: ['agents'], summary: 'Extra agents to run the reading and the experiments the question needs.' },
|
|
66
|
-
{ type: 'business', bundle: ['agents', '
|
|
67
|
+
{ type: 'business', bundle: ['agents', 'npm-release'], summary: 'A way to publish each release, and agents to look it over first.' },
|
|
67
68
|
{ type: 'non-profit', bundle: ['discord'], summary: 'Somewhere the community around the cause can gather and be heard.' },
|
|
68
69
|
{ type: 'not-sure', bundle: [], summary: 'Just the core, and nothing else to decide today. Add modules whenever you know what you need.' },
|
|
69
70
|
],
|
|
70
71
|
// `aliases` — search words a row is findable under that its own copy never uses
|
|
71
|
-
// (task 1003500, amending ADR 0244 §3). Never rendered, like the key.
|
|
72
|
+
// (task 1003500, amending ADR 0244 §3). Never rendered, like the key. npm-release
|
|
72
73
|
// carries none on purpose, so the "a row without aliases still matches on its own
|
|
73
74
|
// words" path is exercised by the same fixture rather than by a special case.
|
|
74
75
|
optional: [
|
|
75
76
|
{ key: 'agents', label: 'Extra agents', summary: 'More AI agents alongside the ones every project gets.', aliases: ['bots', 'assistants', 'automation'] },
|
|
76
|
-
{ key: '
|
|
77
|
+
{ key: 'npm-release', label: 'npm releases', summary: 'Publishes each release of your package to the registry.' },
|
|
77
78
|
{ key: 'discord', label: 'Discord', summary: 'A Discord server that carries your build news.', aliases: ['chatroom', 'talk', 'community'] },
|
|
78
79
|
],
|
|
79
80
|
};
|
|
@@ -92,18 +93,18 @@ const TAILORED = {
|
|
|
92
93
|
bundles: PAYLOAD.bundles.map((b) => {
|
|
93
94
|
if (b.type === 'game') {
|
|
94
95
|
return { ...b, byTeamShape: {
|
|
95
|
-
// solo: the
|
|
96
|
-
solo: { bundle: ['discord'], adjustments: [{ rule: 'building-alone-needs-no-
|
|
97
|
-
// small-team: the rule wants
|
|
96
|
+
// solo: the release pipeline comes off
|
|
97
|
+
solo: { bundle: ['discord'], adjustments: [{ rule: 'building-alone-needs-no-release-pipeline', effect: 'remove', module: 'npm-release', reason: 'You are building this alone, so the release pipeline is off.' }] },
|
|
98
|
+
// small-team: the rule wants npm-release, the bundle already has it — a rule
|
|
98
99
|
// that changed nothing claims nothing
|
|
99
|
-
'small-team': { bundle: ['
|
|
100
|
-
community: { bundle: ['
|
|
100
|
+
'small-team': { bundle: ['npm-release', 'discord'], adjustments: [] },
|
|
101
|
+
community: { bundle: ['npm-release', 'discord'], adjustments: [] },
|
|
101
102
|
} };
|
|
102
103
|
}
|
|
103
104
|
if (b.type === 'research') {
|
|
104
105
|
return { ...b, byTeamShape: {
|
|
105
106
|
solo: { bundle: ['agents'], adjustments: [] },
|
|
106
|
-
'small-team': { bundle: ['agents', '
|
|
107
|
+
'small-team': { bundle: ['agents', 'npm-release'], adjustments: [{ rule: 'a-team-shares-one-release-pipeline', effect: 'add', module: 'npm-release', reason: 'A small team gets one release pipeline to publish through.' }] },
|
|
107
108
|
community: { bundle: ['agents', 'discord'], adjustments: [{ rule: 'a-community-needs-somewhere-to-gather', effect: 'add', module: 'discord', reason: 'You said an open community is building this, so somewhere for it to gather is switched on.' }] },
|
|
108
109
|
} };
|
|
109
110
|
}
|
|
@@ -232,16 +233,16 @@ test('the declared project type preselects its bundle, and the rest of the catal
|
|
|
232
233
|
|
|
233
234
|
// Assert — every opt-in module is a row; the bundle's two are on
|
|
234
235
|
const rows = h.rows();
|
|
235
|
-
assert.deepEqual(rows.map((r) => r.key), ['agents', '
|
|
236
|
-
assert.deepEqual(rows.filter((r) => r.pressed).map((r) => r.key), ['
|
|
236
|
+
assert.deepEqual(rows.map((r) => r.key), ['agents', 'npm-release', 'discord'], 'the whole opt-in catalog is offered');
|
|
237
|
+
assert.deepEqual(rows.filter((r) => r.pressed).map((r) => r.key), ['npm-release', 'discord'], 'the game bundle is preselected');
|
|
237
238
|
assert.equal(rows.find((r) => r.key === 'agents').pressed, false, 'and nothing else is');
|
|
238
239
|
// each row carries the copy an owner can actually read
|
|
239
|
-
assert.equal(rows[1].label, '
|
|
240
|
-
assert.match(rows[1].summary, /
|
|
240
|
+
assert.equal(rows[1].label, 'npm releases');
|
|
241
|
+
assert.match(rows[1].summary, /package to the registry/);
|
|
241
242
|
});
|
|
242
243
|
|
|
243
244
|
test('a different type preselects a different bundle, and "not sure" preselects nothing', async () => {
|
|
244
|
-
for (const [type, expected] of [['research', ['agents']], ['business', ['agents', '
|
|
245
|
+
for (const [type, expected] of [['research', ['agents']], ['business', ['agents', 'npm-release']], ['non-profit', ['discord']], ['not-sure', []], [null, []]]) {
|
|
245
246
|
const h = boot({ state: { projType: type } });
|
|
246
247
|
await h.open();
|
|
247
248
|
assert.deepEqual(h.rows().filter((r) => r.pressed).map((r) => r.key), expected, `type=${type}`);
|
|
@@ -272,8 +273,8 @@ test('a toggle flips one module and leaves the rest of the preselect standing',
|
|
|
272
273
|
h.click('agents');
|
|
273
274
|
|
|
274
275
|
// Assert
|
|
275
|
-
assert.deepEqual(h.rows().filter((r) => r.pressed).map((r) => r.key), ['agents', '
|
|
276
|
-
assert.deepEqual(h.state.modules, ['agents', '
|
|
276
|
+
assert.deepEqual(h.rows().filter((r) => r.pressed).map((r) => r.key), ['agents', 'npm-release']);
|
|
277
|
+
assert.deepEqual(h.state.modules, ['agents', 'npm-release'], 'and the draft holds the real answer, in catalog order');
|
|
277
278
|
assert.ok(h.saves() >= 2, 'each toggle is written to the draft');
|
|
278
279
|
});
|
|
279
280
|
|
|
@@ -285,7 +286,7 @@ test('the first toggle is what turns "not answered" into an answer', async () =>
|
|
|
285
286
|
assert.equal(h.forCreate(), undefined, 'so the create request carries no modules key');
|
|
286
287
|
|
|
287
288
|
h.click('agents');
|
|
288
|
-
assert.deepEqual(h.forCreate(), ['agents', '
|
|
289
|
+
assert.deepEqual(h.forCreate(), ['agents', 'npm-release', 'discord'], 'after a touch it carries the whole set');
|
|
289
290
|
});
|
|
290
291
|
|
|
291
292
|
test('switching every module off is a real answer, not a reset to the bundle', async () => {
|
|
@@ -293,7 +294,7 @@ test('switching every module off is a real answer, not a reset to the bundle', a
|
|
|
293
294
|
// said "none", and Next must not hand them the bundle back.
|
|
294
295
|
const h = boot({ state: { projType: 'game' } });
|
|
295
296
|
await h.open();
|
|
296
|
-
h.click('
|
|
297
|
+
h.click('npm-release');
|
|
297
298
|
h.click('discord');
|
|
298
299
|
|
|
299
300
|
assert.deepEqual(h.state.modules, []);
|
|
@@ -314,7 +315,7 @@ test('Skip walks past WITHOUT answering, so the server applies the bundle the pa
|
|
|
314
315
|
assert.deepEqual(h.shown, [5], 'it advances to the Home step');
|
|
315
316
|
assert.equal(h.state.modules, null, 'and answers nothing');
|
|
316
317
|
assert.equal(h.forCreate(), undefined);
|
|
317
|
-
assert.deepEqual(h.picked(), ['
|
|
318
|
+
assert.deepEqual(h.picked(), ['npm-release', 'discord'], 'while the set they were shown is still the set they get');
|
|
318
319
|
});
|
|
319
320
|
|
|
320
321
|
test('Skip does not erase a toggle already made', async () => {
|
|
@@ -325,7 +326,7 @@ test('Skip does not erase a toggle already made', async () => {
|
|
|
325
326
|
h.click('agents');
|
|
326
327
|
h.skip();
|
|
327
328
|
|
|
328
|
-
assert.deepEqual(h.state.modules, ['agents', '
|
|
329
|
+
assert.deepEqual(h.state.modules, ['agents', 'npm-release', 'discord']);
|
|
329
330
|
});
|
|
330
331
|
|
|
331
332
|
test('Next CONFIRMS the set on screen — looking at it and accepting it is not the same as never being asked', async () => {
|
|
@@ -334,8 +335,8 @@ test('Next CONFIRMS the set on screen — looking at it and accepting it is not
|
|
|
334
335
|
|
|
335
336
|
h.next();
|
|
336
337
|
|
|
337
|
-
assert.deepEqual(h.state.modules, ['
|
|
338
|
-
assert.deepEqual(h.forCreate(), ['
|
|
338
|
+
assert.deepEqual(h.state.modules, ['npm-release', 'discord'], 'the preselect becomes the answer');
|
|
339
|
+
assert.deepEqual(h.forCreate(), ['npm-release', 'discord'], 'and rides the create request');
|
|
339
340
|
});
|
|
340
341
|
|
|
341
342
|
test('changing the project type re-preselects while nothing has been said, and never after', async () => {
|
|
@@ -414,7 +415,7 @@ test('the review names the set even when the picker was never opened (task 10023
|
|
|
414
415
|
// and the module NAMES are not known. "None — just the core" would be a false
|
|
415
416
|
// promise on the last screen before Create: the server is about to apply the
|
|
416
417
|
// type's bundle. The row says that instead, then repaints when the read lands.
|
|
417
|
-
const h = boot({ state: { projType: 'game', step:
|
|
418
|
+
const h = boot({ state: { projType: 'game', step: 8 } }); // 8 = the review since task 1004351
|
|
418
419
|
|
|
419
420
|
// Arrange + Act — the review asks for the value before the read can answer
|
|
420
421
|
const first = h.extrasRow();
|
|
@@ -425,7 +426,7 @@ test('the review names the set even when the picker was never opened (task 10023
|
|
|
425
426
|
|
|
426
427
|
await h.settle();
|
|
427
428
|
assert.equal(h.reviews(), 1, 'the review repaints itself once the names are known');
|
|
428
|
-
assert.equal(h.extrasRow(), '
|
|
429
|
+
assert.equal(h.extrasRow(), 'npm releases, Discord', 'and then it names them');
|
|
429
430
|
});
|
|
430
431
|
|
|
431
432
|
test('the review reads an answered picker back, empty included', async () => {
|
|
@@ -454,26 +455,26 @@ test('the team shape adjusts the type bundle, and the panel says why in the owne
|
|
|
454
455
|
// Act
|
|
455
456
|
await h.open();
|
|
456
457
|
|
|
457
|
-
// Assert — the
|
|
458
|
+
// Assert — the release pipeline came off, and the reason is on screen
|
|
458
459
|
assert.deepEqual(h.rows().filter((r) => r.pressed).map((r) => r.key), ['discord'],
|
|
459
|
-
'solo drops
|
|
460
|
+
'solo drops npm releases, which the game bundle would have preselected');
|
|
460
461
|
assert.equal(h.whyShown(), true);
|
|
461
|
-
assert.deepEqual(h.why(), ['You are building this alone, so the
|
|
462
|
+
assert.deepEqual(h.why(), ['You are building this alone, so the release pipeline is off.']);
|
|
462
463
|
});
|
|
463
464
|
|
|
464
465
|
test('a rule that changed nothing claims nothing', async () => {
|
|
465
|
-
// small-team ADDS
|
|
466
|
+
// small-team ADDS npm-release, and the game bundle already carries it. A reason
|
|
466
467
|
// shown for a change that did not happen is a claim the owner cannot check.
|
|
467
468
|
const h = boot({ state: { projType: 'game', teamShape: 'small-team' }, payload: TAILORED });
|
|
468
469
|
await h.open();
|
|
469
470
|
|
|
470
|
-
assert.deepEqual(h.rows().filter((r) => r.pressed).map((r) => r.key), ['
|
|
471
|
+
assert.deepEqual(h.rows().filter((r) => r.pressed).map((r) => r.key), ['npm-release', 'discord']);
|
|
471
472
|
// No adjustment, so no adjustment-sentence — the rule still claims nothing. But the
|
|
472
473
|
// panel is not silent: the TYPE explains its own preselect (task 1003684), because two
|
|
473
474
|
// extras switched on with no reason beside them is the state ADR 0237 calls worse than
|
|
474
475
|
// no preselect at all. The rule's voice and the type's voice are different things.
|
|
475
|
-
assert.deepEqual(h.why(), ['Somewhere for players to gather, and a
|
|
476
|
-
assert.ok(!h.why().some((s) => /
|
|
476
|
+
assert.deepEqual(h.why(), ['Somewhere for players to gather, and a way to publish each release of the game.']);
|
|
477
|
+
assert.ok(!h.why().some((s) => /release pipeline|somewhere for it to gather is switched on/.test(s)),
|
|
477
478
|
'and no RULE sentence is invented for a change that did not happen');
|
|
478
479
|
assert.equal(h.whyShown(), true, 'the block carries the type\'s reason');
|
|
479
480
|
});
|
|
@@ -496,7 +497,7 @@ test('answering the About step later re-preselects, with no second read', async
|
|
|
496
497
|
h.state.teamShape = 'small-team';
|
|
497
498
|
h.render();
|
|
498
499
|
|
|
499
|
-
assert.deepEqual(h.rows().filter((r) => r.pressed).map((r) => r.key), ['agents', '
|
|
500
|
+
assert.deepEqual(h.rows().filter((r) => r.pressed).map((r) => r.key), ['agents', 'npm-release']);
|
|
500
501
|
assert.equal(h.fetches(), 1, 'and the answer cost no round trip');
|
|
501
502
|
});
|
|
502
503
|
|
|
@@ -520,9 +521,9 @@ test('once the owner has answered, the reasons go — the panel stops arguing',
|
|
|
520
521
|
await h.open();
|
|
521
522
|
assert.equal(h.why().length, 1);
|
|
522
523
|
|
|
523
|
-
h.click('
|
|
524
|
+
h.click('npm-release');
|
|
524
525
|
|
|
525
|
-
assert.deepEqual(h.state.modules, ['
|
|
526
|
+
assert.deepEqual(h.state.modules, ['npm-release', 'discord'], 'their toggle stands over the rule');
|
|
526
527
|
assert.deepEqual(h.why(), [], 'and the rule stops explaining a set that is no longer its own');
|
|
527
528
|
assert.equal(h.whyShown(), false);
|
|
528
529
|
});
|
|
@@ -533,10 +534,10 @@ test('a payload with no byTeamShape degrades to the untailored bundle, never to
|
|
|
533
534
|
const h = boot({ state: { projType: 'game', teamShape: 'solo' }, payload: PAYLOAD });
|
|
534
535
|
await h.open();
|
|
535
536
|
|
|
536
|
-
assert.deepEqual(h.rows().filter((r) => r.pressed).map((r) => r.key), ['
|
|
537
|
+
assert.deepEqual(h.rows().filter((r) => r.pressed).map((r) => r.key), ['npm-release', 'discord']);
|
|
537
538
|
// Degraded, but still explained: with no byTeamShape there is no adjustment to report,
|
|
538
539
|
// and the type's own summary is exactly the untailored reason (task 1003684).
|
|
539
|
-
assert.deepEqual(h.why(), ['Somewhere for players to gather, and a
|
|
540
|
+
assert.deepEqual(h.why(), ['Somewhere for players to gather, and a way to publish each release of the game.']);
|
|
540
541
|
});
|
|
541
542
|
|
|
542
543
|
test('an off-vocabulary team shape adjusts nothing', async () => {
|
|
@@ -545,10 +546,10 @@ test('an off-vocabulary team shape adjusts nothing', async () => {
|
|
|
545
546
|
const h = boot({ state: { projType: 'game', teamShape: 'a-huge-corporation' }, payload: TAILORED });
|
|
546
547
|
await h.open();
|
|
547
548
|
|
|
548
|
-
assert.deepEqual(h.rows().filter((r) => r.pressed).map((r) => r.key), ['
|
|
549
|
+
assert.deepEqual(h.rows().filter((r) => r.pressed).map((r) => r.key), ['npm-release', 'discord']);
|
|
549
550
|
// No rule fires, so nothing is CLAIMED about the unknown shape — the sentence shown is
|
|
550
551
|
// the type's, which is true regardless of who is building it (task 1003684).
|
|
551
|
-
assert.deepEqual(h.why(), ['Somewhere for players to gather, and a
|
|
552
|
+
assert.deepEqual(h.why(), ['Somewhere for players to gather, and a way to publish each release of the game.']);
|
|
552
553
|
});
|
|
553
554
|
|
|
554
555
|
test('a couldn\'t-load read clears any reasons it had already painted', async () => {
|
|
@@ -617,17 +618,17 @@ test('the picker and the server resolve the same set for every answer an owner c
|
|
|
617
618
|
// not move.
|
|
618
619
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
619
620
|
|
|
620
|
-
const
|
|
621
|
+
const NPM_RELEASE = PAYLOAD.optional[1];
|
|
621
622
|
|
|
622
623
|
test('the matcher reads a row\'s own words — its name, its summary and its key', () => {
|
|
623
624
|
// Still lexical, never semantic: the only words it knows are the ones the owner
|
|
624
625
|
// can see, plus the two it cannot — the key, and the declared aliases below.
|
|
625
626
|
const h = boot({ state: { projType: 'game' } });
|
|
626
627
|
|
|
627
|
-
assert.equal(h.matches(
|
|
628
|
-
assert.equal(h.matches(
|
|
629
|
-
assert.equal(h.matches(
|
|
630
|
-
assert.equal(h.matches(
|
|
628
|
+
assert.equal(h.matches(NPM_RELEASE, 'releases'), true, 'by a word in the name');
|
|
629
|
+
assert.equal(h.matches(NPM_RELEASE, 'package'), true, 'by a word in the summary');
|
|
630
|
+
assert.equal(h.matches(NPM_RELEASE, 'npm-release'), true, 'by the key');
|
|
631
|
+
assert.equal(h.matches(NPM_RELEASE, 'discord'), false, 'and never by a word that is in none of them');
|
|
631
632
|
});
|
|
632
633
|
|
|
633
634
|
// ── task 1003500: the aliases, and the gap they close ────────────────────────
|
|
@@ -686,9 +687,9 @@ test('aliases narrow like any other word, and a row without them still matches o
|
|
|
686
687
|
const discord = PAYLOAD.optional[2];
|
|
687
688
|
|
|
688
689
|
assert.equal(h.matches(discord, 'chat news'), true, 'an alias AND a summary word still narrows to the row that has both');
|
|
689
|
-
assert.equal(h.matches(discord, 'chat
|
|
690
|
-
assert.equal(h.matches(
|
|
691
|
-
assert.equal(h.matches(
|
|
690
|
+
assert.equal(h.matches(discord, 'chat package'), false, 'an alias does not rescue a term the row cannot answer');
|
|
691
|
+
assert.equal(h.matches(NPM_RELEASE, 'chat'), false, 'and a row declaring no aliases is not matched by another row\'s');
|
|
692
|
+
assert.equal(h.matches(NPM_RELEASE, 'package'), true, 'while its own words keep working untouched');
|
|
692
693
|
});
|
|
693
694
|
|
|
694
695
|
test('typing an alias filters the band to that row, and the count says so', async () => {
|
|
@@ -704,14 +705,14 @@ test('typing an alias filters the band to that row, and the count says so', asyn
|
|
|
704
705
|
test('a term matches as a SUBSTRING, so a half-typed word still finds its module', () => {
|
|
705
706
|
const h = boot({ state: { projType: 'game' } });
|
|
706
707
|
|
|
707
|
-
assert.equal(h.matches(
|
|
708
|
-
assert.equal(h.matches(
|
|
708
|
+
assert.equal(h.matches(NPM_RELEASE, 'pack'), true, 'the owner is still typing');
|
|
709
|
+
assert.equal(h.matches(NPM_RELEASE, 'kag'), true, 'even mid-word — a dumb filter beats a clever one that finds nothing');
|
|
709
710
|
});
|
|
710
711
|
|
|
711
712
|
test('matching is case-insensitive in both directions', () => {
|
|
712
713
|
const h = boot({ state: { projType: 'game' } });
|
|
713
714
|
|
|
714
|
-
assert.equal(h.matches(
|
|
715
|
+
assert.equal(h.matches(NPM_RELEASE, 'NPM RELEASES'), true, 'a shouted query finds a title-cased row');
|
|
715
716
|
assert.equal(h.matches(PAYLOAD.optional[0], 'ai'), true, 'and a quiet one finds a shouted row');
|
|
716
717
|
});
|
|
717
718
|
|
|
@@ -719,15 +720,15 @@ test('every term must match — typing more words narrows, it never widens', ()
|
|
|
719
720
|
// AND, not OR. An owner adding a word is telling us the list is still too long.
|
|
720
721
|
const h = boot({ state: { projType: 'game' } });
|
|
721
722
|
|
|
722
|
-
assert.equal(h.matches(
|
|
723
|
-
assert.equal(h.matches(
|
|
723
|
+
assert.equal(h.matches(NPM_RELEASE, 'package registry'), true, 'both words are in the row');
|
|
724
|
+
assert.equal(h.matches(NPM_RELEASE, 'package discord'), false, 'one of them is not, so the row is out');
|
|
724
725
|
});
|
|
725
726
|
|
|
726
727
|
test('an empty search is not a search — every row stays', () => {
|
|
727
728
|
const h = boot({ state: { projType: 'game' } });
|
|
728
729
|
|
|
729
|
-
assert.equal(h.matches(
|
|
730
|
-
assert.equal(h.matches(
|
|
730
|
+
assert.equal(h.matches(NPM_RELEASE, ''), true);
|
|
731
|
+
assert.equal(h.matches(NPM_RELEASE, ' '), true, 'and a box holding only spaces is the same as an empty one');
|
|
731
732
|
});
|
|
732
733
|
|
|
733
734
|
test('typing filters the band to the rows that answer, in catalog order', async () => {
|
|
@@ -737,21 +738,21 @@ test('typing filters the band to the rows that answer, in catalog order', async
|
|
|
737
738
|
assert.equal(h.rows().length, 3, 'the whole catalog before anything is typed');
|
|
738
739
|
|
|
739
740
|
// Act — a word from one row's NAME
|
|
740
|
-
h.type('
|
|
741
|
+
h.type('releases');
|
|
741
742
|
|
|
742
743
|
// Assert
|
|
743
|
-
assert.deepEqual(h.rows().map((r) => r.key), ['
|
|
744
|
+
assert.deepEqual(h.rows().map((r) => r.key), ['npm-release']);
|
|
744
745
|
});
|
|
745
746
|
|
|
746
747
|
test('a summary word and a key find their row just as a name word does', async () => {
|
|
747
748
|
const h = boot({ state: { projType: 'game' } });
|
|
748
749
|
await h.open();
|
|
749
750
|
|
|
750
|
-
h.type('
|
|
751
|
-
assert.deepEqual(h.rows().map((r) => r.key), ['
|
|
751
|
+
h.type('package');
|
|
752
|
+
assert.deepEqual(h.rows().map((r) => r.key), ['npm-release'], 'by summary');
|
|
752
753
|
|
|
753
|
-
h.type('
|
|
754
|
-
assert.deepEqual(h.rows().map((r) => r.key), ['
|
|
754
|
+
h.type('npm-release');
|
|
755
|
+
assert.deepEqual(h.rows().map((r) => r.key), ['npm-release'], 'by key');
|
|
755
756
|
|
|
756
757
|
h.type('DISCORD');
|
|
757
758
|
assert.deepEqual(h.rows().map((r) => r.key), ['discord'], 'and case is not part of the question');
|
|
@@ -761,10 +762,10 @@ test('two terms narrow to the rows that carry both', async () => {
|
|
|
761
762
|
const h = boot({ state: { projType: 'game' } });
|
|
762
763
|
await h.open();
|
|
763
764
|
|
|
764
|
-
h.type('
|
|
765
|
-
assert.deepEqual(h.rows().map((r) => r.key), ['
|
|
765
|
+
h.type('package registry');
|
|
766
|
+
assert.deepEqual(h.rows().map((r) => r.key), ['npm-release']);
|
|
766
767
|
|
|
767
|
-
h.type('
|
|
768
|
+
h.type('package discord');
|
|
768
769
|
assert.deepEqual(h.rows(), [], 'and no row carries both of these');
|
|
769
770
|
});
|
|
770
771
|
|
|
@@ -785,7 +786,7 @@ test('a switched-on module the search is HIDING is still switched on', async ()
|
|
|
785
786
|
// toggles, and a view must not be able to edit it.
|
|
786
787
|
const h = boot({ state: { projType: 'game' } });
|
|
787
788
|
await h.open();
|
|
788
|
-
assert.deepEqual(h.picked(), ['
|
|
789
|
+
assert.deepEqual(h.picked(), ['npm-release', 'discord'], 'the game bundle is on');
|
|
789
790
|
|
|
790
791
|
// Act — search for the one module that is OFF, hiding both that are on
|
|
791
792
|
h.type('agents');
|
|
@@ -793,7 +794,7 @@ test('a switched-on module the search is HIDING is still switched on', async ()
|
|
|
793
794
|
// Assert
|
|
794
795
|
assert.deepEqual(h.rows().map((r) => r.key), ['agents'], 'only the match is on screen');
|
|
795
796
|
assert.equal(h.rows()[0].pressed, false, 'and it is off, as it was');
|
|
796
|
-
assert.deepEqual(h.picked(), ['
|
|
797
|
+
assert.deepEqual(h.picked(), ['npm-release', 'discord'], 'the two hidden ones are still switched on');
|
|
797
798
|
assert.equal(h.state.modules, null, 'and typing answered nothing on the owner\'s behalf');
|
|
798
799
|
assert.match(h.filterNote(), /2 extras are switched on/, 'the count under the field says so out loud');
|
|
799
800
|
assert.match(h.filterNote(), /including any this search is hiding/, 'and that some of them are hidden');
|
|
@@ -820,16 +821,16 @@ test('with nothing switched on the count says only what is shown', async () => {
|
|
|
820
821
|
});
|
|
821
822
|
|
|
822
823
|
test('the review names the hidden picks too — a filter never reaches the create request', async () => {
|
|
823
|
-
const h = boot({ state: { projType: 'game', step:
|
|
824
|
+
const h = boot({ state: { projType: 'game', step: 8 } }); // 8 = the review since task 1004351
|
|
824
825
|
await h.open();
|
|
825
826
|
h.click('agents');
|
|
826
|
-
assert.deepEqual(h.forCreate(), ['agents', '
|
|
827
|
+
assert.deepEqual(h.forCreate(), ['agents', 'npm-release', 'discord']);
|
|
827
828
|
|
|
828
829
|
h.type('discord');
|
|
829
830
|
|
|
830
831
|
assert.deepEqual(h.rows().map((r) => r.key), ['discord'], 'one row on screen');
|
|
831
|
-
assert.deepEqual(h.forCreate(), ['agents', '
|
|
832
|
-
assert.equal(h.extrasRow(), 'Extra agents,
|
|
832
|
+
assert.deepEqual(h.forCreate(), ['agents', 'npm-release', 'discord'], 'three modules in the answer');
|
|
833
|
+
assert.equal(h.extrasRow(), 'Extra agents, npm releases, Discord', 'and the review names all three');
|
|
833
834
|
});
|
|
834
835
|
|
|
835
836
|
test('clearing the search brings every row back, untouched', async () => {
|
|
@@ -840,8 +841,8 @@ test('clearing the search brings every row back, untouched', async () => {
|
|
|
840
841
|
|
|
841
842
|
h.type('');
|
|
842
843
|
|
|
843
|
-
assert.deepEqual(h.rows().map((r) => r.key), ['agents', '
|
|
844
|
-
assert.deepEqual(h.rows().filter((r) => r.pressed).map((r) => r.key), ['
|
|
844
|
+
assert.deepEqual(h.rows().map((r) => r.key), ['agents', 'npm-release', 'discord']);
|
|
845
|
+
assert.deepEqual(h.rows().filter((r) => r.pressed).map((r) => r.key), ['npm-release', 'discord'],
|
|
845
846
|
'with the preselect exactly as it was before the search');
|
|
846
847
|
assert.equal(h.filterNote(), '', 'and the count goes quiet again');
|
|
847
848
|
});
|
|
@@ -851,11 +852,11 @@ test('a toggle made while filtered lands in the answer in catalog order, and the
|
|
|
851
852
|
const h = boot({ state: { projType: 'non-profit' } });
|
|
852
853
|
await h.open();
|
|
853
854
|
|
|
854
|
-
h.type('
|
|
855
|
-
h.click('
|
|
855
|
+
h.type('package');
|
|
856
|
+
h.click('npm-release');
|
|
856
857
|
|
|
857
|
-
assert.deepEqual(h.state.modules, ['
|
|
858
|
-
assert.deepEqual(h.rows().map((r) => r.key), ['
|
|
858
|
+
assert.deepEqual(h.state.modules, ['npm-release', 'discord'], 'catalog order, not click order');
|
|
859
|
+
assert.deepEqual(h.rows().map((r) => r.key), ['npm-release'], 'and the search is still the search');
|
|
859
860
|
assert.equal(h.rows()[0].pressed, true, 'the row they just switched on shows it');
|
|
860
861
|
});
|
|
861
862
|
|
|
@@ -241,11 +241,10 @@ test('no wall-clock promises, no operator vocabulary on the stranger’s path',
|
|
|
241
241
|
assert.doesNotMatch(HUB, /about five minutes/, 'the empty state');
|
|
242
242
|
assert.doesNotMatch(HUB, /a runner does the real work/, 'the mine fine print');
|
|
243
243
|
assert.doesNotMatch(SCRIPT, /pinned platform core/, 'the review Shape row');
|
|
244
|
-
/*
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
assert.match(SCRIPT, /\['Shape', 'Cloud host — we stand it up and run it for you, on the platform’s current release'\]/);
|
|
244
|
+
/* Since ADR 0345 (task 1004351) the review names the HALL as hosted by us and gives the
|
|
245
|
+
APP its own row. The assertion still guards the same property this test is about:
|
|
246
|
+
owner vocabulary, and no wall-clock promise. */
|
|
247
|
+
assert.ok(SCRIPT.includes("['Builders\\' hall', 'Hosted by us, on the platform’s current release'],"), 'the review names the hall as hosted by us');
|
|
249
248
|
assert.doesNotMatch(HUB, /\/new-project<\/code> assistant/, 'the done panel’s closing line');
|
|
250
249
|
assert.match(HUB, /Sign-in switches on after the GitHub steps|switch sign-in on/, 'the address step does not promise sign-in "just works"');
|
|
251
250
|
assert.doesNotMatch(HUB, /Sign-in, sharing, everything just works/);
|
package/tests/provision.mjs
CHANGED
|
@@ -130,25 +130,25 @@ t('planCorePinRefresh: a trailing file: pin → refresh; a current one → no-op
|
|
|
130
130
|
// The W3 shape: repo pins 1.19.2, control plane runs 1.19.196 → refresh (heals
|
|
131
131
|
// the silent-rollback hazard before the next repo-driven deploy).
|
|
132
132
|
assert.deepEqual(
|
|
133
|
-
P.planCorePinRefresh({ pinnedDep: 'file:vendor/bongos-core-1.19.2.tgz', coreVersion: '1.19.196' }),
|
|
133
|
+
P.planCorePinRefresh({ pinnedDep: 'file:vendor/bongos-core-1.19.2.tgz', version: '1.0.0', coreVersion: '1.19.196' }),
|
|
134
134
|
{ action: 'refresh', pinned: '1.19.2' });
|
|
135
135
|
// Pin already matches the running core → nothing to commit.
|
|
136
136
|
assert.deepEqual(
|
|
137
|
-
P.planCorePinRefresh({ pinnedDep: 'file:vendor/bongos-core-1.19.196.tgz', coreVersion: '1.19.196' }),
|
|
137
|
+
P.planCorePinRefresh({ pinnedDep: 'file:vendor/bongos-core-1.19.196.tgz', version: '1.0.0', coreVersion: '1.19.196' }),
|
|
138
138
|
{ action: 'current', pinned: '1.19.196' });
|
|
139
139
|
// A non-canonical file: pin can't be compared → refresh heals it to canonical.
|
|
140
140
|
assert.equal(
|
|
141
|
-
P.planCorePinRefresh({ pinnedDep: 'file:vendor/core.tgz', coreVersion: '1.19.196' }).action,
|
|
141
|
+
P.planCorePinRefresh({ pinnedDep: 'file:vendor/core.tgz', version: '1.0.0', coreVersion: '1.19.196' }).action,
|
|
142
142
|
'refresh');
|
|
143
143
|
});
|
|
144
144
|
|
|
145
145
|
t('planCorePinRefresh: registry pins and absent deps are skipped, never rewritten', () => {
|
|
146
146
|
// Registry pin (ADR 0134): npm resolves it within its range — not ours to vendor.
|
|
147
|
-
assert.equal(P.planCorePinRefresh({ pinnedDep: '^1.19.100', coreVersion: '1.19.196' }).action, 'skip');
|
|
148
|
-
assert.equal(P.planCorePinRefresh({ pinnedDep: '1.19.196', coreVersion: '1.19.196' }).action, 'skip');
|
|
147
|
+
assert.equal(P.planCorePinRefresh({ pinnedDep: '^1.19.100', version: '1.0.0', coreVersion: '1.19.196' }).action, 'skip');
|
|
148
|
+
assert.equal(P.planCorePinRefresh({ pinnedDep: '1.19.196', version: '1.0.0', coreVersion: '1.19.196' }).action, 'skip');
|
|
149
149
|
// No @bongos/core dependency at all → not ours to invent.
|
|
150
|
-
assert.equal(P.planCorePinRefresh({ pinnedDep: undefined, coreVersion: '1.19.196' }).action, 'skip');
|
|
151
|
-
assert.equal(P.planCorePinRefresh({ pinnedDep: '', coreVersion: '1.19.196' }).action, 'skip');
|
|
150
|
+
assert.equal(P.planCorePinRefresh({ pinnedDep: undefined, version: '1.0.0', coreVersion: '1.19.196' }).action, 'skip');
|
|
151
|
+
assert.equal(P.planCorePinRefresh({ pinnedDep: '', version: '1.0.0', coreVersion: '1.19.196' }).action, 'skip');
|
|
152
152
|
});
|
|
153
153
|
|
|
154
154
|
t('safeVersionLabel: passes clean semver, rejects any shell metacharacter', () => {
|
|
@@ -248,7 +248,7 @@ await ta('dry-run measures nothing and refuses nothing, even on a full box; an u
|
|
|
248
248
|
|
|
249
249
|
await ta('statusCapacityLine prints "box at N of M" for the CLI', async () => {
|
|
250
250
|
delete process.env.PROVISION_BOX_CEILING;
|
|
251
|
-
// fleetCapacity reads THIS host's /proc/meminfo (absent on a Windows
|
|
251
|
+
// fleetCapacity reads THIS host's /proc/meminfo (absent on a Windows machine → default);
|
|
252
252
|
// the line must carry the count and the source whichever it is.
|
|
253
253
|
const line = await statusCapacityLine(fakeDb(12));
|
|
254
254
|
assert.match(line, /control-plane box: 12 of \d+ project slots taken \((measured|default)/);
|
|
@@ -147,7 +147,8 @@ test('a rule whose module the bundle already carries records no adjustment', ()
|
|
|
147
147
|
});
|
|
148
148
|
|
|
149
149
|
test('a removal rule whose module is absent records no adjustment', () => {
|
|
150
|
-
// Arrange — `non-profit` is `discord` alone,
|
|
150
|
+
// Arrange — `non-profit` is `discord` alone, and `solo` names no live rule (the removal rule for
|
|
151
|
+
// it left with the dev-box module, task 1003886), so there is nothing to remove or to claim.
|
|
151
152
|
const answers = { type: 'non-profit', team_shape: 'solo' };
|
|
152
153
|
|
|
153
154
|
// Act
|
|
@@ -37,7 +37,6 @@ import {
|
|
|
37
37
|
OPTIONAL_MODULES,
|
|
38
38
|
STARTER_BUNDLES,
|
|
39
39
|
HUB_ONLY_MODULES,
|
|
40
|
-
RETIRED_MODULES,
|
|
41
40
|
RULED_TEAM_SHAPES,
|
|
42
41
|
bundleForProjectType,
|
|
43
42
|
allStarterBundles,
|
|
@@ -336,9 +335,6 @@ test('the opt-in catalog declared for the picker IS the on-disk opt-in set', ()
|
|
|
336
335
|
const onDisk = manifestsOnDisk();
|
|
337
336
|
const optInOnDisk = [...onDisk.values()]
|
|
338
337
|
.filter((m) => m.default !== true && !HUB_ONLY_MODULES.includes(m.key))
|
|
339
|
-
// A RETIRED module is still on disk while its removal lands, and must already
|
|
340
|
-
// be gone from the catalog — so it is the one permitted absence (task 1003886).
|
|
341
|
-
.filter((m) => !RETIRED_MODULES.includes(m.key))
|
|
342
338
|
.map((m) => m.key)
|
|
343
339
|
.sort();
|
|
344
340
|
|
|
@@ -350,24 +346,6 @@ test('the opt-in catalog declared for the picker IS the on-disk opt-in set', ()
|
|
|
350
346
|
'the picker\'s catalog has drifted from the manifests — a module was added, removed, or flipped its default');
|
|
351
347
|
});
|
|
352
348
|
|
|
353
|
-
test('a RETIRED module is withdrawn everywhere an owner could still reach it', () => {
|
|
354
|
-
// The retirement is only real if it holds at every offer point at once. A key
|
|
355
|
-
// left in any one of them is still a module a new project can end up with,
|
|
356
|
-
// which is exactly what the withdrawal exists to stop (task 1003886).
|
|
357
|
-
for (const key of RETIRED_MODULES) {
|
|
358
|
-
assert.ok(!OPTIONAL_MODULES.some((m) => m.key === key),
|
|
359
|
-
`retired module "${key}" is still a row the creation picker offers`);
|
|
360
|
-
for (const [type, bundle] of Object.entries(STARTER_BUNDLES)) {
|
|
361
|
-
assert.ok(!bundle.modules.includes(key),
|
|
362
|
-
`retired module "${key}" is still preselected by the "${type}" bundle`);
|
|
363
|
-
}
|
|
364
|
-
// Posting it directly is a real answer that resolves to nothing, not a
|
|
365
|
-
// non-answer: the key is DROPPED, exactly as an unknown one would be.
|
|
366
|
-
assert.deepEqual(normalizeModuleSelection([key]), [],
|
|
367
|
-
`retired module "${key}" is still accepted when an owner posts it directly`);
|
|
368
|
-
}
|
|
369
|
-
});
|
|
370
|
-
|
|
371
349
|
test('every opt-in module carries owner-facing copy, and no module jargon rides along', () => {
|
|
372
350
|
for (const row of OPTIONAL_MODULES) {
|
|
373
351
|
assert.ok(row.label && row.label.length <= 40, `"${row.key}" needs a short label`);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// tests/publish_branch_route.mjs
|
|
2
2
|
//
|
|
3
3
|
// Coverage for server-mediated branch publish (ADR 0055 / task 1025) — the path
|
|
4
|
-
// that lets a
|
|
4
|
+
// that lets a builder on a checkout with no GitHub push credential ship: the client uploads a
|
|
5
5
|
// thin git bundle, the server pushes it + opens the PR + auto-merges with the
|
|
6
6
|
// server-side GITHUB_PUSH_TOKEN.
|
|
7
7
|
//
|
|
@@ -9,12 +9,10 @@
|
|
|
9
9
|
// 1. src/bongos/github-push.js — the push/PR/status logic, exercised through the
|
|
10
10
|
// injectable exec + fetch seams (deps.exec / deps.fetchImpl / deps.token /
|
|
11
11
|
// deps.repoInfo / deps.mkdtemp / deps.rmtmp).
|
|
12
|
-
// 2. The route wiring — a STRUCTURAL guard that POST/GET publish*
|
|
13
|
-
//
|
|
14
|
-
//
|
|
15
|
-
//
|
|
16
|
-
// out of it — a future edit that added allowBoxScope would reintroduce the
|
|
17
|
-
// "a leaked box token can ship arbitrary code" hole, and this test fails first.
|
|
12
|
+
// 2. The route wiring — a STRUCTURAL guard that POST/GET publish* and the
|
|
13
|
+
// attest route are requireBuilder-gated, so an unauthenticated caller (or a
|
|
14
|
+
// session of an undeclared source — tests/government_session_scope.mjs) can
|
|
15
|
+
// never publish.
|
|
18
16
|
//
|
|
19
17
|
// Run: node tests/publish_branch_route.mjs
|
|
20
18
|
|
|
@@ -407,8 +405,7 @@ await t('getPublishStatus: merged, workflow EXISTS but no successful run yet →
|
|
|
407
405
|
});
|
|
408
406
|
|
|
409
407
|
// ===========================================================================
|
|
410
|
-
// Layer 2 — STRUCTURAL guard: the publish routes are requireBuilder
|
|
411
|
-
// NO allowBoxScope (so a box-scoped session is denied — #919 regression guard).
|
|
408
|
+
// Layer 2 — STRUCTURAL guard: the publish routes are requireBuilder-gated.
|
|
412
409
|
// ===========================================================================
|
|
413
410
|
function findRouteLayer(router, routePath, method) {
|
|
414
411
|
return router.stack.find(
|
|
@@ -419,36 +416,31 @@ function handlesOf(layer) {
|
|
|
419
416
|
return layer.route.stack.map((s) => s.handle);
|
|
420
417
|
}
|
|
421
418
|
|
|
422
|
-
await t('POST /tasks/:id/publish-branch: requireBuilder present
|
|
419
|
+
await t('POST /tasks/:id/publish-branch: requireBuilder present', async () => {
|
|
423
420
|
const router = buildTasksRouter();
|
|
424
421
|
const layer = findRouteLayer(router, '/tasks/:id/publish-branch', 'post');
|
|
425
422
|
assert.ok(layer, 'the publish-branch POST route must be registered');
|
|
426
423
|
const handles = handlesOf(layer);
|
|
427
424
|
assert.ok(handles.includes(auth.requireBuilder), 'must be requireBuilder-gated');
|
|
428
|
-
assert.ok(!handles.includes(auth.allowBoxScope), 'must NOT carry allowBoxScope — a box session must be denied (#919)');
|
|
429
425
|
});
|
|
430
426
|
|
|
431
|
-
await t('GET /tasks/:id/publish-status: requireBuilder present
|
|
427
|
+
await t('GET /tasks/:id/publish-status: requireBuilder present', async () => {
|
|
432
428
|
const router = buildTasksRouter();
|
|
433
429
|
const layer = findRouteLayer(router, '/tasks/:id/publish-status', 'get');
|
|
434
430
|
assert.ok(layer, 'the publish-status GET route must be registered');
|
|
435
431
|
const handles = handlesOf(layer);
|
|
436
432
|
assert.ok(handles.includes(auth.requireBuilder), 'must be requireBuilder-gated');
|
|
437
|
-
assert.ok(!handles.includes(auth.allowBoxScope), 'must NOT carry allowBoxScope');
|
|
438
433
|
});
|
|
439
434
|
|
|
440
435
|
// task 1169 (ADR 0058): the laptop ci-land attest endpoint. It posts the trustless
|
|
441
|
-
// gate-author-trust commit status for a Metic+ author, so
|
|
442
|
-
//
|
|
443
|
-
|
|
444
|
-
// box gate (no allowBoxScope), matching publish-branch / publish-status exactly.
|
|
445
|
-
await t('POST /tasks/:id/attest-gate: requireBuilder present, allowBoxScope ABSENT', async () => {
|
|
436
|
+
// gate-author-trust commit status for a Metic+ author, so it carries the same
|
|
437
|
+
// requireBuilder gate as publish-branch / publish-status.
|
|
438
|
+
await t('POST /tasks/:id/attest-gate: requireBuilder present', async () => {
|
|
446
439
|
const router = buildTasksRouter();
|
|
447
440
|
const layer = findRouteLayer(router, '/tasks/:id/attest-gate', 'post');
|
|
448
441
|
assert.ok(layer, 'the attest-gate POST route must be registered');
|
|
449
442
|
const handles = handlesOf(layer);
|
|
450
443
|
assert.ok(handles.includes(auth.requireBuilder), 'must be requireBuilder-gated');
|
|
451
|
-
assert.ok(!handles.includes(auth.allowBoxScope), 'must NOT carry allowBoxScope — a box session must reauth, never self-attest (#919)');
|
|
452
444
|
});
|
|
453
445
|
|
|
454
446
|
// task 1002645: the attest call is the ONE moment the laptop ci-land tells the
|