@bongos/core 1.18.3 → 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 +1575 -285
- 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 +4 -2
- 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 +6 -1
- 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/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 +10 -0
- package/docs/api/openapi.json +843 -17
- package/docs/api-reference.md +23 -10
- 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 +8 -0
- 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/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/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/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/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/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/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 +51 -58
- 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 -27
- 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/login.js +16 -1
- package/scripts/gds/paste-token.js +7 -1
- package/scripts/gds/preflight.js +126 -0
- package/scripts/gds/provision.js +123 -12
- package/scripts/gds/publish-manifest.js +7 -1
- package/scripts/gds/reauth.js +7 -3
- package/scripts/gds/regen-instance-docs.js +47 -0
- package/scripts/gds/run-unit-tests.js +4 -2
- 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 +31 -5
- package/scripts/gds/shell.js +5 -3
- package/scripts/gds/ship.js +319 -29
- 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 +124 -2
- package/scripts/status-mirror-sync.js +114 -84
- package/src/bongos/api-errors.js +1 -0
- package/src/bongos/auth.js +447 -102
- package/src/bongos/db-kernel.js +27 -0
- package/src/bongos/db.js +39 -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 +219 -2
- package/src/bongos/routes/backup.js +58 -2
- package/src/bongos/routes/me.js +35 -1
- package/src/bongos/serve-internal.js +84 -15
- package/src/branding.js +32 -0
- package/src/instance-config.js +30 -0
- package/src/module-api.js +112 -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 +12 -0
- 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/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 +93 -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/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/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 +132 -7
- 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 +80 -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 +139 -0
- package/tests/web_admission_status.mjs +162 -0
- package/tests/write_validation_gaps.mjs +110 -0
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
// tests/devbox_dmg_backdrop.mjs — the Dev Box DMG install-window backdrop is
|
|
2
|
+
// BRANDING-DRIVEN, not baked in (task 1002372).
|
|
3
|
+
//
|
|
4
|
+
// modules/dev-box/app/scripts/gen-dmg-backdrop.py used to hardcode one host's
|
|
5
|
+
// install screen — its palette, its archaic copy, its world motifs — so the
|
|
6
|
+
// vanilla Cloud Bongos build shipped another instance's art. The generator now
|
|
7
|
+
// reads the pack the same way gen-brand.js does (ADR 0145). The fitness identity
|
|
8
|
+
// scan could not have caught this: it only reads .js/.html/.css, so a .py
|
|
9
|
+
// generator is invisible to it — hence this test.
|
|
10
|
+
//
|
|
11
|
+
// It is a SOURCE + ARTIFACT test rather than an execution test on purpose: the
|
|
12
|
+
// generator needs Pillow, which no manifest in this repo declares, so running it
|
|
13
|
+
// is not available in the DB-free unit gate. What is checked instead is the part
|
|
14
|
+
// that actually rots — that every colour literal left in the generator is the
|
|
15
|
+
// NEUTRAL pack's own value (i.e. a fallback, not a brand), and that the committed
|
|
16
|
+
// art still matches the DMG window the installer opens.
|
|
17
|
+
//
|
|
18
|
+
// Run: node tests/devbox_dmg_backdrop.mjs
|
|
19
|
+
|
|
20
|
+
import { strict as assert } from 'node:assert';
|
|
21
|
+
import { test } from 'node:test';
|
|
22
|
+
import fs from 'node:fs';
|
|
23
|
+
import path from 'node:path';
|
|
24
|
+
import { fileURLToPath } from 'node:url';
|
|
25
|
+
|
|
26
|
+
const REPO_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
|
27
|
+
const APP_DIR = path.join(REPO_ROOT, 'modules', 'dev-box', 'app');
|
|
28
|
+
const GENERATOR = path.join(APP_DIR, 'scripts', 'gen-dmg-backdrop.py');
|
|
29
|
+
|
|
30
|
+
const source = fs.readFileSync(GENERATOR, 'utf8');
|
|
31
|
+
const neutral = JSON.parse(fs.readFileSync(path.join(REPO_ROOT, 'config', 'branding.neutral.json'), 'utf8'));
|
|
32
|
+
|
|
33
|
+
// Strip Python comments + every docstring so rationale prose — which must be free to
|
|
34
|
+
// name the old brand, or the artifact a fix deliberately stopped reading — never trips
|
|
35
|
+
// a scan of the CODE (the stripCommentsForIdentity contract in scripts/gds/fitness.js,
|
|
36
|
+
// applied to `#` comments and """…""" blocks).
|
|
37
|
+
function codeOnly(text) {
|
|
38
|
+
return text
|
|
39
|
+
.replace(/"""[\s\S]*?"""/g, '')
|
|
40
|
+
.split(/\r?\n/)
|
|
41
|
+
.map((line) => line.replace(/(^|[^"'])#.*$/, '$1'))
|
|
42
|
+
.join('\n');
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// COLOR_ROLES maps a theme.ui.colors key → the fallback used when a pack omits it.
|
|
46
|
+
function parseColorRoles(text) {
|
|
47
|
+
const block = text.match(/COLOR_ROLES = \{([\s\S]*?)\n\}/);
|
|
48
|
+
assert.ok(block, 'gen-dmg-backdrop.py must declare a COLOR_ROLES map of theme.ui.colors keys');
|
|
49
|
+
const roles = {};
|
|
50
|
+
for (const [, key, hex] of block[1].matchAll(/"([A-Za-z]+)":\s*"(#[0-9a-fA-F]{3,6})"/g)) roles[key] = hex;
|
|
51
|
+
return roles;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
test('the backdrop generator resolves the instance branding pack, like gen-brand.js', () => {
|
|
55
|
+
const code = codeOnly(source);
|
|
56
|
+
assert.match(code, /BONGOS_BRANDING_FILE/, 'honors the same env override gen-brand.js does');
|
|
57
|
+
assert.match(code, /config", "branding\.json/, 'reads the instance pack first');
|
|
58
|
+
assert.match(code, /config", "branding\.neutral\.json/, 'falls back to the neutral starter');
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
test('the product name + tagline come from the pack, never the committed build artifact', () => {
|
|
62
|
+
// src/brand.generated.json holds the same two strings, but it is a TRACKED file
|
|
63
|
+
// carrying the neutral values and nothing on the backdrop path regenerates it.
|
|
64
|
+
// Reading it made a host render draw its own palette + type under the neutral
|
|
65
|
+
// product's name — the pack has to decide both, the same way gen-brand.js does.
|
|
66
|
+
const code = codeOnly(source);
|
|
67
|
+
assert.doesNotMatch(code, /brand\.generated\.json/,
|
|
68
|
+
'the stale tracked artifact must not outrank the resolved pack');
|
|
69
|
+
assert.match(code, /identity\.get\("worldName"\)/, 'the product name derives from the pack identity');
|
|
70
|
+
assert.match(code, /\.get\("tagline"\)/, 'the subhead derives from the pack copy');
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
test('every colour literal in the generator is the NEUTRAL pack value, not a brand palette', () => {
|
|
74
|
+
const roles = parseColorRoles(source);
|
|
75
|
+
const packColors = neutral.theme.ui.colors;
|
|
76
|
+
assert.ok(Object.keys(roles).length >= 6, 'the art draws with a real palette, not one or two colours');
|
|
77
|
+
for (const [role, fallback] of Object.entries(roles)) {
|
|
78
|
+
assert.ok(packColors[role], `COLOR_ROLES key "${role}" must exist in config/branding.neutral.json theme.ui.colors`);
|
|
79
|
+
assert.equal(fallback.toLowerCase(), String(packColors[role]).toLowerCase(),
|
|
80
|
+
`the "${role}" fallback must equal the neutral pack's value — a divergent literal is a hardcoded brand`);
|
|
81
|
+
}
|
|
82
|
+
// …and no OTHER colour is hardcoded: the only hex literals are those fallbacks.
|
|
83
|
+
const hexes = codeOnly(source).match(/#[0-9a-fA-F]{6}\b/g) || [];
|
|
84
|
+
const allowed = new Set(Object.values(roles).map((h) => h.toLowerCase()));
|
|
85
|
+
for (const hex of hexes) {
|
|
86
|
+
assert.ok(allowed.has(hex.toLowerCase()), `hardcoded colour ${hex} — pull it from theme.ui.colors instead`);
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
test('a pack filling the documented theme surface leaves NO neutral colour on the canvas', () => {
|
|
91
|
+
// The one hole a fallback-equals-neutral check cannot see. `sea` used to be a
|
|
92
|
+
// role here, but gen-brand.js's :root emission — the surface a host pack author
|
|
93
|
+
// actually fills for this app — never mentioned it, so a fully-specified host
|
|
94
|
+
// pack still got Cloud Bongos sky-blue ringing the Applications drop target.
|
|
95
|
+
// A role is only safe to fall back on if the pack was PROMPTED to define it.
|
|
96
|
+
const genBrand = fs.readFileSync(path.join(APP_DIR, 'scripts', 'gen-brand.js'), 'utf8');
|
|
97
|
+
const themeCss = genBrand.match(/function buildThemeCss\([\s\S]*?\n\}/);
|
|
98
|
+
assert.ok(themeCss, 'gen-brand.js must declare buildThemeCss — it defines the app theme surface');
|
|
99
|
+
const documented = new Set([...themeCss[0].matchAll(/\bc\.([A-Za-z]+)\b/g)].map(([, k]) => k));
|
|
100
|
+
const roles = parseColorRoles(source);
|
|
101
|
+
for (const role of Object.keys(roles)) {
|
|
102
|
+
assert.ok(documented.has(role),
|
|
103
|
+
`COLOR_ROLES key "${role}" is not on the documented theme surface (gen-brand.js buildThemeCss) — ` +
|
|
104
|
+
'a host pack has no prompt to define it, so its neutral fallback would paint a Cloud Bongos colour ' +
|
|
105
|
+
'into a fully-specified host build');
|
|
106
|
+
}
|
|
107
|
+
// …and nothing reaches the canvas except through that map, so filling the
|
|
108
|
+
// documented roles overrides every literal in the file.
|
|
109
|
+
for (const [, key] of codeOnly(source).matchAll(/\bp\["([A-Za-z]+)"\]/g)) {
|
|
110
|
+
assert.ok(roles[key], `the render draws with p["${key}"], which COLOR_ROLES does not declare`);
|
|
111
|
+
}
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
test('the generator carries no host-brand copy or motif of its own', () => {
|
|
115
|
+
const code = codeOnly(source);
|
|
116
|
+
// The archaic install copy + the world motifs that shipped into the vanilla build.
|
|
117
|
+
for (const re of [/thine/i, /ashore/i, /amazonprimea/i, /off the boats/i, /meander/i, /greek/i, /marble/i]) {
|
|
118
|
+
assert.doesNotMatch(code, re, `host-brand literal ${re} must not appear in the generator's code`);
|
|
119
|
+
}
|
|
120
|
+
// Copy is derived from the pack (or overridden by it), never a fixed sentence.
|
|
121
|
+
assert.match(code, /dmgHeadline/, 'a pack may state its own headline');
|
|
122
|
+
assert.match(code, /copy\.get\("dmgSubhead"\)|dmgSubhead/, 'a pack may state its own subhead');
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
test('the committed art matches the DMG window the installer opens (660x400 @1x, @2x)', () => {
|
|
126
|
+
const dims = (file) => {
|
|
127
|
+
const buf = fs.readFileSync(path.join(APP_DIR, 'build-resources', file));
|
|
128
|
+
assert.equal(buf.subarray(1, 4).toString('latin1'), 'PNG', `${file} is a PNG`);
|
|
129
|
+
return [buf.readUInt32BE(16), buf.readUInt32BE(20)];
|
|
130
|
+
};
|
|
131
|
+
assert.deepEqual(dims('dmg-background.png'), [660, 400]);
|
|
132
|
+
assert.deepEqual(dims('dmg-background@2x.png'), [1320, 800]);
|
|
133
|
+
// The window/backdrop size is duplicated in dmgbuild-settings.py — drift there
|
|
134
|
+
// means Finder scales the art and the icon wells stop lining up with the icons.
|
|
135
|
+
const dmgbuild = fs.readFileSync(path.join(APP_DIR, 'scripts', 'dmgbuild-settings.py'), 'utf8');
|
|
136
|
+
assert.match(dmgbuild, /window_rect = \(\(150, 150\), \(660, 400\)\)/);
|
|
137
|
+
});
|
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
// tests/devbox_icons.mjs — the Dev Box app + tray icons are BRANDING-DRIVEN and
|
|
2
|
+
// carry no host-brand motif (task 1002261).
|
|
3
|
+
//
|
|
4
|
+
// modules/dev-box/app/scripts/gen-icons.js used to draw one host's world into
|
|
5
|
+
// every build — a shipping container with a wave footer, in a hardcoded palette —
|
|
6
|
+
// so the vanilla Cloud Bongos app shipped another instance's emblem on its tray
|
|
7
|
+
// and in its Dock. The motif is now a bongo pair (brand-neutral geometry) and the
|
|
8
|
+
// palette is resolved from the instance pack the way gen-brand.js resolves it
|
|
9
|
+
// (ADR 0145), matching what task 1002372 did for the DMG backdrop.
|
|
10
|
+
//
|
|
11
|
+
// SOURCE + ARTIFACT rather than an execution test, for two reasons: running the
|
|
12
|
+
// generator REWRITES the committed art (so a test run would dirty the tree), and
|
|
13
|
+
// it needs `sharp`, which config-wise belongs to the art-pipeline module — an
|
|
14
|
+
// instance that doesn't enable that module need not have it installed, and this
|
|
15
|
+
// file is not module-gated. What is checked instead is the part that actually
|
|
16
|
+
// rots: that every colour literal left in the generator is the NEUTRAL pack's own
|
|
17
|
+
// value, that nothing reaches the canvas except through the resolved palette, and
|
|
18
|
+
// that the committed PNGs are still the neutral render at the sizes the app loads.
|
|
19
|
+
//
|
|
20
|
+
// Run: node tests/devbox_icons.mjs
|
|
21
|
+
|
|
22
|
+
import { strict as assert } from 'node:assert';
|
|
23
|
+
import { test } from 'node:test';
|
|
24
|
+
import fs from 'node:fs';
|
|
25
|
+
import path from 'node:path';
|
|
26
|
+
import zlib from 'node:zlib';
|
|
27
|
+
import { fileURLToPath } from 'node:url';
|
|
28
|
+
|
|
29
|
+
const REPO_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
|
30
|
+
const APP_DIR = path.join(REPO_ROOT, 'modules', 'dev-box', 'app');
|
|
31
|
+
const GENERATOR = path.join(APP_DIR, 'scripts', 'gen-icons.js');
|
|
32
|
+
const TRAY_DIR = path.join(APP_DIR, 'assets', 'tray');
|
|
33
|
+
const BUILD_DIR = path.join(APP_DIR, 'build-resources');
|
|
34
|
+
|
|
35
|
+
const STATES = ['off', 'on', 'busy', 'alert'];
|
|
36
|
+
// macOS recolours a *Template.png itself (menu-bar light/dark, inverted when
|
|
37
|
+
// selected) and discards whatever colour the source held. Pure black is an OS
|
|
38
|
+
// requirement, not a brand choice — so it is the one literal exempt below.
|
|
39
|
+
const MAC_TEMPLATE_BLACK = '#000000';
|
|
40
|
+
|
|
41
|
+
const source = fs.readFileSync(GENERATOR, 'utf8');
|
|
42
|
+
const neutral = JSON.parse(fs.readFileSync(path.join(REPO_ROOT, 'config', 'branding.neutral.json'), 'utf8'));
|
|
43
|
+
|
|
44
|
+
// Strip comments so rationale prose — which must be free to name the motif a fix
|
|
45
|
+
// deliberately removed, or the artifact it deliberately stopped reading — never
|
|
46
|
+
// trips a scan of the CODE (the stripCommentsForIdentity contract in
|
|
47
|
+
// scripts/gds/fitness.js; the `//` strip is `://`-safe so a URL stays intact).
|
|
48
|
+
function codeOnly(text) {
|
|
49
|
+
return text
|
|
50
|
+
.replace(/\/\*[\s\S]*?\*\//g, '')
|
|
51
|
+
.split(/\r?\n/)
|
|
52
|
+
.map((line) => line.replace(/(^|[^:])\/\/.*$/, '$1'))
|
|
53
|
+
.join('\n');
|
|
54
|
+
}
|
|
55
|
+
const code = codeOnly(source);
|
|
56
|
+
|
|
57
|
+
// Grab a top-level `const NAME = { … }` / `[ … ]` by brace balance, so the tests
|
|
58
|
+
// below don't depend on how the declaration happens to be wrapped.
|
|
59
|
+
function declBlock(name) {
|
|
60
|
+
const start = code.indexOf(`const ${name} = `);
|
|
61
|
+
assert.ok(start >= 0, `gen-icons.js must declare ${name}`);
|
|
62
|
+
const open = code.indexOf('{', start);
|
|
63
|
+
let depth = 0;
|
|
64
|
+
for (let i = open; i < code.length; i++) {
|
|
65
|
+
if (code[i] === '{') depth += 1;
|
|
66
|
+
else if (code[i] === '}' && (depth -= 1) === 0) return code.slice(open, i + 1);
|
|
67
|
+
}
|
|
68
|
+
throw new Error(`gen-icons.js: unbalanced braces in ${name}`);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// COLOR_ROLES maps a theme.ui.colors key → the fallback used when a pack omits it.
|
|
72
|
+
function colorRoles() {
|
|
73
|
+
const roles = {};
|
|
74
|
+
for (const [, key, hex] of declBlock('COLOR_ROLES').matchAll(/([A-Za-z]+):\s*'(#[0-9a-fA-F]{3,6})'/g)) roles[key] = hex;
|
|
75
|
+
assert.ok(Object.keys(roles).length >= 6, 'the art draws with a real palette, not one or two colours');
|
|
76
|
+
return roles;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// `{ '#': p.accentDeep, f: p.accent }` → { '#': 'accentDeep', f: 'accent' }
|
|
80
|
+
function inkRoles(block) {
|
|
81
|
+
const out = {};
|
|
82
|
+
for (const [, quoted, bare, role] of block.matchAll(/(?:'([^']*)'|([A-Za-z]+))\s*:\s*p\.([A-Za-z]+)/g)) {
|
|
83
|
+
out[quoted ?? bare] = role;
|
|
84
|
+
}
|
|
85
|
+
return out;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function trayGrids() {
|
|
89
|
+
const grids = {};
|
|
90
|
+
for (const [, state, body] of declBlock('GRIDS').matchAll(/(off|on|busy|alert):\s*\[([\s\S]*?)\]/g)) {
|
|
91
|
+
grids[state] = [...body.matchAll(/'([^']*)'/g)].map(([, row]) => row);
|
|
92
|
+
}
|
|
93
|
+
return grids;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const inksOf = (grid) => new Set(grid.join('').split('').filter((c) => c !== '.'));
|
|
97
|
+
const sorted = (set) => [...set].sort().join('');
|
|
98
|
+
|
|
99
|
+
// --- a minimal PNG reader (8-bit RGBA, non-interlaced — what the generator emits).
|
|
100
|
+
// Deliberately dependency-free: this gate must run on an instance that has no
|
|
101
|
+
// image library installed.
|
|
102
|
+
function readRgba(file) {
|
|
103
|
+
const buf = fs.readFileSync(file);
|
|
104
|
+
assert.equal(buf.subarray(1, 4).toString('latin1'), 'PNG', `${path.basename(file)} is a PNG`);
|
|
105
|
+
const width = buf.readUInt32BE(16);
|
|
106
|
+
const height = buf.readUInt32BE(20);
|
|
107
|
+
assert.equal(buf[24], 8, `${path.basename(file)} is 8-bit`);
|
|
108
|
+
assert.equal(buf[25], 6, `${path.basename(file)} is RGBA`);
|
|
109
|
+
assert.equal(buf[28], 0, `${path.basename(file)} is non-interlaced`);
|
|
110
|
+
const idat = [];
|
|
111
|
+
for (let o = 8; o < buf.length;) {
|
|
112
|
+
const len = buf.readUInt32BE(o);
|
|
113
|
+
const type = buf.toString('latin1', o + 4, o + 8);
|
|
114
|
+
if (type === 'IDAT') idat.push(buf.subarray(o + 8, o + 8 + len));
|
|
115
|
+
o += 12 + len;
|
|
116
|
+
if (type === 'IEND') break;
|
|
117
|
+
}
|
|
118
|
+
const raw = zlib.inflateSync(Buffer.concat(idat));
|
|
119
|
+
const bpp = 4;
|
|
120
|
+
const stride = width * bpp;
|
|
121
|
+
const out = Buffer.alloc(height * stride);
|
|
122
|
+
const paeth = (a, b, c) => {
|
|
123
|
+
const da = Math.abs(b - c); // |p - a| where p = a + b - c
|
|
124
|
+
const db = Math.abs(a - c); // |p - b|
|
|
125
|
+
const dc = Math.abs(a + b - 2 * c); // |p - c|
|
|
126
|
+
return da <= db && da <= dc ? a : db <= dc ? b : c;
|
|
127
|
+
};
|
|
128
|
+
for (let y = 0; y < height; y++) {
|
|
129
|
+
const filter = raw[y * (stride + 1)];
|
|
130
|
+
const line = raw.subarray(y * (stride + 1) + 1, y * (stride + 1) + 1 + stride);
|
|
131
|
+
for (let i = 0; i < stride; i++) {
|
|
132
|
+
const a = i >= bpp ? out[y * stride + i - bpp] : 0;
|
|
133
|
+
const b = y > 0 ? out[(y - 1) * stride + i] : 0;
|
|
134
|
+
const c = i >= bpp && y > 0 ? out[(y - 1) * stride + i - bpp] : 0;
|
|
135
|
+
let v = line[i];
|
|
136
|
+
if (filter === 1) v += a;
|
|
137
|
+
else if (filter === 2) v += b;
|
|
138
|
+
else if (filter === 3) v += (a + b) >> 1;
|
|
139
|
+
else if (filter === 4) v += paeth(a, b, c);
|
|
140
|
+
out[y * stride + i] = v & 255;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
return { width, height, data: out };
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Every colour that is actually painted (alpha > 0), lowercased.
|
|
147
|
+
function opaqueColors(file) {
|
|
148
|
+
const { width, height, data } = readRgba(file);
|
|
149
|
+
const seen = new Set();
|
|
150
|
+
for (let i = 0; i < width * height; i++) {
|
|
151
|
+
if (data[i * 4 + 3] === 0) continue;
|
|
152
|
+
seen.add(`#${[0, 1, 2].map((j) => data[i * 4 + j].toString(16).padStart(2, '0')).join('')}`);
|
|
153
|
+
}
|
|
154
|
+
return seen;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
test('the icon generator resolves the instance branding pack, like gen-brand.js', () => {
|
|
158
|
+
assert.match(code, /BONGOS_BRANDING_FILE/, 'honors the same env override gen-brand.js does');
|
|
159
|
+
assert.match(code, /'config', 'branding\.json'/, 'reads the instance pack first');
|
|
160
|
+
assert.match(code, /'config', 'branding\.neutral\.json'/, 'falls back to the neutral starter');
|
|
161
|
+
assert.match(code, /branding\.theme/, 'the palette comes from the pack theme');
|
|
162
|
+
assert.match(code, /\.colors/, '…specifically theme.ui.colors');
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
test('the palette comes from the pack, never the committed build artifact', () => {
|
|
166
|
+
// src/brand.generated.json carries colours too, but it is a TRACKED file holding
|
|
167
|
+
// the neutral values and nothing on the icon path regenerates it. Preferring it
|
|
168
|
+
// let a host render draw the host palette under the neutral product's name once
|
|
169
|
+
// already (task 1002372) — the pack has to decide, every time.
|
|
170
|
+
assert.doesNotMatch(code, /brand\.generated\.json/,
|
|
171
|
+
'the stale tracked artifact must not outrank the resolved pack');
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
test('every colour literal in the generator is the NEUTRAL pack value, not a brand palette', () => {
|
|
175
|
+
const roles = colorRoles();
|
|
176
|
+
const packColors = neutral.theme.ui.colors;
|
|
177
|
+
for (const [role, fallback] of Object.entries(roles)) {
|
|
178
|
+
assert.ok(packColors[role], `COLOR_ROLES key "${role}" must exist in config/branding.neutral.json theme.ui.colors`);
|
|
179
|
+
assert.equal(fallback.toLowerCase(), String(packColors[role]).toLowerCase(),
|
|
180
|
+
`the "${role}" fallback must equal the neutral pack's value — a divergent literal is a hardcoded brand`);
|
|
181
|
+
}
|
|
182
|
+
// …and no OTHER colour is hardcoded. The one exemption is the macOS template
|
|
183
|
+
// black, which is an OS requirement (see MAC_TEMPLATE_BLACK above), so it is
|
|
184
|
+
// also pinned to appear ONLY in the template palette.
|
|
185
|
+
const allowed = new Set([...Object.values(roles).map((h) => h.toLowerCase()), MAC_TEMPLATE_BLACK]);
|
|
186
|
+
const withoutGrids = code.replace(declBlock('GRIDS'), '');
|
|
187
|
+
for (const hex of withoutGrids.match(/#[0-9a-fA-F]{6}\b/g) || []) {
|
|
188
|
+
assert.ok(allowed.has(hex.toLowerCase()), `hardcoded colour ${hex} — pull it from theme.ui.colors instead`);
|
|
189
|
+
}
|
|
190
|
+
const macBlock = declBlock('MAC');
|
|
191
|
+
const macHexes = [...macBlock.matchAll(/'(#[0-9a-fA-F]{6})'/g)].map(([, h]) => h.toLowerCase());
|
|
192
|
+
assert.ok(macHexes.length > 0, 'the macOS template palette must state its own black');
|
|
193
|
+
for (const hex of macHexes) {
|
|
194
|
+
assert.equal(hex, MAC_TEMPLATE_BLACK, 'a *Template.png must be pure black — the OS recolours it');
|
|
195
|
+
}
|
|
196
|
+
const nonMacBlack = withoutGrids.replace(macBlock, '').match(/#0{6}\b/g) || [];
|
|
197
|
+
assert.equal(nonMacBlack.length, 0, 'black outside the macOS template palette is a hardcoded colour, not an OS rule');
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
test('a pack filling the documented theme surface leaves NO neutral colour on the canvas', () => {
|
|
201
|
+
// The hole a fallback-equals-neutral check cannot see. A role is only safe to
|
|
202
|
+
// fall back on if the host pack author was PROMPTED to define it — and the
|
|
203
|
+
// prompt is gen-brand.js's :root emission, the app's own theme surface. A role
|
|
204
|
+
// outside it (this is what `sea` did to the DMG backdrop) keeps its Cloud
|
|
205
|
+
// Bongos value even in a fully-specified host build.
|
|
206
|
+
const genBrand = fs.readFileSync(path.join(APP_DIR, 'scripts', 'gen-brand.js'), 'utf8');
|
|
207
|
+
const themeCss = genBrand.match(/function buildThemeCss\([\s\S]*?\n\}/);
|
|
208
|
+
assert.ok(themeCss, 'gen-brand.js must declare buildThemeCss — it defines the app theme surface');
|
|
209
|
+
const documented = new Set([...themeCss[0].matchAll(/\bc\.([A-Za-z]+)\b/g)].map(([, k]) => k));
|
|
210
|
+
const roles = colorRoles();
|
|
211
|
+
for (const role of Object.keys(roles)) {
|
|
212
|
+
assert.ok(documented.has(role),
|
|
213
|
+
`COLOR_ROLES key "${role}" is not on the documented theme surface (gen-brand.js buildThemeCss) — ` +
|
|
214
|
+
'a host pack has no prompt to define it, so its neutral fallback would paint a Cloud Bongos colour ' +
|
|
215
|
+
'into a fully-specified host build');
|
|
216
|
+
}
|
|
217
|
+
// …and nothing reaches the canvas except through that map, so filling the
|
|
218
|
+
// documented roles overrides every literal in the file.
|
|
219
|
+
for (const [, key] of code.matchAll(/\bp\.([A-Za-z]+)\b/g)) {
|
|
220
|
+
assert.ok(roles[key], `the render draws with p.${key}, which COLOR_ROLES does not declare`);
|
|
221
|
+
}
|
|
222
|
+
const used = new Set([...code.matchAll(/\bp\.([A-Za-z]+)\b/g)].map(([, k]) => k));
|
|
223
|
+
for (const role of Object.keys(roles)) {
|
|
224
|
+
assert.ok(used.has(role), `COLOR_ROLES declares "${role}" but nothing draws with it — drop it or use it`);
|
|
225
|
+
}
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
test('the generator carries no host-brand motif of its own', () => {
|
|
229
|
+
for (const re of [/off the boats/i, /amazonprimea/i, /shipping[- ]container/i, /greek/i, /meander/i, /parchment/i]) {
|
|
230
|
+
assert.doesNotMatch(code, re, `host-brand literal ${re} must not appear in the generator's code`);
|
|
231
|
+
}
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
test('the four tray states are 16x16 and differ in SHAPE, not only colour', () => {
|
|
235
|
+
// The macOS template icons are monochrome — the OS throws the colour away — so
|
|
236
|
+
// two states that differ only in their palette are the SAME icon on a Mac.
|
|
237
|
+
const grids = trayGrids();
|
|
238
|
+
assert.deepEqual(Object.keys(grids).sort(), [...STATES].sort(), 'exactly the four tray states');
|
|
239
|
+
for (const [state, grid] of Object.entries(grids)) {
|
|
240
|
+
assert.equal(grid.length, 16, `${state} grid is 16 rows`);
|
|
241
|
+
for (const row of grid) assert.equal(row.length, 16, `${state} grid rows are 16 cells`);
|
|
242
|
+
}
|
|
243
|
+
const silhouette = (grid) => grid.join('\n').replace(/[^.]/g, '#');
|
|
244
|
+
for (let i = 0; i < STATES.length; i++) {
|
|
245
|
+
for (let j = i + 1; j < STATES.length; j++) {
|
|
246
|
+
assert.notEqual(silhouette(grids[STATES[i]]), silhouette(grids[STATES[j]]),
|
|
247
|
+
`tray states "${STATES[i]}" and "${STATES[j]}" share one silhouette — indistinguishable as macOS templates`);
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
test('every mark the art draws has a colour, and every colour marks something', () => {
|
|
253
|
+
// A mark with no palette entry renders as a hole; a palette entry with no mark
|
|
254
|
+
// is dead weight that outlives the shape it was for.
|
|
255
|
+
const grids = trayGrids();
|
|
256
|
+
const macInks = new Set(Object.keys(inkRoles(declBlock('MAC').replace(/'#[0-9a-fA-F]{6}'/g, 'p.macBlack'))));
|
|
257
|
+
assert.equal(sorted(macInks), sorted(new Set(STATES.flatMap((s) => [...inksOf(grids[s])]))),
|
|
258
|
+
'the macOS template palette must cover exactly the inks the grids use');
|
|
259
|
+
const win = declBlock('WIN');
|
|
260
|
+
for (const [, state, block] of win.matchAll(/(off|on|busy|alert):\s*(\{[^}]*\})/g)) {
|
|
261
|
+
assert.equal(sorted(new Set(Object.keys(inkRoles(block)))), sorted(inksOf(grids[state])),
|
|
262
|
+
`the win-${state} palette must cover exactly the inks the ${state} grid uses`);
|
|
263
|
+
}
|
|
264
|
+
// Same contract for the app icon, whose grid is built imperatively.
|
|
265
|
+
const appGrid = code.slice(code.indexOf('function buildAppGrid('), code.indexOf('async function main('));
|
|
266
|
+
const appInks = new Set([...appGrid.matchAll(/'([a-z])'/g)].map(([, c]) => c));
|
|
267
|
+
assert.equal(sorted(appInks), sorted(new Set(Object.keys(inkRoles(declBlock('APP_PALETTE'))))),
|
|
268
|
+
'the app palette must cover exactly the inks buildAppGrid draws');
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
test('the committed PNGs are the 13 files the app and electron-builder load', () => {
|
|
272
|
+
const dims = (file) => {
|
|
273
|
+
const buf = fs.readFileSync(file);
|
|
274
|
+
assert.equal(buf.subarray(1, 4).toString('latin1'), 'PNG', `${path.basename(file)} is a PNG`);
|
|
275
|
+
return [buf.readUInt32BE(16), buf.readUInt32BE(20)];
|
|
276
|
+
};
|
|
277
|
+
// electron-builder derives .icns/.ico from this one, so it must stay 1024².
|
|
278
|
+
assert.deepEqual(dims(path.join(BUILD_DIR, 'icon.png')), [1024, 1024]);
|
|
279
|
+
for (const state of STATES) {
|
|
280
|
+
assert.deepEqual(dims(path.join(TRAY_DIR, `mac-${state}Template.png`)), [16, 16]);
|
|
281
|
+
assert.deepEqual(dims(path.join(TRAY_DIR, `mac-${state}Template@2x.png`)), [32, 32]);
|
|
282
|
+
assert.deepEqual(dims(path.join(TRAY_DIR, `win-${state}.png`)), [32, 32]);
|
|
283
|
+
}
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
test('the committed art is the NEUTRAL render — no host palette was checked in', () => {
|
|
287
|
+
// The source checks above prove the generator READS the pack; only the pixels
|
|
288
|
+
// prove the committed artifact is the vanilla one. Someone rendering with their
|
|
289
|
+
// own config/branding.json present and committing the result is exactly the
|
|
290
|
+
// failure this catches.
|
|
291
|
+
const packValues = new Set(Object.values(neutral.theme.ui.colors).map((h) => String(h).toLowerCase()));
|
|
292
|
+
for (const state of STATES) {
|
|
293
|
+
for (const file of [`mac-${state}Template.png`, `mac-${state}Template@2x.png`]) {
|
|
294
|
+
assert.deepEqual([...opaqueColors(path.join(TRAY_DIR, file))], [MAC_TEMPLATE_BLACK],
|
|
295
|
+
`${file} must be pure black + alpha — the OS recolours a template icon`);
|
|
296
|
+
}
|
|
297
|
+
for (const hex of opaqueColors(path.join(TRAY_DIR, `win-${state}.png`))) {
|
|
298
|
+
assert.ok(packValues.has(hex), `win-${state}.png paints ${hex}, which is not a config/branding.neutral.json colour`);
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
for (const hex of opaqueColors(path.join(BUILD_DIR, 'icon.png'))) {
|
|
302
|
+
assert.ok(packValues.has(hex), `icon.png paints ${hex}, which is not a config/branding.neutral.json colour`);
|
|
303
|
+
}
|
|
304
|
+
});
|