@bongos/core 1.17.1 → 1.17.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 (164) hide show
  1. package/.bongos-core.json +244 -264
  2. package/.claude/skills/blocker-review/SKILL.md +1 -1
  3. package/.claude/skills/builder-claim/SKILL.md +2 -0
  4. package/.claude/skills/builder-exit/SKILL.md +2 -2
  5. package/.claude/skills/builder-redteam/SKILL.md +3 -3
  6. package/.claude/skills/builder-setup/SKILL.md +2 -2
  7. package/.claude/skills/builder-start/SKILL.md +1 -1
  8. package/.claude/skills/dev/SKILL.md +1 -1
  9. package/.claude/skills/idea-triage/SKILL.md +2 -2
  10. package/.claude/skills/ideate/SKILL.md +1 -1
  11. package/.claude/skills/merge-mode/SKILL.md +1 -1
  12. package/.claude/skills/new-project/SKILL.md +3 -0
  13. package/.claude/skills/paint/SKILL.md +1 -1
  14. package/GOVERNANCE.md +1 -1
  15. package/README.md +1 -1
  16. package/docs/adr/0001-stack-choice-single-vps.md +1 -1
  17. package/docs/adr/0008-google-chat-oauth-user-auth.md +1 -1
  18. package/docs/adr/0019-red-team-bounty-payout-table.md +3 -3
  19. package/docs/adr/0020-security-model-public-stub.md +1 -1
  20. package/docs/adr/0023-kind-multipliers-and-idea-bonus.md +1 -1
  21. package/docs/adr/0024-cloneable-repo-local-first-memory.md +1 -1
  22. package/docs/adr/0026-bfg-memory-hygiene-and-cross-builder-learning.md +4 -4
  23. package/docs/adr/0027-bfg-session-inefficiency-evaluator.md +3 -3
  24. package/docs/adr/0030-builders-hall-live-updates.md +1 -1
  25. package/docs/adr/0031-cloud-dev-environments-for-builders.md +4 -4
  26. package/docs/adr/0032-discord-mirror-of-gds.md +2 -2
  27. package/docs/adr/0034-thetes-graduated-newcomer-rank.md +1 -1
  28. package/docs/adr/0044-mediterranean-palette-replacement.md +1 -1
  29. package/docs/adr/0044-per-box-live-game-preview.md +1 -1
  30. package/docs/adr/0054-session-token-reward-cost-plus.md +10 -10
  31. package/docs/adr/0057-container-cost-ledger.md +3 -3
  32. package/docs/adr/0062-medusa-instance-model-decoupling.md +4 -4
  33. package/docs/adr/0065-agpl-nonprofit-ai-first-governance.md +2 -2
  34. package/docs/adr/0085-remove-default-per-builder-spend-cap.md +1 -1
  35. package/docs/adr/0086-goal-scoped-work-hierarchy.md +1 -1
  36. package/docs/adr/0096-require-reward-before-workable.md +9 -9
  37. package/docs/adr/0097-retroactive-reward-backfill.md +3 -3
  38. package/docs/adr/0100-two-repo-instance-model.md +1 -1
  39. package/docs/adr/0101-goal-hierarchy-phase-5-judge-membership-reward.md +3 -3
  40. package/docs/adr/0103-core-first-extraction-cloud-bongos-trunk.md +3 -3
  41. package/docs/adr/0107-module-upstreaming-contribution-to-core-catalog.md +1 -1
  42. package/docs/adr/0120-pay-on-land-and-builder-owned-rebase-gate.md +6 -6
  43. package/docs/adr/0136-update-channel-subscription-policy.md +51 -0
  44. package/docs/adr/0137-upstream-publish-policy-manual-owner-gated.md +40 -0
  45. package/docs/adr/0138-bundled-module-deps-declared-per-module.md +45 -0
  46. package/docs/adr/README.md +9 -6
  47. package/docs/architecture.md +2 -2
  48. package/docs/branding-contract.md +5 -4
  49. package/docs/canonical-permissions.md +1 -1
  50. package/docs/design/vanilla-hall-ui-redesign-scope.md +1 -1
  51. package/docs/file-map.md +18 -17
  52. package/docs/handoff-template.md +2 -2
  53. package/docs/modules-contract.md +6 -1
  54. package/docs/onboarding/diagrams/README.md +7 -7
  55. package/docs/onboarding/drachmae-and-karma.md +18 -18
  56. package/docs/onboarding/primer.md +9 -9
  57. package/docs/recipes/audit-replay.md +2 -2
  58. package/docs/recipes/builder-box-lifecycle.md +2 -2
  59. package/docs/recipes/claude-design-loop.md +1 -1
  60. package/docs/recipes/ops-gotchas.md +1 -1
  61. package/docs/recipes/private-npm-distribution.md +17 -0
  62. package/docs/recipes/staging-environment.md +1 -1
  63. package/docs/recipes/upgrading-the-core.md +55 -0
  64. package/docs/research/cursor-competitive-analysis.md +1 -1
  65. package/docs/research/llm-model-agnostic-and-cheaper-grader.md +1 -1
  66. package/modules/discord/module.json +2 -1
  67. package/modules/economy/achievements.js +9 -1
  68. package/modules/economy/credits.js +10 -1
  69. package/modules/hall-ui/public/builders.js +71 -4
  70. package/modules/hall-ui/public/goal-inbox.js +191 -0
  71. package/modules/hall-ui/public/hall-widgets.js +14 -1
  72. package/modules/hall-ui/public/index.html +3 -0
  73. package/modules/hall-ui/public/new-project-wizard.js +1 -0
  74. package/modules/hall-ui/public/style.css +43 -0
  75. package/modules/provisioning/CLAUDE.md +15 -0
  76. package/modules/provisioning/migrations/provisioning_001_tables.sql +160 -0
  77. package/modules/provisioning/migrations/provisioning_002_standalone_shape.sql +11 -0
  78. package/modules/provisioning/migrations/provisioning_003_github_app_manifest.sql +95 -0
  79. package/modules/provisioning/module.json +17 -0
  80. package/modules/provisioning/onboard-plan.js +37 -0
  81. package/modules/provisioning/provisioning.js +539 -0
  82. package/modules/provisioning/rate-limit.js +35 -0
  83. package/modules/provisioning/routes/provisioning.js +607 -0
  84. package/modules/provisioning/tests/provisioning.mjs +232 -0
  85. package/modules/provisioning/ui/.gitkeep +0 -0
  86. package/modules/security/secret-scrub.js +10 -0
  87. package/package-lock.json +2 -2
  88. package/package.json +7 -2
  89. package/scripts/discord/README.md +1 -1
  90. package/scripts/gds/claim.js +98 -48
  91. package/scripts/gds/claude-materialize.js +30 -9
  92. package/scripts/gds/cutover-codemod.js +241 -0
  93. package/scripts/gds/fitness.js +54 -0
  94. package/scripts/gds/init.js +59 -3
  95. package/scripts/gds/lint-infra-exec.js +70 -0
  96. package/scripts/gds/lint-workflow-exec.js +63 -0
  97. package/scripts/gds/mirror-lineage-check.js +14 -5
  98. package/scripts/gds/mirror-redact.js +8 -0
  99. package/scripts/gds/onboard.js +4 -0
  100. package/scripts/gds/package-core.js +18 -3
  101. package/scripts/gds/publish-manifest.js +60 -2
  102. package/scripts/gds/run-routine.js +51 -13
  103. package/scripts/gds/run-unit-tests.js +4 -0
  104. package/scripts/gds/update-channel.js +186 -0
  105. package/scripts/gds/upgrade.js +116 -9
  106. package/src/bongos/auth.js +14 -1
  107. package/src/bongos/serve-internal.js +10 -1
  108. package/src/branding.js +1 -0
  109. package/src/module-api.js +12 -1
  110. package/src/module-loader/loader.js +36 -4
  111. package/src/module-loader/manifest-schema.js +16 -1
  112. package/tests/branding.mjs +11 -0
  113. package/tests/claim_error_surface.mjs +107 -0
  114. package/tests/claude_materialize.mjs +20 -0
  115. package/tests/consumer_layout_boot.mjs +302 -0
  116. package/tests/cutover_codemod.mjs +184 -0
  117. package/tests/device_flow_start.mjs +25 -2
  118. package/tests/hook_core_resolve.mjs +125 -0
  119. package/tests/infra_exec_paths.mjs +104 -0
  120. package/tests/init.mjs +47 -0
  121. package/tests/mirror_lineage_proof.mjs +14 -0
  122. package/tests/module_loader.mjs +42 -0
  123. package/tests/module_manifest.mjs +19 -0
  124. package/tests/onboard.mjs +13 -0
  125. package/tests/package_core.mjs +8 -0
  126. package/tests/provisioning_github_app_routes.mjs +15 -0
  127. package/tests/publish_manifest.mjs +52 -1
  128. package/tests/run_routine.mjs +26 -1
  129. package/tests/run_routine_two_root.mjs +77 -0
  130. package/tests/scrubber_corpus.mjs +17 -0
  131. package/tests/update_channel.mjs +186 -0
  132. package/tests/upgrade.mjs +131 -0
  133. package/tests/workflow_exec_paths.mjs +98 -0
  134. package/docs/design/container-exterior-tiles.md +0 -49
  135. package/docs/design/container-exterior.md +0 -81
  136. package/docs/design/container-interior.md +0 -77
  137. package/docs/design/world-map-scroll.md +0 -41
  138. package/docs/recipes/claude-gemini-bongos-doors.md +0 -155
  139. package/docs/recipes/cloudbongos-staging-gate.md +0 -139
  140. package/docs/recipes/cloudbongos-standup.md +0 -111
  141. package/docs/recipes/parallel-load-results-2026-05-30.md +0 -63
  142. package/docs/recipes/parallel-load-test.md +0 -32
  143. package/docs/recipes/pixel-art-pipeline.md +0 -411
  144. package/docs/recipes/staging-data.md +0 -50
  145. package/migrations/instance/018_pms_v3_planning_seed.sql +0 -131
  146. package/migrations/instance/080_gds_v4_planning_seed.sql +0 -75
  147. package/migrations/instance/110_medusa_planning_seed.sql +0 -70
  148. package/migrations/instance/120_bongos_v1_planning_seed.sql +0 -69
  149. package/migrations/instance/144_bongos_v1_module_system_criterion.sql +0 -26
  150. package/migrations/instance/159_bongos_v1_goal_hierarchy_criterion.sql +0 -29
  151. package/migrations/instance/172_v2_world_planning_seed.sql +0 -163
  152. package/migrations/instance/173_bongos_v1_c7_core_modularized.sql +0 -33
  153. package/migrations/instance/182_gdsv4_bongos_consolidation.sql +0 -132
  154. package/migrations/instance/183_gdsv4_carryover_themed_goals.sql +0 -116
  155. package/migrations/instance/186_bongos_v1_governance_planning_seed.sql +0 -69
  156. package/scripts/gds/art-stage.js +0 -152
  157. package/server.js +0 -212
  158. package/tests/art_key.mjs +0 -121
  159. package/tests/day_cycle.mjs +0 -197
  160. package/tests/snapshot_picker.mjs +0 -233
  161. package/tests/sprite_facing.mjs +0 -35
  162. package/tests/terrain_picker.mjs +0 -298
  163. package/tests/turn_in_place.mjs +0 -280
  164. package/tests/walkability.mjs +0 -188
@@ -17,7 +17,7 @@ Today it is one fused codebase. A file-grounded coupling analysis (2026-06-18) f
17
17
 
18
18
  1. **Process/HTTP** — one Express app + one Colyseus server. The GDS (`/api/gds`, hall, status) mounts host-agnostic *first*; the **game is the fall-through default**, not a module. Good news: `src/rooms/register.js` + `src/preview-server.js` already factor the game-room set out — the cleanest existing seam and the template for the cut.
19
19
  2. **DB** — *one shared `pg` Pool*: `src/bongos/db.js` does `require('../db')` and reuses the game's pool. Game tables (`world_events`, `players`, migrations 001/002) and ~78 GDS tables share one pool and one flat migration sequence. `track` is a 2-value `CHECK (track IN ('product','internal'))` column on `versions`, woven into analytics + hall UI — an OTB-specific taxonomy.
20
- 3. **Brand/identity** — leaked, not single-config. "Example" is hardcoded across ~50 hits / 28 files; `example.com` across ~90 (cookie domain `.example.com`, OAuth origin, `*.dev.` SSH allowlist); "drachmae" in 23 files; "Off the Boats"/"Example" baked into **grader/architect/onboarding LLM prompts**. Only `devbox-app/src/brand.js` honors the single-config ideal.
20
+ 3. **Brand/identity** — leaked, not single-config. "Example" is hardcoded across ~50 hits / 28 files; `example.com` across ~90 (cookie domain `.example.com`, OAuth origin, `*.dev.` SSH allowlist); "example" in 23 files; "Example"/"Example" baked into **grader/architect/onboarding LLM prompts**. Only `devbox-app/src/brand.js` honors the single-config ideal.
21
21
  4. **Methodology/skills** — already clean: `.claude/skills/builder-*` + idea/blocker/planning/status/recall are portable; the four `otb-*` art skills + `stop-sound.js` are host; `art/` is self-contained (no `src/` imports); Discord is already `DISCORD_BOT_ENABLED`-gated.
22
22
 
23
23
  Why a spike-ADR first: 24 structural tasks (R51–R74) depend on this. They need **one** locked boundary, branding-contract schema, and module mechanism to build against, or they will diverge.
@@ -46,7 +46,7 @@ Why a spike-ADR first: 24 structural tasks (R51–R74) depend on this. They need
46
46
 
47
47
  - **Engine entrypoint** (R51, [#1190](https://example.com/builders#/task/1190)): boots Express + `buildGdsRouter()` + hall + status, host-agnostic, **no Colyseus, no game/tile mounts** — sibling to `src/preview-server.js`. Today's `server.js` becomes the *OTB instance boot* (engine + game module).
48
48
  - **A host instance** = branding pack (§3) + feature-module flags (§4) + owner/repo/OAuth binding (R62, [#1201](https://example.com/builders#/task/1201)) + its own DB (R52).
49
- - Customer 0 = vanilla brand, no modules (the dogfood); customer 1 = OTB (game + art + discord + dev-box, drachmae, Greek pack).
49
+ - Customer 0 = vanilla brand, no modules (the dogfood); customer 1 = OTB (game + art + discord + dev-box, example, Greek pack).
50
50
 
51
51
  ### 3. The branding contract (`config/branding.*` — R53 keystone, [#1192](https://example.com/builders#/task/1192))
52
52
 
@@ -54,7 +54,7 @@ One per-instance config; **resolution precedence: env var → instance config
54
54
 
55
55
  - **Identity:** `productName`, `worldName` (the in-world / civilization name), `company` (+ site).
56
56
  - **Domains:** `publicOrigin`, `buildersOrigin`, `statusOrigin`, `cookieDomain`, box subdomain pattern (`*.dev.`/`sandbox-`/`term-`). *(Load-bearing security strings — a swap must not widen cookie/SSH scope.)*
57
- - **Currency label** (R56, [#1195](https://example.com/builders#/task/1195)): "drachmae" → configurable; the cost-plus reward **math is unchanged**.
57
+ - **Currency label** (R56, [#1195](https://example.com/builders#/task/1195)): "example" → configurable; the cost-plus reward **math is unchanged**.
58
58
  - **Tracks:** replace the 2-value `product|internal` CHECK with a configurable set (R52, [#1191](https://example.com/builders#/task/1191)).
59
59
  - **Theme/palette + copy pack:** hall lore/primer/404 copy, Discord persona, and — critically — the **grader/architect/onboarding prompt brand header** must read the project name from config, or a non-OTB instance grades against the wrong project identity.
60
60
 
@@ -103,5 +103,5 @@ R50 (this ADR) → **R51 entrypoint + R52 DB pool** (independent foundations)
103
103
 
104
104
  - A clear, file-grounded target the 24 epics build against — one boundary, one contract, one module mechanism.
105
105
  - The trust boundary + rank enforcement stay core and unchanged; a feature-module must never be able to bypass server-side rank checks (a Path-C assertion).
106
- - **Hardest knots (called out so the epics plan for them):** (1) shared Pool + flat migrations; (2) the brand-saturated, *not* data-driven hall UI (~50 brand hits, drachmae in 23 files) — the largest mechanical job; (3) inverting game-as-default into a mounted module; (4) domains woven into auth/security — config swaps must not widen cookie/SSH scope; (5) brand inside LLM prompts (grader/architect/onboarding).
106
+ - **Hardest knots (called out so the epics plan for them):** (1) shared Pool + flat migrations; (2) the brand-saturated, *not* data-driven hall UI (~50 brand hits, example in 23 files) — the largest mechanical job; (3) inverting game-as-default into a mounted module; (4) domains woven into auth/security — config swaps must not widen cookie/SSH scope; (5) brand inside LLM prompts (grader/architect/onboarding).
107
107
  - **Risk: scope.** Mitigation: R53 keystone first; modules incremental; Path-C enforces no regression; customer-0 dogfood ([#1206](https://example.com/builders#/task/1206)) is the falsifiable proof the cut actually worked.
@@ -9,7 +9,7 @@
9
9
 
10
10
  ## Context
11
11
 
12
- [ADR 0062](<redacted>.md) decoupled the GDS into **Cloud Bongos** — a configurable, self-hostable build platform where AI agents claim tasks and ship code, with a human owner setting scope and the hard gates. Example (the Off the Boats game) becomes **customer 1** running on it; Cloud Bongos runs on its own plain vanilla brand as **customer 0** and builds itself.
12
+ [ADR 0062](<redacted>.md) decoupled the GDS into **Cloud Bongos** — a configurable, self-hostable build platform where AI agents claim tasks and ship code, with a human owner setting scope and the hard gates. Example (the Example game) becomes **customer 1** running on it; Cloud Bongos runs on its own plain vanilla brand as **customer 0** and builds itself.
13
13
 
14
14
  Criterion **C9 (`medusa-open-and-protected`)** commits Cloud Bongos to being *open, free, and protected from capture*: released to the public as open source on a deliberate, delayed cadence. Three questions had to be answered before any of R69–R73 could be built, because each is downstream of the answer:
15
15
 
@@ -34,7 +34,7 @@ The license covers the **publishable subset only** — the portable methodology
34
34
  The project is run as a **non-profit**: it is **not monetized from its contributors**, and it is structured so no single party can capture it.
35
35
 
36
36
  - No contributor pays to contribute, and no contribution is resold as a proprietary add-on by the project.
37
- - The drachmae/credit economy ([ADR 0054](<redacted>.md)) is an **instance-internal** mechanism — it rewards builders on a given instance out of that instance's own budget; it is not the project monetizing the commons. It ships as a feature an instance configures, not as a revenue stream flowing to a central owner.
37
+ - The example/credit economy ([ADR 0054](<redacted>.md)) is an **instance-internal** mechanism — it rewards builders on a given instance out of that instance's own budget; it is not the project monetizing the commons. It ships as a feature an instance configures, not as a revenue stream flowing to a central owner.
38
38
  - The AGPL is the legal backstop; the non-profit posture is the social one. Together they are the "protected from capture" half of C9.
39
39
 
40
40
  Optional **managed-hosting** (operating instances for owners) was captured as a post-C8 idea in the planning session. If it is ever built, it is a service offered *on top of* the same AGPL artifact — it does not change the artifact's license or the non-profit posture of the platform itself.
@@ -36,4 +36,4 @@ No schema change (`monthly_budget_usd` stays; `NULL` now means "uncapped" instea
36
36
  - **The brake survives.** An Archon can still cap a specific runaway builder by setting a positive `monthly_budget_usd`; clearing it (`null`) returns them to uncapped.
37
37
  - **Cost stays honest.** Spend attribution, the savings ledger, and the public dashboard are untouched — we still see exactly where the $5K goes; we just don't gate work on it.
38
38
  - **Project-wide guards are unaffected.** The separate autonomous-fleet spend brake (`autonomy-gate.js`, `OTB_AUTONOMY_CEILING_USD`) and the art-pipeline `HARD_STOP_USD` ledger cap are independent mechanisms and remain in force.
39
- - **C3's intent narrows** from "every builder has a default ceiling" to "a ceiling is an opt-in, per-builder Archon tool." The onboarding drachmae/karma diagram note is updated to match.
39
+ - **C3's intent narrows** from "every builder has a default ceiling" to "a ceiling is an opt-in, per-builder Archon tool." The onboarding example/karma diagram note is updated to match.
@@ -82,7 +82,7 @@ The create-time gate sees only *declared* touches (advisory — git merge is the
82
82
 
83
83
  ### Deferred (explicit non-decisions)
84
84
 
85
- - **Rewards** — whether *achieving a goal* earns anything beyond the per-task cost-plus already paid ([ADR 0054](<redacted>.md)), and how any reward splits among a goal's members. The leaning is karma by default (budget-safe, since drachmae are real money) with contribution-weighted splits, but this is **not yet decided** and rides a follow-on.
85
+ - **Rewards** — whether *achieving a goal* earns anything beyond the per-task cost-plus already paid ([ADR 0054](<redacted>.md)), and how any reward splits among a goal's members. The leaning is karma by default (budget-safe, since example are real money) with contribution-weighted splits, but this is **not yet decided** and rides a follow-on.
86
86
  - **A sub-Metic builder joining a protected-scope goal** — letting a newcomer work *inside* the dangerous core under a tightly-scoped, Archon-granted goal amends the [ADR 0043](<redacted>.md) hard floor (and needs the ship-time gates to become goal-aware). Deliberately **last**, behind its own security-reviewed ADR. Until then, protected-scope goals require a Metic+ holder.
87
87
  - **An AI relevance / anti-hack judge** at create/claim time (reusing the grader subagent harness; advisory-first, flag-for-Metic, never hard-blocking a newcomer) — a later phase; the deterministic scope + protected-path gates ship first.
88
88
 
@@ -1,4 +1,4 @@
1
- # ADR 0096 — Require a `credits_reward` (drachmae) value before a task is workable
1
+ # ADR 0096 — Require a `credits_reward` (example) value before a task is workable
2
2
 
3
3
  **Date:** 2026-06-28
4
4
  **Status:** Accepted
@@ -7,12 +7,12 @@
7
7
 
8
8
  ## Context — the problem
9
9
 
10
- Every task carries a `credits_reward` integer — the in-world drachmae payout when it
10
+ Every task carries a `credits_reward` integer — the in-world example payout when it
11
11
  ships ([ADR 0023](<redacted>.md), [ADR 0054](<redacted>.md)).
12
12
  That column was created with `NOT NULL DEFAULT 0` (migration `003_pms.sql`). Nothing in
13
13
  the lifecycle ever required it to be set, so the default path was:
14
14
 
15
- > create a task (reward = 0) → promote it to `ready` → claim → ship → **pay 0 drachmae**.
15
+ > create a task (reward = 0) → promote it to `ready` → claim → ship → **pay 0 example**.
16
16
 
17
17
  A task could travel the whole claim→ship→credit pipeline paying nothing. The credit
18
18
  ledger then records a real shipped task with a zero payout — the reward economy is
@@ -96,7 +96,7 @@ complete and no zero-reward `ready` tasks remain.
96
96
 
97
97
  ## Consequences
98
98
 
99
- - No task created/promoted/claimed after this change can be worked while paying 0 drachmae.
99
+ - No task created/promoted/claimed after this change can be worked while paying 0 example.
100
100
  - Existing `ready` + in-flight zero-reward tasks stay fully workable (grandfathered).
101
101
  - The promote/claim refusals name the way forward (suggested value + the `PATCH` call),
102
102
  matching the `TASK_NOT_READY` actionable-hint precedent ([#952](https://example.com/builders#/task/952)).
@@ -112,7 +112,7 @@ The original decision above made a missing reward **block** the two workable tra
112
112
  that turned a missing-value into a hard wall: a perfectly good task with a deps-met,
113
113
  ready-to-work footprint could not be promoted or claimed until someone hand-set a value
114
114
  first. That is friction the methodology doesn't want — the goal is "no task is worked for
115
- 0 drachmae", not "stop work until a human types a number."
115
+ 0 example", not "stop work until a human types a number."
116
116
 
117
117
  **The shift: the gate now AUTO-ASSIGNS a fair, capped value and PROCEEDS.**
118
118
 
@@ -143,7 +143,7 @@ author-set precisely because auto-deriving it from `est_minutes` is gameable (in
143
143
  estimate → inflate the pay). Auto-assigning at the gate crosses that line — so two guards
144
144
  contain the blast radius:
145
145
 
146
- 1. **CAP.** `suggestCreditsReward` is capped at `REWARD_SUGGESTION_CAP = 60` drachmae. The
146
+ 1. **CAP.** `suggestCreditsReward` is capped at `REWARD_SUGGESTION_CAP = 60` example. The
147
147
  formula stays modest (~1 drachma/estimated-minute, weighted by a coarse per-kind factor,
148
148
  floored at 5), and the cap bounds how far an inflated estimate can push the *auto-assigned*
149
149
  value. Anyone who genuinely needs more sets `credits_reward` by hand via `PATCH /tasks/:id`
@@ -161,7 +161,7 @@ contain the blast radius:
161
161
 
162
162
  ### Net effect
163
163
 
164
- No task is ever worked for 0 drachmae **and** no promote/claim is ever blocked for a missing
164
+ No task is ever worked for 0 example **and** no promote/claim is ever blocked for a missing
165
165
  value. The friction is gone; the economy stays funded; the gameable surface is a capped,
166
166
  audited floor sitting under the ungameable cost-plus primary reward.
167
167
 
@@ -170,7 +170,7 @@ audited floor sitting under the ungameable cost-plus primary reward.
170
170
  The first amendment moved assignment to the two *workable* transitions (promote, claim). That
171
171
  still left a window: a freshly **created** `backlog` task read `credits_reward = 0` until it was
172
172
  later promoted or claimed. The owner's intent is stronger — **every task should carry a fair
173
- drachmae value from the moment it is born**, so anyone reading the backlog sees real numbers, not
173
+ example value from the moment it is born**, so anyone reading the backlog sees real numbers, not
174
174
  zeros waiting to be filled in.
175
175
 
176
176
  **The shift: creation is now the PRIMARY assignment point; promote/claim becomes the backstop.**
@@ -204,6 +204,6 @@ just now lands at birth instead of at first-work.
204
204
 
205
205
  ### Net effect (post-1677)
206
206
 
207
- Every task is born with a fair, capped drachmae value. The backlog never shows a placeholder zero;
207
+ Every task is born with a fair, capped example value. The backlog never shows a placeholder zero;
208
208
  promote/claim no longer carries the assignment weight (it's a backstop); the economy stays funded
209
209
  end-to-end with the same anti-gaming ceiling.
@@ -1,4 +1,4 @@
1
- # ADR 0097 — Retroactive drachmae backfill for shipped 0-reward tasks
1
+ # ADR 0097 — Retroactive example backfill for shipped 0-reward tasks
2
2
 
3
3
  - **Date:** 2026-06-28
4
4
  - **Status:** Accepted
@@ -11,7 +11,7 @@
11
11
  A task's `credits_reward` column **defaults to 0**. The per-task ship credit is
12
12
  minted by `modules/economy/credits.js insertMultipliedConfirmCredit`, which
13
13
  computes `applyKindMultiplier(credits_reward, kind)` — so a task that shipped
14
- with `credits_reward = 0` (or NULL) paid `0 × multiplier = 0` drachmae. Many
14
+ with `credits_reward = 0` (or NULL) paid `0 × multiplier = 0` example. Many
15
15
  early ships, and most dev-box / CI agent ships, never had a fair per-task reward
16
16
  set, so the builders who did real, shipped work earned nothing for it on the
17
17
  per-task stream.
@@ -28,7 +28,7 @@ There are **two reward streams** a builder accrues to `builders.total_credits`:
28
28
  `priceModelUsage` returns ~$0 and the reward rounds to 0. So for exactly the
29
29
  ships that missed the per-task reward, the cost-plus stream *also* paid nothing.
30
30
 
31
- The net effect: a real backlog of shipped work that minted no drachmae for its
31
+ The net effect: a real backlog of shipped work that minted no example for its
32
32
  builders. This ADR records the decision (and the tool) to remediate that **once,
33
33
  fairly, and safely** — with an Archon authorizing the amounts.
34
34
 
@@ -16,7 +16,7 @@ ADR 0062 drew the **logical** cut-line — portable core / host instance / gated
16
16
 
17
17
  Two owner priorities now push past the single-repo model:
18
18
 
19
- - **Mercury** — Example' internal platform — should be "its own Cloud Bongos project": not code living inside the Off the Boats repo, and not merely another reskin of one checkout.
19
+ - **Mercury** — Example' internal platform — should be "its own Cloud Bongos project": not code living inside the Example repo, and not merely another reskin of one checkout.
20
20
  - **Core improvements should flow to future projects** on each project's own schedule — an explicit "update to the latest platform" capability.
21
21
 
22
22
  The 2026-06-26 gap analysis (planning [#1604](https://example.com/builders#/task/1604)) found the logical boundary ~70% built and CI-enforced. Since then the load-bearing pieces landed: the bounded kernel ([#1570](https://example.com/builders#/task/1570)), the completed Config-B core-carve ([#1578](https://example.com/builders#/task/1578) + ADR 0093 — BONGOS-V1 C7 now satisfied), the hall-widget registry ([#1612](https://example.com/builders#/task/1612)), and core de-branding ([#1613](https://example.com/builders#/task/1613)). The boundary now holds, so a split is **mechanical, not a leap** — which is exactly the precondition this gate ([#1607](https://example.com/builders#/task/1607)) was told to wait for.
@@ -57,11 +57,11 @@ We adopt a three-part design. Each part is an independent, revertible build task
57
57
 
58
58
  **Leaning (recorded; the money is owner-gated).**
59
59
 
60
- - **Karma by default, not drachmae.** Drachmae are real money (1 = $1) against a tight $5k budget, and a goal is a *coordination construct*, not a unit of billable work — the per-task cost-plus already pays for the actual shipping. A goal-achievement **karma** bonus recognizes the collaboration with **no new monetary spend**.
60
+ - **Karma by default, not example.** Example are real money (1 = $1) against a tight $5k budget, and a goal is a *coordination construct*, not a unit of billable work — the per-task cost-plus already pays for the actual shipping. A goal-achievement **karma** bonus recognizes the collaboration with **no new monetary spend**.
61
61
  - **Contribution-weighted, not membership-flat.** The bonus tracks **who actually shipped tasks under the goal** (reuse the shipped-task/credit attribution + the BFG contribution signals, [ADR 0027](<redacted>.md)), never mere membership — a flat per-member reward would incentivize goal-squatting. A `lead` may take a small coordination share.
62
62
  - **On achievement, not creation.** The reward attaches when a goal is **achieved** (all criteria satisfied + confirmed via `/goal-review`), never on creation or joining — which, with contribution-weighting and the Metic confirmation, closes the free-rider and squatting surfaces.
63
63
 
64
- **Owner-gated.** Whether *any* monetary (drachmae) component attaches, and the exact karma amount/curve, is **Lars's call at build time** — money is owner authority. This ADR records the budget-safe default (karma-only, contribution-weighted); the build task (P5.3) surfaces the money decision explicitly rather than presuming it.
64
+ **Owner-gated.** Whether *any* monetary (example) component attaches, and the exact karma amount/curve, is **Lars's call at build time** — money is owner authority. This ADR records the budget-safe default (karma-only, contribution-weighted); the build task (P5.3) surfaces the money decision explicitly rather than presuming it.
65
65
 
66
66
  ### The child task list (filed backlog under BONGOS-V1, `source_ref` = this ADR)
67
67
 
@@ -76,7 +76,7 @@ We adopt a three-part design. Each part is an independent, revertible build task
76
76
  - **A hard-blocking relevance judge — rejected.** A fallible LLM as a hard gate re-introduces the bottleneck ADR 0086 removed and creates a gaming/DoS surface; the deterministic gates are the hard wall, the judge is a soft signal.
77
77
  - **Keep protected-scope Metic+-only forever — rejected as the long-term posture** (it caps the maneuverability grant's reach), but it **is** the safe default that stays in force until P5.2 ships with its security review.
78
78
  - **Flat per-member goal reward — rejected.** It incentivizes goal-squatting; contribution-weighting is fairer and anti-gaming.
79
- - **A drachmae goal bonus by default — rejected as the default** (real money, tight budget); left as an explicit owner-gated option, not presumed.
79
+ - **A example goal bonus by default — rejected as the default** (real money, tight budget); left as an explicit owner-gated option, not presumed.
80
80
  - **Fold Phase 5 into Phase 4 rather than its own ADR — rejected** (ADR 0086 already decided this): the boundary-widening warrants a standalone security review, which is this document.
81
81
 
82
82
  ---
@@ -17,7 +17,7 @@ A next-day owner review (2026-07-02, task [#1726](https://example.com/builders#/
17
17
 
18
18
  - **Cloud Bongos is the real product** — the thing of durable value — and should live as a clean standalone artifact, with Example as *one consumer of it*, not its host.
19
19
  - **The coupling actively hurts.** One repo carrying both the game and the platform, with a convoluted shared history, is hard to reason about and hand off.
20
- - **The boundary stays theoretical while Example is coupled.** OTB specifics keep leaking into the "portable" core. Evidence: a walk of the ~105 accumulated memory-file learnings on 2026-07-02 found **~85% are platform knowledge wearing OTB-specific framing** (example.com links, the droplet, `drachmae`) — the substance is core, the *wording* is coupled. Only 7 items are genuinely instance-bound.
20
+ - **The boundary stays theoretical while Example is coupled.** OTB specifics keep leaking into the "portable" core. Evidence: a walk of the ~105 accumulated memory-file learnings on 2026-07-02 found **~85% are platform knowledge wearing OTB-specific framing** (example.com links, the droplet, `example`) — the substance is core, the *wording* is coupled. Only 7 items are genuinely instance-bound.
21
21
  - **Mercury is imminent** — the separation should be proven on something that matters to the business now.
22
22
 
23
23
  "Leave Example coupled until later" reads, under these priorities, as "never." The precondition ADR 0100 waited for — a mechanical, CI-enforced boundary — already holds (ADR 0093 core-carve, the module system, `isPublishable()`), so the more ambitious cut is now buildable rather than a leap.
@@ -65,6 +65,6 @@ Each repo runs its own GDS (own DB, tasks, builders, credits) — a shared GDS i
65
65
  §1 is done. The core was extracted into **[github.com/example-owner/cloud-bongos](https://github.com/example-owner/cloud-bongos)** — **private**, `main`, **AGPL-3.0**, **fresh single-commit history**.
66
66
 
67
67
  - **What was extracted:** the `isPublishable()` subtree via R84's `scripts/gds/package-core.js` — **989 core files** (of 2203), redacted in-memory through the fail-closed no-leak gate (0 residual leaks). The pin travels in-repo as `.cloudbongos-core.json`: `core_version 1.14.0`, `source_commit 2783a396…`, `tree_sha256 2bf9f8ee9a97…`. R85 ([#1689](https://example.com/builders#/task/1689)) and R89 ([#1728](https://example.com/builders#/task/1728)) can now depend on this pin.
68
- - **Genericized on the way in:** identity surface branded to Cloud Bongos (README, package.json, CLAUDE.md 298→258 lines, GOVERNANCE); instance identity re-pointed to `docs/project-context.template.md` + `config/branding.neutral.json`; `drachmae→credits`, owner-name and Off-the-Boats/ancient-Greece framing removed.
68
+ - **Genericized on the way in:** identity surface branded to Cloud Bongos (README, package.json, CLAUDE.md 298→258 lines, GOVERNANCE); instance identity re-pointed to `docs/project-context.template.md` + `config/branding.neutral.json`; `example→credits`, owner-name and Off-the-Boats/ancient-Greece framing removed.
69
69
  - **§4 mirror re-evaluation (ADR 0099):** the physical-extraction trigger has now fired, and `cloud-bongos` is the **designated canonical public artifact** — but it is **PRIVATE for now**. So the ADR 0099 generated mirror is **NOT retired yet**; it stays the public surface until the owner flips `cloud-bongos` public, at which point the mirror is retired (or repointed). Retirement is bound to the public-flip, not to this extraction.
70
- - **Deferred to follow-ups (tracked in the GDS):** the exhaustive `Off the Boats`→neutral sweep across the ~32 remaining ADR/test/recipe files; standalone vanilla-boot wiring (`server.js`/entrypoints still reference the reference instance's product layer) — naturally proven by R85/R89; core `package.json` dependency pruning (game deps carried in); dangling in-repo links to topology/instance paths the scaffold populates on `init`; seeding the CORE-bucketed learnings; and tagging R88/R89 to goal 26 (their `goal_id` is null).
70
+ - **Deferred to follow-ups (tracked in the GDS):** the exhaustive `Example`→neutral sweep across the ~32 remaining ADR/test/recipe files; standalone vanilla-boot wiring (`server.js`/entrypoints still reference the reference instance's product layer) — naturally proven by R85/R89; core `package.json` dependency pruning (game deps carried in); dangling in-repo links to topology/instance paths the scaffold populates on `init`; seeding the CORE-bucketed learnings; and tagging R88/R89 to goal 26 (their `goal_id` is null).
@@ -32,7 +32,7 @@ Each repo runs its own GDS with its own builder identities ([ADR 0100](<redacted
32
32
 
33
33
  ### 3. Reward: credit now, money later.
34
34
 
35
- Accepted contributions earn **visible credit** (author + origin in the catalog) immediately. Any monetary reward (drachmae) — and the cross-instance identity/payout mechanics it would require — is **explicitly deferred** to a separate economics decision (owner call, 2026-07-03). This keeps the build unblocked and avoids prematurely solving cross-GDS identity.
35
+ Accepted contributions earn **visible credit** (author + origin in the catalog) immediately. Any monetary reward (example) — and the cross-instance identity/payout mechanics it would require — is **explicitly deferred** to a separate economics decision (owner call, 2026-07-03). This keeps the build unblocked and avoids prematurely solving cross-GDS identity.
36
36
 
37
37
  ### 4. Licensing: AGPL-in, sign-off, no CLA.
38
38
 
@@ -1,4 +1,4 @@
1
- # ADR 0120 — Pay drachmae on land (not on confirm) + a builder-owned rebase gate
1
+ # ADR 0120 — Pay example on land (not on confirm) + a builder-owned rebase gate
2
2
 
3
3
  **Date:** 2026-07-04
4
4
  **Status:** Accepted
@@ -7,7 +7,7 @@
7
7
 
8
8
  ## Context — the problem
9
9
 
10
- The per-task drachmae reward is awarded in **`confirmTask`** (`modules/lifecycle/db.js`,
10
+ The per-task example reward is awarded in **`confirmTask`** (`modules/lifecycle/db.js`,
11
11
  `reward.insertMultipliedConfirmCredit`, `credit_log.reason = 'task.confirmed'`) — at the
12
12
  `completed → confirmed` transition. Confirmed means *verified + credited*, but the code is
13
13
  **not yet on `main`**: the merge to `main` + deploy is the later `confirmed → shipped`
@@ -16,7 +16,7 @@ transition, run by the auto-lander / `publish-reconciler`.
16
16
  So a builder is paid the moment they declare done and pass verification — **before their
17
17
  code lands.** When the branch then can't merge (stale after a big codebase change, a real
18
18
  conflict, or never pushed at all), the task strands at `confirmed` indefinitely with **no
19
- consequence to the builder**: they already have the drachmae, and nothing stops them
19
+ consequence to the builder**: they already have the example, and nothing stops them
20
20
  claiming new work while the stranded branch rots.
21
21
 
22
22
  This is not hypothetical. On 2026-07-04 the confirmed queue held ~17 tasks across three
@@ -38,7 +38,7 @@ Re-couple reward and queue-access to **landed code**, in four parts.
38
38
  The award becomes idempotent on ship (`credit_log.reason = 'task.shipped'`, guarded by a
39
39
  uniqueness check per task, mirroring today's confirm-time guard). **The happy path barely
40
40
  changes:** a clean PR auto-merges within a minute or two of confirm, so the credit lands
41
- almost immediately. The teeth only bite when a task strands — no landed code, no drachmae.
41
+ almost immediately. The teeth only bite when a task strands — no landed code, no example.
42
42
 
43
43
  2. **A stalled land becomes the builder's own chore (`needs_rebase`).** The
44
44
  `publish-reconciler` already detects why a confirmed task won't land — `merge_conflict`,
@@ -73,11 +73,11 @@ can do it.
73
73
 
74
74
  - **Good behavior is unaffected.** Clean work still auto-lands in minutes and pays out then;
75
75
  most builders never see `needs_rebase` or the gate.
76
- - **Stranding now has teeth.** No landed code → no drachmae, and a stalled land blocks new
76
+ - **Stranding now has teeth.** No landed code → no example, and a stalled land blocks new
77
77
  claims until the builder rebases (or releases the task, forfeiting its reward).
78
78
  - **The credit ledger tells the truth.** `credit_log` credits now correspond to code that is
79
79
  actually on `main`, not to verified-but-unlanded branches.
80
- - **Retroactive sting.** The clawback reverses already-shown drachmae for currently-stranded
80
+ - **Retroactive sting.** The clawback reverses already-shown example for currently-stranded
81
81
  tasks. This is intentional (it creates the pressure) and is applied uniformly, but it will
82
82
  feel punitive to builders who were paid under the old rule. The reversal is fully re-earnable:
83
83
  land the task and the `task.shipped` credit books.
@@ -0,0 +1,51 @@
1
+ # ADR 0136 — Update-channel subscription policy (patch-only, health-gated, opt-in, autonomy-gated)
2
+
3
+ **Date:** 2026-07-10
4
+ **Status:** Accepted.
5
+ **Context:** [task 2150](https://example.com/builders#/task/2150) (BONGOS-V1, goal 26). The last piece of the update-consumption workstream: [ADR 0100](<redacted>.md) §2 / [task 1690](https://example.com/builders#/task/1690) gave a consumer the manual `bongos upgrade` channel; [ADR 0134](<redacted>.md) / [task 2090](https://example.com/builders#/task/2090) added the private-registry source; [task 2149](https://example.com/builders#/task/2149) added auto-rollback; [task 2157](https://example.com/builders#/task/2157) proved rollback live on the demo instance. What was missing is the **subscription** — how a consumer instance takes new releases *on a cadence, unattended*, rather than a human running `bongos upgrade` each time. Builds on the scheduled-routines portability contract ([ADR 0115](<redacted>.md)).
6
+
7
+ ## Problem
8
+
9
+ "How do projects subscribe to regular updates?" needs a policy, not just a mechanism. Left unspecified, an auto-updater is a footgun: it could take a breaking major unattended, upgrade an instance nobody meant to enroll, or leave an instance down after a bad release. Two questions had to be answered together: **(1)** how far may an unattended upgrade jump, and **(2)** what has to be true before anything moves on its own. A third question — who *publishes* the releases the subscription consumes — is related but separable, and conflating it would have blocked (1) and (2) behind a publishing-automation decision.
10
+
11
+ ## Decision
12
+
13
+ Adopt the owner-chosen policy (2026-07-09): **patch-only by default, health-gated with auto-rollback, opt-in, and autonomy-gated.**
14
+
15
+ **1. Update channels bound the blast radius.** Each subscribed instance carries an `update_channel`:
16
+
17
+ - `pinned` — never auto-upgrade (manual `bongos upgrade` only).
18
+ - `patch` — take a higher **patch** within the same `major.minor` (`1.17.1 → 1.17.4`, never `1.18.0`). **The default.**
19
+ - `minor` — take a higher **minor** within the same major (`1.17.1 → 1.18.0`, never `2.0.0`).
20
+
21
+ A **major** bump is never automatic under any channel — a breaking change wants a human. Prereleases (`1.17.2-rc.1`) are never auto-targeted. This math is the pure, unit-tested core [`scripts/gds/update-channel.js`](../../scripts/gds/update-channel.js) (`resolveChannelTarget` / `channelAllows`), so the "what should this instance move to?" decision is testable in isolation from any I/O.
22
+
23
+ **2. The registry is the source of truth for "what's newest."** A subscription needs a live feed of published versions — `npm view @bongos/core versions` against the private registry (ADR 0134), not a vendored tarball. The vendored path stays the manual-bump fallback.
24
+
25
+ **3. Every auto-upgrade is health-gated with auto-rollback.** The routine drives `bongos upgrade --to <v> --registry` with auto-rollback **ON** (task 2149): a patch that fails install/migrate/health reverts its pin, reinstalls the previous core, restarts, and records a `rolled_back` ledger row. The DB is left intact (core migrations are additive/forward-safe). So "unattended" never means "unsafe."
26
+
27
+ **4. Triple-gated before anything moves.** The scheduled routine [`core-update-subscription`](../../.claude/scheduled-tasks/core-update-subscription/) is:
28
+
29
+ - **Autonomy-gated** — `requiresAutonomy: true` in [`config/scheduled-routines.json`](../../config/scheduled-routines.json), a no-op until the instance arms `<PREFIX>_AUTONOMY_ENABLED`. Default **OFF** (ADR 0115's rule for autonomous-action routines).
30
+ - **Opt-in** — the roster [`config/update-subscriptions.json`](../../config/update-subscriptions.json) ships **empty**; an instance is touched only after an operator lists it (with its dir, systemd unit, health URL, channel, and an optional `env` map so `migrate` hits the right DB).
31
+ - **Health-gated** — per (3).
32
+
33
+ The routine is `mode: deterministic` (System 4, [ADR 0078](<redacted>.md)) — it runs as plain `node` with no model session — and **instance-wide** (ADR 0115): one run per instance iterating the roster, not one cron per instance-being-watched.
34
+
35
+ **5. Upstream auto-publish is explicitly split out, not decided here.** For a subscription to deliver anything, new patches must be *published*. Today that is a gated owner step (`package-core.js --publish --live`, ADR 0134). This ADR deliberately scopes the subscription to **consume whatever the owner has published** and does **not** decide whether merges to `main` auto-publish a patch. That is its own decision (publishing cadence, provenance, who holds the token) and is filed as [task 2158](https://example.com/builders#/task/2158). Splitting it lets goal 26 close on the consumption half, which is what the switchover needs.
36
+
37
+ **6. Scope: local, same-host instances.** The routine upgrades instances reachable from the control plane on the same host (the co-hosted shape — e.g. the demo instance beside the control plane). Multi-host / SSH orchestration across a real fleet is the deferred managed-fleet-control-plane epic ([task 1948](https://example.com/builders#/task/1948) / idea 303, "no fleet registry exists"); this routine is the per-instance consumption half that epic will call.
38
+
39
+ ## Alternatives considered
40
+
41
+ - **A `provisioning_instances` DB column for the roster + channel.** Rejected for now: the provisioning module models a *request→standup* lifecycle, not a subscription; coupling the subscription to it presumes the fleet-registry schema that is still being designed. A config manifest is migration-free (matches the task's `needs_migration:false`), matches the ADR-0115 "operator configures via a manifest" pattern, and is trivially superseded later by a `loadSubscriptions()` seam swap.
42
+ - **Auto-take minors by default.** Rejected: minors can carry behavior changes; patch-only is the conservative default an operator can widen per instance.
43
+ - **No dry-run / apply-always.** Rejected: `subscribe.js` is dry-run by default (the box-sweep convention); the scheduled step passes `--apply`. A hand run previews safely.
44
+ - **Deciding auto-publish here.** Rejected — see (5); it would have blocked the consumption half behind an orthogonal publishing decision.
45
+
46
+ ## Consequences
47
+
48
+ - **`config/update-subscriptions.json` is the portability surface** for subscriptions — the one place an operator opts an instance in and sets its channel. A vanilla instance boots subscribed to nothing.
49
+ - **The policy is enforced by a pure, tested core**, not prose: [`tests/update_channel.mjs`](../../tests/update_channel.mjs) pins the channel matrix (majors/prereleases never auto, patch stays on its minor, pinned/unknown → no-op).
50
+ - **A follow-up task owns upstream auto-publish** — [task 2158](https://example.com/builders#/task/2158) (whether/how `main` merges publish a patch: cadence, version-bump provenance, token custody, the pre-publish no-leak gate). Until it lands, the subscription simply picks up whatever the owner publishes, which is safe and useful on day one.
51
+ - **No behavior change until armed.** OTB (and any fresh instance) ships this OFF with an empty roster; nothing auto-upgrades until an operator both arms autonomy and enrolls an instance.
@@ -0,0 +1,40 @@
1
+ # 0137 — Upstream publish policy: `@bongos/core` patches are published manually (owner-gated), not auto-published on merge
2
+
3
+ - **Status:** Accepted
4
+ - **Date:** 2026-07-10
5
+ - **Deciders:** example-owner (owner/Archon), Claude. Presented the manual-vs-auto fork; the owner chose to keep publishing a manual, owner-triggered step.
6
+ - **Task:** [#2158](https://example.com/builders#/task/2158) (BONGOS-V1, goal 26) — the **supply** half split out of [ADR 0136](<redacted>.md) §5 / [#2150](https://example.com/builders#/task/2150).
7
+ - **Builds on:** [ADR 0134](<redacted>.md) (private-first npm, tokenless tooling), [ADR 0136](<redacted>.md) (subscription consumption policy). **Relates to:** the deferred managed-fleet-control-plane epic ([#1948](https://example.com/builders#/task/1948)).
8
+
9
+ ## Context
10
+
11
+ [ADR 0136](<redacted>.md) (task 2150) built the **consumption** half of the `@bongos/core` update subscription — the channel math (`pinned`/`patch`/`minor`, never `major`/prerelease unattended) and the auto-upgrade routine that consumes published releases on a cadence. It explicitly deferred (§5) the **supply** half: *"does not decide whether merges to `main` auto-publish a patch. That is its own decision (publishing cadence, provenance, who holds the token)."* This ADR is that decision.
12
+
13
+ Today, per [ADR 0134](<redacted>.md), publishing is a **gated owner step**: `node scripts/gds/package-core.js --publish --live` requires the owner's `npm login` + 2FA, and *the repo/tooling holds no token* ("npm owns auth — this repo never stores a token"). Consumers install with a **read-only** token via an env-fed `.npmrc`. For a subscription to deliver anything, new patches must be published — so the open question was whether to **automate** that publish (on-merge, using a CI-stored write token) or keep it **manual**.
14
+
15
+ ## Decision
16
+
17
+ **Publishing `@bongos/core` stays a manual, owner-gated step. Merges to `main` do NOT auto-publish.**
18
+
19
+ 1. **Trigger / cadence — owner-initiated.** The owner runs `package-core.js --publish --live` when they decide to cut a release. Recommended convention: tag the released commit `core-v<version>` as the deliberate, auditable release marker. The tag *documents* the release; it does **not** trigger CI.
20
+ 2. **Version-bump provenance — deliberate, in-task.** `CORE_VERSION` (`src/module-api.js`) is bumped by hand as part of a task, with a changelog line in the same file (the existing discipline — e.g. task 2148 bumped `1.17.1 → 1.17.2` with a changelog entry). It is **not** auto-bumped. A release publishes whatever `CORE_VERSION` currently is; a stale (un-bumped) version collides with the already-published npm version and npm rejects the duplicate — a natural guard against an accidental no-op republish.
21
+ 3. **Token custody — no write-token at rest.** No npm **write** token is stored in CI or the repo. The owner authenticates at publish time (a Classic Automation token bypasses the interactive 2FA-OTP prompt when scripting locally; interactive `npm login` + 2FA otherwise). This preserves ADR 0134's tokenless-tooling posture. Only the **read** token (for consumer / CI installs, env-fed `NPM_TOKEN`) exists.
22
+ 4. **No-leak / mirror-redact gate — already fail-closed.** `package-core.js` builds the artifact *through* the no-leak gate (docs genericized + secret-scrubbed; functional files scanned for a live-credential leak) and **produces nothing** if anything leaks, so `--publish` structurally cannot upload a leaking artifact. No extra gate is needed for the manual path — and any future automated path inherits the same fail-closed gate because it runs the same build.
23
+ 5. **The subscription is unchanged.** The [ADR 0136](<redacted>.md) routine consumes whatever the owner has published. Its freshness is bounded by the owner's release cadence — intended and acceptable at current scale.
24
+
25
+ ## Why manual over auto (the trade-off)
26
+
27
+ - **Security surface.** <redacted> requires a long-lived npm **automation write** token as a standing CI secret — a powerful credential that could publish to the canonical brand org from any merge. That reverses ADR 0134's deliberate tokenless-tooling posture for little near-term gain.
28
+ - **Low, deliberate volume.** Core patches are infrequent and intentional (`1.17.0 → .1 → .2` over days, each driven by a specific task). The manual publish is not the bottleneck; the consumption side already flows.
29
+ - **Bootstrapped posture** ([CLAUDE.md §4](../../CLAUDE.md)). Don't build and secure CI-publishing machinery before a real fleet justifies it.
30
+
31
+ ## When to revisit
32
+
33
+ When the **managed-fleet-control-plane epic** ([#1948](https://example.com/builders#/task/1948)) stands up a real multi-instance fleet and the manual publish becomes a genuine bottleneck, re-evaluate a **tag-triggered** CI publish (a human still cuts the `core-v<version>` tag; CI runs the already-fail-closed gated publish with a narrowly-scoped token). That is deliberately out of scope here — this ADR keeps the supply side simple so goal 26 closes on the consumption half.
34
+
35
+ ## Consequences
36
+
37
+ - **Owner-only step at each release** (out of a build session; capture as a blocker if it ever gates verification): bump `CORE_VERSION` + changelog in a task, then run `package-core.js --publish --live`.
38
+ - The **release ritual** is documented in [`docs/recipes/private-npm-distribution.md`](../recipes/private-npm-distribution.md) → "Cutting a core release".
39
+ - **No CI secret, no new automation** to maintain.
40
+ - A consumer on the `patch`/`minor` channel only receives an update **after** the owner publishes — the subscription's freshness is owner-paced by design.
@@ -0,0 +1,45 @@
1
+ # 0138 — A core-bundled module's external npm deps are declared per-module; the consumer composes them (core stays lean)
2
+
3
+ - **Status:** Accepted
4
+ - **Date:** 2026-07-10
5
+ - **Deciders:** example-owner (owner/Archon), Claude. Presented the fork (keep core lean + declare per-module vs. hoist the dep into `@bongos/core`); the owner chose to keep core lean.
6
+ - **Task:** [#2160](https://example.com/builders#/task/2160) (BONGOS-V1, goal 26) — rehearsal finding **F2** from the R89 cutover rehearsal ([`docs/reviews/<redacted>.md`](../reviews/<redacted>.md)).
7
+ - **Builds on:** [ADR 0108](<redacted>.md) (core consumed as a pinned npm dep; core deps = express+pg), [ADR 0083](<redacted>.md) (the module manifest contract).
8
+
9
+ ## Context
10
+
11
+ The R89 cutover rehearsal converted the example monolith into a `@bongos/core` consumer and surfaced **F2**: with `dependencies: { "@bongos/core" }` alone the consumer boots and then crashes on `Cannot find module 'colyseus'` — and, more subtly, on `Cannot find module 'discord.js'`. The game/art deps (`colyseus`, `phaser`, `sharp`) are obviously the host's. But `discord.js` is different: the **discord module ships INSIDE `@bongos/core`** (it's one of the ~15 bundled core modules), yet `package-core.js` synthesizes only `express` + `pg` as the core's `dependencies` ([ADR 0108](<redacted>.md) §Context). So the core ships the discord module's *code* but declares none of its *runtime dep* — the consumer is silently expected to provide `discord.js`, with a boot crash as the only feedback.
12
+
13
+ Two facts frame the decision:
14
+
15
+ - **`discord.js` is the only such case today.** A scan of every bundled core module found exactly one external npm require beyond `express`/node built-ins: `discord.js`, in the discord module.
16
+ - **The discord module is opt-in** (`module.json` `default: false`). Most instances never enable it.
17
+
18
+ So the open question (rehearsal F2, "separately decide"): should `@bongos/core` declare its bundled-module deps (e.g. `discord.js`) itself — as `optionalDependencies`/`peerDependencies` — or should the consumer provide them?
19
+
20
+ ## Decision
21
+
22
+ **`@bongos/core` stays lean (`express` + `pg`). A bundled module that needs an external npm dependency declares it in its own `module.json` `dependencies`, and the consumer composes the deps of the modules it ENABLES.**
23
+
24
+ 1. **Core deps unchanged.** `package-core.js` continues to synthesize `dependencies: { express, pg }` (+ the undici pin). We do **not** hoist `discord.js` (or any future module dep) into the core package.
25
+ 2. **Manifest field.** `module.json` gains an optional `dependencies: { "<npm-name>": "<semver-range>" }` block ([`src/module-loader/manifest-schema.js`](../../src/module-loader/manifest-schema.js) validates it — a typo'd range is caught, not silently ignored). The discord manifest declares `{ "discord.js": "^14.26.4" }` — the honest home for the fact.
26
+ 3. **Consumer composition.** Greenfield `bongos init` composes the ENABLED modules' declared deps into the instance `package.json` ([`scripts/gds/init.js`](../../scripts/gds/init.js) `enabledModuleDeps` → `buildInstancePackageJson`), so turning a module on brings its npm deps. Precedence: module deps fill gaps, an explicit pre-existing instance pin wins, `@bongos/core` is always set.
27
+ 4. **The cutover preserves.** The example R89 cutover re-authors the consumer `package.json` from the monolith's **existing** dependency closure ∪ `@bongos/core` (runbook §2) — the monolith already carries `discord.js` + the game/art deps, so preserving is sufficient there. `enabledModuleDeps` is the greenfield/adopt path, not the conversion path.
28
+
29
+ ## Why lean-core over hoisting (the trade-off)
30
+
31
+ - **Hoisting bloats the common case.** `optionalDependencies` still install by default, so declaring `discord.js` in core makes **every** consumer download it (and its transitive `undici`, WebSocket stack, …) for a module that is **off by default**. That directly cuts against [ADR 0108](<redacted>.md)'s deliberate lean-core choice.
32
+ - **`peerDependencies` don't fix the crash.** An *optional* peer dep is not auto-installed by npm, so the boot crash would remain; a *required* peer dep is just hoisting with extra warnings.
33
+ - **Per-module declaration is honest and scales.** The dep lives with the code that needs it, the manifest stays the single source of truth for "what this module is," and the pattern generalizes to any future module without re-touching core.
34
+ - **`--omit=optional` is opt-out, not opt-in.** Relying on consumers to prune an unwanted dep is the wrong default; composing only enabled modules' deps is opt-in by construction.
35
+
36
+ ## Consequences
37
+
38
+ - **`@bongos/core`'s dependency surface stays minimal + auditable** — the property [ADR 0108](<redacted>.md) and `package_core.mjs` (`dependencies == { express, pg }`) already lock in.
39
+ - **A bundled module with an external dep MUST declare it** in `module.json` `dependencies`, or a consumer that enables it crashes at boot. Documented in [`docs/modules-contract.md`](../modules-contract.md).
40
+ - **Greenfield/adopt** instances that enable a module get its deps automatically; the **R89 cutover** stays a preserve-existing-closure step.
41
+ - **No new standing credential or CI machinery** — this is authoring-time composition, not an install-time core change.
42
+
43
+ ## When to revisit
44
+
45
+ If a bundled module ever becomes `default: true` AND carries a heavy external dep, revisit whether that module belongs in the core bundle at all (vs. an opt-in add-on) — the dep-posture question would then be downstream of a module-bundling question.