@bongos/core 1.18.3 → 1.18.18
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.bongos-core.json +1575 -285
- package/.claude/skills/builder-box/SKILL.md +6 -0
- package/.claude/skills/builder-sequence/SKILL.md +97 -0
- package/.claude/skills/builder-ship/SKILL.md +8 -2
- package/.claude/skills/builder-start/SKILL.md +11 -0
- package/.claude/skills/builder-sync/SKILL.md +41 -0
- package/.claude/skills/fix-task/SKILL.md +64 -0
- package/.claude/skills/goal-create/SKILL.md +158 -0
- package/.claude/skills/goal-review/SKILL.md +3 -1
- package/.claude/skills/idea-triage/SKILL.md +14 -8
- package/.claude/skills/merge-mode/SKILL.md +2 -2
- package/.claude/skills/new-project/SKILL.md +1 -0
- package/.claude/skills/planning-session/SKILL.md +45 -18
- package/.env.local.example +4 -3
- package/README.md +4 -2
- package/bin/bongos.js +1 -0
- package/clients/bongos-client/README.md +2 -2
- package/clients/bongos-client/bongos-client.global.js +82 -18
- package/clients/bongos-client/index.cjs +82 -18
- package/clients/bongos-client/index.d.ts +131 -29
- package/clients/bongos-client/index.mjs +82 -18
- package/config/branding.neutral.json +6 -1
- package/config/governance.neutral.json +21 -0
- package/docs/adr/0043-git-ssh-trust-boundary-and-rank-floor-on-permission-paths.md +24 -0
- package/docs/adr/0090-metic-task-authoring.md +1 -1
- package/docs/adr/0144-devbox-rehome-onto-cloudbongos-plane.md +41 -0
- package/docs/adr/0144-federated-single-logout-backchannel.md +42 -0
- package/docs/adr/0145-devbox-app-branding-driven-module.md +44 -0
- package/docs/adr/0145-free-hosted-project-tier-isolation-and-domain-separation.md +62 -0
- package/docs/adr/0146-configurable-reward-mode-cost-plus-only.md +53 -0
- package/docs/adr/0147-standalone-live-docs-regen.md +39 -0
- package/docs/adr/0148-task-scoped-box-source-access.md +48 -0
- package/docs/adr/0149-bongos-dev-local-launcher.md +57 -0
- package/docs/adr/0150-box-first-boot-bringup-vendored-instances.md +68 -0
- package/docs/adr/0151-governance-permissions-as-atom-ranks-as-roles.md +229 -0
- package/docs/adr/0152-landing-gate-served-by-the-core-app.md +92 -0
- package/docs/adr/0152-metic-task-abandonment.md +67 -0
- package/docs/adr/0153-sequential-chain-runner.md +51 -0
- package/docs/adr/0154-goal-owner-criterion-authoring.md +86 -0
- package/docs/adr/README.md +10 -0
- package/docs/api/openapi.json +843 -17
- package/docs/api-reference.md +23 -10
- package/docs/architecture.md +4 -2
- package/docs/branding-contract.md +3 -1
- package/docs/canonical-permissions.md +21 -8
- package/docs/file-map.md +8 -0
- package/docs/onboarding/diagrams/01-task-lifecycle.mmd +2 -2
- package/docs/onboarding/diagrams/02-rank-ladder.mmd +3 -1
- package/docs/onboarding/diagrams/05-versioning.mmd +1 -3
- package/docs/onboarding/diagrams/assertions.json +4 -7
- package/docs/onboarding/drachmae-and-karma.md +3 -3
- package/docs/onboarding/primer.md +17 -16
- package/docs/onboarding/slash-commands.md +124 -0
- package/docs/recipes/builder-box-lifecycle.md +23 -0
- package/docs/recipes/devbox-app-release.md +14 -4
- package/docs/recipes/instance-first-run.md +16 -0
- package/docs/recipes/local-dev.md +81 -0
- package/docs/recipes/ops-gotchas.md +19 -1
- package/docs/recipes/overnight-builder.md +1 -1
- package/docs/recipes/parallel-goal-waves.md +174 -0
- package/docs/recipes/ship-and-regrade.md +2 -2
- package/docs/recipes/standalone-live-docs.md +48 -0
- package/docs/recipes/work-on-a-task-from-your-dev-box.md +108 -0
- package/migrations/003_pms.sql +6 -7
- package/migrations/006_pms_v2.sql +3 -31
- package/migrations/core_193_task_needs_rebase.sql +28 -0
- package/migrations/core_194_confirmed_backlog_clawback.sql +63 -0
- package/migrations/core_195_idea_triage_provenance.sql +57 -0
- package/migrations/core_196_session_reward_topup.sql +69 -0
- package/modules/autonomy/pollers/routine-timers.js +9 -8
- package/modules/builder-settings/wandering-prefs.js +61 -18
- package/modules/dev-box/app/README.md +73 -0
- package/modules/dev-box/app/assets/tray/mac-alertTemplate.png +0 -0
- package/modules/dev-box/app/assets/tray/mac-alertTemplate@2x.png +0 -0
- package/modules/dev-box/app/assets/tray/mac-busyTemplate.png +0 -0
- package/modules/dev-box/app/assets/tray/mac-busyTemplate@2x.png +0 -0
- package/modules/dev-box/app/assets/tray/mac-offTemplate.png +0 -0
- package/modules/dev-box/app/assets/tray/mac-offTemplate@2x.png +0 -0
- package/modules/dev-box/app/assets/tray/mac-onTemplate.png +0 -0
- package/modules/dev-box/app/assets/tray/mac-onTemplate@2x.png +0 -0
- package/modules/dev-box/app/assets/tray/win-alert.png +0 -0
- package/modules/dev-box/app/assets/tray/win-busy.png +0 -0
- package/modules/dev-box/app/assets/tray/win-off.png +0 -0
- package/modules/dev-box/app/assets/tray/win-on.png +0 -0
- package/modules/dev-box/app/build-resources/dmg-background.png +0 -0
- package/modules/dev-box/app/build-resources/dmg-background@2x.png +0 -0
- package/modules/dev-box/app/build-resources/icon.png +0 -0
- package/modules/dev-box/app/electron-builder.tmpl.yml +95 -0
- package/modules/dev-box/app/electron-builder.yml +95 -0
- package/modules/dev-box/app/package-lock.json +5291 -0
- package/modules/dev-box/app/package.json +30 -0
- package/modules/dev-box/app/renderer/app.js +319 -0
- package/modules/dev-box/app/renderer/index.html +45 -0
- package/modules/dev-box/app/renderer/styles.css +246 -0
- package/modules/dev-box/app/renderer/theme.generated.css +16 -0
- package/modules/dev-box/app/scripts/apply-dmg-backdrop.sh +82 -0
- package/modules/dev-box/app/scripts/dmgbuild-settings.py +39 -0
- package/modules/dev-box/app/scripts/gen-brand.js +138 -0
- package/modules/dev-box/app/scripts/gen-dmg-backdrop.py +323 -0
- package/modules/dev-box/app/scripts/gen-icons.js +286 -0
- package/modules/dev-box/app/scripts/vendor-client.js +23 -0
- package/modules/dev-box/app/src/box-controller.js +287 -0
- package/modules/dev-box/app/src/brand.generated.json +13 -0
- package/modules/dev-box/app/src/brand.js +16 -0
- package/modules/dev-box/app/src/config.js +19 -0
- package/modules/dev-box/app/src/gds-client.js +90 -0
- package/modules/dev-box/app/src/main.js +610 -0
- package/modules/dev-box/app/src/pairing.js +57 -0
- package/modules/dev-box/app/src/preload.js +29 -0
- package/modules/dev-box/app/src/session-store.js +58 -0
- package/modules/dev-box/app/src/ssh-setup.js +569 -0
- package/modules/dev-box/app/src/task-picker.js +70 -0
- package/modules/dev-box/app/src/tray.js +99 -0
- package/modules/dev-box/app/src/updater.js +55 -0
- package/modules/dev-box/app/src/vendor/bongos-client.cjs +698 -0
- package/modules/dev-box/app/test/unit.mjs +526 -0
- package/modules/dev-box/box-access.js +121 -4
- package/modules/dev-box/boxes.js +20 -0
- package/modules/dev-box/module.json +1 -1
- package/modules/dev-box/routes/box.js +87 -3
- package/modules/economy/credits.js +116 -25
- package/modules/economy/reward.js +8 -0
- package/modules/economy/streak.js +2 -1
- package/modules/governance/CLAUDE.md +15 -0
- package/modules/governance/catalog.js +419 -0
- package/modules/governance/config.js +174 -0
- package/modules/governance/db.js +125 -0
- package/modules/governance/migrations/governance_001_tables.sql +81 -0
- package/modules/governance/migrations/governance_002_seed_and_backfill.sql +103 -0
- package/modules/governance/migrations/governance_003_task_abandon_metic.sql +28 -0
- package/modules/governance/migrations/governance_004_criterion_create.sql +39 -0
- package/modules/governance/migrations/governance_005_resync_builder_rank_roles.sql +56 -0
- package/modules/governance/module.json +10 -0
- package/modules/governance/protected-surfaces.js +88 -0
- package/modules/governance/protected-surfaces.json +80 -0
- package/modules/governance/rank-roles.js +83 -0
- package/modules/governance/resolver.js +221 -0
- package/modules/governance/routes/governance.js +89 -0
- package/modules/governance/routes/role-sync.js +49 -0
- package/modules/governance/session-scopes.js +127 -0
- package/modules/governance/session-scopes.json +38 -0
- package/modules/hall-ui/public/atlas.css +117 -0
- package/modules/hall-ui/public/atlas.html +3 -114
- package/modules/hall-ui/public/brand-apply.js +34 -6
- package/modules/hall-ui/public/builders.js +187 -219
- package/modules/hall-ui/public/diagrams.html +2 -0
- package/modules/hall-ui/public/dom-utils.js +27 -0
- package/modules/hall-ui/public/drachmae.html +2 -0
- package/modules/hall-ui/public/drachmae.js +54 -1
- package/modules/hall-ui/public/gate.css +98 -0
- package/modules/hall-ui/public/gate.html +3 -95
- package/modules/hall-ui/public/goal-inbox.js +4 -1
- package/modules/hall-ui/public/goals-page.js +626 -114
- package/modules/hall-ui/public/goals.css +79 -1
- package/modules/hall-ui/public/goals.html +38 -11
- package/modules/hall-ui/public/hall-widgets.js +62 -52
- package/modules/hall-ui/public/harbor.css +123 -0
- package/modules/hall-ui/public/harbor.html +3 -120
- package/modules/hall-ui/public/index.html +11 -4
- package/modules/hall-ui/public/instances-panel.js +4 -1
- package/modules/hall-ui/public/modules.css +63 -0
- package/modules/hall-ui/public/modules.html +3 -60
- package/modules/hall-ui/public/new-project-wizard.js +7 -3
- package/modules/hall-ui/public/not-ready.html +2 -0
- package/modules/hall-ui/public/pair.css +73 -0
- package/modules/hall-ui/public/pair.html +3 -70
- package/modules/hall-ui/public/palette.js +302 -0
- package/modules/hall-ui/public/primer.css +13 -0
- package/modules/hall-ui/public/primer.html +2 -0
- package/modules/hall-ui/public/profile.html +2 -0
- package/modules/hall-ui/public/ranks.html +2 -0
- package/modules/hall-ui/public/roadmap.css +179 -0
- package/modules/hall-ui/public/roadmap.html +9 -172
- package/modules/hall-ui/public/roadmap.js +329 -316
- package/modules/hall-ui/public/sessions.css +16 -0
- package/modules/hall-ui/public/sessions.html +3 -13
- package/modules/hall-ui/public/settings.html +2 -0
- package/modules/hall-ui/public/settings.js +4 -4
- package/modules/hall-ui/public/shell.js +143 -18
- package/modules/hall-ui/public/style.css +231 -39
- package/modules/hall-ui/public/task-detail.js +347 -26
- package/modules/hall-ui/public/task.html +6 -1
- package/modules/hall-ui/public/watch.html +2 -0
- package/modules/hall-ui/public/work.html +8 -6
- package/modules/hall-ui/public/work.js +1 -1
- package/modules/ideas/inbox.js +61 -11
- package/modules/ideas/routes/inbox.js +23 -0
- package/modules/lifecycle/claim-eligibility.js +72 -0
- package/modules/lifecycle/db.js +519 -139
- package/modules/lifecycle/done-when.js +18 -3
- package/modules/lifecycle/github-push.js +19 -2
- package/modules/lifecycle/goal-advisory.js +114 -0
- package/modules/lifecycle/goal-authz.js +62 -0
- package/modules/lifecycle/lifecycle.js +9 -0
- package/modules/lifecycle/publish-reconciler.js +109 -6
- package/modules/lifecycle/routes/claims.js +68 -23
- package/modules/lifecycle/routes/done-when.js +110 -15
- package/modules/lifecycle/routes/goals.js +8 -38
- package/modules/lifecycle/routes/tasks.js +38 -6
- package/modules/lifecycle/ship-card.js +6 -0
- package/modules/onboarding/onboarding-state.js +1 -1
- package/modules/platform-identity/CLAUDE.md +15 -0
- package/modules/platform-identity/hub-device.js +147 -0
- package/modules/platform-identity/hub-keys.js +151 -0
- package/modules/platform-identity/migrations/platform_identity_001_tables.sql +114 -0
- package/modules/platform-identity/migrations/platform_identity_002_client_origin_index.sql +18 -0
- package/modules/platform-identity/migrations/platform_identity_003_project_memberships.sql +42 -0
- package/modules/platform-identity/migrations/platform_identity_004_projects.sql +44 -0
- package/modules/platform-identity/migrations/platform_identity_005_builder_activity.sql +42 -0
- package/modules/platform-identity/module.json +18 -0
- package/modules/platform-identity/platform-identity.js +531 -0
- package/modules/platform-identity/pollers/code-sweep.js +43 -0
- package/modules/platform-identity/routes/my-projects.js +140 -0
- package/modules/platform-identity/routes/projects.js +107 -0
- package/modules/platform-identity/routes/scouting.js +35 -0
- package/modules/platform-identity/routes/sso.js +402 -0
- package/modules/platform-identity/tests/platform-identity.mjs +388 -0
- package/modules/platform-identity/ui/.gitkeep +0 -0
- package/modules/security/routes/reports.js +10 -0
- package/modules/sessions/db.js +166 -21
- package/modules/sessions/module.json +1 -1
- package/modules/sessions/pollers/session-reward-reconcile.js +78 -0
- package/modules/sessions/routes/sessions.js +33 -0
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/public-landing/assets/anim/idle/frame_00.webp +0 -0
- package/public-landing/assets/anim/idle/frame_01.webp +0 -0
- package/public-landing/assets/anim/idle/frame_02.webp +0 -0
- package/public-landing/assets/anim/idle/frame_03.webp +0 -0
- package/public-landing/assets/anim/idle/frame_04.webp +0 -0
- package/public-landing/assets/anim/idle/frame_05.webp +0 -0
- package/public-landing/assets/anim/idle/frame_06.webp +0 -0
- package/public-landing/assets/anim/idle/frame_07.webp +0 -0
- package/public-landing/assets/anim/idle/frame_08.webp +0 -0
- package/public-landing/assets/anim/idle/frame_09.webp +0 -0
- package/public-landing/assets/anim/idle/frame_10.webp +0 -0
- package/public-landing/assets/anim/idle/frame_11.webp +0 -0
- package/public-landing/assets/anim/idle/frame_12.webp +0 -0
- package/public-landing/assets/anim/idle/frame_13.webp +0 -0
- package/public-landing/assets/anim/idle/frame_14.webp +0 -0
- package/public-landing/assets/anim/idle/frame_15.webp +0 -0
- package/public-landing/assets/anim/idle/frame_16.webp +0 -0
- package/public-landing/assets/anim/idle/frame_17.webp +0 -0
- package/public-landing/assets/anim/idle/frame_18.webp +0 -0
- package/public-landing/assets/anim/idle/frame_19.webp +0 -0
- package/public-landing/assets/anim/idle/frame_20.webp +0 -0
- package/public-landing/assets/anim/idle/frame_21.webp +0 -0
- package/public-landing/assets/anim/idle/frame_22.webp +0 -0
- package/public-landing/assets/anim/idle/frame_23.webp +0 -0
- package/public-landing/assets/anim/idle/frame_24.webp +0 -0
- package/public-landing/assets/anim/idle/frame_25.webp +0 -0
- package/public-landing/assets/anim/idle/frame_26.webp +0 -0
- package/public-landing/assets/anim/idle/frame_27.webp +0 -0
- package/public-landing/assets/anim/idle/frame_28.webp +0 -0
- package/public-landing/assets/anim/idle/frame_29.webp +0 -0
- package/public-landing/assets/anim/idle/frame_30.webp +0 -0
- package/public-landing/assets/anim/idle/frame_31.webp +0 -0
- package/public-landing/assets/anim/idle/frame_32.webp +0 -0
- package/public-landing/assets/anim/idle/frame_33.webp +0 -0
- package/public-landing/assets/anim/idle/frame_34.webp +0 -0
- package/public-landing/assets/anim/idle/frame_35.webp +0 -0
- package/public-landing/assets/anim/idle/frame_36.webp +0 -0
- package/public-landing/assets/anim/idle/frame_37.webp +0 -0
- package/public-landing/assets/anim/idle/frame_38.webp +0 -0
- package/public-landing/assets/anim/idle/frame_39.webp +0 -0
- package/public-landing/assets/anim/idle/frame_40.webp +0 -0
- package/public-landing/assets/anim/idle/frame_41.webp +0 -0
- package/public-landing/assets/anim/idle/frame_42.webp +0 -0
- package/public-landing/assets/anim/idle/frame_43.webp +0 -0
- package/public-landing/create.html +51 -58
- package/public-landing/gate/README.md +137 -0
- package/public-landing/gate/dispmaps/bar.png +0 -0
- package/public-landing/gate/dispmaps/flat.png +0 -0
- package/public-landing/gate/dispmaps/key.png +0 -0
- package/public-landing/gate/index.html +587 -0
- package/public-landing/gate/previews/cloud-bongos.jpg +0 -0
- package/public-landing/gate/previews/off-the-boats.jpg +0 -0
- package/public-landing/gate/space.css +799 -0
- package/public-landing/gate/space.js +1826 -0
- package/public-landing/gate/sprites/enter.png +0 -0
- package/public-landing/gate/sprites/search.png +0 -0
- package/public-landing/gate/sprites/terminal-blank.png +0 -0
- package/public-landing/gate/sprites/tile.png +0 -0
- package/public-landing/world/assets/clouds/cloud-island-large.png +0 -0
- package/public-landing/world/assets/clouds/cloud-puff.png +0 -0
- package/public-landing/world/assets/creatures/creature-whale-cargo.png +0 -0
- package/public-landing/world/assets/creatures/creature-whale-glide.png +0 -0
- package/public-landing/world/assets/plates/plate-clouds-near.png +0 -0
- package/public-landing/world/assets/plates/scene-establishing.png +0 -0
- package/scripts/gds/abandon.js +6 -4
- package/scripts/gds/api.js +10 -5
- package/scripts/gds/api.ps1 +26 -0
- package/scripts/gds/api.sh +6 -0
- package/scripts/gds/audit-ships.js +7 -1
- package/scripts/gds/backfill-sessions.js +78 -18
- package/scripts/gds/backup.js +23 -1
- package/scripts/gds/box-connect-lib.js +124 -31
- package/scripts/gds/box-sync.js +66 -0
- package/scripts/gds/box.js +225 -53
- package/scripts/gds/cf-tunnel.js +145 -6
- package/scripts/gds/claim.js +34 -3
- package/scripts/gds/cli-lib.js +161 -27
- package/scripts/gds/code.js +6 -2
- package/scripts/gds/deploy-divergence.js +209 -0
- package/scripts/gds/dev-lib.js +309 -0
- package/scripts/gds/dev.js +322 -0
- package/scripts/gds/doctor.js +90 -61
- package/scripts/gds/fetch-art-key.js +1 -1
- package/scripts/gds/fetch-sound-prefs.js +2 -2
- package/scripts/gds/fitness.js +228 -12
- package/scripts/gds/gds-db-backup.service +16 -1
- package/scripts/gds/gds-db-offsite.service +9 -1
- package/scripts/gds/gen-api-client.js +1 -1
- package/scripts/gds/gen-api-docs.js +36 -14
- package/scripts/gds/gen-diagrams.js +45 -28
- package/scripts/gds/gen-file-map.js +2 -1
- package/scripts/gds/gen-freshness.js +32 -0
- package/scripts/gds/gen-repo-map.js +3 -2
- package/scripts/gds/gen-session-index.js +14 -5
- package/scripts/gds/hooks/pre-push.js +2 -2
- package/scripts/gds/init.js +60 -3
- package/scripts/gds/install-git-hooks.js +1 -1
- package/scripts/gds/key-card.js +15 -6
- package/scripts/gds/key-status.js +1 -1
- package/scripts/gds/linkify-refs.js +35 -9
- package/scripts/gds/login.js +16 -1
- package/scripts/gds/paste-token.js +7 -1
- package/scripts/gds/preflight.js +126 -0
- package/scripts/gds/provision.js +123 -12
- package/scripts/gds/publish-manifest.js +7 -1
- package/scripts/gds/reauth.js +7 -3
- package/scripts/gds/regen-instance-docs.js +47 -0
- package/scripts/gds/run-unit-tests.js +4 -2
- package/scripts/gds/seed-bongos-v1-seo-tasks.js +104 -0
- package/scripts/gds/seed-bv1-profiles-tasks.js +126 -0
- package/scripts/gds/sequence.js +425 -0
- package/scripts/gds/session-cost.js +10 -1
- package/scripts/gds/setup.js +31 -5
- package/scripts/gds/shell.js +5 -3
- package/scripts/gds/ship.js +319 -29
- package/scripts/gds/smoke-gds.js +139 -8
- package/scripts/gds/start.js +171 -22
- package/scripts/gds/status.js +1 -1
- package/scripts/gds/triage.js +10 -9
- package/scripts/gds/upgrade.js +124 -2
- package/scripts/status-mirror-sync.js +114 -84
- package/src/bongos/api-errors.js +1 -0
- package/src/bongos/auth.js +447 -102
- package/src/bongos/db-kernel.js +27 -0
- package/src/bongos/db.js +39 -0
- package/src/bongos/module-scope-map.js +27 -1
- package/src/bongos/permission-path-check.js +136 -102
- package/src/bongos/repo-info.js +47 -11
- package/src/bongos/route-rank-check.js +429 -46
- package/src/bongos/routes/_helpers.js +12 -1
- package/src/bongos/routes/auth.js +219 -2
- package/src/bongos/routes/backup.js +58 -2
- package/src/bongos/routes/me.js +35 -1
- package/src/bongos/serve-internal.js +84 -15
- package/src/branding.js +32 -0
- package/src/instance-config.js +30 -0
- package/src/module-api.js +112 -1
- package/src/platform-server.js +86 -9
- package/tests/abandon_task.mjs +59 -3
- package/tests/api_cli_flags.mjs +53 -0
- package/tests/auth_page_gate.mjs +13 -0
- package/tests/backfill_sessions.mjs +127 -1
- package/tests/backup_cli.mjs +54 -0
- package/tests/bongos_box_connect.mjs +77 -0
- package/tests/box_access.mjs +130 -1
- package/tests/box_credential.mjs +125 -0
- package/tests/box_host_keys.mjs +24 -0
- package/tests/box_infra_resolve.mjs +149 -0
- package/tests/box_scope_session.mjs +6 -2
- package/tests/box_sync.mjs +55 -0
- package/tests/box_task_scope.mjs +99 -0
- package/tests/branding.mjs +12 -0
- package/tests/cf_ruleset_preflight.mjs +142 -0
- package/tests/cf_tunnel.mjs +37 -0
- package/tests/claim_gate_rebase.mjs +98 -0
- package/tests/clawback_confirmed_backlog.mjs +103 -0
- package/tests/cli_lib_formatters.mjs +20 -0
- package/tests/confirm_attribution.mjs +30 -46
- package/tests/credit_grant.mjs +198 -10
- package/tests/criterion_authoring_gate.mjs +272 -0
- package/tests/criterion_progress.mjs +33 -1
- package/tests/dev_lib.mjs +253 -0
- package/tests/devbox_artifact_gate.mjs +75 -0
- package/tests/devbox_dmg_backdrop.mjs +137 -0
- package/tests/devbox_icons.mjs +304 -0
- package/tests/devbox_installer_brand.mjs +232 -0
- package/tests/devbox_key_register.mjs +234 -0
- package/tests/docs_openapi_route.mjs +110 -0
- package/tests/doctor_hooks.mjs +141 -0
- package/tests/extract_json.mjs +140 -0
- package/tests/fitness.mjs +89 -0
- package/tests/gen_freshness.mjs +96 -0
- package/tests/goal_advisory.mjs +251 -0
- package/tests/goal_criterion_confirm_ui.mjs +269 -0
- package/tests/goal_routes.mjs +59 -4
- package/tests/goal_tier.mjs +12 -3
- package/tests/governance_catalog.mjs +129 -0
- package/tests/governance_claim_eligibility.mjs +262 -0
- package/tests/governance_config.mjs +187 -0
- package/tests/governance_ownership_scope.mjs +410 -0
- package/tests/governance_page_gates.mjs +324 -0
- package/tests/governance_parity.mjs +336 -0
- package/tests/governance_principals.mjs +232 -0
- package/tests/governance_protected_surfaces.mjs +208 -0
- package/tests/governance_require_permission.mjs +223 -0
- package/tests/governance_resolver.mjs +84 -0
- package/tests/governance_role_sync.mjs +231 -0
- package/tests/governance_seed.mjs +127 -0
- package/tests/governance_session_scope.mjs +434 -0
- package/tests/governance_vanilla_boot.mjs +203 -0
- package/tests/grade_attribution.mjs +7 -7
- package/tests/hall_audit.mjs +223 -0
- package/tests/hall_nav.mjs +191 -0
- package/tests/hall_palette.mjs +206 -0
- package/tests/hall_signout.mjs +124 -0
- package/tests/hook_user_agents.mjs +10 -0
- package/tests/idea_triage.mjs +162 -0
- package/tests/idp_device_exchange.mjs +93 -0
- package/tests/init.mjs +51 -0
- package/tests/instance_config.mjs +65 -0
- package/tests/instance_mismatch_guard.mjs +23 -6
- package/tests/instance_release_config.mjs +104 -0
- package/tests/linkify_refs.mjs +15 -2
- package/tests/module_contributions.mjs +55 -0
- package/tests/module_loader.mjs +10 -5
- package/tests/module_scope_active_claims.mjs +94 -0
- package/tests/needs_rebase.mjs +92 -0
- package/tests/one_active_claim_per_session.mjs +76 -0
- package/tests/override_attribution.mjs +10 -8
- package/tests/override_request_ownership.mjs +8 -4
- package/tests/parse_cookie_decode.mjs +42 -0
- package/tests/pay_on_land.mjs +129 -0
- package/tests/platform_boot.mjs +100 -1
- package/tests/post_login_landing.mjs +42 -0
- package/tests/preflight_checks.mjs +72 -0
- package/tests/provision.mjs +132 -7
- package/tests/publish_branch_route.mjs +53 -0
- package/tests/publish_reconciler.mjs +187 -2
- package/tests/repo_info_binding.mjs +248 -0
- package/tests/reward_gate.mjs +11 -2
- package/tests/reward_mode_cost_plus_only.mjs +116 -0
- package/tests/reward_mode_surfacing.mjs +168 -0
- package/tests/route_permission_check.mjs +301 -0
- package/tests/route_rank_check.mjs +254 -12
- package/tests/route_validation.mjs +30 -0
- package/tests/sequence.mjs +267 -0
- package/tests/session_digest.mjs +44 -0
- package/tests/session_index.mjs +18 -6
- package/tests/session_records.mjs +39 -1
- package/tests/session_reward_on_land.mjs +381 -0
- package/tests/session_reward_reconcile.mjs +229 -0
- package/tests/ship_api_client_pathspec.mjs +100 -0
- package/tests/ship_base_flag.mjs +93 -0
- package/tests/ship_ci_deploy.mjs +80 -3
- package/tests/ship_upstream_deletion.mjs +57 -0
- package/tests/smoke_target.mjs +233 -0
- package/tests/start_env_guard.mjs +87 -0
- package/tests/start_rebase_warning.mjs +103 -0
- package/tests/status_mirror_sync.mjs +122 -0
- package/tests/stop_refcheck.mjs +19 -1
- package/tests/task_goal_link.mjs +15 -6
- package/tests/upgrade.mjs +139 -0
- package/tests/web_admission_status.mjs +162 -0
- package/tests/write_validation_gaps.mjs +110 -0
package/src/bongos/db-kernel.js
CHANGED
|
@@ -183,6 +183,32 @@ async function hasInvitedAccessRequest(login) {
|
|
|
183
183
|
return rows.length > 0;
|
|
184
184
|
}
|
|
185
185
|
|
|
186
|
+
// accessStateByLogin — the three admission-relevant facts about a github login,
|
|
187
|
+
// in ONE round trip: is there already a builder row (member), has an Archon
|
|
188
|
+
// approved them (invited), are they waiting on one (pending). Case-insensitive
|
|
189
|
+
// throughout, matching hasInvitedAccessRequest above.
|
|
190
|
+
//
|
|
191
|
+
// This exists for the "confirming access" waiting page (task 1002097): the page
|
|
192
|
+
// polls a probe that must distinguish "admission is imminent" from "genuine
|
|
193
|
+
// stranger", and doing that with three separate helpers would mean three
|
|
194
|
+
// queries per poll per visitor. It reports FACTS ONLY — the admission DECISION
|
|
195
|
+
// stays in auth.firstTimeAdmissionBlocked, which is the single gate both flows
|
|
196
|
+
// share (SR-19). Nothing here grants access.
|
|
197
|
+
async function accessStateByLogin(login) {
|
|
198
|
+
const { rows } = await pool.query(
|
|
199
|
+
`SELECT
|
|
200
|
+
EXISTS (SELECT 1 FROM builders
|
|
201
|
+
WHERE lower(github_login) = lower($1)) AS member,
|
|
202
|
+
EXISTS (SELECT 1 FROM access_requests
|
|
203
|
+
WHERE lower(github_login) = lower($1) AND status = 'invited') AS invited,
|
|
204
|
+
EXISTS (SELECT 1 FROM access_requests
|
|
205
|
+
WHERE lower(github_login) = lower($1) AND status = 'pending') AS pending`,
|
|
206
|
+
[login]
|
|
207
|
+
);
|
|
208
|
+
const r = rows[0] || {};
|
|
209
|
+
return { member: !!r.member, invited: !!r.invited, pending: !!r.pending };
|
|
210
|
+
}
|
|
211
|
+
|
|
186
212
|
// --- Auth sessions (kernel: the builder_sessions token store auth.js drives) ----
|
|
187
213
|
|
|
188
214
|
// SESSION_TTL_MS — session TTL on create/refresh. Centralized so the migration's
|
|
@@ -247,6 +273,7 @@ module.exports = {
|
|
|
247
273
|
seatFirstAdminIfUnclaimed,
|
|
248
274
|
getBuilderByGithubId,
|
|
249
275
|
hasInvitedAccessRequest,
|
|
276
|
+
accessStateByLogin,
|
|
250
277
|
createSession,
|
|
251
278
|
lookupSession,
|
|
252
279
|
};
|
package/src/bongos/db.js
CHANGED
|
@@ -298,6 +298,15 @@ async function deleteGithubToken({ builderId, p = pool }) {
|
|
|
298
298
|
// shipped." Pure decision helper is exported so tests/rank_gate.mjs can exercise
|
|
299
299
|
// the threshold without a DB.
|
|
300
300
|
|
|
301
|
+
// REQUIRES_RANK_TIER — the canonical rank → tier (1-based) map: the rank "brain".
|
|
302
|
+
// MUST stay byte-identical to permission-path-check.js RANK_TIER and
|
|
303
|
+
// modules/lifecycle/db.js REQUIRES_RANK_TIER (tests/permission_path.mjs guards the
|
|
304
|
+
// drift; gate-review.js + diagram-facts.js parse this literal by regex, so keep it
|
|
305
|
+
// an inline object literal). Restores the binding the LIVE_RANK_LADDER carve
|
|
306
|
+
// (ADR 0102) left dangling — both singleRungPromotionError below and the
|
|
307
|
+
// db.REQUIRES_RANK_TIER export depend on it.
|
|
308
|
+
const REQUIRES_RANK_TIER = { xenos: 1, thetes: 2, metic: 3, archon: 4 };
|
|
309
|
+
|
|
301
310
|
function singleRungPromotionError(oldRank, newRank) {
|
|
302
311
|
const a = REQUIRES_RANK_TIER[oldRank];
|
|
303
312
|
const b = REQUIRES_RANK_TIER[newRank];
|
|
@@ -330,6 +339,24 @@ function singleRungPromotionError(oldRank, newRank) {
|
|
|
330
339
|
// has nothing to collide on, so it's returned unchanged. Pure + exported so
|
|
331
340
|
// tests can exercise it without a DB; claimTask calls it inside the txn.
|
|
332
341
|
|
|
342
|
+
// syncGovernanceRankRole — keep governance_builder_roles in step with builders.rank
|
|
343
|
+
// (BV1.R95b, ADR 0151). Resolved through the `governance.roleSync` kernel port and
|
|
344
|
+
// run on the CALLER's transaction client, so the role rotation is atomic with the
|
|
345
|
+
// rank write beside it. Core never requires modules/governance/ (ADR 0083).
|
|
346
|
+
//
|
|
347
|
+
// A missing port means the governance module is disabled, and skipping is the
|
|
348
|
+
// fail-closed answer: the builder then holds no role and resolves to ZERO
|
|
349
|
+
// permissions. Admitting anything here would make disabling a module a privilege
|
|
350
|
+
// escalation — the same call requirePermission() makes on a missing port.
|
|
351
|
+
//
|
|
352
|
+
// TWINNED with modules/lifecycle/db.js (the boundary-forced _applyRankChangeTx
|
|
353
|
+
// duplication, ADR 0091 §2) — keep the two in sync.
|
|
354
|
+
async function syncGovernanceRankRole(client, builderId, rank) {
|
|
355
|
+
const roleSync = seams.resolveOptional('governance.roleSync');
|
|
356
|
+
if (!roleSync || typeof roleSync.syncRankRole !== 'function') return null;
|
|
357
|
+
return roleSync.syncRankRole(client, { builderId, rank });
|
|
358
|
+
}
|
|
359
|
+
|
|
333
360
|
async function _applyRankChangeTx(client, { builderId, newRank, actorBuilderId, reasonNote = null, enforceSingleRungPromotion = false }) {
|
|
334
361
|
const { rows } = await client.query(
|
|
335
362
|
`SELECT id, rank, status FROM builders WHERE id = $1 FOR UPDATE`,
|
|
@@ -354,6 +381,17 @@ async function _applyRankChangeTx(client, { builderId, newRank, actorBuilderId,
|
|
|
354
381
|
[builderId, newRank]
|
|
355
382
|
);
|
|
356
383
|
|
|
384
|
+
// BV1.R95b — rotate the seeded rank-role IN THIS TRANSACTION. A DEMOTION removes
|
|
385
|
+
// the higher role rather than merely adding the lower one; without that, a demoted
|
|
386
|
+
// Archon keeps resolving Archon permissions (the ADR 0016 freshness promise broken
|
|
387
|
+
// one layer beneath the uncached resolver).
|
|
388
|
+
//
|
|
389
|
+
// Deliberately NOT wrapped in try/catch. If the rotation cannot be written the rank
|
|
390
|
+
// write must roll back with it: a committed rank change whose role rotation failed
|
|
391
|
+
// IS the privilege-retention bug, just with a smaller window. Refusing leaves the
|
|
392
|
+
// builder consistently at their OLD rank, which the actor can retry.
|
|
393
|
+
await syncGovernanceRankRole(client, builderId, newRank);
|
|
394
|
+
|
|
357
395
|
// V3.R28 / #249 — ceremony delta. Promotion (moving UP the ladder = lower
|
|
358
396
|
// index in RANKS) earns +5; demotion is audit-only (0).
|
|
359
397
|
const oldIdx = RANKS.indexOf(oldRank);
|
|
@@ -960,6 +998,7 @@ module.exports = {
|
|
|
960
998
|
SESSION_TTL_MS,
|
|
961
999
|
LIVE_RANK_LADDER,
|
|
962
1000
|
singleRungPromotionError,
|
|
1001
|
+
REQUIRES_RANK_TIER,
|
|
963
1002
|
recentKarmaForBuilder,
|
|
964
1003
|
karmaDelta30d,
|
|
965
1004
|
insertAuditLog,
|
|
@@ -166,6 +166,18 @@ const MODULE_GLOBS = {
|
|
|
166
166
|
// to the kernel key (02-module-map §5).
|
|
167
167
|
'modules/security/',
|
|
168
168
|
],
|
|
169
|
+
// --- configurable roles & permissions: the permission resolver, catalog, and
|
|
170
|
+
// requirePermission gate (BV1 goal 1000030, ADR 0151). A core default:true
|
|
171
|
+
// module. `protected` DERIVES true — its 'modules/governance/' glob is in
|
|
172
|
+
// PROTECTED_GLOBS (the authz core) — so creating a goal scoped to governance is
|
|
173
|
+
// Archon-only (BV1.R57 / POST /goals). The config globs are the per-instance
|
|
174
|
+
// role templates (R96), listed here as goal territory; the module code lands in
|
|
175
|
+
// R93+ (the carve precedes the feature — ADR 0086 §4). Written as exact paths,
|
|
176
|
+
// not 'config/governance*.json' — the touches convention has no wildcards. ---
|
|
177
|
+
governance: [
|
|
178
|
+
'modules/governance/',
|
|
179
|
+
'config/governance.json', 'config/governance.neutral.json',
|
|
180
|
+
],
|
|
169
181
|
// --- the two web surfaces ---
|
|
170
182
|
// BV1.R85: hall-UI graduated to modules/hall-ui/ — public-builders/ moved to
|
|
171
183
|
// modules/hall-ui/public/ and is now a loader-discovered web-surface module
|
|
@@ -194,7 +206,21 @@ const MODULE_GLOBS = {
|
|
|
194
206
|
// core/CLI-side under scripts/discord/.
|
|
195
207
|
'scripts/discord/', 'modules/discord/',
|
|
196
208
|
],
|
|
197
|
-
|
|
209
|
+
// --- per-builder cloud dev boxes (DO droplets + Cloudflare tunnels), the sandbox
|
|
210
|
+
// preview, server-mediated branch publish. First module carved to modules/dev-box/
|
|
211
|
+
// (BV1.R43 — ADR 0083). Like the lifecycle/onboarding CLIs, the dev-box OPERATOR +
|
|
212
|
+
// connect commands travel with the domain but stay under scripts/gds/: box.js (the
|
|
213
|
+
// DO/CF provisioning control plane), box-auth-check, box-connect-lib, and the box
|
|
214
|
+
// smoke test. Listing them here so a `dev-box`-scoped goal's wall covers the box
|
|
215
|
+
// provisioning / bring-up work (BV1.R91, goal 1000051 C2), not just modules/dev-box/.
|
|
216
|
+
// The box BRING-UP scripts (infra/box-*.sh — box-source-fetch, heartbeat, terminal
|
|
217
|
+
// units) are INSTANCE-VENDORED (ADR 0144; not tracked in core), so they are not
|
|
218
|
+
// listed here yet — they come under this key when de-vendored (task 1002243).
|
|
219
|
+
'dev-box': [
|
|
220
|
+
'modules/dev-box/', 'devbox-app/',
|
|
221
|
+
'scripts/gds/box.js', 'scripts/gds/box-auth-check.js', 'scripts/gds/box-connect-lib.js',
|
|
222
|
+
'scripts/gds/smoke-box.sh',
|
|
223
|
+
],
|
|
198
224
|
};
|
|
199
225
|
|
|
200
226
|
// A glob set is protected iff it overlaps the imported PROTECTED_GLOBS — the one
|
|
@@ -9,8 +9,8 @@
|
|
|
9
9
|
// permission-sensitive path" a DETECTABLE, GATEABLE event — exactly the way
|
|
10
10
|
// route-rank-check.js makes "an ungated write route" detectable.
|
|
11
11
|
//
|
|
12
|
-
// This module is PURE and dependency-free (no
|
|
13
|
-
// run in three places with identical logic:
|
|
12
|
+
// This module is PURE and dependency-free (no pg, no network, no require of any
|
|
13
|
+
// other repo module) so it can run in three places with identical logic:
|
|
14
14
|
// 1. the LOCAL pre-push git hook (scripts/gds/hooks/pre-push.js) — fast,
|
|
15
15
|
// cooperative, blocks the honest builder before the push leaves the box;
|
|
16
16
|
// 2. the GRADER pre-pass (modules/grading/grader.js) — folds a finding into the panel
|
|
@@ -25,6 +25,9 @@
|
|
|
25
25
|
|
|
26
26
|
'use strict';
|
|
27
27
|
|
|
28
|
+
const fs = require('node:fs');
|
|
29
|
+
const path = require('node:path');
|
|
30
|
+
|
|
28
31
|
// Rank → tier. MUST mirror REQUIRES_RANK_TIER in src/bongos/db.js. We keep a LOCAL
|
|
29
32
|
// copy (not an import) so this module stays dependency-free for the client-side
|
|
30
33
|
// pre-push hook; tests/permission_path.mjs asserts the two never drift (the
|
|
@@ -32,87 +35,82 @@
|
|
|
32
35
|
// reintroduce here).
|
|
33
36
|
const RANK_TIER = { xenos: 1, thetes: 2, metic: 3, archon: 4 };
|
|
34
37
|
|
|
35
|
-
//
|
|
36
|
-
//
|
|
37
|
-
//
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
//
|
|
41
|
-
//
|
|
42
|
-
//
|
|
38
|
+
// ── the protected-surface REGISTRY (BV1.R101, ADR 0151 §4) ──────────────────
|
|
39
|
+
//
|
|
40
|
+
// GOVERNANCE OWNS THE DATA; THE SUBSTRATE STAYS THE ENFORCER. Which paths are
|
|
41
|
+
// protected, at what rank floor, mapped to which `system: true` catalog
|
|
42
|
+
// permission, is governance-owned data in
|
|
43
|
+
// modules/governance/protected-surfaces.json — no longer a constant baked into
|
|
44
|
+
// this file. Re-mapping a floor there is a POLICY edit. It cannot move
|
|
45
|
+
// enforcement into the app: the three layers above (git pre-push hook, grader
|
|
46
|
+
// pre-pass, post-push main audit) plus gate-review.js and the goal-scope check
|
|
47
|
+
// read the registry out of band — no DB, no server, no HTTP — and remain the
|
|
48
|
+
// authority (ADR 0043). Making the request path the enforcer is the exact
|
|
49
|
+
// regression this task exists to prevent.
|
|
50
|
+
//
|
|
51
|
+
// WHY A REPO FILE AND NOT A DB QUERY: the enforcers run where there is no
|
|
52
|
+
// database and no server (a git hook on a laptop, a CI job). The registry's
|
|
53
|
+
// canonical form must therefore be SYNCHRONOUSLY loadable from the checkout. A
|
|
54
|
+
// DB-backed override belongs to the governance tab, layered on top — never a
|
|
55
|
+
// dependency of the hook.
|
|
56
|
+
//
|
|
57
|
+
// WHY `fs` AND NOT `require`: ADR 0083's one-way rule forbids core from
|
|
58
|
+
// require-ing a MODULE's code (a module's behavior + enable/disable state must
|
|
59
|
+
// never be something the kernel depends on), and scripts/gds/fitness.js enforces
|
|
60
|
+
// that on `require` edges. This is not code — it is a static declarative data
|
|
61
|
+
// file that ships inside the core package next to this file (modules/ ships with
|
|
62
|
+
// @bongos/core, ADR 0108 §1). The sibling trust primitive route-rank-check.js
|
|
63
|
+
// already reads modules/*/routes/*.js off disk the same way, to rank-gate them.
|
|
64
|
+
// Code still flows ONE way only: modules/governance/protected-surfaces.js →
|
|
65
|
+
// src/module-api.js → this matcher. Nothing in core requires the module.
|
|
43
66
|
//
|
|
44
|
-
//
|
|
45
|
-
//
|
|
46
|
-
//
|
|
47
|
-
//
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
'modules/lifecycle/merge-lock.js',
|
|
92
|
-
'modules/lifecycle/ship-preflight.js',
|
|
93
|
-
'modules/lifecycle/github-push.js',
|
|
94
|
-
'modules/lifecycle/conflict-resolve.js',
|
|
95
|
-
'modules/lifecycle/publish-reconciler.js',
|
|
96
|
-
// --- schema: migrations are never newcomer work ---
|
|
97
|
-
'migrations/',
|
|
98
|
-
// --- the deploy substrate ---
|
|
99
|
-
'infra/',
|
|
100
|
-
'.github/', // #863-followup: CI workflows (deploy-prod.yml, grade gate, secrets-scan) are the pipeline
|
|
101
|
-
'CODEOWNERS', // task 1139 (ADR 0058): the code-owner gate list itself
|
|
102
|
-
'config/deploy.json', // task 1139 (ADR 0058): the laptop/ci/bypass deploy switch
|
|
103
|
-
'package.json', // #863-followup: npm scripts + prepare/lifecycle hooks run during install/CI
|
|
104
|
-
'package-lock.json', // task 1143 (ADR 0058): the resolved supply chain npm ci installs on the droplet
|
|
105
|
-
// --- the hooks the gate is DELIVERED through (self-protect Layer 1) ---
|
|
106
|
-
'.husky/', // #863-followup: pre-push (this gate) + pre-commit (secrets guard)
|
|
107
|
-
// --- the trust-boundary documentation + methodology core ---
|
|
108
|
-
'docs/canonical-permissions.md',
|
|
109
|
-
'docs/adr/0016-trust-boundary-server-enforced-permissions.md',
|
|
110
|
-
'docs/adr/0043-git-ssh-trust-boundary-and-rank-floor-on-permission-paths.md', // #863-followup: this fix's own ADR
|
|
111
|
-
'CLAUDE.md',
|
|
112
|
-
// --- local automation that could weaken a gate ---
|
|
113
|
-
'.claude/hooks/',
|
|
114
|
-
'.claude/settings.json',
|
|
115
|
-
];
|
|
67
|
+
// FAIL-CLOSED: a missing/empty/malformed registry THROWS. It never degrades to
|
|
68
|
+
// "nothing is protected" — an unreadable registry is a broken install, not an
|
|
69
|
+
// opted-out module.
|
|
70
|
+
//
|
|
71
|
+
// Resolved from __dirname (this file is <coreRoot>/src/bongos/) rather than via
|
|
72
|
+
// instance-config so the load keeps zero repo imports and works in a bare git
|
|
73
|
+
// hook.
|
|
74
|
+
const REGISTRY_PATH = path.join(__dirname, '..', '..', 'modules', 'governance', 'protected-surfaces.json');
|
|
75
|
+
|
|
76
|
+
function loadRegistry(file) {
|
|
77
|
+
let raw;
|
|
78
|
+
try {
|
|
79
|
+
raw = JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
80
|
+
} catch (e) {
|
|
81
|
+
throw new Error(`protected-surface registry unreadable at ${file}: ${e.message}. Refusing to run with an empty protected set (ADR 0043).`);
|
|
82
|
+
}
|
|
83
|
+
const surfaces = Array.isArray(raw && raw.surfaces) ? raw.surfaces : null;
|
|
84
|
+
if (!surfaces || surfaces.length === 0) {
|
|
85
|
+
throw new Error(`protected-surface registry ${file} declares no surfaces. Refusing to run with an empty protected set (ADR 0043).`);
|
|
86
|
+
}
|
|
87
|
+
const defaultFloor = raw.floor;
|
|
88
|
+
const defaultPermission = raw.permission;
|
|
89
|
+
return Object.freeze(surfaces.map((s) => {
|
|
90
|
+
const glob = s && s.glob;
|
|
91
|
+
const floor = (s && s.floor) || defaultFloor;
|
|
92
|
+
const permission = (s && s.permission) || defaultPermission;
|
|
93
|
+
if (typeof glob !== 'string' || !glob) throw new Error(`protected-surface registry ${file}: a surface is missing 'glob'`);
|
|
94
|
+
if (!RANK_TIER[floor]) throw new Error(`protected-surface registry ${file}: surface "${glob}" has an unknown floor "${floor}"`);
|
|
95
|
+
if (typeof permission !== 'string' || !permission) throw new Error(`protected-surface registry ${file}: surface "${glob}" maps to no permission`);
|
|
96
|
+
return Object.freeze({ glob, floor, permission, group: (s && s.group) || null, why: (s && s.why) || null });
|
|
97
|
+
}));
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// The registry as loaded: [{ glob, floor, permission, group, why }] in file order.
|
|
101
|
+
const PROTECTED_SURFACES = loadRegistry(REGISTRY_PATH);
|
|
102
|
+
|
|
103
|
+
// The glob list, DERIVED from the registry. Kept as an export because it is the
|
|
104
|
+
// long-standing single source every consumer already reads (module-scope-map,
|
|
105
|
+
// gate-review, publish-manifest, search-ingest-md, sequence) — those keep
|
|
106
|
+
// working unchanged and are now registry-backed by construction.
|
|
107
|
+
const PROTECTED_GLOBS = PROTECTED_SURFACES.map((s) => s.glob);
|
|
108
|
+
|
|
109
|
+
// The floor a builder must clear to touch a protected path, DERIVED from the
|
|
110
|
+
// registry (the lowest floor any surface declares — 'metic' today). Exported
|
|
111
|
+
// because main-audit.js and modules/lifecycle/routes/tasks.js gate on it; the
|
|
112
|
+
// per-surface floor below is what actually decides a hit.
|
|
113
|
+
const MIN_TIER = Math.min(...PROTECTED_SURFACES.map((s) => RANK_TIER[s.floor]));
|
|
116
114
|
|
|
117
115
|
const SPECIALTY =
|
|
118
116
|
'deterministic: a builder below Metic must not modify rank / permission / ship-grade / deploy / migration surfaces (ADR 0016, SEC #863)';
|
|
@@ -142,31 +140,54 @@ function escapeRe(s) {
|
|
|
142
140
|
return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
143
141
|
}
|
|
144
142
|
|
|
145
|
-
const COMPILED =
|
|
143
|
+
const COMPILED = PROTECTED_SURFACES.map((s) => ({ ...s, re: globToRegExp(s.glob) }));
|
|
146
144
|
|
|
147
145
|
// Normalize a path the way git reports it (forward slashes, no leading ./).
|
|
148
146
|
function normalize(p) {
|
|
149
147
|
return String(p || '').replace(/\\/g, '/').replace(/^\.\//, '').trim();
|
|
150
148
|
}
|
|
151
149
|
|
|
152
|
-
// Which of `files` are permission-sensitive? Returns
|
|
150
|
+
// Which of `files` are permission-sensitive? Returns
|
|
151
|
+
// [{ file, glob, floor, permission }] — floor + permission come straight from the
|
|
152
|
+
// registry entry that matched, so a governance re-map flows through every caller.
|
|
153
153
|
function matchProtected(files) {
|
|
154
154
|
const out = [];
|
|
155
155
|
for (const raw of files || []) {
|
|
156
156
|
const f = normalize(raw);
|
|
157
157
|
if (!f) continue;
|
|
158
158
|
const hit = COMPILED.find((c) => c.re.test(f));
|
|
159
|
-
if (hit) out.push({ file: f, glob: hit.glob });
|
|
159
|
+
if (hit) out.push({ file: f, glob: hit.glob, floor: hit.floor, permission: hit.permission });
|
|
160
160
|
}
|
|
161
161
|
return out;
|
|
162
162
|
}
|
|
163
163
|
|
|
164
|
+
// The registry entry governing `file`, or null when the path is unprotected.
|
|
165
|
+
function surfaceFor(file) {
|
|
166
|
+
const f = normalize(file);
|
|
167
|
+
const hit = f ? COMPILED.find((c) => c.re.test(f)) : null;
|
|
168
|
+
return hit ? { glob: hit.glob, floor: hit.floor, permission: hit.permission, group: hit.group, why: hit.why } : null;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// The rank floor governing `file` ('metic' today), or null when unprotected.
|
|
172
|
+
function floorFor(file) {
|
|
173
|
+
const s = surfaceFor(file);
|
|
174
|
+
return s ? s.floor : null;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// The system:true catalog permission `file` maps to, or null when unprotected.
|
|
178
|
+
function permissionFor(file) {
|
|
179
|
+
const s = surfaceFor(file);
|
|
180
|
+
return s ? s.permission : null;
|
|
181
|
+
}
|
|
182
|
+
|
|
164
183
|
function tierOf(rank) {
|
|
165
184
|
return RANK_TIER[rank] ?? null; // null = unknown rank
|
|
166
185
|
}
|
|
167
186
|
|
|
168
|
-
// Is this rank below the protected-path floor?
|
|
169
|
-
//
|
|
187
|
+
// Is this rank below the registry's LOWEST protected-path floor? A coarse
|
|
188
|
+
// "could this rank be blocked by anything at all" test — main-audit.js uses it
|
|
189
|
+
// after matchProtected has already established a hit. Unknown ranks are treated
|
|
190
|
+
// as NOT-below here (no finding) — see checkPermissionPaths for why unknown must
|
|
170
191
|
// fail OPEN at the grader layer (the authoritative audit, layer 3, owns the
|
|
171
192
|
// unknown/dishonest case and reads the real rank from the DB).
|
|
172
193
|
function isBelowFloor(rank) {
|
|
@@ -184,21 +205,26 @@ function isBelowFloor(rank) {
|
|
|
184
205
|
// the layer that fails-closed on the dishonest path.
|
|
185
206
|
function checkPermissionPaths({ changedFiles, builderRank } = {}) {
|
|
186
207
|
const protectedHits = matchProtected(changedFiles);
|
|
187
|
-
const
|
|
208
|
+
const tier = tierOf(builderRank);
|
|
188
209
|
const findings = [];
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
210
|
+
for (const h of protectedHits) {
|
|
211
|
+
// Decided against the hit's OWN registry floor, not a global constant — that
|
|
212
|
+
// is what makes "the governance tab can re-map the floor" real. Every surface
|
|
213
|
+
// floors at 'metic' today, so this is identical to the previous global test.
|
|
214
|
+
if (tier === null || tier >= RANK_TIER[h.floor]) continue;
|
|
215
|
+
const floorLabel = h.floor.charAt(0).toUpperCase() + h.floor.slice(1);
|
|
216
|
+
findings.push({
|
|
217
|
+
severity: 'blocker',
|
|
218
|
+
file: h.file,
|
|
219
|
+
glob: h.glob,
|
|
220
|
+
floor: h.floor,
|
|
221
|
+
permission: h.permission,
|
|
222
|
+
summary:
|
|
223
|
+
`${h.file} is a permission-sensitive surface (matches \`${h.glob}\`). `
|
|
224
|
+
+ `Builder rank '${builderRank}' is below ${floorLabel} and may not modify the rank / permission / `
|
|
225
|
+
+ `ship-grade / deploy / migration machinery (ADR 0016, SEC #863). `
|
|
226
|
+
+ `If this change is genuinely needed, an Archon must own it.`,
|
|
227
|
+
});
|
|
202
228
|
}
|
|
203
229
|
return {
|
|
204
230
|
ok: findings.length === 0,
|
|
@@ -215,8 +241,11 @@ function checkPermissionPaths({ changedFiles, builderRank } = {}) {
|
|
|
215
241
|
function toWorkerResult(result) {
|
|
216
242
|
const verdict = result.hardFail ? 'fail' : 'pass';
|
|
217
243
|
const n = result.findings.length;
|
|
244
|
+
// Floor label comes from the findings (registry data), not a hardcoded 'Metic'.
|
|
245
|
+
const floor = n > 0 ? result.findings[0].floor : null;
|
|
246
|
+
const floorLabel = floor ? floor.charAt(0).toUpperCase() + floor.slice(1) : 'the floor';
|
|
218
247
|
const header = verdict === 'fail'
|
|
219
|
-
? `Permission-Path — FAIL (rank '${result.builderRank}' below
|
|
248
|
+
? `Permission-Path — FAIL (rank '${result.builderRank}' below ${floorLabel} touched ${n} protected path(s))`
|
|
220
249
|
: `Permission-Path — PASS`;
|
|
221
250
|
const body = result.findings.map((f) => ` [${f.severity}] ${f.file} — ${f.summary}`).join('\n');
|
|
222
251
|
return {
|
|
@@ -238,9 +267,14 @@ module.exports = {
|
|
|
238
267
|
RANK_TIER,
|
|
239
268
|
MIN_TIER,
|
|
240
269
|
PROTECTED_GLOBS,
|
|
270
|
+
PROTECTED_SURFACES,
|
|
271
|
+
REGISTRY_PATH,
|
|
241
272
|
SPECIALTY,
|
|
242
273
|
globToRegExp,
|
|
243
274
|
matchProtected,
|
|
275
|
+
surfaceFor,
|
|
276
|
+
floorFor,
|
|
277
|
+
permissionFor,
|
|
244
278
|
tierOf,
|
|
245
279
|
isBelowFloor,
|
|
246
280
|
checkPermissionPaths,
|
package/src/bongos/repo-info.js
CHANGED
|
@@ -44,26 +44,61 @@ function loadFromEnv() {
|
|
|
44
44
|
return null;
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
-
|
|
47
|
+
// The directory this file's package occupies: <root>/src/bongos -> <root>. In the core
|
|
48
|
+
// dev checkout that is the repo root; installed, it is node_modules/@bongos/core.
|
|
49
|
+
const PACKAGE_ROOT = path.resolve(__dirname, '..', '..');
|
|
50
|
+
|
|
51
|
+
// Same directory? Normalizes separator style (git prints POSIX separators even on
|
|
52
|
+
// Windows) and surrounding whitespace, and matches the platform's case rules.
|
|
53
|
+
function sameDir(a, b) {
|
|
54
|
+
if (!a || !b) return false;
|
|
55
|
+
const norm = (p) => {
|
|
56
|
+
const abs = path.resolve(String(p).trim());
|
|
57
|
+
return process.platform === 'win32' ? abs.toLowerCase() : abs;
|
|
58
|
+
};
|
|
59
|
+
return norm(a) === norm(b);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Resolve the remote ONLY when this package IS the git repo, never a repo found by
|
|
63
|
+
// walking up out of it.
|
|
64
|
+
//
|
|
65
|
+
// git discovery searches ANCESTORS of cwd. In the core dev checkout PACKAGE_ROOT is the
|
|
66
|
+
// repo root, so `git remote get-url origin` answers correctly. But on a live instance
|
|
67
|
+
// (ADR 0140) the hub runs from node_modules/@bongos/core, which is not a repo — so git
|
|
68
|
+
// climbed out of the package, found the enclosing INSTANCE repo, and returned its origin.
|
|
69
|
+
// That silently bound a DIFFERENT repo than the one the core publishes to, so every
|
|
70
|
+
// publish call 404'd against a repo the builder had no access to.
|
|
71
|
+
//
|
|
72
|
+
// Anchor the answer: ask git for its toplevel first and require it to EQUAL the directory
|
|
73
|
+
// we probed. If it differs, the repo is someone else's — return null so loadRepoInfo falls
|
|
74
|
+
// through to the env pin (<PREFIX>_GITHUB_OWNER/_REPO) or to an explicit no_repo_info. An
|
|
75
|
+
// error is strictly better than a wrong answer here: a misbind produces confident 404s
|
|
76
|
+
// that are hard to trace, while the error is printed verbatim by the publish-reconciler's
|
|
77
|
+
// boot line and names its own cure.
|
|
78
|
+
function loadFromGit(deps = {}) {
|
|
79
|
+
const exec = deps.execSync || execSync;
|
|
80
|
+
const root = deps.packageRoot || PACKAGE_ROOT;
|
|
81
|
+
const opts = { cwd: root, encoding: 'utf8', timeout: 2000, stdio: ['ignore', 'pipe', 'pipe'] };
|
|
48
82
|
try {
|
|
49
|
-
const
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
timeout: 2000,
|
|
53
|
-
stdio: ['ignore', 'pipe', 'pipe'],
|
|
54
|
-
});
|
|
55
|
-
return parseRemote(remote);
|
|
83
|
+
const toplevel = exec('git rev-parse --show-toplevel', opts);
|
|
84
|
+
if (!sameDir(toplevel, root)) return null;
|
|
85
|
+
return parseRemote(exec('git remote get-url origin', opts));
|
|
56
86
|
} catch (_) {
|
|
57
87
|
return null;
|
|
58
88
|
}
|
|
59
89
|
}
|
|
60
90
|
|
|
61
|
-
|
|
91
|
+
// `deps` is the test seam (execSync / packageRoot); production calls this with no args.
|
|
92
|
+
function loadRepoInfo(deps = {}) {
|
|
62
93
|
if (cached) return cached;
|
|
63
94
|
const branch = ic.resolveEnv('GITHUB_BRANCH') || 'main';
|
|
64
|
-
const parsed = loadFromEnv() || loadFromGit();
|
|
95
|
+
const parsed = loadFromEnv() || loadFromGit(deps);
|
|
65
96
|
if (!parsed) {
|
|
66
|
-
cached = {
|
|
97
|
+
cached = {
|
|
98
|
+
error: 'no_repo_info',
|
|
99
|
+
detail:
|
|
100
|
+
'git remote origin not resolvable from this package (an installed core does not sit inside the repo it publishes to) and <PREFIX>_GITHUB_OWNER/REPO unset',
|
|
101
|
+
};
|
|
67
102
|
return cached;
|
|
68
103
|
}
|
|
69
104
|
const base = `https://github.com/${parsed.owner}/${parsed.repo}`;
|
|
@@ -87,5 +122,6 @@ function _resetCacheForTests() {
|
|
|
87
122
|
module.exports = {
|
|
88
123
|
loadRepoInfo,
|
|
89
124
|
parseRemote,
|
|
125
|
+
loadFromGit, // exported for unit tests — the anchor rule is the whole fix
|
|
90
126
|
_resetCacheForTests,
|
|
91
127
|
};
|