@bongos/core 1.18.18 → 1.19.2

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.
Files changed (251) hide show
  1. package/.bongos-core.json +515 -195
  2. package/.claude/skills/builder-box/SKILL.md +1 -1
  3. package/.claude/skills/builder-claim/SKILL.md +1 -1
  4. package/.claude/skills/goal-create/SKILL.md +1 -1
  5. package/.claude/skills/idea-triage/SKILL.md +1 -1
  6. package/.claude/skills/planning-session/SKILL.md +5 -5
  7. package/.claude/skills/scan-before-install/SKILL.md +165 -0
  8. package/clients/bongos-client/README.md +1 -1
  9. package/clients/bongos-client/bongos-client.global.js +63 -41
  10. package/clients/bongos-client/index.cjs +63 -41
  11. package/clients/bongos-client/index.d.ts +78 -44
  12. package/clients/bongos-client/index.mjs +63 -41
  13. package/docs/adr/0020-security-model-public-stub.md +1 -1
  14. package/docs/adr/0097-one-active-claim-per-session-and-worktree-binding.md +2 -0
  15. package/docs/adr/0144-devbox-rehome-onto-cloudbongos-plane.md +1 -1
  16. package/docs/adr/0151-governance-permissions-as-atom-ranks-as-roles.md +2 -1
  17. package/docs/adr/0155-adopt-private-repo-widen-oauth-scope.md +51 -0
  18. package/docs/adr/0156-user-set-display-name-wins.md +62 -0
  19. package/docs/adr/0157-archon-is-rank-and-identity-only.md +93 -0
  20. package/docs/adr/0158-choke-point-verdict-taxonomy.md +174 -0
  21. package/docs/adr/0159-merge-gate-reconciliation.md +88 -0
  22. package/docs/adr/0160-main-audit-repo-verifiable-caller.md +77 -0
  23. package/docs/adr/README.md +8 -0
  24. package/docs/api/openapi.json +1209 -233
  25. package/docs/api-reference.md +61 -46
  26. package/docs/architecture.md +1 -1
  27. package/docs/canonical-permissions.md +61 -16
  28. package/docs/design/vanilla-hall-ui-redesign-scope.md +1 -1
  29. package/docs/file-map.md +4 -1
  30. package/docs/onboarding/diagrams/assertions.json +3 -3
  31. package/docs/recipes/core-release-pipeline.md +179 -0
  32. package/docs/recipes/packaging-the-core.md +2 -0
  33. package/docs/recipes/parallel-goal-waves.md +1 -3
  34. package/docs/recipes/private-npm-distribution.md +2 -0
  35. package/docs/recipes/upgrading-the-core.md +2 -0
  36. package/migrations/core_197_display_name_source.sql +56 -0
  37. package/migrations/core_204_rekey_active_claim_per_session_worktree.sql +77 -0
  38. package/modules/autonomy/routes/autonomy.js +1 -1
  39. package/modules/builder-settings/db.js +7 -1
  40. package/modules/dev-box/app/src/vendor/bongos-client.cjs +63 -41
  41. package/modules/dev-box/routes/box.js +11 -9
  42. package/modules/discord/auth.js +3 -1
  43. package/modules/discord/routes/discord.js +8 -2
  44. package/modules/discord/ship-broadcast.js +48 -1
  45. package/modules/governance/catalog.js +70 -20
  46. package/modules/governance/db.js +102 -0
  47. package/modules/governance/migrations/governance_006_version_create.sql +38 -0
  48. package/modules/governance/migrations/governance_007_delegate_archon_ops_to_metic.sql +72 -0
  49. package/modules/governance/routes/governance.js +235 -6
  50. package/modules/grading/grader-workers/index.js +18 -9
  51. package/modules/hall-ui/public/gate.js +6 -2
  52. package/modules/hall-ui/public/goals-page.js +6 -2
  53. package/modules/hall-ui/public/governance.css +100 -0
  54. package/modules/hall-ui/public/governance.html +77 -0
  55. package/modules/hall-ui/public/governance.js +353 -0
  56. package/modules/hall-ui/public/harbor.js +14 -8
  57. package/modules/hall-ui/public/instances-panel.js +3 -1
  58. package/modules/hall-ui/public/new-project-wizard.js +6 -0
  59. package/modules/hall-ui/public/people.html +51 -0
  60. package/modules/hall-ui/public/people.js +100 -0
  61. package/modules/hall-ui/public/profile.html +2 -1
  62. package/modules/hall-ui/public/profile.js +9 -3
  63. package/modules/hall-ui/public/sessions.js +4 -2
  64. package/modules/hall-ui/public/shell.js +28 -7
  65. package/modules/hall-ui/public/task-detail.js +6 -3
  66. package/modules/hall-ui/public/watch.js +17 -9
  67. package/modules/ideas/blockers.js +44 -7
  68. package/modules/ideas/routes/blockers.js +12 -7
  69. package/modules/ideas/routes/inbox.js +2 -2
  70. package/modules/lifecycle/db.js +84 -17
  71. package/modules/lifecycle/github-push.js +37 -10
  72. package/modules/lifecycle/publish-reconciler.js +128 -31
  73. package/modules/lifecycle/routes/claims.js +10 -4
  74. package/modules/lifecycle/routes/dependencies.js +2 -2
  75. package/modules/lifecycle/routes/done-when.js +4 -4
  76. package/modules/lifecycle/routes/gate-approvals.js +9 -3
  77. package/modules/lifecycle/routes/goals.js +5 -5
  78. package/modules/lifecycle/routes/tasks.js +16 -16
  79. package/modules/lifecycle/routes/versions.js +13 -2
  80. package/modules/lifecycle/ship-card.js +38 -7
  81. package/modules/memory/routes/memory.js +2 -2
  82. package/modules/onboarding/routes/access-requests.js +3 -3
  83. package/modules/platform-identity/routes/projects.js +10 -6
  84. package/modules/platform-identity/routes/scouting.js +2 -2
  85. package/modules/provisioning/migrations/provisioning_004_onboard_mode.sql +30 -0
  86. package/modules/provisioning/provisioning.js +24 -6
  87. package/modules/provisioning/routes/provisioning.js +24 -6
  88. package/modules/security/artifact-scan/aggregate.js +133 -0
  89. package/modules/security/artifact-scan/detectors/capabilities.js +53 -0
  90. package/modules/security/artifact-scan/detectors/config-obfuscation.js +41 -0
  91. package/modules/security/artifact-scan/detectors/credentials.js +112 -0
  92. package/modules/security/artifact-scan/detectors/dependencies.js +115 -0
  93. package/modules/security/artifact-scan/detectors/hooks.js +116 -0
  94. package/modules/security/artifact-scan/detectors/injection.js +118 -0
  95. package/modules/security/artifact-scan/detectors/mcp.js +102 -0
  96. package/modules/security/artifact-scan/detectors/network.js +67 -0
  97. package/modules/security/artifact-scan/detectors/npm-lifecycle.js +67 -0
  98. package/modules/security/artifact-scan/detectors/permissions.js +91 -0
  99. package/modules/security/artifact-scan/detectors/shared.js +219 -0
  100. package/modules/security/artifact-scan/fence-local.js +99 -0
  101. package/modules/security/artifact-scan/fetch.js +330 -0
  102. package/modules/security/artifact-scan/floor.js +319 -0
  103. package/modules/security/artifact-scan/limits.js +66 -0
  104. package/modules/security/artifact-scan/manifest.js +203 -0
  105. package/modules/security/artifact-scan/mirror.js +158 -0
  106. package/modules/security/artifact-scan/quarantine.js +149 -0
  107. package/modules/security/artifact-scan/rebuild-spec.js +120 -0
  108. package/modules/security/artifact-scan/report.js +155 -0
  109. package/modules/security/artifact-scan/sanitize.js +317 -0
  110. package/modules/security/migrations/security_001_tool_scans.sql +145 -0
  111. package/modules/security/module.json +2 -2
  112. package/modules/security/routes/reports.js +11 -7
  113. package/modules/security/routes/scans.js +115 -0
  114. package/modules/security/scan-store.js +214 -0
  115. package/modules/security/tests/aggregate.mjs +283 -0
  116. package/modules/security/tests/artifact_scan.mjs +591 -0
  117. package/modules/security/tests/floor.mjs +549 -0
  118. package/modules/security/tests/mirror.mjs +350 -0
  119. package/modules/security/tests/sanitize_rebuild.mjs +499 -0
  120. package/modules/security/tests/scan_store.mjs +250 -0
  121. package/modules/security/tests/tool_scans_migration.mjs +116 -0
  122. package/modules/sessions/routes/sessions.js +6 -5
  123. package/package-lock.json +2 -2
  124. package/package.json +1 -1
  125. package/public-landing/create.html +13 -3
  126. package/scripts/gds/abandon.js +1 -1
  127. package/scripts/gds/audit-authorship.sh +6 -0
  128. package/scripts/gds/box.js +1 -1
  129. package/scripts/gds/claim.js +15 -0
  130. package/scripts/gds/cli-lib.js +16 -4
  131. package/scripts/gds/gating.js +28 -13
  132. package/scripts/gds/gen-api-docs.js +1 -1
  133. package/scripts/gds/goal.js +234 -0
  134. package/scripts/gds/init.js +70 -15
  135. package/scripts/gds/main-audit.js +80 -11
  136. package/scripts/gds/mirror-redact.js +6 -0
  137. package/scripts/gds/newcomer-floor.js +2 -2
  138. package/scripts/gds/provision.js +230 -94
  139. package/scripts/gds/run-unit-tests.js +9 -1
  140. package/scripts/gds/sandbox-stage.js +66 -9
  141. package/scripts/gds/search-ingest-md.js +1 -0
  142. package/scripts/gds/sequence.js +67 -8
  143. package/scripts/gds/ship.js +183 -50
  144. package/scripts/hall-preview/server.js +1 -1
  145. package/scripts/migrate.sh +99 -14
  146. package/src/bongos/auth.js +129 -27
  147. package/src/bongos/db-kernel.js +66 -5
  148. package/src/bongos/db.js +6 -7
  149. package/src/bongos/github-repos.js +19 -7
  150. package/src/bongos/goal-scope-check.js +1 -1
  151. package/src/bongos/module-scope-map.js +3 -1
  152. package/src/bongos/permission-path-check.js +1 -1
  153. package/src/bongos/route-rank-check.js +73 -30
  154. package/src/bongos/routes/audit-log.js +4 -2
  155. package/src/bongos/routes/auth.js +8 -4
  156. package/src/bongos/routes/backup.js +7 -4
  157. package/src/bongos/routes/builders.js +23 -11
  158. package/src/bongos/routes/github.js +15 -4
  159. package/src/bongos/routes/llm-cache.js +5 -2
  160. package/src/bongos/routes/modules.js +9 -5
  161. package/src/bongos/routes/security.js +21 -12
  162. package/src/bongos/serve-internal.js +22 -7
  163. package/src/module-api.js +86 -1
  164. package/src/platform-server.js +14 -0
  165. package/tests/api_client.mjs +18 -2
  166. package/tests/api_path_404.mjs +3 -0
  167. package/tests/art_stage.mjs +2 -1
  168. package/tests/atlas.mjs +32 -4
  169. package/tests/auth_admission_gate.mjs +17 -5
  170. package/tests/auth_error_brand.mjs +41 -21
  171. package/tests/auth_resolve_failure.mjs +155 -0
  172. package/tests/blocker_link.mjs +88 -0
  173. package/tests/bongos_downloads.mjs +2 -1
  174. package/tests/box_dns_repoint.mjs +2 -0
  175. package/tests/box_ship_permissions.mjs +2 -0
  176. package/tests/box_terminal_ssrf.mjs +23 -17
  177. package/tests/claim_error_surface.mjs +23 -0
  178. package/tests/claude_materialize.mjs +4 -0
  179. package/tests/cli_box_scope.mjs +40 -0
  180. package/tests/cloud_bongos_pack.mjs +34 -4
  181. package/tests/conductor.mjs +2 -1
  182. package/tests/criterion_authoring_gate.mjs +28 -5
  183. package/tests/currency_label.mjs +10 -1
  184. package/tests/deploy_instance.mjs +2 -0
  185. package/tests/devbox_downloads.mjs +2 -1
  186. package/tests/display_name.mjs +95 -6
  187. package/tests/eviction_policy.mjs +2 -1
  188. package/tests/file_map.mjs +10 -0
  189. package/tests/first_admin_bootstrap.mjs +10 -2
  190. package/tests/first_run_engine_check.mjs +2 -1
  191. package/tests/fitness.mjs +26 -6
  192. package/tests/gate_approvals.mjs +15 -2
  193. package/tests/github_push_land_proof.mjs +50 -4
  194. package/tests/github_repos_helpers.mjs +30 -5
  195. package/tests/github_token_store.mjs +23 -0
  196. package/tests/goal_abuse_matrix.mjs +13 -3
  197. package/tests/goal_cli.mjs +89 -0
  198. package/tests/governance_abuse_matrix.mjs +386 -0
  199. package/tests/governance_fold.mjs +156 -0
  200. package/tests/governance_fold_bench.mjs +50 -0
  201. package/tests/governance_live_proof.mjs +201 -0
  202. package/tests/governance_page_gates.mjs +21 -9
  203. package/tests/governance_parity.mjs +11 -2
  204. package/tests/governance_require_permission.mjs +125 -1
  205. package/tests/governance_routes.mjs +334 -0
  206. package/tests/grader.mjs +7 -3
  207. package/tests/hall_nav.mjs +14 -4
  208. package/tests/helpers.mjs +73 -0
  209. package/tests/hook_core_resolve.mjs +2 -1
  210. package/tests/host_topology_skips.mjs +97 -0
  211. package/tests/infra_exec_paths.mjs +14 -4
  212. package/tests/init.mjs +128 -0
  213. package/tests/known_failing_ratchet.mjs +89 -0
  214. package/tests/lifecycle-card.mjs +2 -1
  215. package/tests/main_audit.mjs +72 -1
  216. package/tests/memory_isolation.mjs +56 -9
  217. package/tests/migrate_root_resolution.mjs +124 -1
  218. package/tests/mirror_redact.mjs +19 -0
  219. package/tests/module_api.mjs +3 -1
  220. package/tests/module_loader.mjs +4 -0
  221. package/tests/modules.mjs +35 -7
  222. package/tests/modules_route.mjs +29 -5
  223. package/tests/onboarding_observability.mjs +49 -11
  224. package/tests/onboarding_route_signals.mjs +1 -0
  225. package/tests/one_active_claim_per_session.mjs +80 -0
  226. package/tests/pre_compact.mjs +2 -1
  227. package/tests/preview_server.mjs +2 -1
  228. package/tests/profile_route.mjs +1 -0
  229. package/tests/provision.mjs +170 -0
  230. package/tests/provisioning_onboard_mode.mjs +180 -0
  231. package/tests/publish_manifest.mjs +16 -3
  232. package/tests/publish_reconciler.mjs +181 -10
  233. package/tests/rank_tier_single_source.mjs +205 -0
  234. package/tests/recall_hints.mjs +2 -1
  235. package/tests/repo_map.mjs +4 -0
  236. package/tests/route_permission_check.mjs +14 -5
  237. package/tests/rubric_fixtures.mjs +3 -0
  238. package/tests/run_routine_two_root.mjs +2 -0
  239. package/tests/sandbox_stage.mjs +69 -7
  240. package/tests/search_ingest.mjs +7 -0
  241. package/tests/security_docs.mjs +28 -6
  242. package/tests/sequence.mjs +64 -0
  243. package/tests/session-cards.mjs +2 -1
  244. package/tests/session_start_freshness.mjs +2 -1
  245. package/tests/ship_broadcast_server.mjs +63 -1
  246. package/tests/ship_card.mjs +66 -0
  247. package/tests/ship_ci_deploy.mjs +66 -1
  248. package/tests/ship_smoke_gate.mjs +76 -0
  249. package/tests/versions_route.mjs +15 -0
  250. package/tests/workflow_exec_paths.mjs +16 -4
  251. package/tests/worktree_guard.mjs +2 -1
@@ -0,0 +1,179 @@
1
+ # Recipe — the core release pipeline: how a change to Cloud Bongos reaches a live instance
2
+
3
+ > **The map.** Three sibling recipes each cover one leg — [packaging-the-core](packaging-the-core.md) (build the artifact), [private-npm-distribution](private-npm-distribution.md) (publish it), [upgrading-the-core](upgrading-the-core.md) (consume it). This file is the thing that connects them, and it exists to state one fact loudly:
4
+ >
5
+ > ## **Merging to `main` ships nothing.**
6
+ >
7
+ > A change that has landed on `main` is running on **zero** instances. It reaches users only after someone bumps `CORE_VERSION`, cuts an artifact, and moves an instance's pin. Those are three separate acts by design ([ADR 0137](../adr/<redacted>.md)) — and every layer around them reports success in the meantime.
8
+
9
+ ## Why this file exists
10
+
11
+ Between 2026-07-24 and 2026-08-03, **93 work commits (171 counting merges) — including the entire governance permission system — sat on `main` unreleased for ten days** while the live platform served the previous version. Nothing was broken. `/builder-ship` printed success, the publish-reconciler flipped `confirmed → shipped`, credits paid out. Every signal a builder could see said "delivered"; nothing compared live against `main` (blocker 1000097, task [1002521](https://example.com/builders#/task/1002521)).
12
+
13
+ A second failure followed from the same gap: a builder filed a careful, well-evidenced "prod has been serving stale code for days" report whose central measurement was taken against **the wrong instance**, and which prescribed the npm `--registry` upgrade path for an instance that pins a **vendored tarball** — where it would have moved nothing at all.
14
+
15
+ Both were documentation failures, not code failures. This is the missing document.
16
+
17
+ ---
18
+
19
+ ## The three gates
20
+
21
+ A change crosses three independent gates. **None of them implies the next**, and each has its own actor, action, and proof.
22
+
23
+ | # | Gate | What it means | Who | Proof it happened |
24
+ |---|---|---|---|---|
25
+ | **0** | **Included** | your change is inside the publishable subtree at all | the allowlist | `isPublishable('<path>')` → `true` |
26
+ | **1** | **Landed** | the commit is on core `main` | any builder, via `/builder-ship` | task status `shipped` |
27
+ | **2** | **Released** | a `CORE_VERSION` exists that *contains* the commit, and an artifact was cut from it | a builder bumps + packages; **the owner publishes** | the version is published / a `.tgz` exists |
28
+ | **3** | **Deployed** | a specific instance moved its pin to that version | per instance | that instance's `/version` reports the new `coreVersion` |
29
+
30
+ The GDS vocabulary collides with gate 1 in a way worth naming: **task status `shipped` means "merged to `main`"** — it does not mean released, and it does not mean live. `ship.js` says so in its own comment and, since task 1002521, prints a divergence warning at the end of every ship.
31
+
32
+ ---
33
+
34
+ ## Gate 0 — does your change ship at all?
35
+
36
+ The core is not "this repo." It is exactly the subtree [`isPublishable()`](../../scripts/gds/publish-manifest.js) selects ([ADR 0098](../adr/<redacted>.md)) — a **default-deny, two-layer** gate:
37
+
38
+ ```
39
+ isPublishable(p) == matchesDeny(p) ? false : matchesAllow(p)
40
+ ```
41
+
42
+ A path publishes only if it falls in `PUBLISH_ALLOWLIST` **and** escapes `PUBLISH_DENYLIST`. A new, unclassified top-level surface therefore never leaks by omission — it is simply not published.
43
+
44
+ **A change under a denied path reaches no instance at any version.** The four denial rationales:
45
+
46
+ | Rationale | Examples |
47
+ |---|---|
48
+ | **Topology** — prod infra, deploy machinery, CI, local gate hooks | `infra/`, `scripts/deploy/`, `.github/`, `.claude/hooks/` |
49
+ | **Read-sensitive docs** — the security/audit corpus | `docs/security/`, `docs/audits/` |
50
+ | **Content / copyright** — the instance's product layer and all art | `modules/game/`, `modules/art-pipeline/`, `public/`, `brand/` |
51
+ | **Host-module tests** — tests that statically import an excluded module | see the list in `publish-manifest.js` |
52
+
53
+ Note the deliberate non-exclusion: the rank / trust-boundary / ship-grade machinery **is published** — it is the open-source methodology ([ADR 0065 §4](../adr/<redacted>.md)). Only the *topology* subset of the protected surfaces is held back.
54
+
55
+ Check a path before you assume:
56
+
57
+ ```bash
58
+ node -e "console.log(require('./scripts/gds/publish-manifest').isPublishable('docs/recipes/core-release-pipeline.md'))"
59
+ ```
60
+
61
+ ## Gate 1 — land it on `main`
62
+
63
+ The ordinary claim→ship loop: `/builder-claim N` → build → `/builder-ship`. That chains `completed` → `confirmed` → `shipped`, and credits land at `shipped` ([ADR 0120](../adr/<redacted>.md)).
64
+
65
+ Landing is CI-gated (tasks 1002610/1002611/1002619, ADR 0159): every PR runs the **`unit`** check — the DB-free suite plus the fitness gates, via `npm test` — and branch protection **requires** it, so a red suite blocks the merge. PRs touching the hard-floor safety surfaces ([`CODEOWNERS`](../../CODEOWNERS)) additionally need the owner's review. The owner kill-switch is `node scripts/gds/gating.js on|off|status` — reversible by construction, snapshot-restored.
66
+
67
+ **Stop here and nothing happens to any user.** This is the gate that feels like the finish line and is not.
68
+
69
+ ## Gate 2 — cut a release
70
+
71
+ Three steps. The first two are ordinary claimed work; only the publish (2c) is owner-gated — and 2c is skipped entirely for a vendored-tarball instance.
72
+
73
+ ### 2a. Bump `CORE_VERSION` — the step with no automation behind it
74
+
75
+ `CORE_VERSION` lives in [`src/module-api.js`](../../src/module-api.js) and is bumped **by hand, inside a claimed task**, with a changelog line in the same file. It is never auto-bumped: the version's provenance stays in git history + changelog ([ADR 0137](../adr/<redacted>.md) §2).
76
+
77
+ Semver here is not decoration — it decides **who receives the change automatically** ([ADR 0136](../adr/<redacted>.md)):
78
+
79
+ | Bump | Reaches `patch` subscribers (the default) | Reaches `minor` subscribers |
80
+ |---|---|---|
81
+ | **patch** — a fix; the doorway surface is byte-identical | ✅ | ✅ |
82
+ | **minor** — an added export / additive surface | ❌ **invisible to them** | ✅ |
83
+ | **major** — never automatic for anyone | ❌ | ❌ |
84
+
85
+ The practical trap: a **MINOR is silently withheld from every patch-only instance**, which is the default channel. If a fix must reach the fleet, it belongs behind a *patch* bump — so an additive export shipped alongside a needed fix will strand that fix. The changelog convention is to justify the level explicitly ("the doorway surface is byte-identical to X, so despite the volume this is → PATCH").
86
+
87
+ **A bump is mandatory even for a one-file fix.** `bongos upgrade` refuses an equal version, and npm rejects a duplicate — a natural guard, and the reason "carrier bumps" exist to sweep up commits that landed with no bump behind them.
88
+
89
+ ### 2b. Build the artifact
90
+
91
+ ```bash
92
+ node scripts/gds/package-core.js --ref main # → dist/bongos-core-<v>.tgz + .manifest.json
93
+ node scripts/gds/package-core.js --ref main --self-verify
94
+ ```
95
+
96
+ `package-core` archives the **committed** tree at `--ref`, never your working tree, and stamps the version from `CORE_VERSION` *at that ref*. Full mechanics — the fail-closed no-leak gate, the `tree_sha256` integrity pin, the pruned lockfile — in [packaging-the-core](packaging-the-core.md).
97
+
98
+ > ⚠️ **Packaging a mid-flight `main` produces a stale version number.** If `CORE_VERSION` at your ref is still the previous value, your tarball is stamped with a version the fleet may already have. `bongos upgrade` refuses only the **equal** case (`already on <v>`, and `--force` bypasses even that) — it has **no downgrade guard**, so a target *below* what is installed proceeds and silently installs older code. Confirm the bump is actually in your ref before packaging.
99
+
100
+ ### 2c. Publish — only for the registry path
101
+
102
+ Publishing to npm is a **manual, owner-gated step**; merges do not auto-publish and **no npm write token exists in CI or the repo** ([ADR 0137](../adr/<redacted>.md) §3).
103
+
104
+ ```bash
105
+ node scripts/gds/package-core.js --publish # DRY-RUN — runs the no-leak gate, previews
106
+ node scripts/gds/package-core.js --publish --live # permanent
107
+ ```
108
+
109
+ **`--publish` is not needed for a vendored-tarball instance** — see gate 3. Chasing an npm publish to move a `file:`-pinned instance is a documented dead end.
110
+
111
+ ## Gate 3 — move the instance's pin
112
+
113
+ Each instance decides, independently, when to take a release. **Which mechanism it uses is a per-instance fact you must establish before prescribing anything** — read its `package.json`:
114
+
115
+ | Pin in `package.json` | Path | How it updates |
116
+ |---|---|---|
117
+ | `"@bongos/core": "file:vendor/bongos-core-<v>.tgz"` | **vendored tarball** | ship the `.tgz` to the instance and run `bongos upgrade --from <tgz>`; npm's published version is irrelevant |
118
+ | `"@bongos/core": "^<v>"` | **npm registry** | `bongos upgrade --to <v> --registry`; requires the version to be published and a token to read it |
119
+
120
+ The mechanics of the bump itself — pre-flight, pin-verify, migrate, `.claude/` materialize, restart, the mandatory health check, and auto-rollback — are in [upgrading-the-core](upgrading-the-core.md). Two portable traps that bite hardest here:
121
+
122
+ - **`PGDATABASE` is load-bearing.** `migrate.sh` has a default; on a co-tenant host that default is *someone else's database*. Always set `PGDATABASE=<instance-db>` explicitly or you migrate the wrong DB (the usual outcome is a clean auto-rollback, which then reads as "the upgrade failed").
123
+ - **Never hand-edit `package-lock.json`'s `@bongos/core` entry.** Editing `version` + `resolved` while leaving `integrity` at the old tarball's hash makes `npm ci` resolve the *old* content from the content-addressable cache — the deploy reports healthy and runs old code. Run `npm install file:vendor/bongos-core-<v>.tgz` instead, and verify after: `node -e "console.log(require('./node_modules/@bongos/core/package.json').version)"`.
124
+
125
+ Also: an instance can have **more than one plane to update** (e.g. a web instance plus a control plane that resolves its tooling from its own vendored core). Updating one does not update the other.
126
+
127
+ ---
128
+
129
+ ## Detecting divergence
130
+
131
+ [`scripts/gds/deploy-divergence.js`](../../scripts/gds/deploy-divergence.js) is the comparison that was missing during the ten-day incident. It runs **warn-only at the end of every ship** and can be run by hand any time:
132
+
133
+ ```bash
134
+ node scripts/gds/deploy-divergence.js --url https://<instance-host>
135
+ ```
136
+
137
+ It reports the two gaps **separately**, because they have different fixes:
138
+
139
+ - **UNRELEASED** — commits on `main` past the last `CORE_VERSION` bump. *No instance can take these at any pin* — no version contains them. Fix: bump + land (gate 2a).
140
+ - **UNDEPLOYED** — the live instance's `coreVersion` is behind `main`'s. A release exists; this instance has not moved its pin. Fix: gate 3.
141
+
142
+ Exit codes: `0` aligned, `1` diverged, `2` undetermined.
143
+
144
+ > ### ⚠️ Do not trust its numbers without checking the anchor
145
+ >
146
+ > The tool has a **known anchor defect** — reproduced live on 2026-08-08, where it named `1.18.19` as the last bump while printing `origin/main 1.19.0` two lines above, then counted 70 UNRELEASED commits from that wrong anchor.
147
+ >
148
+ > The mechanism is one compound failure. It locates the bump with `git log <ref> -1 -G"^const CORE_VERSION = "`. `git log` shows no diff for merge commits, so `-G` **cannot match the merge** that lands a bump on `main` — the only candidate is the original commit on the branch, carrying that branch's earlier **commit date**. `-1` then picks the newest *by date*, so a bump authored earlier on a long-lived branch loses to a later-authored one, even when the earlier one is the higher version. Every count downstream inherits the wrong anchor.
149
+ >
150
+ > Separately and by design, it measures from the last *bump commit*, not from what was actually **packaged** — so work landing between the bump and `package-core` reads as UNRELEASED even though the tarball contains it. Comparing against the `core-v*` release tag would be more accurate.
151
+ >
152
+ > Treat a divergence warning as **"look closer"**, never as a precise commit count. The direction is reliable; the arithmetic is not.
153
+
154
+ **Before acting on any "prod is stale" report, establish which instance was measured.** `curl` the `/version` endpoint of each candidate host yourself. Instances that serve different products are pinned at different versions *correctly* — a game-only instance needs no platform release. A report that names the wrong host will read as authoritative regardless of how carefully it was written.
155
+
156
+ ---
157
+
158
+ ## Who can do what
159
+
160
+ | Act | Gate | Who |
161
+ |---|---|---|
162
+ | Land a change on `main` | 1 | any builder holding a claim, behind the required `unit` check (protected paths are Metic+; hard-floor paths need owner review per [`CODEOWNERS`](../../CODEOWNERS); [canonical-permissions](../canonical-permissions.md)) |
163
+ | Bump `CORE_VERSION` | 2a | a builder, inside a claimed task |
164
+ | Build the artifact | 2b | any builder — `package-core` is tokenless and network-free |
165
+ | Publish to npm | 2c | **owner only** — needs `npm login` + 2FA; no write token exists anywhere else |
166
+ | Move an instance's pin | 3 | whoever operates that instance |
167
+
168
+ Automating gate 2c (a tag-triggered CI publish) is deliberately deferred until a real fleet makes the manual step a bottleneck — see [ADR 0137](../adr/<redacted>.md) "When to revisit".
169
+
170
+ ## Where to go next
171
+
172
+ | You want to… | Read |
173
+ |---|---|
174
+ | build / verify / inspect the artifact | [packaging-the-core](packaging-the-core.md) |
175
+ | publish it, or set up private npm access | [private-npm-distribution](private-npm-distribution.md) |
176
+ | move an instance from N → N+1, or subscribe it to updates | [upgrading-the-core](upgrading-the-core.md) |
177
+ | know *why* publishing is manual | [ADR 0137](../adr/<redacted>.md) |
178
+ | know *what* an unattended upgrade is allowed to take | [ADR 0136](../adr/<redacted>.md) |
179
+ | know what counts as "the core" | [ADR 0098](../adr/<redacted>.md) · [ADR 0103](../adr/<redacted>.md) |
@@ -1,5 +1,7 @@
1
1
  # Recipe — packaging the Cloud Bongos core as a pinned, installable artifact
2
2
 
3
+ > **This is one leg of three.** For how a change to Cloud Bongos actually reaches a live instance — and why merging to `main` ships nothing on its own — start at [the core release pipeline](core-release-pipeline.md).
4
+ >
3
5
  > Implements [ADR 0100 §1](../adr/<redacted>.md) (the two-repo instance model). This is **R84**, the first step of the `R84 → R85 → R86` spine: package the core → stand a downstream instance up on a pinned core → make `bongos upgrade` a real update channel. Tooling: [`scripts/gds/package-core.js`](../../scripts/gds/package-core.js) (`bongos package-core`).
4
6
  >
5
7
  > **Real installable npm package ([ADR 0108](../adr/<redacted>.md) §2, task 1882/W2, shipped):** the artifact is `package/`-rooted (the npm convention `npm install ./x.tgz` requires) and carries a synthesized `package.json` — `name: "@bongos/core"`, `version` = `CORE_VERSION`, `main: "src/platform-server.js"`, `exports` for the default entry (`buildPlatformApp`) plus `./serve-internal` (`mountInternalSurfaces`) and `./module-api`, `bin: "bongos"` — and a **pruned** `package-lock.json` (express + pg's own transitive closure only, resolved offline from the source lockfile — no colyseus/phaser/sharp/discord.js). Both files are synthesized fresh, never copied verbatim from the source repo's game-heavy `package.json`. `resolveCoreRoot()`/`resolveInstanceRoot()` ([`src/instance-config.js`](../../src/instance-config.js), W1/task 1881) are what a consumer's own code routes through once installed.
@@ -59,9 +59,7 @@ Worked example from goal 1000030:
59
59
 
60
60
  ## 3. Claiming a parallel set
61
61
 
62
- `claim.js` **cannot hold concurrent claims from one machine.** The app-layer guard permits one claim per *worktree*, but `uniq_active_claim_per_session` (`migrations/180`) is keyed on `creator_session_id` alone — worktree-blind — and the stricter of the two wins. Every worktree on a box shares one CLI session token, so the second `claim.js` fails.
63
-
64
- Worse, it fails with a **misleading** message: `claimTask` maps any Postgres `23505` to `ALREADY_CLAIMED`, so you are told *"Task #N was just claimed by another session"* when nothing is claimed and no retry will ever succeed. Two separate agents burned a full investigation each on this before the cause was found. (Filed as idea 1000617.)
62
+ Concurrent bound claims from one machine **used to be impossible**: the app-layer guard permits one claim per *worktree*, but migration 180's `uniq_active_claim_per_session` was keyed on `creator_session_id` alone — worktree-blind — and the stricter of the two won, so the second `claim.js` failed. Worse, it failed with a misleading message: `claimTask` mapped any Postgres `23505` to `ALREADY_CLAIMED` — *"Task #N was just claimed by another session"* when nothing was claimed (filed as idea 1000617; two agents burned a full investigation each). Both halves are fixed now: task 1002589 made the refusal honest (`session_already_has_bound_claim`, naming the in-flight claim), and task 1002570 (`core_204`) re-keyed the index to `(session, worktree)` — **one bound claim per worktree just works**, matching the guard. The batch endpoint below remains a fine way to claim a whole wave in one call.
65
63
 
66
64
  **Use the batch endpoint**, which was built for exactly this:
67
65
 
@@ -1,5 +1,7 @@
1
1
  # Recipe — private-first npm distribution (core + client)
2
2
 
3
+ > **This is one leg of three.** For the whole path from a change on core `main` to a live instance — and for when publishing to npm is *not* what moves an instance — start at [the core release pipeline](core-release-pipeline.md).
4
+ >
3
5
  > Implements [ADR 0134](../adr/<redacted>.md). Pre-launch, `@bongos/core` and `@bongos/client` publish as **private, scoped** npm packages on the owner's paid `bongos` npm org (the canonical brand); at launch they flip to public. The daily command is `bongos` regardless of package name.
4
6
  >
5
7
  > **Why private must be scoped:** on npm, *unscoped* names are always public — only *scoped* names (`@bongos/…`) can be private, and only on a paid plan. So the packages publish under the `@bongos` scope, which maps to the paid `bongos` org.
@@ -1,5 +1,7 @@
1
1
  # Recipe — upgrading a consumer instance's Cloud Bongos core (`bongos upgrade`)
2
2
 
3
+ > **This is one leg of three.** For the whole path from a change on core `main` to a live instance — including how to tell whether *this* instance takes a vendored tarball or a registry version — start at [the core release pipeline](core-release-pipeline.md).
4
+ >
3
5
  > The R86 update-consumption channel ([ADR 0100](../adr/<redacted>.md) §2, [ADR 0103](../adr/<redacted>.md) §4, task [#1690](https://example.com/builders#/task/1690)). How a **two-repo consumer instance** — one that installs `@bongos/core` as a vendored-tarball dependency — pulls the core from version N → N+1. Tooling: [`scripts/gds/upgrade.js`](../../scripts/gds/upgrade.js) (`bongos upgrade`). First proven live on emersonian-circles 1.15.0 → 1.16.0 (task [#1976](https://example.com/builders#/task/1976)).
4
6
 
5
7
  ## What the channel does
@@ -0,0 +1,56 @@
1
+ -- core_197_display_name_source.sql — "your name wins, always" (task 1002568).
2
+ --
3
+ -- THE DEFECT. `upsertBuilder` (src/bongos/db-kernel.js) runs on EVERY sign-in,
4
+ -- not just at account creation, and its ON CONFLICT (github_id) DO UPDATE
5
+ -- unconditionally re-set `display_name = EXCLUDED.display_name` — where EXCLUDED
6
+ -- carries the GitHub-derived name. So a builder who renamed themselves in hall
7
+ -- Settings (PATCH /me/display-name) saw the value save, then silently revert at
8
+ -- the next authentication. All four login paths clobber it (pollDeviceFlow,
9
+ -- completeWebFlow, completeIdpFlow, completeDeviceExchange).
10
+ --
11
+ -- THE FIX needs one bit of state the row does not currently carry: WHO last set
12
+ -- this name. With it, the upsert keeps seeding + re-syncing GitHub's name for
13
+ -- builders who never renamed (a GitHub rename still propagates), and stops
14
+ -- overwriting the name of anyone who set their own. See ADR 0156.
15
+ --
16
+ -- WHY THIS IS A CORE MIGRATION, NOT modules/builder-settings/migrations/. The
17
+ -- column sits on the CORE `builders` table and is read by CORE code — the guard
18
+ -- lives inside db-kernel.js's upsert, on the auth path. A module migration may
19
+ -- only touch its own <key>-namespaced objects (ADR 0083 §Decision #5), and core
20
+ -- schema must never depend on whether a module happens to be enabled. Same
21
+ -- placement as the other builders-table preference columns (037, 075, 119, 164).
22
+ --
23
+ -- 'github' is the correct default for every existing row: before this migration
24
+ -- there was no durable user-set name — whatever is stored survived the last
25
+ -- sign-in, which means it IS the GitHub-derived value. A builder who wants their
26
+ -- own name re-saves it once and it sticks from then on.
27
+ --
28
+ -- Safe DDL: ADD COLUMN ... NOT NULL DEFAULT <constant> does not rewrite the
29
+ -- table on Postgres 11+ (the default is stored in the catalog). Forward-safe:
30
+ -- every existing reader SELECTs an explicit column list, so old code on the new
31
+ -- schema neither sees nor needs this column.
32
+ -- Idempotent: safe to re-run.
33
+
34
+ ALTER TABLE builders
35
+ ADD COLUMN IF NOT EXISTS display_name_source text NOT NULL DEFAULT 'github';
36
+
37
+ -- Only two values are meaningful, and the upsert guard keys on 'user' exactly —
38
+ -- a typo'd third value would silently fall through to "GitHub wins", which is
39
+ -- the bug this migration exists to close. CHECK constraints have no
40
+ -- ADD ... IF NOT EXISTS, so guard the add (the migration 042 idiom) and keep the
41
+ -- file replay-safe.
42
+ DO $$
43
+ BEGIN
44
+ IF NOT EXISTS (
45
+ SELECT 1 FROM pg_constraint
46
+ WHERE conrelid = 'builders'::regclass
47
+ AND conname = 'builders_display_name_source_check'
48
+ ) THEN
49
+ ALTER TABLE builders
50
+ ADD CONSTRAINT builders_display_name_source_check
51
+ CHECK (display_name_source IN ('github', 'user'));
52
+ END IF;
53
+ END $$;
54
+
55
+ INSERT INTO schema_migrations (version) VALUES ('core_197_display_name_source')
56
+ ON CONFLICT DO NOTHING;
@@ -0,0 +1,77 @@
1
+ -- core_204_rekey_active_claim_per_session_worktree.sql
2
+ -- Applied under claim 1002172 (task 1002570 -- parallel worktrees can't claim:
3
+ -- migration 180's index still keys on the auth session). Number 204 reserved by
4
+ -- the claim. A CORE migration, not a lifecycle_ module one, by rule: it drops
5
+ -- an index on the shared pre-module `claims` table, and module migrations must
6
+ -- stay additive within their own <key>_ tables (ADR 0083 section Decision #5 -- the
7
+ -- fitness gate enforces both).
8
+ -- Idempotent on re-run.
9
+ --
10
+ -- THE PROBLEM. The authoritative app guard (shouldRefuseSessionClaim,
11
+ -- modules/lifecycle/routes/claims.js) keys the one-active-claim rule on the
12
+ -- WORKING TREE: one auth session may hold several active bound claims as long
13
+ -- as each lands in its own worktree (one worktree = one branch = one HEAD; only
14
+ -- same-tree claims commingle a ship). Migration 180's backstop index was never
15
+ -- updated to match -- keyed on creator_session_id ALONE, it refused at the DB
16
+ -- layer the parallel second-worktree claim the app layer had just approved, so
17
+ -- the documented worktree-per-task parallel workflow was broken.
18
+ --
19
+ -- THE FIX. Re-key the partial unique index to
20
+ -- (creator_session_id, COALESCE(worktree_name, '')). COALESCE rather than
21
+ -- PG15's NULLS NOT DISTINCT, and it is load-bearing: in a plain multi-column
22
+ -- unique index NULLs are DISTINCT, so two worktree-less claims (worktree_name
23
+ -- NULL -- one shared clone whose single branch genuinely commingles) would BOTH
24
+ -- insert and re-open the hole task 1642 closed. Folding NULL to '' keeps the
25
+ -- shared-clone case self-colliding on every supported Postgres. (Degenerate
26
+ -- corner: a claim named '' collides with the NULL group -- the same "no worktree
27
+ -- info, assume one shared clone" answer the app guard gives.)
28
+ --
29
+ -- The predicate is unchanged from 180: released_at IS NULL is the claims-table
30
+ -- "active" convention (there is no status column), and NULL sessions
31
+ -- (bind_session=false web claims -- many may coexist, adoptable by any of the
32
+ -- builder's CLI sessions) stay excluded.
33
+ --
34
+ -- The error surface survives the swap: claimUniqueViolationError
35
+ -- (modules/lifecycle/db.js, task 1002589) matches the OLD and NEW index names,
36
+ -- so a 23505 from either generation renders as session_already_has_bound_claim,
37
+ -- never the false ALREADY_CLAIMED.
38
+
39
+ BEGIN;
40
+
41
+ -- Step 1 -- heal before CREATE (the migration 174/180 shape): a unique index
42
+ -- fails to create if existing rows already violate it, which would block
43
+ -- deploy. Under the old session-only index no session can hold two bound
44
+ -- active claims at all, so this normally closes nothing -- it only bites on a
45
+ -- DB whose old index was dropped by hand. Keep the EARLIEST active bound claim
46
+ -- per (session, folded worktree) and close the rest, exactly as 180 did.
47
+ WITH ranked AS (
48
+ SELECT c.id,
49
+ ROW_NUMBER() OVER (
50
+ PARTITION BY c.creator_session_id, COALESCE(c.worktree_name, '')
51
+ ORDER BY c.claimed_at, c.id
52
+ ) AS rn
53
+ FROM claims c
54
+ WHERE c.released_at IS NULL
55
+ AND c.creator_session_id IS NOT NULL
56
+ )
57
+ UPDATE claims c
58
+ SET released_at = now(),
59
+ outcome = COALESCE(c.outcome, 'abandoned')
60
+ FROM ranked r
61
+ WHERE c.id = r.id
62
+ AND r.rn > 1;
63
+
64
+ -- Step 2 -- swap the index. The old name goes away entirely rather than staying
65
+ -- alongside: keeping it would re-refuse exactly what the guard (and the new
66
+ -- index) deliberately allow.
67
+ DROP INDEX IF EXISTS uniq_active_claim_per_session;
68
+
69
+ CREATE UNIQUE INDEX IF NOT EXISTS uniq_active_claim_per_session_worktree
70
+ ON claims (creator_session_id, (COALESCE(worktree_name, '')))
71
+ WHERE released_at IS NULL AND creator_session_id IS NOT NULL;
72
+
73
+ -- (The index name in claimUniqueViolationError's SESSION_CLAIM_INDEXES set and
74
+ -- this file must stay in lockstep -- tests/one_active_claim_per_session.mjs pins
75
+ -- both sides.)
76
+
77
+ COMMIT;
@@ -20,7 +20,7 @@ const autonomyGate = require('../../../scripts/gds/autonomy-gate');
20
20
  module.exports = function buildAutonomyRouter() {
21
21
  const router = express.Router();
22
22
 
23
- router.get('/autonomy/precheck', auth.requireBuilder, auth.requireRank('archon'), async (req, res) => {
23
+ router.get('/autonomy/precheck', auth.requireBuilder, auth.requirePermission('autonomy.precheck'), async (req, res) => {
24
24
  const routine = String(req.query.routine || '').slice(0, 64);
25
25
  if (!routine) return res.fail('routine_required', { status: 400, message: 'pass ?routine=<name>' });
26
26
  try {
@@ -143,10 +143,16 @@ async function getBuilderDisplayName(builderId) {
143
143
  // writes it verbatim and returns the saved string. Own-scoped: the route only
144
144
  // ever passes the AUTHENTICATED builder's id. display_name is `text NOT NULL`
145
145
  // (migration 003), so a validated non-empty string is always a legal write.
146
+ //
147
+ // Also stamps display_name_source='user' (core migration 197): that flag is what
148
+ // makes the name durable — core's upsertBuilder re-syncs the GitHub name on
149
+ // every sign-in and skips any row marked 'user'. Without the stamp the save
150
+ // reverts at the next authentication (task 1002568, ADR 0156).
146
151
  async function setBuilderDisplayName(builderId, displayName) {
147
152
  const { rows } = await pool.query(
148
153
  `UPDATE builders
149
- SET display_name = $2
154
+ SET display_name = $2,
155
+ display_name_source = 'user'
150
156
  WHERE id = $1
151
157
  RETURNING display_name`,
152
158
  [builderId, displayName]