@bongos/core 1.18.0 → 1.18.18
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 +1740 -640
- package/.claude/skills/builder-box/SKILL.md +6 -0
- package/.claude/skills/builder-sequence/SKILL.md +97 -0
- package/.claude/skills/builder-ship/SKILL.md +8 -2
- package/.claude/skills/builder-start/SKILL.md +11 -0
- package/.claude/skills/builder-sync/SKILL.md +41 -0
- package/.claude/skills/fix-task/SKILL.md +64 -0
- package/.claude/skills/goal-create/SKILL.md +158 -0
- package/.claude/skills/goal-review/SKILL.md +3 -1
- package/.claude/skills/idea-triage/SKILL.md +14 -8
- package/.claude/skills/merge-mode/SKILL.md +2 -2
- package/.claude/skills/new-project/SKILL.md +1 -0
- package/.claude/skills/planning-session/SKILL.md +45 -18
- package/.env.local.example +4 -3
- package/README.md +5 -3
- package/bin/bongos.js +1 -0
- package/clients/bongos-client/README.md +2 -2
- package/clients/bongos-client/bongos-client.global.js +82 -18
- package/clients/bongos-client/index.cjs +82 -18
- package/clients/bongos-client/index.d.ts +131 -29
- package/clients/bongos-client/index.mjs +82 -18
- package/config/branding.neutral.json +11 -2
- package/config/governance.neutral.json +21 -0
- package/docs/adr/0043-git-ssh-trust-boundary-and-rank-floor-on-permission-paths.md +24 -0
- package/docs/adr/0090-metic-task-authoring.md +1 -1
- package/docs/adr/0142-project-context-is-prose-only.md +41 -0
- package/docs/adr/0143-onboarding-repo-picker-oauth-scope.md +35 -0
- package/docs/adr/0144-devbox-rehome-onto-cloudbongos-plane.md +41 -0
- package/docs/adr/0144-federated-single-logout-backchannel.md +42 -0
- package/docs/adr/0145-devbox-app-branding-driven-module.md +44 -0
- package/docs/adr/0145-free-hosted-project-tier-isolation-and-domain-separation.md +62 -0
- package/docs/adr/0146-configurable-reward-mode-cost-plus-only.md +53 -0
- package/docs/adr/0147-standalone-live-docs-regen.md +39 -0
- package/docs/adr/0148-task-scoped-box-source-access.md +48 -0
- package/docs/adr/0149-bongos-dev-local-launcher.md +57 -0
- package/docs/adr/0150-box-first-boot-bringup-vendored-instances.md +68 -0
- package/docs/adr/0151-governance-permissions-as-atom-ranks-as-roles.md +229 -0
- package/docs/adr/0152-landing-gate-served-by-the-core-app.md +92 -0
- package/docs/adr/0152-metic-task-abandonment.md +67 -0
- package/docs/adr/0153-sequential-chain-runner.md +51 -0
- package/docs/adr/0154-goal-owner-criterion-authoring.md +86 -0
- package/docs/adr/README.md +15 -2
- package/docs/api/openapi.json +2648 -483
- package/docs/api-reference.md +73 -13
- package/docs/architecture.md +4 -2
- package/docs/branding-contract.md +3 -1
- package/docs/canonical-permissions.md +21 -8
- package/docs/file-map.md +15 -7
- package/docs/onboarding/diagrams/01-task-lifecycle.mmd +2 -2
- package/docs/onboarding/diagrams/02-rank-ladder.mmd +3 -1
- package/docs/onboarding/diagrams/05-versioning.mmd +1 -3
- package/docs/onboarding/diagrams/assertions.json +4 -7
- package/docs/onboarding/drachmae-and-karma.md +3 -3
- package/docs/onboarding/primer.md +17 -16
- package/docs/onboarding/slash-commands.md +124 -0
- package/docs/recipes/builder-box-lifecycle.md +23 -0
- package/docs/recipes/devbox-app-release.md +14 -4
- package/docs/recipes/instance-first-run.md +16 -0
- package/docs/recipes/local-dev.md +81 -0
- package/docs/recipes/ops-gotchas.md +19 -1
- package/docs/recipes/overnight-builder.md +1 -1
- package/docs/recipes/packaging-the-core.md +13 -0
- package/docs/recipes/parallel-goal-waves.md +174 -0
- package/docs/recipes/ship-and-regrade.md +2 -2
- package/docs/recipes/standalone-live-docs.md +48 -0
- package/docs/recipes/upgrading-the-core.md +5 -2
- package/docs/recipes/work-on-a-task-from-your-dev-box.md +108 -0
- package/migrations/003_pms.sql +6 -7
- package/migrations/006_pms_v2.sql +3 -31
- package/migrations/039_newcomer_friendly_column.sql +5 -0
- package/migrations/102_remove_box_first_provision_approval.sql +4 -0
- package/migrations/core_192_github_oauth_tokens.sql +33 -0
- package/migrations/core_193_task_needs_rebase.sql +28 -0
- package/migrations/core_194_confirmed_backlog_clawback.sql +63 -0
- package/migrations/core_195_idea_triage_provenance.sql +57 -0
- package/migrations/core_196_session_reward_topup.sql +69 -0
- package/modules/autonomy/pollers/routine-timers.js +9 -8
- package/modules/builder-settings/wandering-prefs.js +61 -18
- package/modules/dev-box/app/README.md +73 -0
- package/modules/dev-box/app/assets/tray/mac-alertTemplate.png +0 -0
- package/modules/dev-box/app/assets/tray/mac-alertTemplate@2x.png +0 -0
- package/modules/dev-box/app/assets/tray/mac-busyTemplate.png +0 -0
- package/modules/dev-box/app/assets/tray/mac-busyTemplate@2x.png +0 -0
- package/modules/dev-box/app/assets/tray/mac-offTemplate.png +0 -0
- package/modules/dev-box/app/assets/tray/mac-offTemplate@2x.png +0 -0
- package/modules/dev-box/app/assets/tray/mac-onTemplate.png +0 -0
- package/modules/dev-box/app/assets/tray/mac-onTemplate@2x.png +0 -0
- package/modules/dev-box/app/assets/tray/win-alert.png +0 -0
- package/modules/dev-box/app/assets/tray/win-busy.png +0 -0
- package/modules/dev-box/app/assets/tray/win-off.png +0 -0
- package/modules/dev-box/app/assets/tray/win-on.png +0 -0
- package/modules/dev-box/app/build-resources/dmg-background.png +0 -0
- package/modules/dev-box/app/build-resources/dmg-background@2x.png +0 -0
- package/modules/dev-box/app/build-resources/icon.png +0 -0
- package/modules/dev-box/app/electron-builder.tmpl.yml +95 -0
- package/modules/dev-box/app/electron-builder.yml +95 -0
- package/modules/dev-box/app/package-lock.json +5291 -0
- package/modules/dev-box/app/package.json +30 -0
- package/modules/dev-box/app/renderer/app.js +319 -0
- package/modules/dev-box/app/renderer/index.html +45 -0
- package/modules/dev-box/app/renderer/styles.css +246 -0
- package/modules/dev-box/app/renderer/theme.generated.css +16 -0
- package/modules/dev-box/app/scripts/apply-dmg-backdrop.sh +82 -0
- package/modules/dev-box/app/scripts/dmgbuild-settings.py +39 -0
- package/modules/dev-box/app/scripts/gen-brand.js +138 -0
- package/modules/dev-box/app/scripts/gen-dmg-backdrop.py +323 -0
- package/modules/dev-box/app/scripts/gen-icons.js +286 -0
- package/modules/dev-box/app/scripts/vendor-client.js +23 -0
- package/modules/dev-box/app/src/box-controller.js +287 -0
- package/modules/dev-box/app/src/brand.generated.json +13 -0
- package/modules/dev-box/app/src/brand.js +16 -0
- package/modules/dev-box/app/src/config.js +19 -0
- package/modules/dev-box/app/src/gds-client.js +90 -0
- package/modules/dev-box/app/src/main.js +610 -0
- package/modules/dev-box/app/src/pairing.js +57 -0
- package/modules/dev-box/app/src/preload.js +29 -0
- package/modules/dev-box/app/src/session-store.js +58 -0
- package/modules/dev-box/app/src/ssh-setup.js +569 -0
- package/modules/dev-box/app/src/task-picker.js +70 -0
- package/modules/dev-box/app/src/tray.js +99 -0
- package/modules/dev-box/app/src/updater.js +55 -0
- package/modules/dev-box/app/src/vendor/bongos-client.cjs +698 -0
- package/modules/dev-box/app/test/unit.mjs +526 -0
- package/modules/dev-box/box-access.js +121 -4
- package/modules/dev-box/boxes.js +20 -0
- package/modules/dev-box/module.json +1 -1
- package/modules/dev-box/routes/box.js +87 -3
- package/modules/economy/credits.js +116 -25
- package/modules/economy/reward.js +8 -0
- package/modules/economy/streak.js +2 -1
- package/modules/governance/CLAUDE.md +15 -0
- package/modules/governance/catalog.js +419 -0
- package/modules/governance/config.js +174 -0
- package/modules/governance/db.js +125 -0
- package/modules/governance/migrations/governance_001_tables.sql +81 -0
- package/modules/governance/migrations/governance_002_seed_and_backfill.sql +103 -0
- package/modules/governance/migrations/governance_003_task_abandon_metic.sql +28 -0
- package/modules/governance/migrations/governance_004_criterion_create.sql +39 -0
- package/modules/governance/migrations/governance_005_resync_builder_rank_roles.sql +56 -0
- package/modules/governance/module.json +10 -0
- package/modules/governance/protected-surfaces.js +88 -0
- package/modules/governance/protected-surfaces.json +80 -0
- package/modules/governance/rank-roles.js +83 -0
- package/modules/governance/resolver.js +221 -0
- package/modules/governance/routes/governance.js +89 -0
- package/modules/governance/routes/role-sync.js +49 -0
- package/modules/governance/session-scopes.js +127 -0
- package/modules/governance/session-scopes.json +38 -0
- package/modules/hall-ui/public/atlas.css +117 -0
- package/modules/hall-ui/public/atlas.html +3 -114
- package/modules/hall-ui/public/brand-apply.js +34 -6
- package/modules/hall-ui/public/builders.js +187 -219
- package/modules/hall-ui/public/diagrams.html +2 -0
- package/modules/hall-ui/public/dom-utils.js +27 -0
- package/modules/hall-ui/public/drachmae.html +2 -0
- package/modules/hall-ui/public/drachmae.js +54 -1
- package/modules/hall-ui/public/gate.css +98 -0
- package/modules/hall-ui/public/gate.html +3 -95
- package/modules/hall-ui/public/goal-inbox.js +4 -1
- package/modules/hall-ui/public/goals-page.js +626 -114
- package/modules/hall-ui/public/goals.css +79 -1
- package/modules/hall-ui/public/goals.html +38 -11
- package/modules/hall-ui/public/hall-widgets.js +62 -52
- package/modules/hall-ui/public/harbor.css +123 -0
- package/modules/hall-ui/public/harbor.html +3 -120
- package/modules/hall-ui/public/index.html +11 -4
- package/modules/hall-ui/public/instances-panel.js +4 -1
- package/modules/hall-ui/public/modules.css +63 -0
- package/modules/hall-ui/public/modules.html +3 -60
- package/modules/hall-ui/public/my-projects.html +78 -0
- package/modules/hall-ui/public/my-projects.js +142 -0
- package/modules/hall-ui/public/new-project-wizard.js +7 -3
- package/modules/hall-ui/public/not-ready.html +2 -0
- package/modules/hall-ui/public/pair.css +73 -0
- package/modules/hall-ui/public/pair.html +3 -70
- package/modules/hall-ui/public/palette.js +302 -0
- package/modules/hall-ui/public/primer.css +13 -0
- package/modules/hall-ui/public/primer.html +2 -0
- package/modules/hall-ui/public/profile.html +2 -0
- package/modules/hall-ui/public/ranks.html +2 -0
- package/modules/hall-ui/public/roadmap.css +179 -0
- package/modules/hall-ui/public/roadmap.html +9 -172
- package/modules/hall-ui/public/roadmap.js +329 -316
- package/modules/hall-ui/public/scouting.html +45 -0
- package/modules/hall-ui/public/scouting.js +73 -0
- package/modules/hall-ui/public/sessions.css +16 -0
- package/modules/hall-ui/public/sessions.html +3 -13
- package/modules/hall-ui/public/settings.html +2 -0
- package/modules/hall-ui/public/settings.js +4 -4
- package/modules/hall-ui/public/shell.js +143 -18
- package/modules/hall-ui/public/style.css +231 -39
- package/modules/hall-ui/public/task-detail.js +347 -26
- package/modules/hall-ui/public/task.html +6 -1
- package/modules/hall-ui/public/watch.html +2 -0
- package/modules/hall-ui/public/work.html +8 -6
- package/modules/hall-ui/public/work.js +1 -1
- package/modules/ideas/inbox.js +61 -11
- package/modules/ideas/routes/inbox.js +23 -0
- package/modules/lifecycle/claim-eligibility.js +72 -0
- package/modules/lifecycle/db.js +519 -139
- package/modules/lifecycle/done-when.js +18 -3
- package/modules/lifecycle/github-push.js +19 -2
- package/modules/lifecycle/goal-advisory.js +114 -0
- package/modules/lifecycle/goal-authz.js +62 -0
- package/modules/lifecycle/lifecycle.js +9 -0
- package/modules/lifecycle/publish-reconciler.js +109 -6
- package/modules/lifecycle/routes/claims.js +68 -23
- package/modules/lifecycle/routes/done-when.js +110 -15
- package/modules/lifecycle/routes/goals.js +8 -38
- package/modules/lifecycle/routes/tasks.js +38 -6
- package/modules/lifecycle/ship-card.js +6 -0
- package/modules/onboarding/onboarding-state.js +1 -1
- package/modules/platform-identity/CLAUDE.md +15 -0
- package/modules/platform-identity/hub-device.js +147 -0
- package/modules/platform-identity/hub-keys.js +151 -0
- package/modules/platform-identity/migrations/platform_identity_001_tables.sql +114 -0
- package/modules/platform-identity/migrations/platform_identity_002_client_origin_index.sql +18 -0
- package/modules/platform-identity/migrations/platform_identity_003_project_memberships.sql +42 -0
- package/modules/platform-identity/migrations/platform_identity_004_projects.sql +44 -0
- package/modules/platform-identity/migrations/platform_identity_005_builder_activity.sql +42 -0
- package/modules/platform-identity/module.json +18 -0
- package/modules/platform-identity/platform-identity.js +531 -0
- package/modules/platform-identity/pollers/code-sweep.js +43 -0
- package/modules/platform-identity/routes/my-projects.js +140 -0
- package/modules/platform-identity/routes/projects.js +107 -0
- package/modules/platform-identity/routes/scouting.js +35 -0
- package/modules/platform-identity/routes/sso.js +402 -0
- package/modules/platform-identity/tests/platform-identity.mjs +388 -0
- package/modules/platform-identity/ui/.gitkeep +0 -0
- package/modules/security/routes/reports.js +10 -0
- package/modules/sessions/db.js +166 -21
- package/modules/sessions/module.json +1 -1
- package/modules/sessions/pollers/session-reward-reconcile.js +78 -0
- package/modules/sessions/routes/sessions.js +33 -0
- package/modules/status-ui/public/explore.html +54 -0
- package/modules/status-ui/public/explore.js +67 -0
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/public-landing/assets/anim/idle/frame_00.webp +0 -0
- package/public-landing/assets/anim/idle/frame_01.webp +0 -0
- package/public-landing/assets/anim/idle/frame_02.webp +0 -0
- package/public-landing/assets/anim/idle/frame_03.webp +0 -0
- package/public-landing/assets/anim/idle/frame_04.webp +0 -0
- package/public-landing/assets/anim/idle/frame_05.webp +0 -0
- package/public-landing/assets/anim/idle/frame_06.webp +0 -0
- package/public-landing/assets/anim/idle/frame_07.webp +0 -0
- package/public-landing/assets/anim/idle/frame_08.webp +0 -0
- package/public-landing/assets/anim/idle/frame_09.webp +0 -0
- package/public-landing/assets/anim/idle/frame_10.webp +0 -0
- package/public-landing/assets/anim/idle/frame_11.webp +0 -0
- package/public-landing/assets/anim/idle/frame_12.webp +0 -0
- package/public-landing/assets/anim/idle/frame_13.webp +0 -0
- package/public-landing/assets/anim/idle/frame_14.webp +0 -0
- package/public-landing/assets/anim/idle/frame_15.webp +0 -0
- package/public-landing/assets/anim/idle/frame_16.webp +0 -0
- package/public-landing/assets/anim/idle/frame_17.webp +0 -0
- package/public-landing/assets/anim/idle/frame_18.webp +0 -0
- package/public-landing/assets/anim/idle/frame_19.webp +0 -0
- package/public-landing/assets/anim/idle/frame_20.webp +0 -0
- package/public-landing/assets/anim/idle/frame_21.webp +0 -0
- package/public-landing/assets/anim/idle/frame_22.webp +0 -0
- package/public-landing/assets/anim/idle/frame_23.webp +0 -0
- package/public-landing/assets/anim/idle/frame_24.webp +0 -0
- package/public-landing/assets/anim/idle/frame_25.webp +0 -0
- package/public-landing/assets/anim/idle/frame_26.webp +0 -0
- package/public-landing/assets/anim/idle/frame_27.webp +0 -0
- package/public-landing/assets/anim/idle/frame_28.webp +0 -0
- package/public-landing/assets/anim/idle/frame_29.webp +0 -0
- package/public-landing/assets/anim/idle/frame_30.webp +0 -0
- package/public-landing/assets/anim/idle/frame_31.webp +0 -0
- package/public-landing/assets/anim/idle/frame_32.webp +0 -0
- package/public-landing/assets/anim/idle/frame_33.webp +0 -0
- package/public-landing/assets/anim/idle/frame_34.webp +0 -0
- package/public-landing/assets/anim/idle/frame_35.webp +0 -0
- package/public-landing/assets/anim/idle/frame_36.webp +0 -0
- package/public-landing/assets/anim/idle/frame_37.webp +0 -0
- package/public-landing/assets/anim/idle/frame_38.webp +0 -0
- package/public-landing/assets/anim/idle/frame_39.webp +0 -0
- package/public-landing/assets/anim/idle/frame_40.webp +0 -0
- package/public-landing/assets/anim/idle/frame_41.webp +0 -0
- package/public-landing/assets/anim/idle/frame_42.webp +0 -0
- package/public-landing/assets/anim/idle/frame_43.webp +0 -0
- package/public-landing/create.html +252 -62
- package/public-landing/gate/README.md +137 -0
- package/public-landing/gate/dispmaps/bar.png +0 -0
- package/public-landing/gate/dispmaps/flat.png +0 -0
- package/public-landing/gate/dispmaps/key.png +0 -0
- package/public-landing/gate/index.html +587 -0
- package/public-landing/gate/previews/cloud-bongos.jpg +0 -0
- package/public-landing/gate/previews/off-the-boats.jpg +0 -0
- package/public-landing/gate/space.css +799 -0
- package/public-landing/gate/space.js +1826 -0
- package/public-landing/gate/sprites/enter.png +0 -0
- package/public-landing/gate/sprites/search.png +0 -0
- package/public-landing/gate/sprites/terminal-blank.png +0 -0
- package/public-landing/gate/sprites/tile.png +0 -0
- package/public-landing/world/assets/clouds/cloud-island-large.png +0 -0
- package/public-landing/world/assets/clouds/cloud-puff.png +0 -0
- package/public-landing/world/assets/creatures/creature-whale-cargo.png +0 -0
- package/public-landing/world/assets/creatures/creature-whale-glide.png +0 -0
- package/public-landing/world/assets/plates/plate-clouds-near.png +0 -0
- package/public-landing/world/assets/plates/scene-establishing.png +0 -0
- package/scripts/gds/abandon.js +6 -4
- package/scripts/gds/api.js +10 -5
- package/scripts/gds/api.ps1 +26 -0
- package/scripts/gds/api.sh +6 -0
- package/scripts/gds/audit-ships.js +7 -1
- package/scripts/gds/backfill-sessions.js +78 -18
- package/scripts/gds/backup.js +23 -1
- package/scripts/gds/box-connect-lib.js +124 -31
- package/scripts/gds/box-sync.js +66 -0
- package/scripts/gds/box.js +225 -53
- package/scripts/gds/cf-tunnel.js +145 -6
- package/scripts/gds/claim.js +34 -3
- package/scripts/gds/cli-lib.js +161 -21
- package/scripts/gds/code.js +6 -2
- package/scripts/gds/deploy-divergence.js +209 -0
- package/scripts/gds/dev-lib.js +309 -0
- package/scripts/gds/dev.js +322 -0
- package/scripts/gds/doctor.js +90 -61
- package/scripts/gds/fetch-art-key.js +1 -1
- package/scripts/gds/fetch-sound-prefs.js +2 -2
- package/scripts/gds/fitness.js +228 -12
- package/scripts/gds/gds-db-backup.service +16 -1
- package/scripts/gds/gds-db-offsite.service +9 -1
- package/scripts/gds/gen-api-client.js +1 -1
- package/scripts/gds/gen-api-docs.js +36 -14
- package/scripts/gds/gen-diagrams.js +45 -28
- package/scripts/gds/gen-file-map.js +2 -1
- package/scripts/gds/gen-freshness.js +32 -0
- package/scripts/gds/gen-repo-map.js +3 -2
- package/scripts/gds/gen-session-index.js +14 -5
- package/scripts/gds/hooks/pre-push.js +2 -2
- package/scripts/gds/init.js +60 -3
- package/scripts/gds/install-git-hooks.js +1 -1
- package/scripts/gds/key-card.js +15 -6
- package/scripts/gds/key-status.js +1 -1
- package/scripts/gds/linkify-refs.js +35 -9
- package/scripts/gds/lint-migrations.js +102 -0
- package/scripts/gds/login.js +58 -11
- package/scripts/gds/mirror-redact.js +5 -0
- package/scripts/gds/package-core.js +263 -4
- package/scripts/gds/paste-token.js +7 -1
- package/scripts/gds/preflight.js +126 -0
- package/scripts/gds/provision.js +268 -19
- package/scripts/gds/publish-manifest.js +22 -1
- package/scripts/gds/reauth.js +7 -3
- package/scripts/gds/regen-instance-docs.js +47 -0
- package/scripts/gds/run-unit-tests.js +42 -16
- package/scripts/gds/seed-bongos-v1-seo-tasks.js +104 -0
- package/scripts/gds/seed-bv1-profiles-tasks.js +126 -0
- package/scripts/gds/sequence.js +425 -0
- package/scripts/gds/session-cost.js +10 -1
- package/scripts/gds/setup.js +72 -5
- package/scripts/gds/shell.js +5 -3
- package/scripts/gds/ship.js +433 -59
- package/scripts/gds/smoke-gds.js +139 -8
- package/scripts/gds/start.js +171 -22
- package/scripts/gds/status.js +1 -1
- package/scripts/gds/triage.js +10 -9
- package/scripts/gds/upgrade.js +271 -7
- package/scripts/migrate.sh +10 -0
- package/scripts/status-mirror-sync.js +114 -84
- package/src/bongos/api-errors.js +1 -0
- package/src/bongos/auth.js +809 -107
- package/src/bongos/db-kernel.js +27 -0
- package/src/bongos/db.js +105 -0
- package/src/bongos/github-repos.js +93 -0
- package/src/bongos/module-scope-map.js +27 -1
- package/src/bongos/permission-path-check.js +136 -102
- package/src/bongos/repo-info.js +47 -11
- package/src/bongos/route-rank-check.js +429 -46
- package/src/bongos/routes/_helpers.js +12 -1
- package/src/bongos/routes/auth.js +312 -13
- package/src/bongos/routes/backup.js +58 -2
- package/src/bongos/routes/github.js +90 -0
- package/src/bongos/routes/instance.js +15 -3
- package/src/bongos/routes/me.js +35 -1
- package/src/bongos/routes.js +2 -0
- package/src/bongos/serve-internal.js +84 -15
- package/src/branding.js +49 -2
- package/src/instance-config.js +46 -0
- package/src/module-api.js +140 -1
- package/src/platform-server.js +86 -9
- package/tests/abandon_task.mjs +59 -3
- package/tests/api_cli_flags.mjs +53 -0
- package/tests/auth_page_gate.mjs +13 -0
- package/tests/backfill_sessions.mjs +127 -1
- package/tests/backup_cli.mjs +54 -0
- package/tests/bongos_box_connect.mjs +77 -0
- package/tests/box_access.mjs +130 -1
- package/tests/box_credential.mjs +125 -0
- package/tests/box_host_keys.mjs +24 -0
- package/tests/box_infra_resolve.mjs +149 -0
- package/tests/box_scope_session.mjs +6 -2
- package/tests/box_sync.mjs +55 -0
- package/tests/box_task_scope.mjs +99 -0
- package/tests/branding.mjs +41 -23
- package/tests/cf_ruleset_preflight.mjs +142 -0
- package/tests/cf_tunnel.mjs +37 -0
- package/tests/claim_gate_rebase.mjs +98 -0
- package/tests/clawback_confirmed_backlog.mjs +103 -0
- package/tests/cli_lib_formatters.mjs +20 -0
- package/tests/confirm_attribution.mjs +30 -46
- package/tests/credit_grant.mjs +198 -10
- package/tests/criterion_authoring_gate.mjs +272 -0
- package/tests/criterion_progress.mjs +33 -1
- package/tests/dev_lib.mjs +253 -0
- package/tests/devbox_artifact_gate.mjs +75 -0
- package/tests/devbox_dmg_backdrop.mjs +137 -0
- package/tests/devbox_icons.mjs +304 -0
- package/tests/devbox_installer_brand.mjs +232 -0
- package/tests/devbox_key_register.mjs +234 -0
- package/tests/docs_openapi_route.mjs +110 -0
- package/tests/doctor_hooks.mjs +141 -0
- package/tests/extract_json.mjs +140 -0
- package/tests/fitness.mjs +89 -0
- package/tests/gen_freshness.mjs +96 -0
- package/tests/github_repos_helpers.mjs +91 -0
- package/tests/github_token_store.mjs +121 -0
- package/tests/goal_advisory.mjs +251 -0
- package/tests/goal_criterion_confirm_ui.mjs +269 -0
- package/tests/goal_routes.mjs +59 -4
- package/tests/goal_tier.mjs +12 -3
- package/tests/governance_catalog.mjs +129 -0
- package/tests/governance_claim_eligibility.mjs +262 -0
- package/tests/governance_config.mjs +187 -0
- package/tests/governance_ownership_scope.mjs +410 -0
- package/tests/governance_page_gates.mjs +324 -0
- package/tests/governance_parity.mjs +336 -0
- package/tests/governance_principals.mjs +232 -0
- package/tests/governance_protected_surfaces.mjs +208 -0
- package/tests/governance_require_permission.mjs +223 -0
- package/tests/governance_resolver.mjs +84 -0
- package/tests/governance_role_sync.mjs +231 -0
- package/tests/governance_seed.mjs +127 -0
- package/tests/governance_session_scope.mjs +434 -0
- package/tests/governance_vanilla_boot.mjs +203 -0
- package/tests/grade_attribution.mjs +7 -7
- package/tests/hall_audit.mjs +223 -0
- package/tests/hall_nav.mjs +191 -0
- package/tests/hall_palette.mjs +206 -0
- package/tests/hall_signout.mjs +124 -0
- package/tests/hook_user_agents.mjs +10 -0
- package/tests/idea_triage.mjs +162 -0
- package/tests/idp_device_exchange.mjs +198 -0
- package/tests/idp_federation.mjs +100 -0
- package/tests/init.mjs +51 -0
- package/tests/instance_config.mjs +65 -0
- package/tests/instance_mismatch_guard.mjs +23 -6
- package/tests/instance_release_config.mjs +104 -0
- package/tests/linkify_refs.mjs +15 -2
- package/tests/migrations_forward_safe.mjs +86 -0
- package/tests/module_api.mjs +1 -1
- package/tests/module_contributions.mjs +55 -0
- package/tests/module_loader.mjs +10 -5
- package/tests/module_scope_active_claims.mjs +94 -0
- package/tests/needs_rebase.mjs +92 -0
- package/tests/one_active_claim_per_session.mjs +76 -0
- package/tests/override_attribution.mjs +10 -8
- package/tests/override_request_ownership.mjs +8 -4
- package/tests/package_core.mjs +246 -1
- package/tests/parse_cookie_decode.mjs +42 -0
- package/tests/pay_on_land.mjs +129 -0
- package/tests/platform_boot.mjs +100 -1
- package/tests/post_login_landing.mjs +42 -0
- package/tests/preflight_checks.mjs +72 -0
- package/tests/provision.mjs +289 -7
- package/tests/pruned_lockfile_integrity.mjs +79 -0
- package/tests/publish_branch_route.mjs +53 -0
- package/tests/publish_reconciler.mjs +187 -2
- package/tests/repo_info_binding.mjs +248 -0
- package/tests/reward_gate.mjs +11 -2
- package/tests/reward_mode_cost_plus_only.mjs +116 -0
- package/tests/reward_mode_surfacing.mjs +168 -0
- package/tests/route_permission_check.mjs +301 -0
- package/tests/route_rank_check.mjs +254 -12
- package/tests/route_validation.mjs +30 -0
- package/tests/sequence.mjs +267 -0
- package/tests/session_digest.mjs +44 -0
- package/tests/session_index.mjs +18 -6
- package/tests/session_records.mjs +39 -1
- package/tests/session_reward_on_land.mjs +381 -0
- package/tests/session_reward_reconcile.mjs +229 -0
- package/tests/ship_api_client_pathspec.mjs +100 -0
- package/tests/ship_base_flag.mjs +93 -0
- package/tests/ship_ci_deploy.mjs +140 -3
- package/tests/ship_upstream_deletion.mjs +57 -0
- package/tests/smoke_target.mjs +233 -0
- package/tests/start_env_guard.mjs +87 -0
- package/tests/start_rebase_warning.mjs +103 -0
- package/tests/status_mirror_sync.mjs +122 -0
- package/tests/stop_refcheck.mjs +19 -1
- package/tests/task_goal_link.mjs +15 -6
- package/tests/upgrade.mjs +393 -5
- package/tests/web_admission_status.mjs +162 -0
- package/tests/write_validation_gaps.mjs +110 -0
- package/.gitignore +0 -91
- package/docs/adr/0139-platform-history-migration-to-cloudbongos.md +0 -94
- package/docs/adr/0140-cloudbongos-standalone-reprovision.md +0 -43
- package/scripts/hall-preview/fixtures/access-requests.json +0 -20
- package/scripts/hall-preview/fixtures/achievements.json +0 -76
- package/scripts/hall-preview/fixtures/analytics__builder.json +0 -251
- package/scripts/hall-preview/fixtures/audit-log.json +0 -68
- package/scripts/hall-preview/fixtures/blockers__detail.json +0 -12
- package/scripts/hall-preview/fixtures/box__me.json +0 -5
- package/scripts/hall-preview/fixtures/boxes__cost-ledger.json +0 -69
- package/scripts/hall-preview/fixtures/builders__active-claims.json +0 -1
- package/scripts/hall-preview/fixtures/builders__directory.json +0 -81
- package/scripts/hall-preview/fixtures/builders__roster.json +0 -48
- package/scripts/hall-preview/fixtures/goals.json +0 -502
- package/scripts/hall-preview/fixtures/goals__25.json +0 -96
- package/scripts/hall-preview/fixtures/goals__26.json +0 -67
- package/scripts/hall-preview/fixtures/goals__3.json +0 -103
- package/scripts/hall-preview/fixtures/goals__35.json +0 -49
- package/scripts/hall-preview/fixtures/goals__conflicts.json +0 -154
- package/scripts/hall-preview/fixtures/goals__detail.json +0 -96
- package/scripts/hall-preview/fixtures/goals__inbox.json +0 -5
- package/scripts/hall-preview/fixtures/goals__requests.json +0 -3
- package/scripts/hall-preview/fixtures/grades__by-builder.json +0 -32
- package/scripts/hall-preview/fixtures/inbox.json +0 -330
- package/scripts/hall-preview/fixtures/inbox__detail.json +0 -15
- package/scripts/hall-preview/fixtures/instance.json +0 -124
- package/scripts/hall-preview/fixtures/leaderboard.json +0 -184
- package/scripts/hall-preview/fixtures/me.json +0 -476
- package/scripts/hall-preview/fixtures/me__art-key__own.json +0 -6
- package/scripts/hall-preview/fixtures/me__event-sound-prefs.json +0 -209
- package/scripts/hall-preview/fixtures/me__recent-ships.json +0 -304
- package/scripts/hall-preview/fixtures/me__render.json +0 -5
- package/scripts/hall-preview/fixtures/me__skill-prefs.json +0 -143
- package/scripts/hall-preview/fixtures/me__sound-prefs.json +0 -67
- package/scripts/hall-preview/fixtures/me__wandering.json +0 -43
- package/scripts/hall-preview/fixtures/override-requests.json +0 -15
- package/scripts/hall-preview/fixtures/provisioning__fleet.json +0 -1
- package/scripts/hall-preview/fixtures/provisioning__fleet__cost-ledger.json +0 -1
- package/scripts/hall-preview/fixtures/provisioning__instances.json +0 -1
- package/scripts/hall-preview/fixtures/public__analytics__ecosystem.json +0 -516
- package/scripts/hall-preview/fixtures/public__bounty-table.json +0 -37
- package/scripts/hall-preview/fixtures/public__cost-summary.json +0 -742
- package/scripts/hall-preview/fixtures/public__estimation-drift.json +0 -41
- package/scripts/hall-preview/fixtures/public__grades.json +0 -50
- package/scripts/hall-preview/fixtures/public__progress.json +0 -124
- package/scripts/hall-preview/fixtures/public__recent-shipped.json +0 -426
- package/scripts/hall-preview/fixtures/public__repo-health.json +0 -160
- package/scripts/hall-preview/fixtures/public__repo-info.json +0 -5
- package/scripts/hall-preview/fixtures/security__docs.json +0 -10
- package/scripts/hall-preview/fixtures/security__reports.json +0 -31
- package/scripts/hall-preview/fixtures/sessions__search.json +0 -9
- package/scripts/hall-preview/fixtures/tasks.json +0 -2420
- package/scripts/hall-preview/fixtures/tasks__claimable.json +0 -2555
- package/scripts/hall-preview/fixtures/tasks__detail.json +0 -111
- package/scripts/hall-preview/fixtures/tasks__peer-votes__feed.json +0 -330
- package/scripts/hall-preview/fixtures/versions.json +0 -89
package/tests/upgrade.mjs
CHANGED
|
@@ -16,10 +16,11 @@ import { strict as assert } from 'node:assert';
|
|
|
16
16
|
import { createRequire } from 'node:module';
|
|
17
17
|
import { mkdtempSync, mkdirSync, writeFileSync, readFileSync, existsSync } from 'node:fs';
|
|
18
18
|
import { tmpdir } from 'node:os';
|
|
19
|
-
import { join } from 'node:path';
|
|
19
|
+
import { join, dirname } from 'node:path';
|
|
20
20
|
|
|
21
21
|
const require = createRequire(import.meta.url);
|
|
22
22
|
const u = require('../scripts/gds/upgrade.js');
|
|
23
|
+
const pc = require('../scripts/gds/package-core.js'); // the packager — reuse its EXACT hash fns to build reference pins
|
|
23
24
|
|
|
24
25
|
let passed = 0, failed = 0;
|
|
25
26
|
const pending = [];
|
|
@@ -159,7 +160,9 @@ t('full: pre-flight → pin → install → migrate → materialize → restart
|
|
|
159
160
|
let materialized = null;
|
|
160
161
|
let regenArgs = null;
|
|
161
162
|
const res = await u.runUpgrade(
|
|
162
|
-
|
|
163
|
+
// healthCheck:false — this test exercises the full pipeline, not the health poll (which would need
|
|
164
|
+
// a fetch mock); a managed-restart bump now requires a --health-url unless health is opted out (H9).
|
|
165
|
+
{ to: '1.15.0', from: join(src, 'bongos-core-1.15.0.tgz'), instance: dir, service: 'emersonian.service', healthCheck: false },
|
|
163
166
|
{
|
|
164
167
|
log: () => {}, err: () => {},
|
|
165
168
|
run: fakeRun(calls, { dirty: '', installTo: '1.15.0', dir }),
|
|
@@ -398,7 +401,7 @@ t('runUpgrade --registry (full): pins the exact version + writes the env-fed .np
|
|
|
398
401
|
const dir = scratchConsumer({ pinned: '1.16.0', installed: '1.16.0' });
|
|
399
402
|
const calls = [];
|
|
400
403
|
const res = await u.runUpgrade(
|
|
401
|
-
{ to: '1.17.0', registry: true, instance: dir, service: 'demo.service' },
|
|
404
|
+
{ to: '1.17.0', registry: true, instance: dir, service: 'demo.service', healthCheck: false }, // H9: opt out (no health poll under test)
|
|
402
405
|
{
|
|
403
406
|
log: () => {}, err: () => {},
|
|
404
407
|
run: fakeRun(calls, { dirty: '', installTo: '1.17.0', dir }),
|
|
@@ -479,7 +482,7 @@ t('rollback on migrate failure: reverts pin + reinstalls previous core + rolled_
|
|
|
479
482
|
const dir = scratchConsumer({ pinned: '1.14.1', installed: '1.14.1' });
|
|
480
483
|
const calls = [], queries = [];
|
|
481
484
|
const res = await u.runUpgrade(
|
|
482
|
-
{ to: '1.15.0', from: join(src, 'bongos-core-1.15.0.tgz'), instance: dir, service: 'demo.service' },
|
|
485
|
+
{ to: '1.15.0', from: join(src, 'bongos-core-1.15.0.tgz'), instance: dir, service: 'demo.service', healthCheck: false }, // H9: opt out — this test is about migrate-failure rollback, not health
|
|
483
486
|
{
|
|
484
487
|
log: () => {}, err: () => {},
|
|
485
488
|
run: pinAwareRun(calls, { dir, failMigrate: true }),
|
|
@@ -502,7 +505,7 @@ t('rollback on npm-install failure: reverts pin + reinstalls previous core', asy
|
|
|
502
505
|
const dir = scratchConsumer({ pinned: '1.14.1', installed: '1.14.1' });
|
|
503
506
|
const calls = [], queries = [];
|
|
504
507
|
const res = await u.runUpgrade(
|
|
505
|
-
{ to: '1.15.0', from: join(src, 'bongos-core-1.15.0.tgz'), instance: dir, service: 'demo.service' },
|
|
508
|
+
{ to: '1.15.0', from: join(src, 'bongos-core-1.15.0.tgz'), instance: dir, service: 'demo.service', healthCheck: false }, // H9: opt out — this test is about install-failure rollback, not health
|
|
506
509
|
{
|
|
507
510
|
log: () => {}, err: () => {},
|
|
508
511
|
run: pinAwareRun(calls, { dir, failInstallForVersion: '1.15.0' }),
|
|
@@ -547,6 +550,391 @@ t('restorePin: writes a previous dep string back verbatim (file: or registry)',
|
|
|
547
550
|
assert.equal(JSON.parse(readFileSync(join(dir, 'package.json'), 'utf8')).dependencies['@bongos/core'], '^1.16.0');
|
|
548
551
|
});
|
|
549
552
|
|
|
553
|
+
// ---- 7. integrity-pin verification (task 1002216 / audit H8, finding F1) ----
|
|
554
|
+
// Build a reference pin exactly as the packager does — canonicalTreeHash over the pinned
|
|
555
|
+
// files' sha256, so a matching installed tree recomputes byte-identically.
|
|
556
|
+
function pinManifestFor(files, { tarballSha = null, tarballBytes = 0 } = {}) {
|
|
557
|
+
const meta = files
|
|
558
|
+
.map((f) => ({ path: f.path, mode: f.mode || '0000644', sha256: pc.sha256hex(Buffer.from(f.content)) }))
|
|
559
|
+
.sort((a, b) => (a.path < b.path ? -1 : a.path > b.path ? 1 : 0));
|
|
560
|
+
const m = { artifact: 'bongos-core', tree_sha256: pc.canonicalTreeHash(meta), files: meta };
|
|
561
|
+
if (tarballSha) m.tarball = { name: 'bongos-core.tgz', sha256: tarballSha, bytes: tarballBytes };
|
|
562
|
+
return m;
|
|
563
|
+
}
|
|
564
|
+
function writeCoreTree(coreDir, files) {
|
|
565
|
+
for (const f of files) {
|
|
566
|
+
const abs = join(coreDir, ...f.path.split('/'));
|
|
567
|
+
mkdirSync(dirname(abs), { recursive: true });
|
|
568
|
+
writeFileSync(abs, f.content);
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
const PIN_FILES = [{ path: 'a.js', content: 'console.log(1)\n' }, { path: 'sub/b.json', content: '{"x":1}\n' }];
|
|
572
|
+
|
|
573
|
+
t('verifyInstalledPin: matching installed tree + tarball bytes → ok (both checks)', () => {
|
|
574
|
+
const dir = mkdtempSync(join(tmpdir(), 'otb-pin-'));
|
|
575
|
+
const coreDir = join(dir, 'node_modules', '@bongos', 'core');
|
|
576
|
+
writeCoreTree(coreDir, PIN_FILES);
|
|
577
|
+
const tgzBytes = Buffer.from('deterministic-fake-tgz');
|
|
578
|
+
const tgz = join(dir, 'vendor', 'bongos-core-1.15.0.tgz');
|
|
579
|
+
mkdirSync(dirname(tgz), { recursive: true });
|
|
580
|
+
writeFileSync(tgz, tgzBytes);
|
|
581
|
+
const m = pinManifestFor(PIN_FILES, { tarballSha: pc.sha256hex(tgzBytes), tarballBytes: tgzBytes.length });
|
|
582
|
+
const res = u.verifyInstalledPin({ coreDir, referenceManifest: m, tgzPath: tgz });
|
|
583
|
+
assert.equal(res.ran, true);
|
|
584
|
+
assert.equal(res.ok, true, res.problems.join('; '));
|
|
585
|
+
assert.deepEqual(res.checks.map((c) => c.name).sort(), ['tarball.sha256', 'tree_sha256']);
|
|
586
|
+
});
|
|
587
|
+
|
|
588
|
+
t('verifyInstalledPin: a corrupted installed file → mismatch (ran, not ok)', () => {
|
|
589
|
+
const dir = mkdtempSync(join(tmpdir(), 'otb-pin-'));
|
|
590
|
+
const coreDir = join(dir, 'node_modules', '@bongos', 'core');
|
|
591
|
+
writeCoreTree(coreDir, PIN_FILES);
|
|
592
|
+
const m = pinManifestFor(PIN_FILES);
|
|
593
|
+
writeFileSync(join(coreDir, 'a.js'), 'console.log(999) // tampered\n'); // corrupt AFTER pinning
|
|
594
|
+
const res = u.verifyInstalledPin({ coreDir, referenceManifest: m, tgzPath: null });
|
|
595
|
+
assert.equal(res.ran, true);
|
|
596
|
+
assert.equal(res.ok, false);
|
|
597
|
+
assert.ok(res.problems.some((p) => /content mismatch: a\.js/.test(p)), res.problems.join('; '));
|
|
598
|
+
});
|
|
599
|
+
|
|
600
|
+
t('verifyInstalledPin: a missing installed file → mismatch', () => {
|
|
601
|
+
const dir = mkdtempSync(join(tmpdir(), 'otb-pin-'));
|
|
602
|
+
const coreDir = join(dir, 'node_modules', '@bongos', 'core');
|
|
603
|
+
writeCoreTree(coreDir, [PIN_FILES[0]]); // omit sub/b.json
|
|
604
|
+
const res = u.verifyInstalledPin({ coreDir, referenceManifest: pinManifestFor(PIN_FILES), tgzPath: null });
|
|
605
|
+
assert.equal(res.ok, false);
|
|
606
|
+
assert.ok(res.problems.some((p) => /missing installed file: sub\/b\.json/.test(p)), res.problems.join('; '));
|
|
607
|
+
});
|
|
608
|
+
|
|
609
|
+
t('verifyInstalledPin: tarball bytes differ from the pin → mismatch', () => {
|
|
610
|
+
const dir = mkdtempSync(join(tmpdir(), 'otb-pin-'));
|
|
611
|
+
const coreDir = join(dir, 'node_modules', '@bongos', 'core');
|
|
612
|
+
writeCoreTree(coreDir, PIN_FILES);
|
|
613
|
+
const tgz = join(dir, 'vendor', 'bongos-core-1.15.0.tgz');
|
|
614
|
+
mkdirSync(dirname(tgz), { recursive: true });
|
|
615
|
+
writeFileSync(tgz, Buffer.from('the-actual-bytes'));
|
|
616
|
+
const m = pinManifestFor(PIN_FILES, { tarballSha: pc.sha256hex(Buffer.from('DIFFERENT-bytes')), tarballBytes: 9 });
|
|
617
|
+
const res = u.verifyInstalledPin({ coreDir, referenceManifest: m, tgzPath: tgz });
|
|
618
|
+
assert.equal(res.ok, false);
|
|
619
|
+
assert.ok(res.problems.some((p) => /tarball bytes/.test(p)), res.problems.join('; '));
|
|
620
|
+
});
|
|
621
|
+
|
|
622
|
+
t('verifyInstalledPin: no pin in the reference manifest → ran:false (nothing to check)', () => {
|
|
623
|
+
const res = u.verifyInstalledPin({ coreDir: '/nonexistent', referenceManifest: {}, tgzPath: null });
|
|
624
|
+
assert.equal(res.ran, false);
|
|
625
|
+
assert.equal(res.ok, false);
|
|
626
|
+
});
|
|
627
|
+
|
|
628
|
+
t('resolveReferenceManifest: prefers the committed vendored side-car (strong)', () => {
|
|
629
|
+
const dir = mkdtempSync(join(tmpdir(), 'otb-pin-'));
|
|
630
|
+
const coreDir = join(dir, 'node_modules', '@bongos', 'core');
|
|
631
|
+
mkdirSync(join(dir, 'vendor'), { recursive: true });
|
|
632
|
+
writeFileSync(join(dir, 'vendor', 'bongos-core-1.15.0.manifest.json'), JSON.stringify(pinManifestFor(PIN_FILES)));
|
|
633
|
+
writeFileSync(join(dir, 'vendor', 'bongos-core-1.15.0.tgz'), 'x');
|
|
634
|
+
const ref = u.resolveReferenceManifest({ instanceDir: dir, coreDir, targetVersion: '1.15.0' });
|
|
635
|
+
assert.equal(ref.source, 'vendored side-car manifest');
|
|
636
|
+
assert.equal(ref.strong, true);
|
|
637
|
+
assert.ok(ref.tgzPath && existsSync(ref.tgzPath));
|
|
638
|
+
});
|
|
639
|
+
|
|
640
|
+
t('resolveReferenceManifest: falls back to the installed inner manifest (not strong)', () => {
|
|
641
|
+
const dir = mkdtempSync(join(tmpdir(), 'otb-pin-'));
|
|
642
|
+
const coreDir = join(dir, 'node_modules', '@bongos', 'core');
|
|
643
|
+
mkdirSync(coreDir, { recursive: true });
|
|
644
|
+
writeFileSync(join(coreDir, '.bongos-core.json'), JSON.stringify(pinManifestFor(PIN_FILES)));
|
|
645
|
+
const ref = u.resolveReferenceManifest({ instanceDir: dir, coreDir, targetVersion: '1.15.0' });
|
|
646
|
+
assert.equal(ref.source, 'installed inner manifest');
|
|
647
|
+
assert.equal(ref.strong, false);
|
|
648
|
+
assert.equal(ref.tgzPath, null);
|
|
649
|
+
});
|
|
650
|
+
|
|
651
|
+
// A spawn shim whose `npm install` writes an installed core tree (+ version) so the
|
|
652
|
+
// real pin-verify step can run against it. `files` is what actually lands on disk.
|
|
653
|
+
function installWritingTree(calls, { dir, version, files }) {
|
|
654
|
+
return (cmd, args) => {
|
|
655
|
+
calls.push(`${cmd} ${(args || []).join(' ')}`.trim());
|
|
656
|
+
if (cmd === 'git' && args[0] === 'status') return { status: 0, stdout: '' };
|
|
657
|
+
if (cmd === 'npm' && args[0] === 'install') {
|
|
658
|
+
const coreDir = join(dir, 'node_modules', '@bongos', 'core');
|
|
659
|
+
writeCoreTree(coreDir, files);
|
|
660
|
+
writeFileSync(join(coreDir, 'package.json'), JSON.stringify({ name: '@bongos/core', version }) + '\n');
|
|
661
|
+
}
|
|
662
|
+
return { status: 0 };
|
|
663
|
+
};
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
t('runUpgrade: a matching pin passes verification (upgrade proceeds)', async () => {
|
|
667
|
+
const dir = scratchConsumer({ pinned: '1.14.1', installed: '1.14.1' });
|
|
668
|
+
mkdirSync(join(dir, 'vendor'), { recursive: true });
|
|
669
|
+
writeFileSync(join(dir, 'vendor', 'bongos-core-1.15.0.tgz'), 'fake-tgz');
|
|
670
|
+
writeFileSync(join(dir, 'vendor', 'bongos-core-1.15.0.manifest.json'),
|
|
671
|
+
JSON.stringify(pinManifestFor(PIN_FILES, { tarballSha: pc.sha256hex(Buffer.from('fake-tgz')), tarballBytes: 8 })));
|
|
672
|
+
const logs = [];
|
|
673
|
+
const res = await u.runUpgrade(
|
|
674
|
+
{ to: '1.15.0', instance: dir },
|
|
675
|
+
{
|
|
676
|
+
log: (m) => logs.push(m), err: () => {},
|
|
677
|
+
run: installWritingTree([], { dir, version: '1.15.0', files: PIN_FILES }),
|
|
678
|
+
materialize: () => ({ skills: 1 }), regenerateApiArtifacts: () => ({ ok: true, ran: [] }),
|
|
679
|
+
}
|
|
680
|
+
);
|
|
681
|
+
assert.equal(res.ok, true, res.error);
|
|
682
|
+
assert.ok(logs.some((m) => /integrity pin verified.*tarball\.sha256.*tree_sha256/.test(m)), logs.join('\n'));
|
|
683
|
+
});
|
|
684
|
+
|
|
685
|
+
t('runUpgrade: a pin MISMATCH after install fails and auto-rolls-back', async () => {
|
|
686
|
+
const dir = scratchConsumer({ pinned: '1.14.1', installed: '1.14.1' });
|
|
687
|
+
mkdirSync(join(dir, 'vendor'), { recursive: true });
|
|
688
|
+
writeFileSync(join(dir, 'vendor', 'bongos-core-1.15.0.tgz'), 'fake-tgz');
|
|
689
|
+
// The reference pin is for PIN_FILES, but install writes DIFFERENT content → tree mismatch.
|
|
690
|
+
writeFileSync(join(dir, 'vendor', 'bongos-core-1.15.0.manifest.json'), JSON.stringify(pinManifestFor(PIN_FILES)));
|
|
691
|
+
const corrupted = [{ path: 'a.js', content: 'console.log(666)\n' }, { path: 'sub/b.json', content: '{"x":1}\n' }];
|
|
692
|
+
const res = await u.runUpgrade(
|
|
693
|
+
{ to: '1.15.0', instance: dir },
|
|
694
|
+
{
|
|
695
|
+
log: () => {}, err: () => {},
|
|
696
|
+
run: installWritingTree([], { dir, version: '1.15.0', files: corrupted }),
|
|
697
|
+
materialize: () => ({ skills: 1 }), regenerateApiArtifacts: () => ({ ok: true, ran: [] }),
|
|
698
|
+
}
|
|
699
|
+
);
|
|
700
|
+
assert.equal(res.ok, false);
|
|
701
|
+
assert.ok(/integrity pin MISMATCH/.test(res.error || ''), res.error);
|
|
702
|
+
assert.equal(res.rolledBack, true);
|
|
703
|
+
assert.equal(u.readPinnedCoreVersion(dir), '1.14.1', 'pin reverted to the previous version on rollback');
|
|
704
|
+
});
|
|
705
|
+
|
|
706
|
+
t('runUpgrade: --require-pin makes a MISSING reference manifest fatal', async () => {
|
|
707
|
+
const dir = scratchConsumer({ pinned: '1.14.1', installed: '1.14.1' });
|
|
708
|
+
mkdirSync(join(dir, 'vendor'), { recursive: true });
|
|
709
|
+
writeFileSync(join(dir, 'vendor', 'bongos-core-1.15.0.tgz'), 'fake-tgz'); // tgz present, but NO manifest
|
|
710
|
+
const res = await u.runUpgrade(
|
|
711
|
+
{ to: '1.15.0', instance: dir, requirePin: true },
|
|
712
|
+
{
|
|
713
|
+
log: () => {}, err: () => {},
|
|
714
|
+
run: installWritingTree([], { dir, version: '1.15.0', files: PIN_FILES }),
|
|
715
|
+
materialize: () => ({ skills: 1 }), regenerateApiArtifacts: () => ({ ok: true, ran: [] }),
|
|
716
|
+
}
|
|
717
|
+
);
|
|
718
|
+
assert.equal(res.ok, false);
|
|
719
|
+
assert.ok(/no reference manifest/.test(res.error || ''), res.error);
|
|
720
|
+
assert.equal(res.rolledBack, true, 'unverifiable pin rolls back under --require-pin');
|
|
721
|
+
});
|
|
722
|
+
|
|
723
|
+
t('runUpgrade: --skip-pin-verify bypasses the check (no manifest, still succeeds)', async () => {
|
|
724
|
+
const dir = scratchConsumer({ pinned: '1.14.1', installed: '1.14.1' });
|
|
725
|
+
mkdirSync(join(dir, 'vendor'), { recursive: true });
|
|
726
|
+
writeFileSync(join(dir, 'vendor', 'bongos-core-1.15.0.tgz'), 'fake-tgz');
|
|
727
|
+
const res = await u.runUpgrade(
|
|
728
|
+
{ to: '1.15.0', instance: dir, skipPinVerify: true },
|
|
729
|
+
{
|
|
730
|
+
log: () => {}, err: () => {},
|
|
731
|
+
run: installWritingTree([], { dir, version: '1.15.0', files: PIN_FILES }),
|
|
732
|
+
materialize: () => ({ skills: 1 }), regenerateApiArtifacts: () => ({ ok: true, ran: [] }),
|
|
733
|
+
}
|
|
734
|
+
);
|
|
735
|
+
assert.equal(res.ok, true, res.error);
|
|
736
|
+
});
|
|
737
|
+
|
|
738
|
+
// ---- 8. mandatory post-restart health check (task 1002222 / audit H9, finding F5) ----
|
|
739
|
+
t('runUpgrade: a managed-restart bump WITHOUT --health-url is refused up front (H9 mandatory)', async () => {
|
|
740
|
+
const src = mkdtempSync(join(tmpdir(), 'src-'));
|
|
741
|
+
writeFileSync(join(src, 'bongos-core-1.15.0.tgz'), 'TGZ');
|
|
742
|
+
const dir = scratchConsumer({ pinned: '1.14.1', installed: '1.14.1' });
|
|
743
|
+
const calls = [];
|
|
744
|
+
const res = await u.runUpgrade(
|
|
745
|
+
{ to: '1.15.0', from: join(src, 'bongos-core-1.15.0.tgz'), instance: dir, service: 'demo.service' }, // restarts, no --health-url, not opted out
|
|
746
|
+
{ log: () => {}, err: () => {}, run: fakeRun(calls, { dirty: '', installTo: '1.15.0', dir }), materialize: () => ({ skills: 1 }), regenerateApiArtifacts: () => ({ ok: true, ran: [] }) }
|
|
747
|
+
);
|
|
748
|
+
assert.equal(res.ok, false);
|
|
749
|
+
assert.match(res.error, /health check is mandatory/);
|
|
750
|
+
assert.equal(u.readPinnedCoreVersion(dir), '1.14.1', 'refused in pre-flight → pin untouched');
|
|
751
|
+
assert.ok(!calls.some((c) => /npm install/.test(c)), 'refused before any install');
|
|
752
|
+
});
|
|
753
|
+
|
|
754
|
+
t('runUpgrade: --no-health-check lets a managed-restart bump proceed without a URL', async () => {
|
|
755
|
+
const src = mkdtempSync(join(tmpdir(), 'src-'));
|
|
756
|
+
writeFileSync(join(src, 'bongos-core-1.15.0.tgz'), 'TGZ');
|
|
757
|
+
const dir = scratchConsumer({ pinned: '1.14.1', installed: '1.14.1' });
|
|
758
|
+
const logs = [];
|
|
759
|
+
const res = await u.runUpgrade(
|
|
760
|
+
{ to: '1.15.0', from: join(src, 'bongos-core-1.15.0.tgz'), instance: dir, service: 'demo.service', healthCheck: false },
|
|
761
|
+
{ log: (m) => logs.push(m), err: () => {}, run: fakeRun([], { dirty: '', installTo: '1.15.0', dir }), materialize: () => ({ skills: 1 }), regenerateApiArtifacts: () => ({ ok: true, ran: [] }) }
|
|
762
|
+
);
|
|
763
|
+
assert.equal(res.ok, true, res.error);
|
|
764
|
+
assert.ok(logs.some((m) => /health check skipped \(--no-health-check\)/.test(m)), logs.join('\n'));
|
|
765
|
+
});
|
|
766
|
+
|
|
767
|
+
t('runUpgrade: a managed-restart bump WITH a healthy --health-url confirms + succeeds', async () => {
|
|
768
|
+
const src = mkdtempSync(join(tmpdir(), 'src-'));
|
|
769
|
+
writeFileSync(join(src, 'bongos-core-1.15.0.tgz'), 'TGZ');
|
|
770
|
+
const dir = scratchConsumer({ pinned: '1.14.1', installed: '1.14.1' });
|
|
771
|
+
const logs = [];
|
|
772
|
+
const res = await u.runUpgrade(
|
|
773
|
+
{ to: '1.15.0', from: join(src, 'bongos-core-1.15.0.tgz'), instance: dir, service: 'demo.service', healthUrl: 'http://x/healthz' },
|
|
774
|
+
{
|
|
775
|
+
log: (m) => logs.push(m), err: () => {}, run: fakeRun([], { dirty: '', installTo: '1.15.0', dir }),
|
|
776
|
+
materialize: () => ({ skills: 1 }), regenerateApiArtifacts: () => ({ ok: true, ran: [] }),
|
|
777
|
+
fetch: async () => ({ ok: true, status: 200 }), sleep: async () => {}, now: () => 0,
|
|
778
|
+
}
|
|
779
|
+
);
|
|
780
|
+
assert.equal(res.ok, true, res.error);
|
|
781
|
+
assert.equal(res.healthOk, true);
|
|
782
|
+
assert.ok(logs.some((m) => /health http:\/\/x\/healthz → 200/.test(m)), logs.join('\n'));
|
|
783
|
+
});
|
|
784
|
+
|
|
785
|
+
t('runUpgrade: no managed restart + no --health-url proceeds but flags the bump as unconfirmed', async () => {
|
|
786
|
+
const src = mkdtempSync(join(tmpdir(), 'src-'));
|
|
787
|
+
writeFileSync(join(src, 'bongos-core-1.15.0.tgz'), 'TGZ');
|
|
788
|
+
const dir = scratchConsumer({ pinned: '1.14.1', installed: '1.14.1' });
|
|
789
|
+
const logs = [];
|
|
790
|
+
const res = await u.runUpgrade(
|
|
791
|
+
{ to: '1.15.0', from: join(src, 'bongos-core-1.15.0.tgz'), instance: dir }, // no service/restart-cmd, no --health-url
|
|
792
|
+
{ log: (m) => logs.push(m), err: () => {}, run: fakeRun([], { dirty: '', installTo: '1.15.0', dir }), materialize: () => ({ skills: 1 }), regenerateApiArtifacts: () => ({ ok: true, ran: [] }) }
|
|
793
|
+
);
|
|
794
|
+
assert.equal(res.ok, true, res.error);
|
|
795
|
+
assert.equal(res.healthOk, null);
|
|
796
|
+
assert.ok(logs.some((m) => /not confirmed serving/.test(m)), logs.join('\n'));
|
|
797
|
+
});
|
|
798
|
+
|
|
799
|
+
// ---- regenerateNavDocs (ADR 0147 / task 1002314) ----------------------------
|
|
800
|
+
t('regenerateNavDocs: default set is the whole-file nav docs (session-index only)', () => {
|
|
801
|
+
assert.deepEqual(u.NAV_WHOLE_FILE_GENERATORS, ['gen-session-index.js']);
|
|
802
|
+
});
|
|
803
|
+
|
|
804
|
+
t('regenerateNavDocs: spawns each generator from the CORE against the INSTANCE root (cwd + CLOUDBONGOS_INSTANCE_ROOT), ok when all succeed', () => {
|
|
805
|
+
const calls = [];
|
|
806
|
+
const run = (cmd, args, opts) => { calls.push({ cmd, arg0: args[0], cwd: opts && opts.cwd, env: opts && opts.env }); return { status: 0 }; };
|
|
807
|
+
const fsImpl = { existsSync: () => true };
|
|
808
|
+
const res = u.regenerateNavDocs({ instanceDir: '/inst', coreRoot: '/core', generators: ['gen-session-index.js'] }, run, fsImpl);
|
|
809
|
+
assert.equal(res.ok, true);
|
|
810
|
+
assert.deepEqual(res.ran, ['gen-session-index.js']);
|
|
811
|
+
assert.deepEqual(res.failed, []);
|
|
812
|
+
assert.equal(calls.length, 1);
|
|
813
|
+
assert.equal(calls[0].cwd, '/inst', 'runs with cwd = the instance dir');
|
|
814
|
+
assert.equal(calls[0].env.CLOUDBONGOS_INSTANCE_ROOT, '/inst', 'sets the instance-root env so the generator writes into the instance');
|
|
815
|
+
assert.equal(calls[0].arg0, join('/core', 'scripts', 'gds', 'gen-session-index.js'), 'spawns the generator from the CORE package');
|
|
816
|
+
});
|
|
817
|
+
|
|
818
|
+
t('regenerateNavDocs: a MISSING generator is recorded as failed but the others still run (best-effort)', () => {
|
|
819
|
+
const spawned = [];
|
|
820
|
+
const run = (_cmd, args) => { spawned.push(args[0]); return { status: 0 }; };
|
|
821
|
+
const fsImpl = { existsSync: (p) => !String(p).endsWith('gen-missing.js') };
|
|
822
|
+
const res = u.regenerateNavDocs({ instanceDir: '/inst', coreRoot: '/core', generators: ['gen-missing.js', 'gen-session-index.js'] }, run, fsImpl);
|
|
823
|
+
assert.equal(res.ok, false);
|
|
824
|
+
assert.deepEqual(res.ran, ['gen-session-index.js']);
|
|
825
|
+
assert.equal(res.failed.length, 1);
|
|
826
|
+
assert.equal(res.failed[0].gen, 'gen-missing.js');
|
|
827
|
+
assert.equal(spawned.length, 1, 'the missing generator is not spawned; the present one still is');
|
|
828
|
+
});
|
|
829
|
+
|
|
830
|
+
t('regenerateNavDocs: a generator that exits nonzero is LOUD-failed (ok:false) without aborting the rest', () => {
|
|
831
|
+
const run = (_cmd, args) => ({ status: String(args[0]).endsWith('gen-boom.js') ? 2 : 0 });
|
|
832
|
+
const fsImpl = { existsSync: () => true };
|
|
833
|
+
const res = u.regenerateNavDocs({ instanceDir: '/inst', coreRoot: '/core', generators: ['gen-boom.js', 'gen-session-index.js'] }, run, fsImpl);
|
|
834
|
+
assert.equal(res.ok, false);
|
|
835
|
+
assert.deepEqual(res.ran, ['gen-session-index.js']);
|
|
836
|
+
assert.equal(res.failed.length, 1);
|
|
837
|
+
assert.equal(res.failed[0].gen, 'gen-boom.js');
|
|
838
|
+
assert.match(res.failed[0].reason, /exited 2/);
|
|
839
|
+
});
|
|
840
|
+
|
|
841
|
+
// ---- healDocAssets (task 1002441) -------------------------------------------
|
|
842
|
+
// An upgrade replaces node_modules wholesale, so it WIPES the two gitignored, core-scoped
|
|
843
|
+
// generated doc assets (atlas.json + the diagram .svg/.png renders) and the new tarball carries
|
|
844
|
+
// only the .mmd sources. These pin that the upgrade puts them back.
|
|
845
|
+
|
|
846
|
+
// A fake core tree. `renders` controls whether each .mmd already has its .svg + .png beside it.
|
|
847
|
+
function docAssetFs({ atlas = true, sources = ['01-a.mmd', '02-b.mmd'], renders = true } = {}) {
|
|
848
|
+
const present = new Set();
|
|
849
|
+
if (atlas) present.add(join('/core', 'modules', 'hall-ui', 'public', 'atlas.json'));
|
|
850
|
+
present.add(join('/core', 'scripts', 'gds', 'gen-atlas.js'));
|
|
851
|
+
present.add(join('/core', 'scripts', 'render-diagrams.sh'));
|
|
852
|
+
if (renders) {
|
|
853
|
+
for (const s of sources) {
|
|
854
|
+
const base = join('/core', 'docs', 'onboarding', 'diagrams', s.replace(/\.mmd$/, ''));
|
|
855
|
+
present.add(`${base}.svg`); present.add(`${base}.png`);
|
|
856
|
+
}
|
|
857
|
+
}
|
|
858
|
+
return { existsSync: (p) => present.has(String(p)), readdirSync: () => sources, _present: present };
|
|
859
|
+
}
|
|
860
|
+
|
|
861
|
+
t('missingDocAssets: a freshly-upgraded core (only .mmd sources, no atlas.json) reports BOTH groups missing', () => {
|
|
862
|
+
const fsImpl = docAssetFs({ atlas: false, renders: false });
|
|
863
|
+
assert.deepEqual(u.missingDocAssets('/core', fsImpl), ['atlas', 'diagrams']);
|
|
864
|
+
});
|
|
865
|
+
|
|
866
|
+
t('missingDocAssets: a fully-generated core reports nothing missing (steady state — no work)', () => {
|
|
867
|
+
assert.deepEqual(u.missingDocAssets('/core', docAssetFs()), []);
|
|
868
|
+
});
|
|
869
|
+
|
|
870
|
+
t('missingDocAssets: only atlas.json gone → only the atlas group', () => {
|
|
871
|
+
assert.deepEqual(u.missingDocAssets('/core', docAssetFs({ atlas: false })), ['atlas']);
|
|
872
|
+
});
|
|
873
|
+
|
|
874
|
+
t('missingDocAssets: a half-rendered diagram set (one .mmd lacking its .png) counts as missing', () => {
|
|
875
|
+
const fsImpl = docAssetFs();
|
|
876
|
+
fsImpl._present.delete(join('/core', 'docs', 'onboarding', 'diagrams', '02-b.png'));
|
|
877
|
+
assert.deepEqual(u.missingDocAssets('/core', fsImpl), ['diagrams']);
|
|
878
|
+
});
|
|
879
|
+
|
|
880
|
+
t('missingDocAssets: a core with NO diagram dir does not report diagrams (readdir throws → nothing to render)', () => {
|
|
881
|
+
const fsImpl = docAssetFs();
|
|
882
|
+
fsImpl.readdirSync = () => { throw new Error('ENOENT'); };
|
|
883
|
+
assert.deepEqual(u.missingDocAssets('/core', fsImpl), []);
|
|
884
|
+
});
|
|
885
|
+
|
|
886
|
+
t('healDocAssets: the post-upgrade case regenerates BOTH, node for the atlas and bash for the renders, cwd = coreRoot', () => {
|
|
887
|
+
const calls = [];
|
|
888
|
+
const run = (cmd, args, opts) => { calls.push({ cmd, arg0: args[0], cwd: opts && opts.cwd }); return { status: 0 }; };
|
|
889
|
+
const res = u.healDocAssets({ coreRoot: '/core' }, run, docAssetFs({ atlas: false, renders: false }));
|
|
890
|
+
assert.equal(res.ok, true);
|
|
891
|
+
assert.deepEqual(res.ran, ['atlas', 'diagrams']);
|
|
892
|
+
assert.equal(calls.length, 2);
|
|
893
|
+
assert.equal(calls[0].cmd, process.execPath, 'atlas generator runs under node');
|
|
894
|
+
assert.equal(calls[0].arg0, join('/core', 'scripts', 'gds', 'gen-atlas.js'));
|
|
895
|
+
assert.equal(calls[1].cmd, 'bash', 'the diagram renderer is a shell script');
|
|
896
|
+
assert.equal(calls[1].arg0, join('/core', 'scripts', 'render-diagrams.sh'));
|
|
897
|
+
assert.ok(calls.every((c) => c.cwd === '/core'), 'both run against the CORE root, where the assets live');
|
|
898
|
+
});
|
|
899
|
+
|
|
900
|
+
t('healDocAssets: nothing missing → spawns NOTHING (safe to call from a deploy loop)', () => {
|
|
901
|
+
let spawned = 0;
|
|
902
|
+
const res = u.healDocAssets({ coreRoot: '/core' }, () => { spawned++; return { status: 0 }; }, docAssetFs());
|
|
903
|
+
assert.equal(spawned, 0);
|
|
904
|
+
assert.deepEqual(res.missing, []);
|
|
905
|
+
assert.equal(res.ok, true);
|
|
906
|
+
});
|
|
907
|
+
|
|
908
|
+
t('healDocAssets: only the MISSING group is regenerated (atlas present → renderer only)', () => {
|
|
909
|
+
const spawned = [];
|
|
910
|
+
const run = (_c, args) => { spawned.push(args[0]); return { status: 0 }; };
|
|
911
|
+
const res = u.healDocAssets({ coreRoot: '/core' }, run, docAssetFs({ renders: false }));
|
|
912
|
+
assert.deepEqual(res.ran, ['diagrams']);
|
|
913
|
+
assert.equal(spawned.length, 1);
|
|
914
|
+
assert.match(String(spawned[0]), /render-diagrams\.sh$/);
|
|
915
|
+
});
|
|
916
|
+
|
|
917
|
+
t('healDocAssets: a failing renderer (no Chromium) is LOUD-failed but the atlas still heals — never aborts the bump', () => {
|
|
918
|
+
const run = (_c, args) => ({ status: String(args[0]).endsWith('render-diagrams.sh') ? 1 : 0 });
|
|
919
|
+
const res = u.healDocAssets({ coreRoot: '/core' }, run, docAssetFs({ atlas: false, renders: false }));
|
|
920
|
+
assert.equal(res.ok, false, 'reported as incomplete');
|
|
921
|
+
assert.deepEqual(res.ran, ['atlas'], 'the independent atlas still regenerated');
|
|
922
|
+
assert.equal(res.failed.length, 1);
|
|
923
|
+
assert.equal(res.failed[0].group, 'diagrams');
|
|
924
|
+
assert.match(res.failed[0].reason, /exited 1/);
|
|
925
|
+
});
|
|
926
|
+
|
|
927
|
+
t('healDocAssets: a generator absent from this core is recorded, not spawned', () => {
|
|
928
|
+
const fsImpl = docAssetFs({ atlas: false, renders: false });
|
|
929
|
+
fsImpl._present.delete(join('/core', 'scripts', 'render-diagrams.sh'));
|
|
930
|
+
const spawned = [];
|
|
931
|
+
const res = u.healDocAssets({ coreRoot: '/core' }, (_c, args) => { spawned.push(args[0]); return { status: 0 }; }, fsImpl);
|
|
932
|
+
assert.equal(res.ok, false);
|
|
933
|
+
assert.deepEqual(res.ran, ['atlas']);
|
|
934
|
+
assert.match(res.failed[0].reason, /not found in core/);
|
|
935
|
+
assert.equal(spawned.length, 1, 'the absent script is never spawned');
|
|
936
|
+
});
|
|
937
|
+
|
|
550
938
|
await Promise.all(pending);
|
|
551
939
|
console.log(`\nupgrade: ${passed} passed, ${failed} failed`);
|
|
552
940
|
process.exit(failed ? 1 : 0);
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
// tests/web_admission_status.mjs — auth.webAdmissionStatus (task 1002097).
|
|
2
|
+
//
|
|
3
|
+
// The stub must go into require.cache BEFORE auth.js loads: auth.js destructures
|
|
4
|
+
// accessStateByLogin at require time, so patching the export object afterwards
|
|
5
|
+
// would not rebind what it already captured.
|
|
6
|
+
//
|
|
7
|
+
// Run: node tests/web_admission_status.mjs
|
|
8
|
+
|
|
9
|
+
import { strict as assert } from 'node:assert';
|
|
10
|
+
import { createRequire } from 'node:module';
|
|
11
|
+
|
|
12
|
+
const require = createRequire(import.meta.url);
|
|
13
|
+
|
|
14
|
+
// --- stub the kernel BEFORE auth.js captures its bindings ------------------
|
|
15
|
+
const kernelPath = require.resolve('../src/bongos/db-kernel.js');
|
|
16
|
+
const realKernel = require(kernelPath);
|
|
17
|
+
|
|
18
|
+
let dbState = { member: false, invited: false, pending: false };
|
|
19
|
+
let dbThrows = false;
|
|
20
|
+
|
|
21
|
+
require.cache[kernelPath].exports = {
|
|
22
|
+
...realKernel,
|
|
23
|
+
accessStateByLogin: async () => {
|
|
24
|
+
if (dbThrows) throw new Error('simulated DB outage');
|
|
25
|
+
return dbState;
|
|
26
|
+
},
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
const auth = require('../src/bongos/auth.js');
|
|
30
|
+
|
|
31
|
+
const FIRST_ADMIN = 'ownerlogin';
|
|
32
|
+
// OTB is a permanent entry in LEGACY_ENV_PREFIXES, so this resolves regardless
|
|
33
|
+
// of the instance's own env prefix — and regardless of whether the branding
|
|
34
|
+
// pack configures a firstAdmin (this repo's does not).
|
|
35
|
+
process.env.OTB_FIRST_ADMIN = FIRST_ADMIN;
|
|
36
|
+
|
|
37
|
+
function setDb({ member = false, invited = false, pending = false } = {}) {
|
|
38
|
+
dbState = { member, invited, pending };
|
|
39
|
+
dbThrows = false;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function withOpenEnrollment(val, fn) {
|
|
43
|
+
const prev = process.env.OTB_OPEN_XENOS_ENROLLMENT;
|
|
44
|
+
if (val === undefined) delete process.env.OTB_OPEN_XENOS_ENROLLMENT;
|
|
45
|
+
else process.env.OTB_OPEN_XENOS_ENROLLMENT = val;
|
|
46
|
+
return Promise.resolve()
|
|
47
|
+
.then(fn)
|
|
48
|
+
.finally(() => {
|
|
49
|
+
if (prev === undefined) delete process.env.OTB_OPEN_XENOS_ENROLLMENT;
|
|
50
|
+
else process.env.OTB_OPEN_XENOS_ENROLLMENT = prev;
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
let passed = 0;
|
|
55
|
+
let failed = 0;
|
|
56
|
+
async function t(name, fn) {
|
|
57
|
+
try { await fn(); passed++; console.log(` ok ${name}`); }
|
|
58
|
+
catch (e) { failed++; console.log(` FAIL ${name}\n ${e.message}`); }
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
await t('a blank or missing login is never admitted', async () => {
|
|
62
|
+
setDb({ member: true });
|
|
63
|
+
for (const bad of ['', ' ', null, undefined]) {
|
|
64
|
+
assert.deepEqual(await auth.webAdmissionStatus(bad), { admitted: false, expectedSoon: false });
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
await t('an existing builder is admitted', async () => {
|
|
69
|
+
setDb({ member: true });
|
|
70
|
+
assert.deepEqual(await auth.webAdmissionStatus('someone'), { admitted: true, expectedSoon: true });
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
await t('an invited login is admitted', async () => {
|
|
74
|
+
setDb({ invited: true });
|
|
75
|
+
assert.deepEqual(await auth.webAdmissionStatus('someone'), { admitted: true, expectedSoon: true });
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
await t('PENDING is the keep-polling case: not admitted, but expected soon', async () => {
|
|
79
|
+
// The whole point of the waiting page — an Archon is about to flip this to
|
|
80
|
+
// invited, so the visitor should wait rather than see a rejection.
|
|
81
|
+
setDb({ pending: true });
|
|
82
|
+
assert.deepEqual(await auth.webAdmissionStatus('someone'), { admitted: false, expectedSoon: true });
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
await t('a genuine stranger is neither admitted nor expected', async () => {
|
|
86
|
+
// Must be false, or the stranger gets a spinner that never resolves instead
|
|
87
|
+
// of the request-access page.
|
|
88
|
+
setDb({});
|
|
89
|
+
assert.deepEqual(await auth.webAdmissionStatus('nobody'), { admitted: false, expectedSoon: false });
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
await t('the configured firstAdmin is admitted with nothing in the DB', async () => {
|
|
93
|
+
// The bootstrap window this task was filed for: a fresh instance where the
|
|
94
|
+
// owner has no builder row and no access_request yet.
|
|
95
|
+
setDb({});
|
|
96
|
+
assert.deepEqual(await auth.webAdmissionStatus(FIRST_ADMIN), { admitted: true, expectedSoon: true });
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
await t('the firstAdmin match is case-insensitive and trims', async () => {
|
|
100
|
+
setDb({});
|
|
101
|
+
assert.equal((await auth.webAdmissionStatus('OwNerLogin')).admitted, true);
|
|
102
|
+
assert.equal((await auth.webAdmissionStatus(` ${FIRST_ADMIN.toUpperCase()} `)).admitted, true);
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
await t('open enrollment admits anyone', async () => {
|
|
106
|
+
setDb({});
|
|
107
|
+
await withOpenEnrollment('1', async () => {
|
|
108
|
+
assert.deepEqual(await auth.webAdmissionStatus('anyone'), { admitted: true, expectedSoon: true });
|
|
109
|
+
});
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
await t('a DB outage fails CLOSED on admitted', async () => {
|
|
113
|
+
// Never let an error read as "you're in".
|
|
114
|
+
dbThrows = true;
|
|
115
|
+
// The helper logs the outage by design; mute it so the unit gate's output
|
|
116
|
+
// stays readable (the assertions below are what prove the behaviour).
|
|
117
|
+
const realError = console.error;
|
|
118
|
+
console.error = () => {};
|
|
119
|
+
try {
|
|
120
|
+
const stranger = await auth.webAdmissionStatus('nobody');
|
|
121
|
+
assert.equal(stranger.admitted, false);
|
|
122
|
+
assert.equal(stranger.expectedSoon, false);
|
|
123
|
+
// ...but the owner may still keep polling through a transient outage, since
|
|
124
|
+
// the bootstrap window is exactly when the DB may be settling.
|
|
125
|
+
const owner = await auth.webAdmissionStatus(FIRST_ADMIN);
|
|
126
|
+
assert.equal(owner.admitted, false);
|
|
127
|
+
assert.equal(owner.expectedSoon, true);
|
|
128
|
+
} finally {
|
|
129
|
+
console.error = realError;
|
|
130
|
+
dbThrows = false;
|
|
131
|
+
}
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
await t('ANTI-DRIFT: admitted always equals the real gate\'s verdict', async () => {
|
|
135
|
+
// The probe must never disagree with firstTimeAdmissionBlocked — that is the
|
|
136
|
+
// one gate both sign-in flows call. Sweep the full input space.
|
|
137
|
+
for (const member of [false, true]) {
|
|
138
|
+
for (const invited of [false, true]) {
|
|
139
|
+
for (const openEnroll of [undefined, '1']) {
|
|
140
|
+
for (const login of ['nobody', FIRST_ADMIN]) {
|
|
141
|
+
setDb({ member, invited });
|
|
142
|
+
await withOpenEnrollment(openEnroll, async () => {
|
|
143
|
+
const probe = await auth.webAdmissionStatus(login);
|
|
144
|
+
const blocked = auth.firstTimeAdmissionBlocked({
|
|
145
|
+
existing: member ? { id: 1 } : null,
|
|
146
|
+
approved: invited,
|
|
147
|
+
login,
|
|
148
|
+
});
|
|
149
|
+
assert.equal(
|
|
150
|
+
probe.admitted, !blocked,
|
|
151
|
+
`drift: member=${member} invited=${invited} open=${openEnroll} login=${login} `
|
|
152
|
+
+ `→ probe.admitted=${probe.admitted} but gate blocked=${blocked}`,
|
|
153
|
+
);
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
console.log(`\nweb admission status (task 1002097): ${passed} passed, ${failed} failed`);
|
|
162
|
+
process.exit(failed > 0 ? 1 : 0);
|