@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
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
# Work on a task from your dev box
|
|
2
|
+
|
|
3
|
+
> Your dev box is **task-scoped**: claim a task and the box pulls **only the code
|
|
4
|
+
> that task needs** — the module(s) its work touches, on top of a small base
|
|
5
|
+
> instance — never the whole repo. Your GitHub token never leaves the server; the
|
|
6
|
+
> box fetches its slice through the control plane. See
|
|
7
|
+
> [ADR 0148](../adr/<redacted>.md) (the scope model) and
|
|
8
|
+
> [ADR 0150](../adr/<redacted>.md) (how the box
|
|
9
|
+
> comes up).
|
|
10
|
+
|
|
11
|
+
## TL;DR
|
|
12
|
+
|
|
13
|
+
In your dev box's terminal:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
/builder-start # see what you can claim
|
|
17
|
+
/builder-claim <N> # claim task N (locks it to your session)
|
|
18
|
+
/builder-sync # pull task N's code onto the box now (re-scopes in place)
|
|
19
|
+
# …work in /workspace…
|
|
20
|
+
/builder-ship # smoke + review, then land + deploy — awards credits
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
That's the whole loop. `/builder-sync` is the new step: it materializes the claimed
|
|
24
|
+
task's code within seconds instead of waiting for the box's ~10-minute refresh.
|
|
25
|
+
|
|
26
|
+
## The model — what's on your box
|
|
27
|
+
|
|
28
|
+
A task-scoped box carries two layers:
|
|
29
|
+
|
|
30
|
+
1. **The base instance** — always present, whatever you've claimed: the methodology
|
|
31
|
+
(`CLAUDE.md`, `.claude/`), the build tooling (`scripts/`, `src/bongos/`),
|
|
32
|
+
`docs/`, `infra/`, and the **test suite** (`tests/`). Enough for Claude to hold
|
|
33
|
+
context and run the `/builder-*` CLI, and to run your module's tests.
|
|
34
|
+
2. **Your claimed task's code** — the module directories the task's goal scopes
|
|
35
|
+
(`goals.scope_modules` → globs, [ADR 0086](../adr/<redacted>.md)). Claim a `dev-box` task → you get
|
|
36
|
+
`modules/dev-box/`; claim a `game` task → you get the game world too. Nothing
|
|
37
|
+
else.
|
|
38
|
+
|
|
39
|
+
What you do **not** get: other builders' modules, the product/game runtime (unless
|
|
40
|
+
your task is game-scoped), and — the point — **no GitHub credential on the box**.
|
|
41
|
+
The box asks the control plane (`GET /box/source-access`) for its slice on each
|
|
42
|
+
fetch; the server holds the token and returns a scoped, cone-mode sparse-checkout
|
|
43
|
+
spec. Lose the claim or a rank, and the next fetch is cut off at the server.
|
|
44
|
+
|
|
45
|
+
## Step by step
|
|
46
|
+
|
|
47
|
+
### 1. Connect to your box
|
|
48
|
+
|
|
49
|
+
If you haven't yet, run **`/builder-connect`** (or follow the
|
|
50
|
+
[dev-box lifecycle recipe](builder-box-lifecycle.md)) to raise and connect to your
|
|
51
|
+
box, then **`/builder-setup`** once on the box to sign the GDS CLI in. The browser
|
|
52
|
+
terminal drops you into `/workspace` — the one working folder.
|
|
53
|
+
|
|
54
|
+
> **`bongos shell` / `bongos code` re-scope on connect (BV1.R101).** Connecting with
|
|
55
|
+
> either verb runs the sync for you first, so you land in `/workspace` already scoped
|
|
56
|
+
> to your active claim — no manual `/builder-sync` needed just to arrive. (The browser
|
|
57
|
+
> terminal drops you straight into `/workspace`; run `/builder-sync` there if you
|
|
58
|
+
> claimed or switched since your last sync.)
|
|
59
|
+
|
|
60
|
+
### 2. See what you can claim — `/builder-start`
|
|
61
|
+
|
|
62
|
+
Lists the tasks that are `ready` and parallel-safe against everyone else's active
|
|
63
|
+
claims. Pick one.
|
|
64
|
+
|
|
65
|
+
### 3. Claim it — `/builder-claim <N>`
|
|
66
|
+
|
|
67
|
+
Locks task N to your session. **This is what drives your box's scope:** source
|
|
68
|
+
access is computed from your **active claim** — its task → goal → `scope_modules`.
|
|
69
|
+
|
|
70
|
+
### 4. Pull the code now — `/builder-sync`
|
|
71
|
+
|
|
72
|
+
Re-runs the box's source-fetch immediately, so `/workspace` re-scopes to task N's
|
|
73
|
+
modules within seconds. (A ~10-minute cron does this on its own as a backstop — you
|
|
74
|
+
just don't want to wait for it.) Off a box there's nothing to sync — your source is
|
|
75
|
+
your local checkout — and the command tells you so.
|
|
76
|
+
|
|
77
|
+
### 5. Work — in `/workspace`
|
|
78
|
+
|
|
79
|
+
`/workspace` now holds the base instance **plus** task N's module code. One folder:
|
|
80
|
+
when you switch tasks it **re-scopes in place**, so there are no per-task folders to
|
|
81
|
+
manage.
|
|
82
|
+
|
|
83
|
+
### 6. Ship — `/builder-ship`
|
|
84
|
+
|
|
85
|
+
Runs the smoke tests and the review gate, then lands + deploys and awards credits.
|
|
86
|
+
|
|
87
|
+
## Switching tasks
|
|
88
|
+
|
|
89
|
+
One box, one working folder, one active claim at a time (the server enforces
|
|
90
|
+
one-claim-per-session). To move to a different task:
|
|
91
|
+
|
|
92
|
+
1. `/builder-ship` (or `/builder-release`) the current claim.
|
|
93
|
+
2. `/builder-claim <M>` the new one.
|
|
94
|
+
3. `/builder-sync` — the box re-scopes to task M in place.
|
|
95
|
+
|
|
96
|
+
## Notes + gotchas
|
|
97
|
+
|
|
98
|
+
- **`tests/` is always in the base** — so a module's tests run on the box (they
|
|
99
|
+
live in the top-level `tests/`, not inside `modules/<key>/`).
|
|
100
|
+
- **Idle boxes rest.** A box with no activity is reclaimed after ~10–15 min; the
|
|
101
|
+
next connect re-provisions a fresh one and re-fetches your scope. **Uncommitted
|
|
102
|
+
work on a reaped box is lost** — ship or commit before you step away.
|
|
103
|
+
- **`/builder-sync` is idempotent** — safe to re-run; it just does, on demand, what
|
|
104
|
+
the cron does on its schedule.
|
|
105
|
+
- **A fresh box comes up with the base already cloned.** If a box ever comes up
|
|
106
|
+
*bare* (no code, no terminal), that's the packaging bug
|
|
107
|
+
[ADR 0150](../adr/<redacted>.md) fixed on the
|
|
108
|
+
control plane — flag it rather than working around it.
|
package/migrations/003_pms.sql
CHANGED
|
@@ -223,17 +223,16 @@ CREATE INDEX IF NOT EXISTS idx_session_logs_builder ON session_logs (builder_id)
|
|
|
223
223
|
CREATE INDEX IF NOT EXISTS idx_session_logs_ended ON session_logs (ended_at);
|
|
224
224
|
|
|
225
225
|
-- =========================================================================
|
|
226
|
-
-- Seed: the
|
|
226
|
+
-- Seed: the platform PMS-V1 version
|
|
227
227
|
-- =========================================================================
|
|
228
|
-
-- Instance-seed guard (task 1704 / ADR 0105):
|
|
229
|
-
--
|
|
230
|
-
--
|
|
228
|
+
-- Instance-seed guard (task 1704 / ADR 0105): OTB platform scope-truth that must
|
|
229
|
+
-- NOT seed onto a vanilla / non-owning DB. scripts/migrate.sh sets
|
|
230
|
+
-- app.seed_instance_data='off' on such a boot; unset (direct psql / OTB) = seed.
|
|
231
|
+
-- (Game V1 was product/primea scope; relocated to the amazonprimea instance's
|
|
232
|
+
-- migrations/instance/ per task 1002423 — the neutral core carries no game seed.)
|
|
231
233
|
DO $seed$ BEGIN IF current_setting('app.seed_instance_data', true) IS DISTINCT FROM 'off' THEN
|
|
232
234
|
INSERT INTO versions (id, name, track, status, scope_doc_path, started_at, done_when)
|
|
233
235
|
VALUES
|
|
234
|
-
('V1', 'Game V1 — first playable purchase loop', 'product', 'building',
|
|
235
|
-
'limitations/current-version.md', '2026-05-08'::timestamptz,
|
|
236
|
-
'A visitor can land in the world, walk to the vendor, buy a real item, and Hermeslines fulfills it.'),
|
|
237
236
|
('PMS-V1', 'PMS V1 — project management system, parallel internal track', 'internal', 'building',
|
|
238
237
|
'limitations/pms-v1.md', '2026-05-09'::timestamptz,
|
|
239
238
|
'Builders auth via GitHub. Slash commands claim/ship tasks atomically. Backlog migrated. Public status page live.')
|
|
@@ -337,38 +337,10 @@ ON CONFLICT (version_id, criterion_id) DO UPDATE
|
|
|
337
337
|
END IF; END $seed$;
|
|
338
338
|
|
|
339
339
|
-- =========================================================================
|
|
340
|
-
--
|
|
340
|
+
-- (Game V1 done-when criteria removed — product/primea scope, not core.)
|
|
341
|
+
-- Relocated to the amazonprimea instance's migrations/instance/ per task
|
|
342
|
+
-- 1002423; the neutral core seeds no game scope-truth.
|
|
341
343
|
-- =========================================================================
|
|
342
|
-
-- The V1 done-when (CLAUDE.md §9): "the experience supports the full
|
|
343
|
-
-- purchase loop: land in the world → walk to the vendor → buy a real
|
|
344
|
-
-- item → Hermeslines fulfills it." Decomposed into checkable criteria.
|
|
345
|
-
|
|
346
|
-
-- Instance-seed guard (task 1704 / ADR 0105): OTB V1 game scope-truth. FK-references
|
|
347
|
-
-- the V1 version guarded above, so this must skip on a vanilla DB too.
|
|
348
|
-
DO $seed$ BEGIN IF current_setting('app.seed_instance_data', true) IS DISTINCT FROM 'off' THEN
|
|
349
|
-
INSERT INTO done_when_criteria (version_id, criterion_id, criterion_md, sort_order) VALUES
|
|
350
|
-
('V1', 'world-land',
|
|
351
|
-
'Visitor lands in the persistent world from amazonprimea.com',
|
|
352
|
-
10),
|
|
353
|
-
('V1', 'walk-to-vendor',
|
|
354
|
-
'Visitor can walk to Hyperion at the shipping container with WASD',
|
|
355
|
-
20),
|
|
356
|
-
('V1', 'auth-magic-link',
|
|
357
|
-
'Visitor authenticates via Stripe-tied magic link before purchase',
|
|
358
|
-
30),
|
|
359
|
-
('V1', 'vendor-purchase',
|
|
360
|
-
'Vendor dialog supports real-product purchase via Stripe',
|
|
361
|
-
40),
|
|
362
|
-
('V1', 'hermeslines-fulfill',
|
|
363
|
-
'Order intake reaches Hermeslines fulfillment system (whatever the format)',
|
|
364
|
-
50),
|
|
365
|
-
('V1', 'order-confirmation',
|
|
366
|
-
'Visitor receives order confirmation in-world',
|
|
367
|
-
60)
|
|
368
|
-
ON CONFLICT (version_id, criterion_id) DO UPDATE
|
|
369
|
-
SET criterion_md = EXCLUDED.criterion_md,
|
|
370
|
-
sort_order = EXCLUDED.sort_order;
|
|
371
|
-
END IF; END $seed$;
|
|
372
344
|
|
|
373
345
|
-- =========================================================================
|
|
374
346
|
-- 7. Migration record
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
-- core_193_task_needs_rebase.sql — ADR 0120 part 2: the `needs_rebase` flag.
|
|
2
|
+
--
|
|
3
|
+
-- A confirmed task whose land the publish-reconciler cannot complete after the
|
|
4
|
+
-- normal auto-land grace (its retries + the ADR 0082 generated-file self-heal)
|
|
5
|
+
-- is flagged needs_rebase=true. It is a SIGNAL, not a status change: the task
|
|
6
|
+
-- stays 'confirmed' (its work is done) — it is simply marked as awaiting *its
|
|
7
|
+
-- builder's* rebase. Attribution to the builder who did the work is derived from
|
|
8
|
+
-- tasks.shipped_by (confirmTask/applyGrade/decideOverrideRequest all COALESCE it
|
|
9
|
+
-- to the claim holder), so no separate owner column is needed.
|
|
10
|
+
--
|
|
11
|
+
-- The claim gate (core claimTask, ADR 0120 part 3) refuses that builder's NEW
|
|
12
|
+
-- claims while any of their tasks is needs_rebase; landing the task (shipTask)
|
|
13
|
+
-- or releasing it clears the flag. This is a CORE migration (not a lifecycle
|
|
14
|
+
-- module migration) because it ALTERs the core `tasks` table — the module
|
|
15
|
+
-- migration namespacing rule forbids a module migration touching non-namespaced
|
|
16
|
+
-- core objects (the discord precedent).
|
|
17
|
+
--
|
|
18
|
+
-- Safe DDL: ADD COLUMN ... NOT NULL DEFAULT <constant> does not rewrite the
|
|
19
|
+
-- table on Postgres 11+ (the default is stored in the catalog).
|
|
20
|
+
|
|
21
|
+
ALTER TABLE tasks ADD COLUMN IF NOT EXISTS needs_rebase boolean NOT NULL DEFAULT false;
|
|
22
|
+
ALTER TABLE tasks ADD COLUMN IF NOT EXISTS needs_rebase_reason text;
|
|
23
|
+
ALTER TABLE tasks ADD COLUMN IF NOT EXISTS needs_rebase_at timestamptz;
|
|
24
|
+
|
|
25
|
+
-- The claim gate + hall/CLI ask "does builder B own any needs_rebase task?",
|
|
26
|
+
-- keyed on shipped_by. Partial index keeps that lookup cheap (only the handful
|
|
27
|
+
-- of flagged rows are indexed).
|
|
28
|
+
CREATE INDEX IF NOT EXISTS idx_tasks_needs_rebase ON tasks (shipped_by) WHERE needs_rebase;
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
-- core_194_confirmed_backlog_clawback.sql — ADR 0120 part 4: one-time clawback
|
|
2
|
+
-- + re-gate of the confirmed backlog that accrued under the OLD pay-on-confirm
|
|
3
|
+
-- rule (parts 1-3 moved BOTH the pay and the queue gate to the LAND).
|
|
4
|
+
--
|
|
5
|
+
-- For every task still at status='confirmed' (verified but NOT yet merged to
|
|
6
|
+
-- main) this migration:
|
|
7
|
+
-- 1. Reverses its task.confirmed per-task credit — a compensating NEGATIVE
|
|
8
|
+
-- credit_log row (reason='task.confirmed_reversal') per (task, builder), so
|
|
9
|
+
-- the builders.total_credits trigger nets it back to 0. The reward is fully
|
|
10
|
+
-- RE-EARNABLE: when the task finally lands, part 1a books a fresh
|
|
11
|
+
-- 'task.shipped' credit. Achievements already unlocked are LEFT INTACT
|
|
12
|
+
-- (reversing gamification is punitive + messy — ADR 0120 §Consequences).
|
|
13
|
+
-- 2. Flags the task needs_rebase=true (reason 'clawback:confirmed_backlog') so
|
|
14
|
+
-- the part-3 claim gate applies the new incentive to the work that stranded
|
|
15
|
+
-- under the old rule, rather than grandfathering it.
|
|
16
|
+
--
|
|
17
|
+
-- Idempotent: a task already carrying a task.confirmed_reversal row is skipped,
|
|
18
|
+
-- and needs_rebase is only set where still false — a re-run is a no-op. It runs
|
|
19
|
+
-- ONCE per DB (schema_migrations), at the deploy that first carries it; that is
|
|
20
|
+
-- WHERE the retroactive sting lands, so it hits real builders (the owner
|
|
21
|
+
-- included) only when this version is actually deployed.
|
|
22
|
+
--
|
|
23
|
+
-- Deliberately NOT reversed: the idea-promotion bonus and the net-negative
|
|
24
|
+
-- bonus. The ADR names only the 'task.confirmed' delta; the idea bonus stays
|
|
25
|
+
-- with its capturer (it is not double-paid on re-land — maybeAwardIdeaPromotion
|
|
26
|
+
-- Bonus guards on the existing 'idea_promotion_bonus' row), and the net-negative
|
|
27
|
+
-- bonus derives from grade-time signals that never moved off confirm.
|
|
28
|
+
--
|
|
29
|
+
-- DML only (no schema change): safe to run inside one transaction.
|
|
30
|
+
|
|
31
|
+
BEGIN;
|
|
32
|
+
|
|
33
|
+
-- 1. Compensating reversal of the task.confirmed credit for still-confirmed tasks.
|
|
34
|
+
INSERT INTO credit_log (builder_id, task_id, delta, reason, description)
|
|
35
|
+
SELECT cl.builder_id,
|
|
36
|
+
cl.task_id,
|
|
37
|
+
-SUM(cl.delta),
|
|
38
|
+
'task.confirmed_reversal',
|
|
39
|
+
'ADR 0120 clawback: reverse premature task.confirmed credit for still-confirmed (unlanded) task ' || cl.task_id
|
|
40
|
+
FROM credit_log cl
|
|
41
|
+
JOIN tasks t ON t.id = cl.task_id
|
|
42
|
+
WHERE t.status = 'confirmed'
|
|
43
|
+
AND cl.reason = 'task.confirmed'
|
|
44
|
+
AND NOT EXISTS (
|
|
45
|
+
SELECT 1 FROM credit_log r
|
|
46
|
+
WHERE r.task_id = cl.task_id
|
|
47
|
+
AND r.builder_id = cl.builder_id
|
|
48
|
+
AND r.reason = 'task.confirmed_reversal'
|
|
49
|
+
)
|
|
50
|
+
GROUP BY cl.builder_id, cl.task_id
|
|
51
|
+
HAVING SUM(cl.delta) <> 0;
|
|
52
|
+
|
|
53
|
+
-- 2. Re-gate: flag every still-confirmed task needs_rebase for its builder
|
|
54
|
+
-- (attribution via tasks.shipped_by), so the claim gate (part 3) bites.
|
|
55
|
+
UPDATE tasks
|
|
56
|
+
SET needs_rebase = true,
|
|
57
|
+
needs_rebase_reason = 'clawback:confirmed_backlog',
|
|
58
|
+
needs_rebase_at = COALESCE(needs_rebase_at, now()),
|
|
59
|
+
updated_at = now()
|
|
60
|
+
WHERE status = 'confirmed'
|
|
61
|
+
AND needs_rebase = false;
|
|
62
|
+
|
|
63
|
+
COMMIT;
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
-- core_195_idea_triage_provenance.sql — give a triage VERDICT somewhere to
|
|
2
|
+
-- record what it decided (task 1002447).
|
|
3
|
+
--
|
|
4
|
+
-- Triage has three verbs; only two of them had storage. `status` records THAT an
|
|
5
|
+
-- idea was discarded or merged, and `promoted_to_task_id` records the task a
|
|
6
|
+
-- promotion produced — but nothing recorded WHICH idea a merge folded into, or
|
|
7
|
+
-- WHY a discard happened. `promoted_to_task_id` cannot stand in for the merge
|
|
8
|
+
-- target: it is `REFERENCES tasks(id)`, so writing an idea id there either trips
|
|
9
|
+
-- the FK or silently points at an unrelated task. BOTH callers that documented
|
|
10
|
+
-- or performed that write are corrected alongside this migration — the
|
|
11
|
+
-- /idea-triage skill doc and the interactive walker `scripts/gds/triage.js`.
|
|
12
|
+
-- Without a target, "merge" is just "discard" wearing a different label.
|
|
13
|
+
--
|
|
14
|
+
-- CORE, not modules/ideas/migrations/, even though the ideas module owns every
|
|
15
|
+
-- query against this table: `idea_inbox` is a CORE table, and a module migration
|
|
16
|
+
-- may only touch its own <key>_-prefixed objects — `fitness.js`
|
|
17
|
+
-- checkModuleMigrationsAdditive hard-fails an `ALTER TABLE idea_inbox` from a
|
|
18
|
+
-- module (ADR 0083 §Decision #5). The module's "new ideas migration goes in
|
|
19
|
+
-- modules/ideas/migrations/" note applies to objects the module OWNS, and it
|
|
20
|
+
-- owns none.
|
|
21
|
+
--
|
|
22
|
+
-- Two additive, nullable columns. Forward-safe by construction: every existing
|
|
23
|
+
-- reader SELECTs an explicit column list, so old code on the new schema neither
|
|
24
|
+
-- sees nor needs these (tests/migrations_forward_safe.mjs).
|
|
25
|
+
|
|
26
|
+
BEGIN;
|
|
27
|
+
|
|
28
|
+
ALTER TABLE idea_inbox
|
|
29
|
+
ADD COLUMN IF NOT EXISTS merged_into_idea_id bigint REFERENCES idea_inbox(id);
|
|
30
|
+
|
|
31
|
+
ALTER TABLE idea_inbox
|
|
32
|
+
ADD COLUMN IF NOT EXISTS triage_note text;
|
|
33
|
+
|
|
34
|
+
-- A target exists IFF the idea is merged. Without this, two ordinary PATCHes
|
|
35
|
+
-- build a merge CHAIN: set A's target while leaving A open, then merge C into A
|
|
36
|
+
-- — whose status still reads 'open', so a status-based no-chains check waves it
|
|
37
|
+
-- through. One direction only (`target IS NULL OR status='merged'`), because
|
|
38
|
+
-- every pre-existing merged row has a NULL target and must stay valid.
|
|
39
|
+
-- CHECK constraints have no ADD ... IF NOT EXISTS, so guard the add (the
|
|
40
|
+
-- migration 042 idiom) and keep the file replay-safe.
|
|
41
|
+
DO $$
|
|
42
|
+
BEGIN
|
|
43
|
+
IF NOT EXISTS (
|
|
44
|
+
SELECT 1 FROM pg_constraint
|
|
45
|
+
WHERE conrelid = 'idea_inbox'::regclass
|
|
46
|
+
AND conname = 'idea_inbox_merge_target_needs_merged_status'
|
|
47
|
+
) THEN
|
|
48
|
+
ALTER TABLE idea_inbox
|
|
49
|
+
ADD CONSTRAINT idea_inbox_merge_target_needs_merged_status
|
|
50
|
+
CHECK (merged_into_idea_id IS NULL OR status = 'merged');
|
|
51
|
+
END IF;
|
|
52
|
+
END $$;
|
|
53
|
+
|
|
54
|
+
INSERT INTO schema_migrations (version) VALUES ('core_195_idea_triage_provenance')
|
|
55
|
+
ON CONFLICT DO NOTHING;
|
|
56
|
+
|
|
57
|
+
COMMIT;
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
-- core_196_session_reward_topup.sql
|
|
2
|
+
-- Let a multi-ship session be paid for ALL of its cost, not just its first ship.
|
|
3
|
+
--
|
|
4
|
+
-- WHY THIS IS A CORE MIGRATION, NOT modules/economy/. credit_log is a CORE table — the
|
|
5
|
+
-- economy module owns the queries against it, not the table itself (ADR 0083 §Decision #5;
|
|
6
|
+
-- modules/economy/CLAUDE.md "Tables: None owned"). A module migration may only touch its own
|
|
7
|
+
-- economy_-namespaced objects, so this shipped as economy_002_* and reddened the fitness gate
|
|
8
|
+
-- three ways (destructive DDL + index + alter on a non-namespaced table). The real defect was
|
|
9
|
+
-- placement, not DDL: as a module migration these columns only existed when the economy module
|
|
10
|
+
-- happened to be ENABLED, which makes core schema depend on module enablement — the exact
|
|
11
|
+
-- coupling the rule forbids. Relocated per the core_195 precedent (task 1002447).
|
|
12
|
+
--
|
|
13
|
+
-- schema_migrations is stem-keyed, so this re-applies once under its new stem. That is safe
|
|
14
|
+
-- BECAUSE every statement below is IF [NOT] EXISTS — the re-run is a no-op on any database
|
|
15
|
+
-- that already had it as economy_002.
|
|
16
|
+
--
|
|
17
|
+
-- THE DEFECT. Migration 091 made the cost-plus session reward idempotent with a
|
|
18
|
+
-- PARTIAL UNIQUE INDEX on credit_log (session_id) WHERE reason='session.token_reward'
|
|
19
|
+
-- — "the hard DB-level backstop against a double payout". That is correct and stays.
|
|
20
|
+
-- But it was the ONLY control, so once a session booked its first reward row, every
|
|
21
|
+
-- later ship in the same session was a hard no-op. A session's cost keeps growing
|
|
22
|
+
-- after its first ship, and that increment had nowhere to land.
|
|
23
|
+
--
|
|
24
|
+
-- Measured: three tasks shipped from one session, priced $14.30 / $27.76 / $40.32,
|
|
25
|
+
-- paid 17 drachmae where the whole session was worth round(40.32 × 1.20) = 48.
|
|
26
|
+
-- Roughly 65% of real, shipped, graded work went unrewarded. Multi-ship sessions are
|
|
27
|
+
-- normal — one claim at a time, but ship -> new worktree -> claim again.
|
|
28
|
+
--
|
|
29
|
+
-- THE FIX is watermark accounting: each landed ship books the DIFFERENCE between what
|
|
30
|
+
-- the whole session is now worth and what it has already been paid. That needs more
|
|
31
|
+
-- than one row per session, so uniqueness moves from (session_id) to
|
|
32
|
+
-- (session_id, reward_basis) — still scoped to the same reason.
|
|
33
|
+
--
|
|
34
|
+
-- WHY A CUMULATIVE BASIS AND NOT THE DELTA. Two concurrent uploads at the SAME cost
|
|
35
|
+
-- level must still collide and DO NOTHING. reward_basis is the CUMULATIVE owed total
|
|
36
|
+
-- at the moment of the row, so it is monotonic and identical for a racing pair. A bare
|
|
37
|
+
-- delta would be wrong: two genuine increments that happened to be equal would
|
|
38
|
+
-- collide and one payment would be silently lost.
|
|
39
|
+
--
|
|
40
|
+
-- Additive and non-destructive. No historical row changes value.
|
|
41
|
+
|
|
42
|
+
-- (a) The cumulative cost-plus total this session was owed when the row was written.
|
|
43
|
+
-- NOT NULL DEFAULT 0 (rather than nullable) so the unique index below has clean
|
|
44
|
+
-- semantics: Postgres treats NULLs as DISTINCT in a unique index, so a nullable
|
|
45
|
+
-- basis would let two legacy rows share a session_id and would WEAKEN the very
|
|
46
|
+
-- backstop 091 added.
|
|
47
|
+
--
|
|
48
|
+
-- Existing rows therefore land on reward_basis = 0, which is the marker for
|
|
49
|
+
-- "booked before top-ups existed". The application refuses to top those up: doing
|
|
50
|
+
-- so would be an uncontrolled retroactive payout across every historical session.
|
|
51
|
+
-- Reconstructing what past sessions are owed is a deliberate, owner-signed-off
|
|
52
|
+
-- backfill, tracked separately.
|
|
53
|
+
ALTER TABLE credit_log
|
|
54
|
+
ADD COLUMN IF NOT EXISTS reward_basis integer NOT NULL DEFAULT 0;
|
|
55
|
+
|
|
56
|
+
-- (b) Swap the uniqueness key. CREATE the replacement BEFORE dropping the original so
|
|
57
|
+
-- the double-pay backstop is never absent, not even inside this transaction.
|
|
58
|
+
--
|
|
59
|
+
-- For legacy rows (all at basis 0) the new index is exactly as strict as the old
|
|
60
|
+
-- one — (session_id, 0) is unique per session — so nothing already paid can be
|
|
61
|
+
-- paid twice.
|
|
62
|
+
CREATE UNIQUE INDEX IF NOT EXISTS uniq_credit_session_token_reward_basis
|
|
63
|
+
ON credit_log (session_id, reward_basis)
|
|
64
|
+
WHERE reason = 'session.token_reward';
|
|
65
|
+
|
|
66
|
+
DROP INDEX IF EXISTS uniq_credit_session_token_reward;
|
|
67
|
+
|
|
68
|
+
-- Counter hygiene: as a core migration this consumes core number 196 (next free after
|
|
69
|
+
-- core_195), so the core allocator floor advances with it.
|
|
@@ -8,10 +8,12 @@
|
|
|
8
8
|
// contributes.pollers:["routine-timers"]; the loader (src/module-loader/loader.js
|
|
9
9
|
// startModulePollers) requires THIS file and calls start(deps) at boot, stop() at
|
|
10
10
|
// shutdown — the module-agnostic successor to the hardcoded `routineTimers.start()`
|
|
11
|
-
// block server.js used to carry. server.js
|
|
12
|
-
//
|
|
13
|
-
//
|
|
14
|
-
//
|
|
11
|
+
// block server.js used to carry. src/platform-server.js is now the ONLY caller of
|
|
12
|
+
// startModulePollers (task 1002419) — server.js, the retired game/droplet
|
|
13
|
+
// entrypoint, was the previous one, and while it was gone NO module poller ran at
|
|
14
|
+
// all. It runs as a single process (docker-entrypoint.sh `exec`s it, no cluster),
|
|
15
|
+
// so a second always-on instance on the same DB still can't double-run — and the
|
|
16
|
+
// FOR UPDATE row lock in tallyPeerVotes is the hard guard if one ever does.
|
|
15
17
|
//
|
|
16
18
|
// WHY THIS EXISTS
|
|
17
19
|
// ADR 0078 demoted peer-vote-tally to `mode: deterministic` and built an EXTERNAL
|
|
@@ -100,10 +102,9 @@ async function runPeerVoteTally(deps = {}) {
|
|
|
100
102
|
|
|
101
103
|
// Start the timer(s). Idempotent. Kill-switched by ROUTINE_TIMERS_DISABLED=1.
|
|
102
104
|
// Unref'd so it never holds the process open on a systemd restart. Started by the
|
|
103
|
-
// loader's startModulePollers
|
|
104
|
-
//
|
|
105
|
-
//
|
|
106
|
-
// hard guard if one ever does).
|
|
105
|
+
// loader's startModulePollers from src/platform-server.js — the single always-on
|
|
106
|
+
// entrypoint (task 1002419) — so a second instance on the same DB can't double-run
|
|
107
|
+
// (and the FOR UPDATE row lock in tallyPeerVotes is the hard guard if one ever does).
|
|
107
108
|
function start(deps = {}) {
|
|
108
109
|
if (process.env.ROUTINE_TIMERS_DISABLED === '1') {
|
|
109
110
|
console.log('[routine-timers] disabled (ROUTINE_TIMERS_DISABLED=1)');
|
|
@@ -18,13 +18,14 @@
|
|
|
18
18
|
// blob; future behavior knobs add their own reserved keys. A missing key falls
|
|
19
19
|
// back to the rank-derived default below, so the column stays sparse.
|
|
20
20
|
//
|
|
21
|
-
// Resolution = default-by-
|
|
21
|
+
// Resolution = default-by-role, capped-by-role, server-enforced (see the ROLE
|
|
22
|
+
// METADATA block below for why a ceiling is metadata and not a permission).
|
|
22
23
|
// - DEFAULT level rides the trust ladder (Xenos→Locked … Archon→Exploratory).
|
|
23
|
-
// -
|
|
24
|
-
//
|
|
25
|
-
//
|
|
24
|
+
// - CEILING per role: Xenos and Thetes are capped at their default (they may
|
|
25
|
+
// dial DOWN for more focus, never UP); Metic and Archon may set any level
|
|
26
|
+
// (their rank is a starting default, not a ceiling). Owner decision
|
|
26
27
|
// 2026-06-19: "Thetes is gated; Metic is just a starting default."
|
|
27
|
-
// - effectiveLevel = clamp(override ?? default, 0,
|
|
28
|
+
// - effectiveLevel = clamp(override ?? default, 0, ceiling). So a Xenos who
|
|
28
29
|
// stores Exploratory is still resolved to Locked — opening the setting up
|
|
29
30
|
// for self-service later stays safe because the clamp is the enforcement,
|
|
30
31
|
// not the UI.
|
|
@@ -98,15 +99,54 @@ const LEVEL_CONTRACT = Object.freeze({
|
|
|
98
99
|
+ 'investigations worth the user\'s attention as you go, while still landing the claimed task.',
|
|
99
100
|
});
|
|
100
101
|
|
|
101
|
-
//
|
|
102
|
-
//
|
|
103
|
-
//
|
|
104
|
-
//
|
|
105
|
-
|
|
102
|
+
// ---- the wandering ceiling as ROLE METADATA (BV1.R107, ADR 0151) ----
|
|
103
|
+
//
|
|
104
|
+
// The governance model makes a permission a BOOLEAN atom and a rank a seeded
|
|
105
|
+
// ROLE. A wandering ceiling is neither: it is a NUMBER in 0..3. Spelling it as
|
|
106
|
+
// permissions would need one catalog key per level (`me.wandering.level.0..3`) —
|
|
107
|
+
// four keys no route consumes individually, against the catalog's first rule
|
|
108
|
+
// ("a permission no route consumes does not exist") and multiplying the key count
|
|
109
|
+
// to encode a quantity that was never a yes/no capability.
|
|
110
|
+
//
|
|
111
|
+
// So the ceiling is ROLE METADATA: a property carried BY the role, alongside —
|
|
112
|
+
// not inside — the permission that authorizes the write. The two compose:
|
|
113
|
+
// • the PERMISSION `me.wandering.set` (catalog: floor xenos, scope 'own',
|
|
114
|
+
// resource 'builder_prefs') answers MAY this builder set their OWN level. It
|
|
115
|
+
// is ownership-scoped, so it is answered by resolver.authorizeOwned(), never
|
|
116
|
+
// by an unconditional "do they hold it" — that is the route gate's business.
|
|
117
|
+
// • the ROLE METADATA below answers HOW HIGH they may set it. Holding the
|
|
118
|
+
// permission is never sufficient on its own: effectiveLevel() clamps to the
|
|
119
|
+
// role's ceiling server-side, so the ceiling is enforcement, not UI copy.
|
|
120
|
+
//
|
|
121
|
+
// Keyed by ROLE KEY. The four seeded rank-roles are named for the ranks they seed
|
|
122
|
+
// (governance_002), so a role key and a rank string coincide — which is why the
|
|
123
|
+
// rank-keyed helpers below still resolve correctly, and why BV1.R108 can fold this
|
|
124
|
+
// table onto the one live ladder without changing a value. A role this table does
|
|
125
|
+
// not name carries NO wandering metadata and resolves to the safest floor
|
|
126
|
+
// (Locked), the same fail-closed posture the resolver takes for a rank with no
|
|
127
|
+
// seeded role.
|
|
128
|
+
const ROLE_WANDERING = Object.freeze({
|
|
129
|
+
xenos: Object.freeze({ default: 0, ceiling: 0 }),
|
|
130
|
+
thetes: Object.freeze({ default: 1, ceiling: 1 }),
|
|
131
|
+
metic: Object.freeze({ default: 2, ceiling: 3 }),
|
|
132
|
+
archon: Object.freeze({ default: 3, ceiling: 3 }),
|
|
133
|
+
});
|
|
134
|
+
const NO_ROLE_WANDERING = Object.freeze({ default: 0, ceiling: 0 });
|
|
135
|
+
|
|
136
|
+
function roleWandering(roleKey) {
|
|
137
|
+
return Object.prototype.hasOwnProperty.call(ROLE_WANDERING, roleKey)
|
|
138
|
+
? ROLE_WANDERING[roleKey]
|
|
139
|
+
: NO_ROLE_WANDERING;
|
|
140
|
+
}
|
|
106
141
|
|
|
107
|
-
//
|
|
108
|
-
//
|
|
109
|
-
const
|
|
142
|
+
// DERIVED projections of the metadata above, kept because scripts/gds/session-eval.js
|
|
143
|
+
// and the /me payload read them by rank. Deriving them means the two views cannot drift.
|
|
144
|
+
const RANK_DEFAULT = Object.freeze(
|
|
145
|
+
Object.fromEntries(Object.entries(ROLE_WANDERING).map(([role, meta]) => [role, meta.default])),
|
|
146
|
+
);
|
|
147
|
+
const RANK_MAX = Object.freeze(
|
|
148
|
+
Object.fromEntries(Object.entries(ROLE_WANDERING).map(([role, meta]) => [role, meta.ceiling])),
|
|
149
|
+
);
|
|
110
150
|
|
|
111
151
|
const TOP_LEVEL = LEVELS.length - 1; // 3
|
|
112
152
|
const RESERVED_KEY = 'wandering';
|
|
@@ -120,9 +160,10 @@ for (const key of LEVELS) {
|
|
|
120
160
|
for (let i = 0; i < LEVELS.length; i++) {
|
|
121
161
|
if (LEVEL_META[LEVELS[i]].n !== i) throw new Error(`wandering-prefs: LEVEL_META.${LEVELS[i]}.n must equal its index ${i}`);
|
|
122
162
|
}
|
|
123
|
-
for (const
|
|
124
|
-
if (!(
|
|
125
|
-
if (
|
|
163
|
+
for (const [role, meta] of Object.entries(ROLE_WANDERING)) {
|
|
164
|
+
if (!isLevelInt(meta.default)) throw new Error(`wandering-prefs: role '${role}' has a non-level default`);
|
|
165
|
+
if (!isLevelInt(meta.ceiling)) throw new Error(`wandering-prefs: role '${role}' has a non-level ceiling`);
|
|
166
|
+
if (meta.default > meta.ceiling) throw new Error(`wandering-prefs: default > ceiling for role '${role}'`);
|
|
126
167
|
}
|
|
127
168
|
|
|
128
169
|
// ---- pure helpers ----
|
|
@@ -141,11 +182,11 @@ function levelIndex(key) {
|
|
|
141
182
|
}
|
|
142
183
|
|
|
143
184
|
function defaultLevelForRank(rank) {
|
|
144
|
-
return
|
|
185
|
+
return roleWandering(rank).default;
|
|
145
186
|
}
|
|
146
187
|
|
|
147
188
|
function maxLevelForRank(rank) {
|
|
148
|
-
return
|
|
189
|
+
return roleWandering(rank).ceiling;
|
|
149
190
|
}
|
|
150
191
|
|
|
151
192
|
// The override a builder has stored (the raw 'wandering' key off behavior_prefs),
|
|
@@ -262,6 +303,8 @@ module.exports = {
|
|
|
262
303
|
LEVELS,
|
|
263
304
|
LEVEL_META,
|
|
264
305
|
LEVEL_CONTRACT,
|
|
306
|
+
ROLE_WANDERING,
|
|
307
|
+
roleWandering,
|
|
265
308
|
RANK_DEFAULT,
|
|
266
309
|
RANK_MAX,
|
|
267
310
|
TOP_LEVEL,
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# OTB Dev Box — the desktop switch for a builder's hosted box
|
|
2
|
+
|
|
3
|
+
A menu-bar (macOS) / system-tray (Windows) app that turns a builder's hosted
|
|
4
|
+
dev box on and off with one switch. Built for the newcomer who should never
|
|
5
|
+
see a terminal: sign in with browser clicks (app pairing, ADR 0045), flip the
|
|
6
|
+
switch, and connect from Claude Desktop or the browser terminal.
|
|
7
|
+
|
|
8
|
+
What it does beyond the switch — each fixing a failure observed in the
|
|
9
|
+
2026-06-10 onboarding sessions that killed the old `curl | bash` flow:
|
|
10
|
+
|
|
11
|
+
- **Pairing sign-in** — opens the builders' hall, one Approve click, token
|
|
12
|
+
delivered over the app's own secret channel (`/api/gds/auth/app-pair/*`).
|
|
13
|
+
The session is written to `~/.config/otb/gds-session.json`, the same file
|
|
14
|
+
the `/builder-*` CLI uses — pairing the app signs in the CLI too.
|
|
15
|
+
- **SSH key done right** — generates `~/.ssh/otb_builder` (ed25519) with real
|
|
16
|
+
600 perms (icacls ACL on Windows) and registers it via `POST /box/ssh-key`.
|
|
17
|
+
- **known_hosts kept fresh** — Claude Desktop's embedded ssh2 reads ONLY
|
|
18
|
+
`~/.ssh/known_hosts`, and host keys change on every re-provision; the app
|
|
19
|
+
re-scans and replaces entries each time the box turns active.
|
|
20
|
+
- **Claude Desktop connection file** — installs the managed-settings file to
|
|
21
|
+
the system path with a native elevation prompt (osascript / UAC), the step
|
|
22
|
+
the agent-driven flow could never finish.
|
|
23
|
+
- **Native notifications** — box ready, rested while idle.
|
|
24
|
+
- **No heartbeats** — the app never sends `claude_active`; the droplet's idle
|
|
25
|
+
sweep stays authoritative so an unused box still reclaims itself (~10 min).
|
|
26
|
+
|
|
27
|
+
## Layout
|
|
28
|
+
|
|
29
|
+
- `src/main.js` — Electron main: tray, window, IPC, wiring.
|
|
30
|
+
- `src/brand.js` — every brand string (ADR 0004: the civ name lives here only).
|
|
31
|
+
- `src/{pairing,box-controller,gds-client,session-store,ssh-setup,updater}.js` — the
|
|
32
|
+
logic, Electron-free and unit-tested in `test/unit.mjs`.
|
|
33
|
+
- `src/updater.js` — in-app update check (#924): semver compare + platform→target
|
|
34
|
+
resolution. `main.js` polls `GET /downloads/devbox/version` on open + every 12h
|
|
35
|
+
and, when the published release is strictly newer, shows the Update banner; the
|
|
36
|
+
Update button downloads the installer and hands it to the OS (browser-download
|
|
37
|
+
fallback on any error). **Bumping `config/devbox-app.json` version is what makes
|
|
38
|
+
the banner appear in the field** — see [`docs/recipes/devbox-app-release.md`](../docs/recipes/devbox-app-release.md).
|
|
39
|
+
- `renderer/` — the panel UI (sandboxed, contextIsolated; preload exposes
|
|
40
|
+
named verbs only).
|
|
41
|
+
- `scripts/gen-icons.js` — regenerates all icons from pixel grids (`npm run
|
|
42
|
+
icons`; borrows `sharp` from the repo root's node_modules).
|
|
43
|
+
|
|
44
|
+
## Develop
|
|
45
|
+
|
|
46
|
+
```
|
|
47
|
+
cd devbox-app
|
|
48
|
+
npm install
|
|
49
|
+
npm test # hermetic unit tests, no Electron needed
|
|
50
|
+
npm start # run against prod (or OTB_DEVBOX_API_BASE=http://… npm start)
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Dev/test env hooks: `OTB_DEVBOX_API_BASE` (point at a stub/staging GDS),
|
|
54
|
+
`OTB_DEVBOX_NO_ELEVATE=1` (skip the managed-settings elevation prompt),
|
|
55
|
+
`OTB_DEVBOX_SCREENSHOT_DIR` (write a PNG of the panel on every state change —
|
|
56
|
+
how CI/verification captures proof).
|
|
57
|
+
|
|
58
|
+
## Build & release
|
|
59
|
+
|
|
60
|
+
```
|
|
61
|
+
npm run dist:mac # dmg, arm64 + x64 (unsigned → ad-hoc)
|
|
62
|
+
npm run dist:win # NSIS one-click installer, x64 + arm64
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
CI builds both on every push that touches `devbox-app/**`
|
|
66
|
+
(`.github/workflows/build-devbox-app.yml`) and uploads them as artifacts.
|
|
67
|
+
Publishing = attach the artifacts to a release on the PUBLIC releases repo
|
|
68
|
+
named in `config/devbox-app.json`, then bump that config and ship — the
|
|
69
|
+
branded `/downloads/devbox/*` redirects follow the config. Full recipe:
|
|
70
|
+
`docs/recipes/devbox-app-release.md`.
|
|
71
|
+
|
|
72
|
+
This subproject is self-contained on purpose: its `package.json` never feeds
|
|
73
|
+
the droplet's `npm ci`, and nothing under `devbox-app/` runs server-side.
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|