@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
|
@@ -15,8 +15,64 @@ const api = require('../../../src/module-api');
|
|
|
15
15
|
const auth = api;
|
|
16
16
|
const doneWhen = require('../done-when');
|
|
17
17
|
const db = require('../db');
|
|
18
|
+
// The goal authority PREDICATE (ADR 0154) — the same owner|manager test routes/goals.js
|
|
19
|
+
// uses for visibility / invites / roles, so "who may manage this goal" has
|
|
20
|
+
// one definition. A module sibling, like ../db.
|
|
21
|
+
const { isGoalOwnerOrManager } = require('../goal-authz');
|
|
18
22
|
const { validateOrRespond, parseId, asyncHandler, LIMITS } = api;
|
|
19
23
|
|
|
24
|
+
// authorizeCriterionCreate — the ORDERED, FAIL-CLOSED decision for a non-Archon
|
|
25
|
+
// POST /versions/:id/done-when (ADR 0154). PURE + exported so every rejection is
|
|
26
|
+
// pinned by a DB-free unit test (the authorizeGoalTaskCreate precedent in
|
|
27
|
+
// routes/goals.js). The route resolves goal/version first — their absence is a
|
|
28
|
+
// 404/400 there — so this gate only decides AUTHORITY, in this order:
|
|
29
|
+
// archon → allow · goal named? · owner|manager? (MASKED) · goal open? · version open?
|
|
30
|
+
// Authority sits ahead of both state checks on purpose — see the no-leak note below.
|
|
31
|
+
// An Archon short-circuits: version-level (goal-less) criteria and any goal in any
|
|
32
|
+
// state stay theirs. Returns { ok:true }, { ok:false, status, body }, or
|
|
33
|
+
// { ok:false, maskAsBadGoalId:true } (the route renders the identical 400).
|
|
34
|
+
//
|
|
35
|
+
// "Version open" = `planning` or `building`. NOT just `building`: a planning session
|
|
36
|
+
// scopes a version at status='planning', which is the main moment criteria get
|
|
37
|
+
// written. `shipped` is closed (the same `version_closed` refusal
|
|
38
|
+
// authorizeGoalTaskCreate uses), and `frozen` means the scope is deliberately
|
|
39
|
+
// locked — for a SCOPE write that is a refusal, even though task authoring allows it.
|
|
40
|
+
const CRITERION_OPEN_VERSION_STATUSES = ['planning', 'building'];
|
|
41
|
+
|
|
42
|
+
function authorizeCriterionCreate({ goal, version, actorId, actorRank, actorMembership }) {
|
|
43
|
+
if (actorRank === 'archon') return { ok: true };
|
|
44
|
+
if (!goal) {
|
|
45
|
+
return { ok: false, status: 403, body: {
|
|
46
|
+
error: 'goal_id_required',
|
|
47
|
+
message: 'Name the goal this criterion belongs to — a version-level criterion is Archon-only (ADR 0154).',
|
|
48
|
+
} };
|
|
49
|
+
}
|
|
50
|
+
// NO-LEAK ORDERING — authority BEFORE state, and the refusal is MASKED.
|
|
51
|
+
// Before ADR 0154 this route was Archon-only, and an Archon can already read
|
|
52
|
+
// every goal, so no probe existed. Now a Metic may call it, and distinguishable
|
|
53
|
+
// refusals (400 "no such goal" vs 403 "not yours" vs 409 "not open") would turn
|
|
54
|
+
// it into an oracle for which goal ids exist on a version — including PRIVATE
|
|
55
|
+
// goals (ADR 0112). So a caller who is not the owner/manager gets byte-identical
|
|
56
|
+
// `bad_goal_id` to a caller naming a goal that does not exist. Only someone who
|
|
57
|
+
// already holds the goal learns anything further about it.
|
|
58
|
+
if (!isGoalOwnerOrManager({ goal, actorId, actorRank, actorMembership })) {
|
|
59
|
+
return { ok: false, maskAsBadGoalId: true };
|
|
60
|
+
}
|
|
61
|
+
if (goal.status !== 'open') {
|
|
62
|
+
return { ok: false, status: 409, body: {
|
|
63
|
+
error: 'goal_not_open', status: goal.status,
|
|
64
|
+
message: 'Only an open goal can be given new criteria.',
|
|
65
|
+
} };
|
|
66
|
+
}
|
|
67
|
+
if (!version || !CRITERION_OPEN_VERSION_STATUSES.includes(version.status)) {
|
|
68
|
+
return { ok: false, status: 409, body: {
|
|
69
|
+
error: 'version_closed', status: version ? version.status : null,
|
|
70
|
+
message: 'Only an Archon may add a criterion to a shipped or frozen version.',
|
|
71
|
+
} };
|
|
72
|
+
}
|
|
73
|
+
return { ok: true };
|
|
74
|
+
}
|
|
75
|
+
|
|
20
76
|
module.exports = function buildDoneWhenRouter() {
|
|
21
77
|
const router = express.Router();
|
|
22
78
|
|
|
@@ -35,16 +91,32 @@ module.exports = function buildDoneWhenRouter() {
|
|
|
35
91
|
res.json({ version_id: versionId, criteria, goals });
|
|
36
92
|
}, { errorCode: 'list_failed' }));
|
|
37
93
|
|
|
38
|
-
// Auth (
|
|
39
|
-
// scope-truth seeds (versions/goals/criteria) out of the
|
|
40
|
-
// so a brand-new criterion on a RUNNING instance can no
|
|
41
|
-
// plain migration (migrations/instance/ applies only under
|
|
42
|
-
// GDS_APPLY_INSTANCE_SEEDS=1, a DR step). This is the vector a
|
|
43
|
-
// uses to add a criterion to a live version WITHOUT a droplet
|
|
44
|
-
//
|
|
45
|
-
//
|
|
46
|
-
//
|
|
47
|
-
|
|
94
|
+
// Auth (Metic+, then the goal's own authority): create a done-when criterion at
|
|
95
|
+
// RUNTIME. ADR 0105 moved scope-truth seeds (versions/goals/criteria) out of the
|
|
96
|
+
// normal migration path, so a brand-new criterion on a RUNNING instance can no
|
|
97
|
+
// longer be added by a plain migration (migrations/instance/ applies only under
|
|
98
|
+
// the owner opt-in GDS_APPLY_INSTANCE_SEEDS=1, a DR step). This is the vector a
|
|
99
|
+
// planning session uses to add a criterion to a live version WITHOUT a droplet
|
|
100
|
+
// migrate.
|
|
101
|
+
//
|
|
102
|
+
// ADR 0154 — authority over a GOAL's criteria follows GOAL OWNERSHIP, not global
|
|
103
|
+
// rank. A Metic who may create the goal (POST /goals) and author the tasks under
|
|
104
|
+
// it (POST /tasks, ADR 0090) may now write the criteria those tasks gate — but
|
|
105
|
+
// only inside a goal they own or manage. Deliberately NOT a flat re-rank:
|
|
106
|
+
// - An unsatisfied criterion permanently blocks version close (version-close.js
|
|
107
|
+
// counts satisfied === total per VERSION, regardless of goal) and goal
|
|
108
|
+
// auto-achieve (db.achieveGoalIfComplete). There is NO delete route, so it
|
|
109
|
+
// cannot be undone via the API — the same backward ratchet as unsatisfy,
|
|
110
|
+
// which stays Archon.
|
|
111
|
+
// - So a non-Archon must name a goal_id (version-level criteria stay Archon),
|
|
112
|
+
// must be that goal's OWNER or MANAGER (isGoalOwnerOrManager — the same
|
|
113
|
+
// predicate behind visibility/invite/role management), and the goal must be
|
|
114
|
+
// open on a planning/building version. That bounds the blast radius to a
|
|
115
|
+
// workspace they already hold. An unauthorized caller is masked behind
|
|
116
|
+
// bad_goal_id so the route is not an existence oracle (ADR 0112).
|
|
117
|
+
// Pinned in route-rank-check.MODULE_EXPECTED_RANKS.
|
|
118
|
+
// rank: metic+archon — criterion create, walled by goal ownership (ADR 0154).
|
|
119
|
+
router.post('/versions/:id/done-when', auth.requireBuilder, auth.requireRank('metic', 'archon'), asyncHandler('POST /versions/:id/done-when', async (req, res) => {
|
|
48
120
|
const versionId = String(req.params.id || '').trim();
|
|
49
121
|
if (!versionId) return res.fail('version_id_required', 400);
|
|
50
122
|
if (validateOrRespond(req, res, {
|
|
@@ -55,12 +127,29 @@ module.exports = function buildDoneWhenRouter() {
|
|
|
55
127
|
})) return;
|
|
56
128
|
const version = await db.getVersion(versionId);
|
|
57
129
|
if (!version) return res.fail('version_not_found', { status: 404, message: `No version '${versionId}'.` });
|
|
130
|
+
// The ONE bad_goal_id refusal, used both for a genuinely absent/foreign goal
|
|
131
|
+
// and for the masked unauthorized case below — same code, same message, same
|
|
132
|
+
// envelope, so the two are indistinguishable on the wire.
|
|
133
|
+
const badGoalId = () => res.fail('bad_goal_id', { status: 400, message: `goal_id ${req.body.goal_id} does not exist on version '${versionId}'.` });
|
|
58
134
|
// A goal_id, if given, must exist AND belong to this version.
|
|
135
|
+
let goal = null;
|
|
59
136
|
if (req.body.goal_id != null) {
|
|
60
|
-
|
|
61
|
-
if (!goal || String(goal.version_id) !== String(versionId))
|
|
62
|
-
|
|
63
|
-
|
|
137
|
+
goal = await db.getGoal(req.body.goal_id);
|
|
138
|
+
if (!goal || String(goal.version_id) !== String(versionId)) return badGoalId();
|
|
139
|
+
}
|
|
140
|
+
// ADR 0154: the authority wall. Checked AFTER the goal's version linkage so a
|
|
141
|
+
// bad ref is still the clearer 400. Membership is only read when a goal was
|
|
142
|
+
// named and the caller isn't an Archon — a primary-key hit on
|
|
143
|
+
// goal_members (goal_id, builder_id) (migration 160), on a low-frequency route.
|
|
144
|
+
const actorMembership = (goal && req.builder.rank !== 'archon')
|
|
145
|
+
? await db.getGoalMember({ goalId: goal.id, builderId: req.builder.id })
|
|
146
|
+
: null;
|
|
147
|
+
const decision = authorizeCriterionCreate({
|
|
148
|
+
goal, version, actorId: req.builder.id, actorRank: req.builder.rank, actorMembership,
|
|
149
|
+
});
|
|
150
|
+
if (!decision.ok) {
|
|
151
|
+
if (decision.maskAsBadGoalId) return badGoalId();
|
|
152
|
+
return res.status(decision.status).json(decision.body);
|
|
64
153
|
}
|
|
65
154
|
try {
|
|
66
155
|
const criterion = await db.createCriterion({
|
|
@@ -98,7 +187,8 @@ module.exports = function buildDoneWhenRouter() {
|
|
|
98
187
|
// criterion belongs to a goal and was its LAST unsatisfied one, the goal auto-
|
|
99
188
|
// flips open→achieved (ADR 0086 §6 / BV1.R63). Re-ranked Archon→Metic+ so the
|
|
100
189
|
// planner — not only the Archon — closes the criterion/goal loop; the route is
|
|
101
|
-
// pinned in route-rank-check.
|
|
190
|
+
// pinned in route-rank-check.MODULE_EXPECTED_RANKS.lifecycle so a further
|
|
191
|
+
// downgrade is caught.
|
|
102
192
|
// rank: metic+archon — criterion confirmation + goal auto-achieve (the closing loop).
|
|
103
193
|
router.post('/done-when/:criterionId/satisfy', auth.requireBuilder, auth.requireRank('metic', 'archon'), asyncHandler('POST /done-when/:criterionId/satisfy', async (req, res) => {
|
|
104
194
|
if (validateOrRespond(req, res, {
|
|
@@ -134,3 +224,8 @@ module.exports = function buildDoneWhenRouter() {
|
|
|
134
224
|
|
|
135
225
|
return router;
|
|
136
226
|
};
|
|
227
|
+
|
|
228
|
+
// ADR 0154: the pure ordered gate for POST /versions/:id/done-when — exported for
|
|
229
|
+
// tests/criterion_authoring_gate.mjs (DB-free), the authorizeGoalTaskCreate
|
|
230
|
+
// precedent in routes/goals.js.
|
|
231
|
+
module.exports.authorizeCriterionCreate = authorizeCriterionCreate;
|
|
@@ -38,6 +38,11 @@ const { isModuleKey, scopeIncludesProtected, moduleKeys } = api.moduleScopeMap;
|
|
|
38
38
|
const goalScopeCheck = api.goalScopeCheck;
|
|
39
39
|
// The pure conflict detector (task 1736) — a module sibling, like ../db.
|
|
40
40
|
const goalConflicts = require('../goal-conflicts');
|
|
41
|
+
// The goal authority model (task 1002538 / ADR 0154) — moved to a module sibling
|
|
42
|
+
// so done-when.js can share it verbatim. authorizeGoalManagement is re-exported at
|
|
43
|
+
// the bottom of this file so the existing DB-free unit tests keep importing it from
|
|
44
|
+
// here; isGoalOwner is used internally only and is exported from ../goal-authz.
|
|
45
|
+
const { isGoalOwner, authorizeGoalManagement } = require('../goal-authz');
|
|
41
46
|
|
|
42
47
|
const VALID_GOAL_STATUSES = ['open', 'achieved', 'archived'];
|
|
43
48
|
const VALID_MEMBER_ROLES = ['lead', 'member'];
|
|
@@ -122,18 +127,9 @@ function authorizeGoalTaskCreate({ goal, version, isMember, builderRank, touches
|
|
|
122
127
|
return { ok: true };
|
|
123
128
|
}
|
|
124
129
|
|
|
125
|
-
// The goal AUTHORITY MODEL (
|
|
126
|
-
//
|
|
127
|
-
//
|
|
128
|
-
// The owner holds every goal power.
|
|
129
|
-
// - MANAGER — the goal_members.role 'lead' value (the hall renders it
|
|
130
|
-
// "manager"). Owner-equivalent EXCEPT: a manager cannot DEMOTE a fellow
|
|
131
|
-
// manager (owner-only), and cannot transfer ownership.
|
|
132
|
-
// A goal with created_by NULL (the migration-160 system defaults) has no owner;
|
|
133
|
-
// Archons act as one — the archon arm of both gates below.
|
|
134
|
-
function isGoalOwner(goal, builderId) {
|
|
135
|
-
return goal.created_by != null && String(goal.created_by) === String(builderId);
|
|
136
|
-
}
|
|
130
|
+
// The goal AUTHORITY MODEL (owner vs manager) now lives in ../goal-authz.js —
|
|
131
|
+
// isGoalOwner + authorizeGoalManagement are imported at the top of this file and
|
|
132
|
+
// re-exported at the bottom (task 1002538 / ADR 0154).
|
|
137
133
|
|
|
138
134
|
// authorizeMemberRoleChange — the ORDERED, FAIL-CLOSED authorization decision for
|
|
139
135
|
// PATCH /goals/:id/members/:builderId (task 1735). Today the only role-assignment
|
|
@@ -201,32 +197,6 @@ function authorizeMemberRoleChange({ goal, actorId, actorRank, actorMembership,
|
|
|
201
197
|
return { ok: true };
|
|
202
198
|
}
|
|
203
199
|
|
|
204
|
-
// authorizeGoalManagement — the shared ORDERED, FAIL-CLOSED prefix for actions
|
|
205
|
-
// a goal's own authority performs (task 1736: conflict resolution; the same
|
|
206
|
-
// three steps open authorizeMemberRoleChange above): goal exists → goal open →
|
|
207
|
-
// actor is owner|manager|Archon. Returns { ok:true } or { ok:false, status,
|
|
208
|
-
// body }. PURE + exported, same precedent as its siblings.
|
|
209
|
-
function authorizeGoalManagement({ goal, actorId, actorRank, actorMembership }) {
|
|
210
|
-
if (!goal) {
|
|
211
|
-
return { ok: false, status: 404, body: { error: 'goal_not_found' } };
|
|
212
|
-
}
|
|
213
|
-
if (goal.status !== 'open') {
|
|
214
|
-
return { ok: false, status: 409, body: {
|
|
215
|
-
error: 'goal_not_open', status: goal.status,
|
|
216
|
-
message: 'Only an open goal can be managed.',
|
|
217
|
-
} };
|
|
218
|
-
}
|
|
219
|
-
const actorIsOwner = isGoalOwner(goal, actorId);
|
|
220
|
-
const actorIsManager = !!actorMembership && actorMembership.role === 'lead';
|
|
221
|
-
if (actorRank !== 'archon' && !actorIsOwner && !actorIsManager) {
|
|
222
|
-
return { ok: false, status: 403, body: {
|
|
223
|
-
error: 'not_owner_or_manager',
|
|
224
|
-
message: 'Only this goal\'s owner or a manager (or an Archon) may do this.',
|
|
225
|
-
} };
|
|
226
|
-
}
|
|
227
|
-
return { ok: true };
|
|
228
|
-
}
|
|
229
|
-
|
|
230
200
|
// authorizeOwnershipTransfer — the ORDERED, FAIL-CLOSED decision for
|
|
231
201
|
// POST /goals/:id/transfer (task 1735): the owner hands the goal to another
|
|
232
202
|
// builder (the old owner keeps their manager row). Ownership is creator-
|
|
@@ -8,6 +8,8 @@ const auth = api;
|
|
|
8
8
|
const db = require('../db');
|
|
9
9
|
const hierarchy = require('../hierarchy');
|
|
10
10
|
const { suggestCriteria } = require('../criterion-suggest');
|
|
11
|
+
// The orphan-task advisory (task 1002539) — pure, fail-open; see goal-advisory.js.
|
|
12
|
+
const { buildGoalAdvisory } = require('../goal-advisory');
|
|
11
13
|
// grading carved to modules/grading/ (BV1.R73 / ADR 0091 §4): the lifecycle no
|
|
12
14
|
// longer imports the grader — it resolves the `grade` kernel PORT at grade time
|
|
13
15
|
// (seams.resolveOptional('grade'), in POST /tasks/:id/grade below).
|
|
@@ -79,7 +81,7 @@ function detectsMigration(...texts) {
|
|
|
79
81
|
// Same deliberate-locality call as the MIGRATION_KEYWORD_RE mirror above.
|
|
80
82
|
|
|
81
83
|
// VOTABLE_TASK_STATUSES — a task is votable once it's verified work the project
|
|
82
|
-
// keeps (confirmed = past the grader,
|
|
84
|
+
// keeps (confirmed = past the grader, not yet paid; or shipped = deployed + paid);
|
|
83
85
|
// backlog/ready/completed are not. Imported above from ../task-classifier (D1).
|
|
84
86
|
|
|
85
87
|
// #853 (ADR 0041): temporary, default-OFF global grader bypass. When the env
|
|
@@ -216,6 +218,14 @@ module.exports = function buildTasksRouter() {
|
|
|
216
218
|
}
|
|
217
219
|
}
|
|
218
220
|
const tasks = await db.listTasks({ versionId: version, status, kind, discipline, goalId });
|
|
221
|
+
// ?include=deps (hall redesign task 1002439) — enrich each task with its
|
|
222
|
+
// dependency summary (`blocked_by[]` with per-target satisfied state, and a
|
|
223
|
+
// `blocks` count) so the Goals rollup + Tasks dependency view can paint
|
|
224
|
+
// ready/blocked without an N+1 of /tasks/:id fetches. Opt-in so the default
|
|
225
|
+
// list shape stays unchanged for every other caller. Two aggregate queries.
|
|
226
|
+
if (String(req.query.include || '').split(',').includes('deps')) {
|
|
227
|
+
await db.attachTaskDepSummaries(tasks);
|
|
228
|
+
}
|
|
219
229
|
res.json({ tasks });
|
|
220
230
|
});
|
|
221
231
|
|
|
@@ -709,12 +719,17 @@ module.exports = function buildTasksRouter() {
|
|
|
709
719
|
console.error('[gds] POST /tasks suggest-criteria (non-blocking)', e && e.message);
|
|
710
720
|
}
|
|
711
721
|
}
|
|
722
|
+
// task 1002539 — the orphan-task advisory (the /goal-create auto-pickup
|
|
723
|
+
// hook). Nothing 4xxs when a task has no goal, so surface it here instead of
|
|
724
|
+
// blocking. Non-blocking by construction — see goal-advisory.js.
|
|
725
|
+
const goal_advisory = await buildGoalAdvisory({ db, versionId: body.version_id, task });
|
|
712
726
|
res.status(201).json({
|
|
713
727
|
task,
|
|
714
728
|
credits_reward: task.credits_reward,
|
|
715
729
|
needs_migration: needsMigration,
|
|
716
730
|
criteria,
|
|
717
731
|
suggested_criteria,
|
|
732
|
+
goal_advisory,
|
|
718
733
|
});
|
|
719
734
|
} catch (err) {
|
|
720
735
|
console.error('[gds] POST /tasks', err);
|
|
@@ -955,13 +970,26 @@ module.exports = function buildTasksRouter() {
|
|
|
955
970
|
});
|
|
956
971
|
|
|
957
972
|
// rank: metic+archon — backlog→ready promotion; scope mutation; ADR 0090 (was archon).
|
|
973
|
+
//
|
|
974
|
+
// Also the UN-ABANDON path (ADR 0152): 'abandoned' → 'backlog'. When abandon moved
|
|
975
|
+
// down to Metic+ it needed an undo at the same rank, or a Metic's mis-retire would
|
|
976
|
+
// be unrecoverable without Archon SQL (no other route restores an abandoned task —
|
|
977
|
+
// PATCH refuses `status` outright). A restore lands in **backlog**, never straight
|
|
978
|
+
// to ready: the task was retired for a reason, so it re-enters as un-workable and
|
|
979
|
+
// takes a second, deliberate promote to become claimable again.
|
|
958
980
|
router.post('/tasks/:id/promote', auth.requireBuilder, auth.requireRank('metic', 'archon'), async (req, res) => {
|
|
959
981
|
const id = Number(req.params.id);
|
|
960
982
|
const task = await db.getTask(id);
|
|
961
983
|
if (!task) return res.fail('task_not_found', 404);
|
|
962
|
-
if (!['backlog', 'blocked'].includes(task.status)) {
|
|
984
|
+
if (!['backlog', 'blocked', 'abandoned'].includes(task.status)) {
|
|
963
985
|
return res.fail('cannot_promote', 409, { current_status: task.status });
|
|
964
986
|
}
|
|
987
|
+
// The restore branch returns early: no reward gate (that is a promote-to-WORKABLE
|
|
988
|
+
// concern and backlog is the pre-workable state — same reasoning as /demote).
|
|
989
|
+
if (task.status === 'abandoned') {
|
|
990
|
+
const restored = await db.restoreTask({ taskId: id });
|
|
991
|
+
return res.json({ ok: true, restored: true, status: restored.taskStatus });
|
|
992
|
+
}
|
|
965
993
|
// task 1670 / ADR 0096 + task 1673 amendment (2026-06-28): a task must never
|
|
966
994
|
// become workable paying 0 drachmae — but the gate AUTO-ASSIGNS rather than
|
|
967
995
|
// BLOCKS. If credits_reward is 0/NULL, assign the capped suggestion (from
|
|
@@ -1113,10 +1141,14 @@ module.exports = function buildTasksRouter() {
|
|
|
1113
1141
|
// on a task with an active claim (release it first); idempotent on an already-
|
|
1114
1142
|
// abandoned task. The abandon + its `reason` land in audit_log via the writing
|
|
1115
1143
|
// middleware, so the ledger keeps a record of who retired what.
|
|
1116
|
-
// rank: archon —
|
|
1117
|
-
//
|
|
1118
|
-
//
|
|
1119
|
-
|
|
1144
|
+
// rank: metic+archon — scope removal; the inverse of /promote, and re-ranked to
|
|
1145
|
+
// match it (ADR 0152, was archon under ADR 0090). A Metic who may author, shape,
|
|
1146
|
+
// and promote work may also retire it: leaving the kill switch an Archon rung
|
|
1147
|
+
// higher than the create switch only forced the owner to clean up after the
|
|
1148
|
+
// triage they already delegated. Not one-way any more — /promote restores an
|
|
1149
|
+
// abandoned task to backlog at the same rank — and the guards below (shipped
|
|
1150
|
+
// refused, active claim refused) plus the audit_log entry are unchanged.
|
|
1151
|
+
router.post('/tasks/:id/abandon', auth.requireBuilder, auth.requireRank('metic', 'archon'), async (req, res) => {
|
|
1120
1152
|
if (validateOrRespond(req, res, {
|
|
1121
1153
|
reason: { type: 'string' }, // free-text retire reason (sliced to 2000 below)
|
|
1122
1154
|
})) return;
|
|
@@ -247,6 +247,12 @@ function buildCardModel(input = {}) {
|
|
|
247
247
|
} else if (credits.awarded != null) {
|
|
248
248
|
const total = fmtInt(credits.total);
|
|
249
249
|
creditsText = total ? `+${credits.awarded} → ${total} ${brand.currency}` : `+${credits.awarded} ${brand.currency}`;
|
|
250
|
+
// cost-plus-only (ADR 0146): the per-task estimate is withheld, so a `+0`
|
|
251
|
+
// here is correct. Say why on the card itself — unexplained it reads as a
|
|
252
|
+
// broken payout (task 1002469). `perTaskCredit` is injected by the caller
|
|
253
|
+
// (which resolves economy's rewardPolicy); absent ⇒ assume the paying
|
|
254
|
+
// default and print nothing, so other instances are untouched.
|
|
255
|
+
if (credits.perTaskCredit === false) creditsText += ' · est. not paid (cost-plus only)';
|
|
250
256
|
}
|
|
251
257
|
// Usage — lead with the dollar cost (the honest measure of the work), then
|
|
252
258
|
// split the token count into 'new' (fresh input + Claude's output + the
|
|
@@ -129,7 +129,7 @@ const STAGE_HINTS = {
|
|
|
129
129
|
auth_complete: 'First obtain the repo — clone it (or open thy hosted dev box, where it is already cloned) — then sign in via GitHub Device Flow or the web sign-in.',
|
|
130
130
|
pick_disciplines: 'Pick the work that calls thee — engineer, artist, ideator, or any mix.',
|
|
131
131
|
first_start: 'Run /builder-start to see claimable works.',
|
|
132
|
-
first_claim: 'Claim a task with /builder-claim N.',
|
|
132
|
+
first_claim: 'Claim a task with /builder-claim N — then on thy dev box run /builder-sync, and thy box pulls exactly that task\'s code (naught else) within seconds.',
|
|
133
133
|
ship_three: 'Carry three tasks through with /builder-ship — the third makes thee a Thetes.',
|
|
134
134
|
graduated: '',
|
|
135
135
|
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# @bongos/core — installed platform dependency (not your project's CLAUDE.md)
|
|
2
|
+
|
|
3
|
+
This file is a placeholder shipped inside the **@bongos/core** package. The core's own
|
|
4
|
+
internal agent notes are intentionally omitted from the published artifact so they cannot
|
|
5
|
+
load into your instance's context.
|
|
6
|
+
|
|
7
|
+
**It does not govern this repository.** Your own root `CLAUDE.md`, your `.claude/`
|
|
8
|
+
directory, and the live Bongos database are the source of truth for how work happens here
|
|
9
|
+
and who may do what.
|
|
10
|
+
|
|
11
|
+
You are seeing this only because an agent opened a file under `node_modules/@bongos/core/`.
|
|
12
|
+
Nothing in here needs to be read to use the platform — run `bongos help` for commands.
|
|
13
|
+
|
|
14
|
+
_To work ON the platform core itself, use the standalone @bongos/core source repository,
|
|
15
|
+
not this vendored copy._
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
// modules/platform-identity/hub-device.js — the hub's GitHub device flow for CLI
|
|
2
|
+
// federation (ADR 0141 §1, task #1002108).
|
|
3
|
+
//
|
|
4
|
+
// This is a DELIBERATELY SELF-CONTAINED GitHub device flow — it does NOT reuse
|
|
5
|
+
// core auth's startDeviceFlow/pollDeviceFlow, because those mint a hub BUILDER
|
|
6
|
+
// session (and run the hub's invite-only admission gate). Federation must instead
|
|
7
|
+
// establish a platform ACCOUNT — open to anyone with a GitHub account, since a
|
|
8
|
+
// platform account is pure identity and project admission stays LOCAL (ADR 0141:
|
|
9
|
+
// identity federated, authority local). So the flow here runs GitHub's device
|
|
10
|
+
// endpoints against the hub's own OAuth app and returns the raw GitHub identity;
|
|
11
|
+
// the route then upserts the platform account and mints an assertion. The ~40
|
|
12
|
+
// lines of GitHub mechanics duplicated from core auth are boundary-forced
|
|
13
|
+
// duplication (ADR 0091 §2) — the module cannot import core internals.
|
|
14
|
+
'use strict';
|
|
15
|
+
|
|
16
|
+
const api = require('../../src/module-api');
|
|
17
|
+
|
|
18
|
+
const { resolveEnv, userAgent } = api;
|
|
19
|
+
const SCOPE = 'read:user';
|
|
20
|
+
|
|
21
|
+
// In-memory device_code -> { client_id, interval, expires_at, last_polled_at }.
|
|
22
|
+
// Ephemeral, like core auth's deviceFlows: a hub restart drops pending flows (the
|
|
23
|
+
// CLI just re-runs). The client_id is BOUND here at start (resolved from the
|
|
24
|
+
// user-typed origin by the route) so the poll mints for exactly that client — the
|
|
25
|
+
// CLI never gets to name the audience (ADR 0141 §1 security fix).
|
|
26
|
+
const hubDeviceFlows = new Map();
|
|
27
|
+
|
|
28
|
+
// Absolute cap on concurrent pending flows — a backstop against memory growth from
|
|
29
|
+
// a flood of /sso/device/start calls (the per-IP write limiter is the primary
|
|
30
|
+
// control; this bounds a distributed flood). Old expired entries are reaped first,
|
|
31
|
+
// so a legitimate load never hits this.
|
|
32
|
+
const MAX_FLOWS = 2000;
|
|
33
|
+
|
|
34
|
+
// The hub's own GitHub OAuth-app client id (public). Device flow needs only the
|
|
35
|
+
// client_id (no secret) for both start and the token poll.
|
|
36
|
+
function hubGithubClientId() {
|
|
37
|
+
return resolveEnv('GITHUB_CLIENT_ID') || null;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function hubDeviceConfigured() {
|
|
41
|
+
return !!hubGithubClientId();
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Start a GitHub device flow against the hub's app, BOUND to `federatedClientId`
|
|
45
|
+
// (the client the route resolved from the user-typed origin). Returns the codes to
|
|
46
|
+
// show the user. Throws with an actionable message on a GitHub OAuth-app misconfig.
|
|
47
|
+
async function startHubGithubDevice(federatedClientId) {
|
|
48
|
+
const clientId = hubGithubClientId();
|
|
49
|
+
if (!clientId) throw new Error('hub GitHub app not configured');
|
|
50
|
+
if (!federatedClientId) throw new Error('federated client_id required');
|
|
51
|
+
// Reap expired first, then refuse a NEW start past the cap (never evict a live one).
|
|
52
|
+
for (const [k, v] of hubDeviceFlows) if (v.expires_at < Date.now()) hubDeviceFlows.delete(k);
|
|
53
|
+
if (hubDeviceFlows.size >= MAX_FLOWS) throw new Error('too many device flows in flight; try again shortly');
|
|
54
|
+
const res = await fetch('https://github.com/login/device/code', {
|
|
55
|
+
method: 'POST',
|
|
56
|
+
headers: { 'Content-Type': 'application/x-www-form-urlencoded', Accept: 'application/json' },
|
|
57
|
+
body: new URLSearchParams({ client_id: clientId, scope: SCOPE }).toString(),
|
|
58
|
+
});
|
|
59
|
+
if (!res.ok) {
|
|
60
|
+
const text = await res.text().catch(() => '');
|
|
61
|
+
throw new Error(`github device/code failed (${res.status}): ${text.slice(0, 160)}`);
|
|
62
|
+
}
|
|
63
|
+
const data = await res.json();
|
|
64
|
+
if (data.error) {
|
|
65
|
+
const hint = data.error === 'device_flow_disabled'
|
|
66
|
+
? ' — enable Device Flow on the hub GitHub app'
|
|
67
|
+
: '';
|
|
68
|
+
throw new Error(`github rejected device-flow start: ${data.error}${hint}`);
|
|
69
|
+
}
|
|
70
|
+
if (!data.device_code) throw new Error('github device/code returned no device_code');
|
|
71
|
+
hubDeviceFlows.set(data.device_code, {
|
|
72
|
+
client_id: federatedClientId,
|
|
73
|
+
interval: data.interval || 5,
|
|
74
|
+
expires_at: Date.now() + (data.expires_in || 900) * 1000,
|
|
75
|
+
last_polled_at: 0,
|
|
76
|
+
});
|
|
77
|
+
return {
|
|
78
|
+
device_code: data.device_code,
|
|
79
|
+
user_code: data.user_code,
|
|
80
|
+
verification_uri: data.verification_uri,
|
|
81
|
+
interval: data.interval || 5,
|
|
82
|
+
expires_in: data.expires_in || 900,
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Poll GitHub for the device flow's completion. Returns:
|
|
87
|
+
// { state: 'pending' } | { state: 'slow_down', interval } | { state: 'expired' }
|
|
88
|
+
// | { state: 'denied' } | { state: 'ok', identity: { github_id, github_login,
|
|
89
|
+
// display_name, avatar_url } }
|
|
90
|
+
// Identity ONLY — no session, no rank. The route mints the assertion from it.
|
|
91
|
+
async function pollHubGithubDevice(deviceCode) {
|
|
92
|
+
const flow = hubDeviceFlows.get(deviceCode);
|
|
93
|
+
if (!flow) return { state: 'expired' };
|
|
94
|
+
if (flow.expires_at < Date.now()) { hubDeviceFlows.delete(deviceCode); return { state: 'expired' }; }
|
|
95
|
+
const now = Date.now();
|
|
96
|
+
if (now - flow.last_polled_at < flow.interval * 1000) return { state: 'slow_down', interval: flow.interval };
|
|
97
|
+
flow.last_polled_at = now;
|
|
98
|
+
|
|
99
|
+
const clientId = hubGithubClientId();
|
|
100
|
+
const res = await fetch('https://github.com/login/oauth/access_token', {
|
|
101
|
+
method: 'POST',
|
|
102
|
+
headers: { 'Content-Type': 'application/x-www-form-urlencoded', Accept: 'application/json' },
|
|
103
|
+
body: new URLSearchParams({
|
|
104
|
+
client_id: clientId,
|
|
105
|
+
device_code: deviceCode,
|
|
106
|
+
grant_type: 'urn:ietf:params:oauth:grant-type:device_code',
|
|
107
|
+
}).toString(),
|
|
108
|
+
});
|
|
109
|
+
const data = await res.json();
|
|
110
|
+
if (data.error === 'authorization_pending') return { state: 'pending' };
|
|
111
|
+
if (data.error === 'slow_down') { flow.interval = (flow.interval || 5) + 5; return { state: 'slow_down', interval: flow.interval }; }
|
|
112
|
+
if (data.error === 'expired_token') { hubDeviceFlows.delete(deviceCode); return { state: 'expired' }; }
|
|
113
|
+
if (data.error === 'access_denied') { hubDeviceFlows.delete(deviceCode); return { state: 'denied' }; }
|
|
114
|
+
if (data.access_token) {
|
|
115
|
+
hubDeviceFlows.delete(deviceCode);
|
|
116
|
+
const ghRes = await fetch('https://api.github.com/user', {
|
|
117
|
+
headers: {
|
|
118
|
+
Authorization: `Bearer ${data.access_token}`,
|
|
119
|
+
'User-Agent': userAgent(),
|
|
120
|
+
Accept: 'application/vnd.github+json',
|
|
121
|
+
},
|
|
122
|
+
});
|
|
123
|
+
if (!ghRes.ok) throw new Error(`github /user failed (${ghRes.status})`);
|
|
124
|
+
const u = await ghRes.json();
|
|
125
|
+
return {
|
|
126
|
+
state: 'ok',
|
|
127
|
+
// client_id is the one BOUND at start (from the user-typed origin) — the CLI
|
|
128
|
+
// never names the audience; the mint uses this (ADR 0141 §1 security fix).
|
|
129
|
+
client_id: flow.client_id,
|
|
130
|
+
identity: {
|
|
131
|
+
github_id: u.id,
|
|
132
|
+
github_login: u.login,
|
|
133
|
+
display_name: u.name || u.login,
|
|
134
|
+
avatar_url: u.avatar_url,
|
|
135
|
+
},
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
return { state: 'pending' };
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
module.exports = {
|
|
142
|
+
hubGithubClientId,
|
|
143
|
+
hubDeviceConfigured,
|
|
144
|
+
startHubGithubDevice,
|
|
145
|
+
pollHubGithubDevice,
|
|
146
|
+
_flows: hubDeviceFlows, // exposed for tests
|
|
147
|
+
};
|