@bongos/core 1.18.3 → 1.18.19
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 +1576 -286
- 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/migrate.sh +99 -14
- 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 +124 -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/migrate_root_resolution.mjs +124 -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/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
package/src/instance-config.js
CHANGED
|
@@ -65,6 +65,35 @@ function defaultApiBase(b = safeBrand()) {
|
|
|
65
65
|
return (typeof origin === 'string' && origin.trim()) || 'http://localhost:3000';
|
|
66
66
|
}
|
|
67
67
|
|
|
68
|
+
// The API base a CLI command should actually talk to:
|
|
69
|
+
// <PREFIX>_API_BASE (env) → the PERSISTED session's api_base → defaultApiBase()
|
|
70
|
+
// The session beats the branding default because a builder authenticated against
|
|
71
|
+
// instance A must keep hitting A even when this checkout's own branding names B —
|
|
72
|
+
// which is exactly what broke re-auth (task 1002427). cliClient() had this order
|
|
73
|
+
// right; paste-token.js hardcoded the founder host and reauth.js used the bare
|
|
74
|
+
// branding default, so a token minted by one instance was verified against
|
|
75
|
+
// another and rejected 401. Every base resolution routes through here so the
|
|
76
|
+
// three cannot drift again.
|
|
77
|
+
//
|
|
78
|
+
// `session` is injected by callers that already loaded it (cliClient) and read
|
|
79
|
+
// from the session file otherwise; pass null to mean "no session" without a disk
|
|
80
|
+
// read. Never returns empty — an unusable api_base falls back to the default.
|
|
81
|
+
function resolveApiBase({ session, env = process.env, b = safeBrand() } = {}) {
|
|
82
|
+
const fromEnv = resolveEnv('API_BASE', { env, prefix: envPrefix(b) });
|
|
83
|
+
if (fromEnv) return fromEnv;
|
|
84
|
+
let s = session;
|
|
85
|
+
if (s === undefined) {
|
|
86
|
+
try {
|
|
87
|
+
const raw = readConfigFileSync('gds-session.json', { b });
|
|
88
|
+
s = raw ? JSON.parse(raw) : null;
|
|
89
|
+
} catch (_) {
|
|
90
|
+
s = null; // unreadable/corrupt session must never break base resolution
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
const fromSession = s && typeof s.api_base === 'string' && s.api_base.trim();
|
|
94
|
+
return fromSession || defaultApiBase(b);
|
|
95
|
+
}
|
|
96
|
+
|
|
68
97
|
// Absolute path to the instance config dir — the WRITE target and first read.
|
|
69
98
|
function configHome(b = safeBrand()) {
|
|
70
99
|
return path.join(os.homedir(), '.config', configDirName(b));
|
|
@@ -202,6 +231,7 @@ module.exports = {
|
|
|
202
231
|
configDirName,
|
|
203
232
|
envPrefix,
|
|
204
233
|
defaultApiBase,
|
|
234
|
+
resolveApiBase,
|
|
205
235
|
configHome,
|
|
206
236
|
configReadDirs,
|
|
207
237
|
configPath,
|
package/src/module-api.js
CHANGED
|
@@ -237,8 +237,131 @@ const { validateOrRespond, LIMITS, parseId, asyncHandler, corsPublicGet, parsePa
|
|
|
237
237
|
// was --skip-pin-verify, which the unattended auto-upgrade subscription cannot pass).
|
|
238
238
|
// package-core now drops npm-stripped files from the manifest + pin so it describes exactly
|
|
239
239
|
// what npm installs. Build-tool fix, no doorway export change → semver PATCH.
|
|
240
|
+
// 1.18.4 — ship the platform-identity hub module in the core package (task 1002235, goal 1000041).
|
|
241
|
+
// The module (ADR 0141: /sso/*, projects catalog, my-projects, scouting + its migrations) was
|
|
242
|
+
// built but never added to publish-manifest PUBLISH_ALLOWLIST, so it did not ship in
|
|
243
|
+
// @bongos/core — the hub's enabledModules() threw "unknown module platform-identity" and
|
|
244
|
+
// provision.js's federation load MODULE_NOT_FOUNDed. Reclassified IN (sibling of provisioning);
|
|
245
|
+
// default:false → shipped ≠ enabled, dormant on every instance until the hub turns it on. PATCH.
|
|
246
|
+
// 1.18.5 — configurable post-login landing (task 1002240, goal 1000041). A successful web sign-in
|
|
247
|
+
// that carried no explicit ?return= / same-apex Referer now lands on branding.landing.postLogin
|
|
248
|
+
// (auth.js postLoginLanding) instead of a hardcoded /builders. Portable default stays
|
|
249
|
+
// '/builders' (the hall always exists); env-overridable per instance via
|
|
250
|
+
// <PREFIX>_POST_LOGIN_LANDING. cloudbongos.com sets it to /gate/ so a visitor who signs in
|
|
251
|
+
// from the orbs returns to the orbs, never auto-redirected into the builders' hall. The value
|
|
252
|
+
// is validated through the open-redirect allowlist. Additive config read, no doorway change → PATCH.
|
|
253
|
+
// 1.18.6 — post-login ?return= fix (task 1002241). parseCookie() now URL-DECODES the cookie
|
|
254
|
+
// value, symmetric with Express res.cookie's percent-ENCODING. Before this, a return
|
|
255
|
+
// target like "/create" was stored as "%2Fcreate"; safeReturnTo() rejected the encoded
|
|
256
|
+
// form and every explicit ?return= fell through to the post-login landing (on
|
|
257
|
+
// cloudbongos.com, /gate/) — most visibly the onboarding "Connect GitHub" button, which
|
|
258
|
+
// bounced to the orbs instead of back to the wizard. Also un-breaks the #726 subdomain
|
|
259
|
+
// return. A bug fix in a shared route helper, no doorway change → PATCH.
|
|
260
|
+
// 1.18.7 — federated single-logout (task 1002238, goal 1000041, ADR 0144). Signing out of the hub now
|
|
261
|
+
// signs you out of every federated project too. Core gains: a `builder.logout` seam event
|
|
262
|
+
// (emitted by POST /auth/logout), a POST /auth/backchannel-logout endpoint (dormant unless the
|
|
263
|
+
// instance federates — idp set), and auth.js verifyHubLogoutToken. The hub (platform-identity)
|
|
264
|
+
// listens for builder.logout and fans a hub-signed EdDSA logout token out to each federated
|
|
265
|
+
// instance the builder entered, which verifies it (aud-scoped, purpose-tagged, ~120s TTL) and
|
|
266
|
+
// revokes that builder's local sessions. Identity/trust boundary intact (the token carries only
|
|
267
|
+
// a subject + purpose, never a rank). Additive event + route, no doorway change → PATCH.
|
|
268
|
+
// 1.18.8 — hall sign-out works on every page (task 1002245). The real sign-out (POST /auth/logout:
|
|
269
|
+
// revoke session + clear cookies + emit the `builder.logout` federated single-logout) was wired
|
|
270
|
+
// ONLY in the index script (builders.js); shell.js draws the sign-out control on EVERY hall page
|
|
271
|
+
// as a bare GET anchor to the POST-only endpoint, so on non-index pages the click 404'd — the
|
|
272
|
+
// session was never revoked and the hub never notified (the 1.18.7 SLO was bypassed). Centralized
|
|
273
|
+
// in shell.js (doLogout + a delegated click listener), with tests/hall_signout.mjs proving it.
|
|
274
|
+
// Also carries minor /create wizard polish (tasks 1002246/1002247). Client + shared-surface fix,
|
|
275
|
+
// no doorway change → PATCH.
|
|
276
|
+
// 1.18.9 — /create onboarding: the wizard's own "Sign in with GitHub" now requests repo scope up front
|
|
277
|
+
// (read:user + public_repo via &repos=1), so a visitor who signs in THROUGH the create flow grants
|
|
278
|
+
// everything in one GitHub screen and never hits the separate "Connect GitHub" step at Home —
|
|
279
|
+
// ensureRepoAccess()'s probe finds the stored token and shows the picker/creator directly. Also
|
|
280
|
+
// reworks the residual need-connect copy + button ("Allow repo access") so a user already signed
|
|
281
|
+
// in with identity-only scope isn't asked to "connect" as if signing in twice. Hall + CLI sign-in
|
|
282
|
+
// stay identity-only (ADR 0143 least-privilege default). Client-only (public-landing/create.html),
|
|
283
|
+
// no doorway change → PATCH. (task 1002259)
|
|
284
|
+
// 1.18.10 — reward-mode go-live: the configurable per-instance branding.reward.mode knob (economy;
|
|
285
|
+
// ADR 0146) lands together with the goal-1000037 pay-on-land migrations. Cloud Bongos selects
|
|
286
|
+
// "cost-plus-only" (instance config), so a shipped task pays only the cost-plus session reward,
|
|
287
|
+
// not the per-task author estimate; the estimate still rides the row for display + the no-zero
|
|
288
|
+
// claim gate. This deploy applies core_193 (tasks.needs_rebase columns) + core_194 (the one-time
|
|
289
|
+
// confirmed-backlog clawback). Default stays "cost-plus-and-estimate" → every other instance is
|
|
290
|
+
// unchanged; economy-only, no doorway change → PATCH. (task 1002270 + goal 1000037 go-live 1002273)
|
|
291
|
+
// 1.18.12 — Docs-surface correctness (task 1002374). User-facing "GDS"/"Game Development System" →
|
|
292
|
+
// "Bongos" (primer, OpenAPI spec title/description + reference header, the two CLI session
|
|
293
|
+
// messages the primer quotes). 04-architecture diagram REDRAWN for the standalone model —
|
|
294
|
+
// own repo + auto-pull deploy (ADR 0129) + vendored @bongos/core (ADR 0108), product-neutral;
|
|
295
|
+
// it had hardcoded the reference game monorepo (amazonprimea.com / GitHub-Actions deploy-prod.yml
|
|
296
|
+
// / "the live game"). Docs/prose + diagram generator only, no schema/doorway change → PATCH.
|
|
297
|
+
// 1.18.11 — Standalone instances get live-updating docs (ADR 0147, task 1002314). gen-session-index.js
|
|
298
|
+
// now resolves the INSTANCE root (was hardcoded to the core, so a standalone box indexed
|
|
299
|
+
// node_modules/@bongos/core instead of its own sessions); adds upgrade.regenerateNavDocs() +
|
|
300
|
+
// a regen-instance-docs.js CLI wired into init/upgrade/provision-deploy; INSTANCE_GITIGNORE
|
|
301
|
+
// ignores the whole-file nav docs. Docs-pipeline only, no schema/doorway change → PATCH.
|
|
302
|
+
// (go-live 1002318)
|
|
303
|
+
// 1.18.12 — docs-surface correctness: GDS→Bongos rename + standalone architecture redraw
|
|
304
|
+
// (task 1002374). Docs/prose + diagram generator only, no schema/doorway change → PATCH.
|
|
305
|
+
// 1.18.13 — `bongos dev`: one-command LOCAL launcher for a standalone instance (task 1002397,
|
|
306
|
+
// ADR 0149). New CLI verb + scripts/gds/dev.js + dev-lib.js so a freshly-created,
|
|
307
|
+
// localhost-configured project stands up locally in one step. No schema/doorway change → PATCH.
|
|
308
|
+
// 1.18.14 — module pollers actually start on a standalone instance (task 1002419). The loader's
|
|
309
|
+
// startModulePollers had ZERO callers once the game entrypoint server.js was retired, so
|
|
310
|
+
// EVERY module poller was dead — most visibly lifecycle/publish-reconciler, which is what
|
|
311
|
+
// drives confirmed→shipped, leaving merged tasks stranded at 'confirmed' indefinitely
|
|
312
|
+
// (12 rows, oldest 6.2 days). src/platform-server.js now starts them at boot behind the
|
|
313
|
+
// same isEnabled gate mountModuleRoutes uses, and stops them on SIGTERM/SIGINT. Wiring
|
|
314
|
+
// only — no schema/doorway change → PATCH. Any instance on ≤1.18.13 lands nothing until
|
|
315
|
+
// it takes this bump.
|
|
316
|
+
// 1.18.15 — re-release of the 1.18.14 module-poller fix (task 1002419) under a fresh number.
|
|
317
|
+
// A SEPARATE release had already published 1.18.14 for the task-scoped dev-box go-live
|
|
318
|
+
// (goal 1000051), so the poller-fix 1.18.14 could never reach an instance already pinned to
|
|
319
|
+
// the dev-box 1.18.14 (same version = npm no-op; the pull-deploy loop skips npm ci when the
|
|
320
|
+
// lockfile is unchanged). This bump carries the identical src/platform-server.js poller
|
|
321
|
+
// wiring forward on a number no instance has taken yet, so the pin can actually move. No
|
|
322
|
+
// code delta vs the 1.18.14 already on main — version constant only → PATCH. (task 1002421)
|
|
323
|
+
// 1.18.16 — go-live carrier for the hall version-UI work already merged to main since 1.18.15:
|
|
324
|
+
// the Roadmap "Command deck" redesign (task 1002426) and the sidebar reorg — nav order
|
|
325
|
+
// Roadmap→Goals→Tasks, the "Work" board relabelled "Tasks", goal-detail task parity
|
|
326
|
+
// (task 1002425, PR #17). Both landed on main with NO version bump, so no vendored-tarball
|
|
327
|
+
// instance could take them (the pull-deploy pin only moves on a fresh number). No new code
|
|
328
|
+
// delta beyond this constant → PATCH. (go-live task 1002431)
|
|
329
|
+
// 1.18.17 — Goals page reorganised into a task-readiness rollup (grouped
|
|
330
|
+
// Active/Blocked/Achieved; each goal rolls its tasks into ready/in-progress/
|
|
331
|
+
// blocked + a next claimable task) and the task-list API gained an opt-in
|
|
332
|
+
// `?include=deps` that enriches each task with blocked_by[]/blocks so the
|
|
333
|
+
// hall can paint readiness without an N+1 (db.attachTaskDepSummaries). New
|
|
334
|
+
// export is additive; the enrichment is opt-in → PATCH. (task 1002439)
|
|
335
|
+
// 1.18.18 — go-live carrier for 171 commits that landed on main between 2026-07-24
|
|
336
|
+
// and 2026-08-03 with no bump behind them, so no vendored-tarball instance
|
|
337
|
+
// could take any of them. Headline: the governance permission system
|
|
338
|
+
// (BV1.R95–R109 — resolver + kernel port, first-class principals, session
|
|
339
|
+
// scopes, requirePermission(), ownership-scoped grants, config/governance.json)
|
|
340
|
+
// and the route-rank/validation hardening that followed it; the ship.js
|
|
341
|
+
// Windows project-path fix so Windows sessions actually get paid (89f291c);
|
|
342
|
+
// the sequential chain runner (task 1002484); Metic task abandon/un-abandon
|
|
343
|
+
// (ADR 0152); goal-authored criteria + the orphan-task goal_advisory (ADR
|
|
344
|
+
// 0154); the nightly-backup 203/EXEC fix (task 1002479); and a broad hall
|
|
345
|
+
// pass (jump palette, sidebar regroup, browser-side idea triage, mobile
|
|
346
|
+
// overflow). This bump also carries the /version-vs-CORE_VERSION divergence
|
|
347
|
+
// check added under task 1002521 — the gap above went unnoticed for ten days
|
|
348
|
+
// precisely because nothing compared live against main. The doorway surface
|
|
349
|
+
// is byte-identical to 1.18.17 (governance is reached through the kernel
|
|
350
|
+
// port, not a new export), so despite the size this is → PATCH. (task 1002521)
|
|
351
|
+
// 1.18.19 — SINGLE-FIX patch cut from the core-v1.18.18 tag, not from main: core
|
|
352
|
+
// migrations were silently skipped on any checkout whose absolute path
|
|
353
|
+
// contains a space. `for f in $MIGRATIONS` word-split the newline-separated
|
|
354
|
+
// list on IFS, fragmenting every path; the `[ -e "$f" ]` guard then dropped
|
|
355
|
+
// all of them and schema_migrations stayed empty. The module loop is quoted,
|
|
356
|
+
// so module migrations ran against an empty schema and the operator saw a
|
|
357
|
+
// misleading `relation "achievements" does not exist` from modules/economy
|
|
358
|
+
// instead of the real cause. Now read with a newline-safe herestring, and a
|
|
359
|
+
// no-op guard makes the skip FATAL rather than silent. No doorway change, no
|
|
360
|
+
// schema change, one script + its test → PATCH. Deliberately does NOT carry
|
|
361
|
+
// the unreleased 1.19 tree on main, so a blocked instance can take the fix
|
|
362
|
+
// alone. (task 1002611)
|
|
240
363
|
// ---------------------------------------------------------------------------
|
|
241
|
-
const CORE_VERSION = '1.18.
|
|
364
|
+
const CORE_VERSION = '1.18.19'; // single-fix patch off the 1.18.18 tag: migrations skipped on a path with a space (task 1002611); see changelog above
|
|
242
365
|
|
|
243
366
|
// A namespaced logger so a module's log lines are attributable + consistent.
|
|
244
367
|
// Usage: const log = api.logger('dev-box'); log.info('mounted');
|
package/src/platform-server.js
CHANGED
|
@@ -29,10 +29,17 @@
|
|
|
29
29
|
// by ABSENCE (the game middleware is simply never mounted), not by a runtime
|
|
30
30
|
// check, exactly as preview-server.js enforces "no internal API" by absence.
|
|
31
31
|
//
|
|
32
|
-
//
|
|
33
|
-
//
|
|
34
|
-
//
|
|
35
|
-
//
|
|
32
|
+
// BACKGROUND SERVICES — started here, at the bottom of this file (task 1002419).
|
|
33
|
+
// R51 deferred "which background services an instance runs" to the feature-module
|
|
34
|
+
// system. That system shipped: a module DECLARES contributes.pollers in its
|
|
35
|
+
// manifest and the instance's enabled-module set decides what actually runs
|
|
36
|
+
// (ADR 0083 / 0093 §1). So the policy question is already answered — the enabled
|
|
37
|
+
// set IS the policy — and this entrypoint just has to honour it, exactly as it
|
|
38
|
+
// already honours contributes.routes (mountModuleRoutes) and contributes.
|
|
39
|
+
// webSurfaces (moduleWebSurfaces). Until this was wired, startModulePollers had
|
|
40
|
+
// ZERO callers post-split — its only caller was ever the retired game entrypoint
|
|
41
|
+
// server.js — so on a standalone instance EVERY module poller was dead: the
|
|
42
|
+
// confirmed→shipped sweep never fired and shipped tasks stranded at 'confirmed'.
|
|
36
43
|
|
|
37
44
|
const http = require('http');
|
|
38
45
|
const fs = require('fs');
|
|
@@ -40,6 +47,8 @@ const path = require('path');
|
|
|
40
47
|
const express = require('express');
|
|
41
48
|
const { mountInternalSurfaces, isStatusHost, isBuildersHost, injectBrandingGlobal } = require('./bongos/serve-internal');
|
|
42
49
|
const { isApiShapedPath, apiNotFoundBody } = require('./bongos/api-path-404');
|
|
50
|
+
const moduleLoader = require('./module-loader/loader');
|
|
51
|
+
const { isModuleEnabled } = require('./modules');
|
|
43
52
|
|
|
44
53
|
const PORT = Number(process.env.PORT) || 3000;
|
|
45
54
|
const HOST = process.env.HOST || '127.0.0.1';
|
|
@@ -63,6 +72,19 @@ function landingPage(file) {
|
|
|
63
72
|
return _landingPages.get(file);
|
|
64
73
|
}
|
|
65
74
|
function landingHtml() { return landingPage('index.html'); }
|
|
75
|
+
// The interactive landing gate (public-landing/gate/, ported from amazonprimea
|
|
76
|
+
// in task 1002407). When an instance ships it, it is the apex front door
|
|
77
|
+
// (task 1002408). Unlike the placeholder landing it is a self-contained baked
|
|
78
|
+
// design that reads no window.__BRANDING__, so it is served RAW, not injected.
|
|
79
|
+
// Memoized: absolute path to gate/index.html, or '' when the instance ships no gate.
|
|
80
|
+
let _gateIndexPath; // undefined = unresolved, '' = absent, string = present
|
|
81
|
+
function gateIndexPath() {
|
|
82
|
+
if (_gateIndexPath === undefined) {
|
|
83
|
+
const p = path.join(LANDING_DIR, 'gate', 'index.html');
|
|
84
|
+
_gateIndexPath = fs.existsSync(p) ? p : '';
|
|
85
|
+
}
|
|
86
|
+
return _gateIndexPath;
|
|
87
|
+
}
|
|
66
88
|
function isApexHost(req) {
|
|
67
89
|
return !isStatusHost(req) && !isBuildersHost(req);
|
|
68
90
|
}
|
|
@@ -81,16 +103,38 @@ function buildPlatformApp() {
|
|
|
81
103
|
// The whole internal surface — shared verbatim with server.js (ADR 0062 R51).
|
|
82
104
|
mountInternalSurfaces(app);
|
|
83
105
|
|
|
84
|
-
// Apex landing. On the apex host (not status./builders.)
|
|
85
|
-
//
|
|
86
|
-
//
|
|
87
|
-
//
|
|
106
|
+
// Apex landing. On the apex host (not status./builders.):
|
|
107
|
+
// * if this instance ships the interactive landing gate (public-landing/gate/),
|
|
108
|
+
// the gate is the front door — GET / 302-redirects to /gate/ (mirrors the
|
|
109
|
+
// amazonprimea Caddy rule this replaces, task 1002408). The redirect (not a
|
|
110
|
+
// direct serve at /) is required so the browser base URL becomes /gate/ and
|
|
111
|
+
// the gate's relative asset paths (sprites/…, ../world, ../assets) resolve.
|
|
112
|
+
// * otherwise GET / serves the branding-injected minimal placeholder.
|
|
113
|
+
// Both fall through to the 404 tail when neither is present, and to the landing
|
|
114
|
+
// asset handler below for non-/ paths (e.g. the mascot image).
|
|
88
115
|
app.get('/', (req, res, next) => {
|
|
89
116
|
if (!isApexHost(req)) return next();
|
|
117
|
+
if (gateIndexPath()) return res.redirect(302, '/gate/');
|
|
90
118
|
const html = landingHtml();
|
|
91
119
|
if (!html) return next();
|
|
92
120
|
res.type('html').send(html);
|
|
93
121
|
});
|
|
122
|
+
// /gate → /gate/ (so the gate's relative asset paths resolve against the dir),
|
|
123
|
+
// and /gate/ serves the gate's own index RAW — a self-contained baked design,
|
|
124
|
+
// so no window.__BRANDING__ injection. The gate's sub-assets (/gate/*, /world/*,
|
|
125
|
+
// /assets/*) are served by the express.static(LANDING_DIR) handler below with no
|
|
126
|
+
// extra rule, which also closes the amazonprimea Caddy gap where /assets/anim/idle/*
|
|
127
|
+
// (the buddha idle frames) fell through to the app instead of the static server.
|
|
128
|
+
// One handler for both forms — Express's non-strict routing matches /gate and
|
|
129
|
+
// /gate/ interchangeably, so branch on the real req.path: /gate 301→ /gate/
|
|
130
|
+
// (so relative asset paths resolve against the dir), /gate/ serves the index.
|
|
131
|
+
app.get(['/gate', '/gate/'], (req, res, next) => {
|
|
132
|
+
if (!isApexHost(req)) return next();
|
|
133
|
+
const p = gateIndexPath();
|
|
134
|
+
if (!p) return next();
|
|
135
|
+
if (!req.path.endsWith('/')) return res.redirect(301, '/gate/');
|
|
136
|
+
return res.type('html').sendFile(p);
|
|
137
|
+
});
|
|
94
138
|
// The branded project-creation experience (goal 35, task 2081) — a sibling
|
|
95
139
|
// landing page at a clean /create URL, branding-injected like the landing.
|
|
96
140
|
app.get('/create', (req, res, next) => {
|
|
@@ -122,6 +166,28 @@ function buildPlatformApp() {
|
|
|
122
166
|
return app;
|
|
123
167
|
}
|
|
124
168
|
|
|
169
|
+
// Start each ENABLED module's background pollers through the loader — module-
|
|
170
|
+
// agnostic: it requires modules/<key>/pollers/<name>.js and fires start(). Same
|
|
171
|
+
// `{ isEnabled: isModuleEnabled }` gate mountModuleRoutes uses, so a module that
|
|
172
|
+
// is off contributes no poller, exactly as it contributes no route. Non-blocking
|
|
173
|
+
// + failure-isolated by the loader, and every poller is independently idempotent,
|
|
174
|
+
// unref'd and self-gating (PUBLISH_RECONCILER_DISABLED, ROUTINE_TIMERS_DISABLED,
|
|
175
|
+
// DISCORD_BOT_ENABLED), so a poller problem can never take the platform down.
|
|
176
|
+
//
|
|
177
|
+
// Safe to start unconditionally — no cross-process lease needed — because this
|
|
178
|
+
// entrypoint runs as a SINGLE process (docker-entrypoint.sh `exec`s it; there is
|
|
179
|
+
// no cluster/replica topology). The one poller with a hard single-runner
|
|
180
|
+
// requirement, the Discord gateway, is double-gated off anyway: its module is
|
|
181
|
+
// default:false AND it needs DISCORD_BOT_ENABLED=true. If an instance ever runs
|
|
182
|
+
// more than one web process, this is the line that needs a lease
|
|
183
|
+
// (modules/lifecycle/merge-lock.js has the pattern).
|
|
184
|
+
//
|
|
185
|
+
// Deliberately OUTSIDE buildPlatformApp(): importing this module (tests) must
|
|
186
|
+
// bind no port and start no timers. Exported so a test can drive it directly.
|
|
187
|
+
function startBackgroundServices() {
|
|
188
|
+
return moduleLoader.startModulePollers({}, { isEnabled: isModuleEnabled });
|
|
189
|
+
}
|
|
190
|
+
|
|
125
191
|
// Start only when run as the entry (`node src/platform-server.js`). Importing the
|
|
126
192
|
// module (the smoke test) must NOT bind a port.
|
|
127
193
|
if (require.main === module) {
|
|
@@ -129,7 +195,18 @@ if (require.main === module) {
|
|
|
129
195
|
const httpServer = http.createServer(app);
|
|
130
196
|
httpServer.listen(PORT, HOST, () => {
|
|
131
197
|
console.log(`Cloud Bongos platform (GDS-only, no game; ADR 0062 R51) listening on ${HOST}:${PORT}`);
|
|
198
|
+
startBackgroundServices();
|
|
132
199
|
});
|
|
200
|
+
|
|
201
|
+
// Graceful shutdown — stop each module poller (e.g. close the Discord gateway
|
|
202
|
+
// socket cleanly on a systemd restart / Ctrl-C so no zombie session is left for
|
|
203
|
+
// Discord to reap). stopModulePollers awaits each stop and isolates errors, so
|
|
204
|
+
// a stuck poller never blocks the exit.
|
|
205
|
+
for (const sig of ['SIGTERM', 'SIGINT']) {
|
|
206
|
+
process.on(sig, () => {
|
|
207
|
+
moduleLoader.stopModulePollers().finally(() => process.exit(0));
|
|
208
|
+
});
|
|
209
|
+
}
|
|
133
210
|
}
|
|
134
211
|
|
|
135
|
-
module.exports = { buildPlatformApp };
|
|
212
|
+
module.exports = { buildPlatformApp, startBackgroundServices };
|
package/tests/abandon_task.mjs
CHANGED
|
@@ -30,7 +30,7 @@ process.env.NODE_ENV = 'test';
|
|
|
30
30
|
|
|
31
31
|
const require = createRequire(import.meta.url);
|
|
32
32
|
const db = require('../modules/lifecycle/db.js');
|
|
33
|
-
const { abandonTask } = db;
|
|
33
|
+
const { abandonTask, restoreTask } = db;
|
|
34
34
|
|
|
35
35
|
const { test, summary } = makeRunner();
|
|
36
36
|
|
|
@@ -39,7 +39,8 @@ const { test, summary } = makeRunner();
|
|
|
39
39
|
// notFound → the FOR UPDATE select matches no row
|
|
40
40
|
// status → the row's current status
|
|
41
41
|
// hasActiveClaim → the claims lookup returns an unreleased claim
|
|
42
|
-
|
|
42
|
+
// updatedStatus → what the UPDATE ... RETURNING reports (restore returns 'backlog')
|
|
43
|
+
function fakePool({ status = 'ready', notFound = false, hasActiveClaim = false, updatedStatus = 'abandoned' }) {
|
|
43
44
|
return makeSqlAwareClient((s, params) => {
|
|
44
45
|
if (/^SELECT id, title, status FROM tasks WHERE id = \$1 FOR UPDATE/.test(s)) {
|
|
45
46
|
return { rows: notFound ? [] : [{ id: params[0], title: 'fixture task', status }] };
|
|
@@ -48,7 +49,7 @@ function fakePool({ status = 'ready', notFound = false, hasActiveClaim = false }
|
|
|
48
49
|
return { rows: hasActiveClaim ? [{ id: 999 }] : [] };
|
|
49
50
|
}
|
|
50
51
|
if (/^UPDATE tasks/.test(s)) {
|
|
51
|
-
return { rows: [{ id: params[0], title: 'fixture task', status:
|
|
52
|
+
return { rows: [{ id: params[0], title: 'fixture task', status: updatedStatus }] };
|
|
52
53
|
}
|
|
53
54
|
return { rows: [] }; // BEGIN / COMMIT / ROLLBACK
|
|
54
55
|
});
|
|
@@ -134,5 +135,60 @@ await test('abandon: a missing task throws TASK_NOT_FOUND and rolls back', async
|
|
|
134
135
|
assert.ok(find(pool._client.queries, /^ROLLBACK/), 'must ROLLBACK');
|
|
135
136
|
});
|
|
136
137
|
|
|
138
|
+
// =========================================================================
|
|
139
|
+
// restoreTask (ADR 0152) — the undo that had to exist before abandon could be
|
|
140
|
+
// handed to Metics. Same txn shape as abandonTask, inverted.
|
|
141
|
+
|
|
142
|
+
await test('restore: an abandoned task flips to backlog, COMMITs, reports previousStatus', async () => {
|
|
143
|
+
const pool = fakePool({ status: 'abandoned', updatedStatus: 'backlog' });
|
|
144
|
+
const out = await restoreTask({ taskId: TASK }, { pool });
|
|
145
|
+
|
|
146
|
+
assert.equal(out.taskStatus, 'backlog', 'restores to backlog, never straight to ready');
|
|
147
|
+
assert.equal(out.previousStatus, 'abandoned');
|
|
148
|
+
assert.ok(find(pool._client.queries, /^UPDATE tasks/), 'the tasks UPDATE must run');
|
|
149
|
+
assert.ok(find(pool._client.queries, /^COMMIT/), 'must COMMIT');
|
|
150
|
+
assert.ok(!find(pool._client.queries, /^ROLLBACK/), 'no ROLLBACK on the happy path');
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
await test("restore: clears only an 'Abandoned: …' value_summary, leaving any other note", async () => {
|
|
154
|
+
const pool = fakePool({ status: 'abandoned', updatedStatus: 'backlog' });
|
|
155
|
+
await restoreTask({ taskId: TASK }, { pool });
|
|
156
|
+
const upd = find(pool._client.queries, /^UPDATE tasks/);
|
|
157
|
+
const sql = String(upd.sql).replace(/\s+/g, ' ');
|
|
158
|
+
assert.match(sql, /status = 'backlog'/);
|
|
159
|
+
assert.match(sql, /value_summary LIKE 'Abandoned: %' THEN NULL ELSE value_summary END/,
|
|
160
|
+
'the retire stamp is cleared conditionally — an unrelated summary survives');
|
|
161
|
+
assert.equal(upd.params.length, 1, 'only the task id is bound');
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
await test('restore: a task that is not abandoned is refused and rolls back', async () => {
|
|
165
|
+
for (const status of ['ready', 'backlog', 'shipped']) {
|
|
166
|
+
const pool = fakePool({ status });
|
|
167
|
+
await assert.rejects(
|
|
168
|
+
() => restoreTask({ taskId: TASK }, { pool }),
|
|
169
|
+
(err) => err.code === 'TASK_NOT_ABANDONED',
|
|
170
|
+
`${status} must not be restorable`,
|
|
171
|
+
);
|
|
172
|
+
assert.equal(all(pool._client.queries, /^UPDATE tasks/).length, 0, `must not UPDATE a ${status} task`);
|
|
173
|
+
assert.ok(find(pool._client.queries, /^ROLLBACK/), 'must ROLLBACK');
|
|
174
|
+
}
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
await test('restore: a missing task throws TASK_NOT_FOUND and rolls back', async () => {
|
|
178
|
+
const pool = fakePool({ notFound: true });
|
|
179
|
+
await assert.rejects(
|
|
180
|
+
() => restoreTask({ taskId: 424242 }, { pool }),
|
|
181
|
+
(err) => err.code === 'TASK_NOT_FOUND',
|
|
182
|
+
);
|
|
183
|
+
assert.equal(all(pool._client.queries, /^UPDATE tasks/).length, 0);
|
|
184
|
+
assert.ok(find(pool._client.queries, /^ROLLBACK/), 'must ROLLBACK');
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
await test('restore: never consults claims (an abandoned task has none by construction)', async () => {
|
|
188
|
+
const pool = fakePool({ status: 'abandoned', updatedStatus: 'backlog' });
|
|
189
|
+
await restoreTask({ taskId: TASK }, { pool });
|
|
190
|
+
assert.equal(all(pool._client.queries, /SELECT id FROM claims/).length, 0);
|
|
191
|
+
});
|
|
192
|
+
|
|
137
193
|
// -------------------------------------------------------------------------
|
|
138
194
|
summary();
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
// tests/api_cli_flags.mjs
|
|
2
|
+
//
|
|
3
|
+
// Unit test for two PURE helpers exported from scripts/gds/cli-lib.js in support
|
|
4
|
+
// of scripts/gds/api.js's CLI flags (task 1002475):
|
|
5
|
+
// - stripBom(s) → drop a leading UTF-8 BOM (U+FEFF); pass clean input through
|
|
6
|
+
// - resolveQuiet(args, env) → true when --quiet or GDS_QUIET should suppress
|
|
7
|
+
// api.js's stderr status banner
|
|
8
|
+
// Extracted out of api.js's own top-level IIFE (which has no exports and would
|
|
9
|
+
// run on require()) so this behavior is unit-testable without spawning a
|
|
10
|
+
// process or a network call. Deterministic, no DB/network/filesystem/live
|
|
11
|
+
// server. Run: node tests/api_cli_flags.mjs
|
|
12
|
+
|
|
13
|
+
import { strict as assert } from 'node:assert';
|
|
14
|
+
import { createRequire } from 'node:module';
|
|
15
|
+
|
|
16
|
+
const require = createRequire(import.meta.url);
|
|
17
|
+
const cli = require('../scripts/gds/cli-lib.js');
|
|
18
|
+
|
|
19
|
+
let passed = 0;
|
|
20
|
+
let failed = 0;
|
|
21
|
+
function test(name, fn) {
|
|
22
|
+
try { fn(); passed++; console.log(` ok ${name}`); }
|
|
23
|
+
catch (err) { failed++; console.log(` FAIL ${name}`); console.log(` ${err.message}`); }
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
test('stripBom: removes a leading U+FEFF', () => {
|
|
27
|
+
assert.equal(cli.stripBom('{"a":1}'), '{"a":1}');
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
test('stripBom: clean input (no BOM) passes through untouched', () => {
|
|
31
|
+
assert.equal(cli.stripBom('{"a":1}'), '{"a":1}');
|
|
32
|
+
assert.equal(cli.stripBom(''), '');
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
test('stripBom: non-string input passes through untouched (edge)', () => {
|
|
36
|
+
assert.equal(cli.stripBom(null), null);
|
|
37
|
+
assert.equal(cli.stripBom(undefined), undefined);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
test('resolveQuiet: --quiet flag present → true', () => {
|
|
41
|
+
assert.equal(cli.resolveQuiet(['GET', '/api/gds/me', '--quiet'], {}), true);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
test('resolveQuiet: GDS_QUIET env var set → true', () => {
|
|
45
|
+
assert.equal(cli.resolveQuiet(['GET', '/api/gds/me'], { GDS_QUIET: '1' }), true);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
test('resolveQuiet: neither flag nor env → false (default off)', () => {
|
|
49
|
+
assert.equal(cli.resolveQuiet(['GET', '/api/gds/me'], {}), false);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
console.log(`\napi.js CLI-flag helpers (task 1002475) tests: ${passed} passed, ${failed} failed`);
|
|
53
|
+
process.exit(failed > 0 ? 1 : 0);
|
package/tests/auth_page_gate.mjs
CHANGED
|
@@ -29,6 +29,17 @@ db.lookupSession = async () => {
|
|
|
29
29
|
};
|
|
30
30
|
const auth = require('../src/bongos/auth.js');
|
|
31
31
|
|
|
32
|
+
// BV1.R107: the page gate now also resolves a permission (page.view.public) through
|
|
33
|
+
// the `governance` kernel port, so an authenticated request needs the port stubbed —
|
|
34
|
+
// exactly as tests/governance_require_permission.mjs stubs it for requirePermission.
|
|
35
|
+
// The audience is unchanged; tests/governance_page_gates.mjs is the parity proof.
|
|
36
|
+
const seams = require('../src/module-seams.js');
|
|
37
|
+
const catalog = require('../modules/governance/catalog.js');
|
|
38
|
+
seams._reset();
|
|
39
|
+
seams.registerProvider('governance', {
|
|
40
|
+
resolveBuilderPermissions: async () => catalog.seedForRank('xenos'),
|
|
41
|
+
});
|
|
42
|
+
|
|
32
43
|
// Minimal res double capturing redirect target + status.
|
|
33
44
|
function fakeRes() {
|
|
34
45
|
return {
|
|
@@ -88,5 +99,7 @@ await t('session lookup error → fails CLOSED (redirect, next() NOT called)', a
|
|
|
88
99
|
'must redirect to sign-in on a lookup error (fail closed)');
|
|
89
100
|
});
|
|
90
101
|
|
|
102
|
+
seams._reset(); // leave the seam registry as we found it (the governance_* suites' pattern)
|
|
103
|
+
|
|
91
104
|
console.log(`\n${passed} passed, ${failed} failed`);
|
|
92
105
|
process.exit(failed ? 1 : 0);
|
|
@@ -11,9 +11,14 @@
|
|
|
11
11
|
|
|
12
12
|
import { strict as assert } from 'node:assert';
|
|
13
13
|
import { createRequire } from 'node:module';
|
|
14
|
+
import fs from 'node:fs';
|
|
15
|
+
import os from 'node:os';
|
|
16
|
+
import path from 'node:path';
|
|
14
17
|
|
|
15
18
|
const require = createRequire(import.meta.url);
|
|
16
19
|
const bf = require('../scripts/gds/backfill-sessions.js');
|
|
20
|
+
const { encodeProjectPath } = require('../scripts/gds/ship.js');
|
|
21
|
+
const { looksLikeGraderTranscript } = require('../scripts/gds/session-cost.js');
|
|
17
22
|
|
|
18
23
|
let passed = 0;
|
|
19
24
|
let failed = 0;
|
|
@@ -58,7 +63,7 @@ test('extracts a release marker', () => {
|
|
|
58
63
|
test('does NOT false-positive on CLAUDE.md markdown #N links', () => {
|
|
59
64
|
// The exact shape that saturates CLAUDE.md / the injected context.
|
|
60
65
|
const claudeMd = asst(
|
|
61
|
-
'See [#435](https://
|
|
66
|
+
'See [#435](https://example.com/builders#/task/435) and ADR 0024 ([#395](x), [#410](y)). ' +
|
|
62
67
|
'Phase 2.2 [#421] shipped. (#607) mirrors to Discord.'
|
|
63
68
|
);
|
|
64
69
|
assert.deepEqual(bf.extractTaskIds([claudeMd]), []);
|
|
@@ -97,5 +102,126 @@ test('inScope matches every historical project-path family', () => {
|
|
|
97
102
|
assert.equal(bf.inScope('-Users-x-Desktop-SomeOtherProject'), false);
|
|
98
103
|
});
|
|
99
104
|
|
|
105
|
+
// --- regressions: the tool was a silent no-op on any non-legacy checkout -----
|
|
106
|
+
|
|
107
|
+
test('inScope matches THIS checkout, not just the four legacy names', () => {
|
|
108
|
+
// The bug: PROJECT_TOKENS was a hardcoded list of the owner's old project
|
|
109
|
+
// names, so every other clone skipped every folder and reported "0 transcripts"
|
|
110
|
+
// while exiting 0. The scope token must be derived from the repo itself.
|
|
111
|
+
const derived = encodeProjectPath(path.resolve('.'));
|
|
112
|
+
assert.ok(bf.inScope(derived), 'the current checkout must be in scope');
|
|
113
|
+
assert.ok(
|
|
114
|
+
bf.inScope(`${derived}--claude-worktrees-task-1002503`),
|
|
115
|
+
'a per-task worktree dir must be in scope too'
|
|
116
|
+
);
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
test('a Windows cwd encodes to the dir name Claude Code actually creates', () => {
|
|
120
|
+
// Claude Code replaces every non-alphanumeric char, so a Windows cwd (drive
|
|
121
|
+
// colon, backslashes, spaces) encodes to one long dashed name. This is the
|
|
122
|
+
// shape the old [/.] encoder no-op'd on, which is why no transcript was found.
|
|
123
|
+
assert.equal(
|
|
124
|
+
encodeProjectPath('C:\\Users\\rocke\\OneDrive\\Desktop\\Cloud Bongos\\cloud-bongos'),
|
|
125
|
+
'C--Users-rocke-OneDrive-Desktop-Cloud-Bongos-cloud-bongos'
|
|
126
|
+
);
|
|
127
|
+
// And the matching loop accepts it when it IS the derived token — asserted
|
|
128
|
+
// against inScope itself, machine-independently, via the real first token.
|
|
129
|
+
const derived = bf.PROJECT_TOKENS[0];
|
|
130
|
+
assert.ok(bf.inScope(derived), 'the derived token must match itself through inScope');
|
|
131
|
+
assert.equal(bf.inScope('C--Users-someone-else-Desktop-OtherProject'), false);
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
test('buildUploadPayload sends model_usage at the TOP level', () => {
|
|
135
|
+
// The route reads body.model_usage and never looks inside metrics; dropping it
|
|
136
|
+
// silently under-pays via the opus cache_read fallback. Guard the field set.
|
|
137
|
+
const digest = {
|
|
138
|
+
totalTokens: 135734889,
|
|
139
|
+
durationSeconds: 6407,
|
|
140
|
+
startedAt: '2026-07-26T21:18:16.356Z',
|
|
141
|
+
endedAt: '2026-07-26T23:05:03.560Z',
|
|
142
|
+
detail: [{ i: 0, role: 'assistant' }],
|
|
143
|
+
metrics: { assistant_turns: 1, model_usage: { 'claude-opus-5': { input: 1, output: 2 } } },
|
|
144
|
+
};
|
|
145
|
+
const payload = bf.buildUploadPayload({ sessionId: 'abc', taskIds: [1002471], digest });
|
|
146
|
+
assert.deepEqual(payload.model_usage, { 'claude-opus-5': { input: 1, output: 2 } });
|
|
147
|
+
assert.equal(payload.session_id, 'abc');
|
|
148
|
+
assert.deepEqual(payload.task_ids, [1002471]);
|
|
149
|
+
assert.equal(payload.total_tokens, 135734889);
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
test('buildUploadPayload defaults model_usage to {} rather than undefined', () => {
|
|
153
|
+
// An unpriceable digest must still upload (the server prices from total_tokens);
|
|
154
|
+
// an undefined field would fail the route's object validator.
|
|
155
|
+
const payload = bf.buildUploadPayload({ sessionId: 'x', taskIds: [], digest: { metrics: {} } });
|
|
156
|
+
assert.deepEqual(payload.model_usage, {});
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
test('grader detection matches a NEW worker persona, not just the known four', () => {
|
|
160
|
+
// A closed name list would let a future grader worker be uploaded as a builder
|
|
161
|
+
// session and earn credit for spend the ship path never pays.
|
|
162
|
+
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'backfill-grader2-'));
|
|
163
|
+
const write = (name, text) => {
|
|
164
|
+
const f = path.join(dir, `${name}.jsonl`);
|
|
165
|
+
fs.writeFileSync(f, JSON.stringify({ type: 'user', message: { role: 'user', content: text } }) + '\n');
|
|
166
|
+
return f;
|
|
167
|
+
};
|
|
168
|
+
try {
|
|
169
|
+
for (const [persona, joiner] of [['Narc', '— a specialist'], ['Quality', 'specialist — an'],
|
|
170
|
+
['Efficiency Monger', '— a specialist'], ['Cartographer', '— a specialist']]) {
|
|
171
|
+
const f = write(persona.replace(/\s/g, ''), `You are the **${persona}** ${joiner} adversarial subagent for the project.`);
|
|
172
|
+
assert.ok(looksLikeGraderTranscript(f), `${persona} must be detected as a grader worker`);
|
|
173
|
+
}
|
|
174
|
+
// A real session that merely mentions the panel must NOT be skipped.
|
|
175
|
+
const real = write('real', 'The adversarial subagent panel flagged my diff — help me fix it.');
|
|
176
|
+
assert.equal(looksLikeGraderTranscript(real), false, 'a builder session must not be skipped');
|
|
177
|
+
} finally {
|
|
178
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
179
|
+
}
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
test('extracts a 7-digit task id from a ship.js invocation', () => {
|
|
183
|
+
// {1,4} matched nothing on a live instance, so no modern session could ever pay.
|
|
184
|
+
const ids = bf.extractTaskIds([toolUse('Bash', { command: 'node scripts/gds/ship.js 1002471' })]);
|
|
185
|
+
assert.deepEqual(ids, [1002471]);
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
test('extracts 7-digit ids from claim/release/Claimed markers', () => {
|
|
189
|
+
assert.deepEqual(bf.extractTaskIds([asst('Running /builder-claim 1002503 now')]), [1002503]);
|
|
190
|
+
assert.deepEqual(bf.extractTaskIds([asst('builder-release 1002446 — handing it back')]), [1002446]);
|
|
191
|
+
assert.deepEqual(bf.extractTaskIds([asst('Claimed task #1002475.')]), [1002475]);
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
test('7-digit ids survive extractTaskIds (the n < 10000 clamp is gone)', () => {
|
|
195
|
+
// Widening TASK_MARKERS alone was not enough: the clamp dropped them again.
|
|
196
|
+
const entries = [
|
|
197
|
+
toolUse('Bash', { command: 'node scripts/gds/ship.js 1002471' }),
|
|
198
|
+
asst('Claimed #284.'), // legacy 3-digit ids must still work
|
|
199
|
+
];
|
|
200
|
+
assert.deepEqual(bf.extractTaskIds(entries), [284, 1002471]);
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
test('grader worker transcripts are detectable and would otherwise be paid', () => {
|
|
204
|
+
// The grader panel writes each worker as its OWN transcript in the same project
|
|
205
|
+
// dir. A worker quotes the diff, so it carries real ship markers — uploading one
|
|
206
|
+
// would book a cost-plus reward the live ship path never pays.
|
|
207
|
+
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'backfill-grader-'));
|
|
208
|
+
const file = path.join(dir, 'aaaaaaaa-0000-0000-0000-000000000000.jsonl');
|
|
209
|
+
const graderPrompt =
|
|
210
|
+
'You are the **Narc** — a specialist adversarial subagent for this project. ' +
|
|
211
|
+
'Review the diff shipped by node scripts/gds/ship.js 1002471 --summary "x".';
|
|
212
|
+
try {
|
|
213
|
+
fs.writeFileSync(file, JSON.stringify({ type: 'user', message: { role: 'user', content: graderPrompt } }) + '\n');
|
|
214
|
+
assert.ok(looksLikeGraderTranscript(file), 'the grader worker prompt must be detected');
|
|
215
|
+
// Load-bearing: without the skip, this transcript yields a real shipped id.
|
|
216
|
+
assert.deepEqual(bf.extractTaskIds([asst(graderPrompt)]), [1002471]);
|
|
217
|
+
|
|
218
|
+
const normal = path.join(dir, 'bbbbbbbb-0000-0000-0000-000000000000.jsonl');
|
|
219
|
+
fs.writeFileSync(normal, JSON.stringify({ type: 'user', message: { role: 'user', content: 'fix the backfill script' } }) + '\n');
|
|
220
|
+
assert.equal(looksLikeGraderTranscript(normal), false, 'a real builder session must NOT be skipped');
|
|
221
|
+
} finally {
|
|
222
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
223
|
+
}
|
|
224
|
+
});
|
|
225
|
+
|
|
100
226
|
console.log(`\nbackfill_sessions: ${passed} passed, ${failed} failed`);
|
|
101
227
|
process.exit(failed ? 1 : 0);
|