@bongos/core 1.17.0 → 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 (230) hide show
  1. package/.bongos-core.json +363 -312
  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/CLAUDE.md +11 -238
  15. package/GOVERNANCE.md +1 -1
  16. package/README.md +1 -1
  17. package/bin/bongos.js +1 -0
  18. package/clients/bongos-client/README.md +4 -4
  19. package/clients/bongos-client/bongos-client.global.js +14 -1
  20. package/clients/bongos-client/index.cjs +14 -1
  21. package/clients/bongos-client/index.d.ts +19 -2
  22. package/clients/bongos-client/index.mjs +11 -1
  23. package/clients/bongos-client/package.json +1 -1
  24. package/docs/adr/0001-stack-choice-single-vps.md +1 -1
  25. package/docs/adr/0008-google-chat-oauth-user-auth.md +1 -1
  26. package/docs/adr/0019-red-team-bounty-payout-table.md +3 -3
  27. package/docs/adr/0020-security-model-public-stub.md +1 -1
  28. package/docs/adr/0023-kind-multipliers-and-idea-bonus.md +1 -1
  29. package/docs/adr/0024-cloneable-repo-local-first-memory.md +1 -1
  30. package/docs/adr/0026-bfg-memory-hygiene-and-cross-builder-learning.md +4 -4
  31. package/docs/adr/0027-bfg-session-inefficiency-evaluator.md +3 -3
  32. package/docs/adr/0030-builders-hall-live-updates.md +1 -1
  33. package/docs/adr/0031-cloud-dev-environments-for-builders.md +4 -4
  34. package/docs/adr/0032-discord-mirror-of-gds.md +2 -2
  35. package/docs/adr/0034-thetes-graduated-newcomer-rank.md +1 -1
  36. package/docs/adr/0044-mediterranean-palette-replacement.md +1 -1
  37. package/docs/adr/0044-per-box-live-game-preview.md +1 -1
  38. package/docs/adr/0054-session-token-reward-cost-plus.md +10 -10
  39. package/docs/adr/0057-container-cost-ledger.md +3 -3
  40. package/docs/adr/0062-medusa-instance-model-decoupling.md +4 -4
  41. package/docs/adr/0065-agpl-nonprofit-ai-first-governance.md +2 -2
  42. package/docs/adr/0085-remove-default-per-builder-spend-cap.md +1 -1
  43. package/docs/adr/0086-goal-scoped-work-hierarchy.md +1 -1
  44. package/docs/adr/0096-require-reward-before-workable.md +9 -9
  45. package/docs/adr/0097-retroactive-reward-backfill.md +3 -3
  46. package/docs/adr/0100-two-repo-instance-model.md +1 -1
  47. package/docs/adr/0101-goal-hierarchy-phase-5-judge-membership-reward.md +3 -3
  48. package/docs/adr/0103-core-first-extraction-cloud-bongos-trunk.md +3 -3
  49. package/docs/adr/0107-module-upstreaming-contribution-to-core-catalog.md +1 -1
  50. package/docs/adr/0120-pay-on-land-and-builder-owned-rebase-gate.md +6 -6
  51. package/docs/adr/0133-github-app-manifest-onboarding-signin.md +2 -0
  52. package/docs/adr/0134-private-first-npm-distribution.md +20 -6
  53. package/docs/adr/0135-module-upstream-submission-interim-queue.md +34 -0
  54. package/docs/adr/0136-update-channel-subscription-policy.md +51 -0
  55. package/docs/adr/0137-upstream-publish-policy-manual-owner-gated.md +40 -0
  56. package/docs/adr/0138-bundled-module-deps-declared-per-module.md +45 -0
  57. package/docs/adr/README.md +14 -10
  58. package/docs/api/openapi.json +315 -6
  59. package/docs/api-reference.md +10 -5
  60. package/docs/architecture.md +3 -3
  61. package/docs/branding-contract.md +5 -4
  62. package/docs/canonical-permissions.md +1 -1
  63. package/docs/design/vanilla-hall-ui-redesign-scope.md +1 -1
  64. package/docs/file-map.md +20 -19
  65. package/docs/handoff-template.md +2 -2
  66. package/docs/modules-contract.md +8 -1
  67. package/docs/onboarding/diagrams/README.md +7 -7
  68. package/docs/onboarding/drachmae-and-karma.md +18 -18
  69. package/docs/onboarding/primer.md +9 -9
  70. package/docs/recipes/audit-replay.md +2 -2
  71. package/docs/recipes/builder-box-lifecycle.md +2 -2
  72. package/docs/recipes/claude-design-loop.md +1 -1
  73. package/docs/recipes/instance-deploy.md +4 -4
  74. package/docs/recipes/instance-first-run.md +51 -0
  75. package/docs/recipes/ops-gotchas.md +1 -1
  76. package/docs/recipes/packaging-the-core.md +5 -5
  77. package/docs/recipes/private-npm-distribution.md +39 -12
  78. package/docs/recipes/self-host.md +1 -1
  79. package/docs/recipes/staging-environment.md +1 -1
  80. package/docs/recipes/upgrading-the-core.md +63 -8
  81. package/docs/research/cursor-competitive-analysis.md +1 -1
  82. package/docs/research/llm-model-agnostic-and-cheaper-grader.md +1 -1
  83. package/migrations/core_191_module_submissions.sql +45 -0
  84. package/modules/autonomy/CLAUDE.md +11 -45
  85. package/modules/builder-settings/CLAUDE.md +11 -54
  86. package/modules/dev-box/CLAUDE.md +11 -34
  87. package/modules/discord/CLAUDE.md +11 -46
  88. package/modules/discord/module.json +2 -1
  89. package/modules/economy/CLAUDE.md +11 -44
  90. package/modules/economy/achievements.js +9 -1
  91. package/modules/economy/credits.js +10 -1
  92. package/modules/grading/CLAUDE.md +11 -38
  93. package/modules/hall-ui/CLAUDE.md +11 -57
  94. package/modules/hall-ui/public/builders.js +71 -4
  95. package/modules/hall-ui/public/goal-inbox.js +191 -0
  96. package/modules/hall-ui/public/goals-page.js +227 -22
  97. package/modules/hall-ui/public/goals.html +16 -0
  98. package/modules/hall-ui/public/hall-widgets.js +14 -1
  99. package/modules/hall-ui/public/index.html +3 -0
  100. package/modules/hall-ui/public/new-project-wizard.js +6 -4
  101. package/modules/hall-ui/public/style.css +43 -0
  102. package/modules/ideas/CLAUDE.md +11 -33
  103. package/modules/lifecycle/CLAUDE.md +11 -59
  104. package/modules/lifecycle/routes/goals.js +80 -0
  105. package/modules/memory/CLAUDE.md +11 -36
  106. package/modules/onboarding/CLAUDE.md +11 -51
  107. package/modules/provisioning/CLAUDE.md +15 -0
  108. package/modules/provisioning/migrations/provisioning_001_tables.sql +160 -0
  109. package/modules/provisioning/migrations/provisioning_002_standalone_shape.sql +11 -0
  110. package/modules/provisioning/migrations/provisioning_003_github_app_manifest.sql +95 -0
  111. package/modules/provisioning/module.json +17 -0
  112. package/modules/provisioning/onboard-plan.js +37 -0
  113. package/modules/provisioning/provisioning.js +539 -0
  114. package/modules/provisioning/rate-limit.js +35 -0
  115. package/modules/provisioning/routes/provisioning.js +607 -0
  116. package/modules/provisioning/tests/provisioning.mjs +232 -0
  117. package/modules/provisioning/ui/.gitkeep +0 -0
  118. package/modules/security/CLAUDE.md +11 -40
  119. package/modules/security/secret-scrub.js +10 -0
  120. package/modules/sessions/CLAUDE.md +11 -35
  121. package/modules/status-ui/CLAUDE.md +11 -43
  122. package/package-lock.json +2 -2
  123. package/package.json +7 -2
  124. package/public-landing/create.html +58 -12
  125. package/public-landing/index.html +40 -21
  126. package/scripts/discord/README.md +1 -1
  127. package/scripts/gds/CLAUDE.md +11 -140
  128. package/scripts/gds/claim.js +98 -48
  129. package/scripts/gds/claude-materialize.js +174 -20
  130. package/scripts/gds/claude-md-template.js +84 -0
  131. package/scripts/gds/cli-lib.js +112 -2
  132. package/scripts/gds/cutover-codemod.js +241 -0
  133. package/scripts/gds/fitness.js +54 -0
  134. package/scripts/gds/gen-api-client.js +10 -10
  135. package/scripts/gds/init.js +112 -16
  136. package/scripts/gds/lint-infra-exec.js +70 -0
  137. package/scripts/gds/lint-workflow-exec.js +63 -0
  138. package/scripts/gds/migration-namespace.js +1 -1
  139. package/scripts/gds/mirror-lineage-check.js +14 -5
  140. package/scripts/gds/mirror-redact.js +8 -0
  141. package/scripts/gds/module.js +118 -4
  142. package/scripts/gds/npmrc.js +93 -0
  143. package/scripts/gds/onboard.js +17 -24
  144. package/scripts/gds/package-core.js +74 -17
  145. package/scripts/gds/provision.js +102 -17
  146. package/scripts/gds/publish-manifest.js +60 -2
  147. package/scripts/gds/run-routine.js +51 -13
  148. package/scripts/gds/run-unit-tests.js +4 -0
  149. package/scripts/gds/seed-adopt-tasks.js +1 -1
  150. package/scripts/gds/status.js +6 -1
  151. package/scripts/gds/task.js +166 -0
  152. package/scripts/gds/update-channel.js +186 -0
  153. package/scripts/gds/upgrade.js +172 -25
  154. package/src/bongos/CLAUDE.md +11 -60
  155. package/src/bongos/auth.js +24 -0
  156. package/src/bongos/module-submissions.js +56 -0
  157. package/src/bongos/route-rank-check.js +6 -0
  158. package/src/bongos/routes/CLAUDE.md +11 -39
  159. package/src/bongos/routes/auth.js +6 -1
  160. package/src/bongos/routes/modules.js +123 -21
  161. package/src/bongos/serve-internal.js +10 -1
  162. package/src/branding.js +1 -0
  163. package/src/instance-config.js +1 -1
  164. package/src/module-api.js +24 -1
  165. package/src/module-loader/loader.js +36 -4
  166. package/src/module-loader/manifest-schema.js +16 -1
  167. package/tests/branding.mjs +11 -0
  168. package/tests/claim_error_surface.mjs +107 -0
  169. package/tests/claude_materialize.mjs +125 -0
  170. package/tests/consumer_layout_boot.mjs +302 -0
  171. package/tests/cutover_codemod.mjs +184 -0
  172. package/tests/device_flow_start.mjs +96 -0
  173. package/tests/first_run_engine_check.mjs +79 -0
  174. package/tests/goal_membership_requests.mjs +33 -3
  175. package/tests/hook_core_resolve.mjs +125 -0
  176. package/tests/hook_user_agents.mjs +62 -0
  177. package/tests/infra_exec_paths.mjs +104 -0
  178. package/tests/init.mjs +106 -6
  179. package/tests/instance_mismatch_guard.mjs +101 -0
  180. package/tests/mirror_lineage_proof.mjs +14 -0
  181. package/tests/module_cli.mjs +130 -2
  182. package/tests/module_loader.mjs +42 -0
  183. package/tests/module_manifest.mjs +19 -0
  184. package/tests/module_submissions.mjs +135 -0
  185. package/tests/npmrc.mjs +105 -0
  186. package/tests/onboard.mjs +62 -0
  187. package/tests/package_core.mjs +76 -22
  188. package/tests/platform_boot.mjs +5 -2
  189. package/tests/provision.mjs +107 -8
  190. package/tests/provisioning_github_app_routes.mjs +44 -2
  191. package/tests/publish_manifest.mjs +52 -1
  192. package/tests/run_routine.mjs +26 -1
  193. package/tests/run_routine_two_root.mjs +77 -0
  194. package/tests/scrubber_corpus.mjs +17 -0
  195. package/tests/served_paths_shipped.mjs +2 -2
  196. package/tests/task_cli.mjs +99 -0
  197. package/tests/update_channel.mjs +186 -0
  198. package/tests/upgrade.mjs +220 -25
  199. package/tests/workflow_exec_paths.mjs +98 -0
  200. package/docs/design/container-exterior-tiles.md +0 -49
  201. package/docs/design/container-exterior.md +0 -81
  202. package/docs/design/container-interior.md +0 -77
  203. package/docs/design/world-map-scroll.md +0 -41
  204. package/docs/recipes/claude-gemini-bongos-doors.md +0 -155
  205. package/docs/recipes/cloudbongos-staging-gate.md +0 -139
  206. package/docs/recipes/cloudbongos-standup.md +0 -111
  207. package/docs/recipes/parallel-load-results-2026-05-30.md +0 -63
  208. package/docs/recipes/parallel-load-test.md +0 -32
  209. package/docs/recipes/pixel-art-pipeline.md +0 -411
  210. package/docs/recipes/staging-data.md +0 -50
  211. package/migrations/instance/018_pms_v3_planning_seed.sql +0 -131
  212. package/migrations/instance/080_gds_v4_planning_seed.sql +0 -75
  213. package/migrations/instance/110_medusa_planning_seed.sql +0 -70
  214. package/migrations/instance/120_bongos_v1_planning_seed.sql +0 -69
  215. package/migrations/instance/144_bongos_v1_module_system_criterion.sql +0 -26
  216. package/migrations/instance/159_bongos_v1_goal_hierarchy_criterion.sql +0 -29
  217. package/migrations/instance/172_v2_world_planning_seed.sql +0 -163
  218. package/migrations/instance/173_bongos_v1_c7_core_modularized.sql +0 -33
  219. package/migrations/instance/182_gdsv4_bongos_consolidation.sql +0 -132
  220. package/migrations/instance/183_gdsv4_carryover_themed_goals.sql +0 -116
  221. package/migrations/instance/186_bongos_v1_governance_planning_seed.sql +0 -69
  222. package/scripts/gds/art-stage.js +0 -152
  223. package/server.js +0 -212
  224. package/tests/art_key.mjs +0 -121
  225. package/tests/day_cycle.mjs +0 -197
  226. package/tests/snapshot_picker.mjs +0 -233
  227. package/tests/sprite_facing.mjs +0 -35
  228. package/tests/terrain_picker.mjs +0 -298
  229. package/tests/turn_in_place.mjs +0 -280
  230. package/tests/walkability.mjs +0 -188
@@ -1,77 +0,0 @@
1
- # Container building — interior atmosphere design (V2)
2
-
3
- - **Task:** [#645](https://example.com/builders#/task/645) (V2 · product · creative). Promoted from idea_inbox [#164](https://example.com/builders#/idea/164) (builder 25 art/design brief, 2026-06-04).
4
- - **Date:** 2026-06-21
5
- - **Builds on:** [`container-exterior.md`](container-exterior.md) — this is the **interior** counterpart of that structure-level brief. The granular interior layers it calls out are the **layout** ([#642](https://example.com/builders#/task/642)) and the **floor-tile pixel spec** ([#644](https://example.com/builders#/task/644)); palette is governed by [ADR 0044](../adr/0044-mediterranean-palette-replacement.md).
6
- - **Status:** Atmosphere direction locked. Colour reconciled to ADR 0044 (below). Tile generation waits for [#640](https://example.com/builders#/task/640) / ADR 0044, same sequencing rule as the exterior.
7
-
8
- ## ⚠️ Colour: the source brief is superseded — Mediterranean palette, not Poke-Mart blue
9
-
10
- idea_inbox [#164](https://example.com/builders#/idea/164)'s brief (this task's source, 2026-06-04) built the interior on a **saturated Pokémon "Poke Mart" blue wave-scale floor** and **orange-red container walls** as the nostalgia anchor. That was written **before** the palette was locked. The authoritative decisions win (exactly as the exterior briefs defer):
11
-
12
- - **[ADR 0044](../adr/0044-mediterranean-palette-replacement.md)** (2026-06-10): the world has **one Mediterranean palette**, and it states explicitly — *"there is no 'Poke Mart interior' context that needs the temperate palette."* So the interior is **not** a palette exception; it shares the world's hues.
13
- - **Blocker [#7](https://example.com/builders#/blocker/7)** (2026-06-10, locked `style_guide.md` rule): the container body is **weathered black/charcoal corrugated metal**, not red/orange. The interior-facing walls are the same shell.
14
- - **Lars (2026-06-21, this session):** Mediterranean governs the interior. The layered *geometry* survives untouched — only the hues are corrected.
15
-
16
- Concretely (geometry preserved, hue corrected — the exact move [`container-exterior-tiles.md`](container-exterior-tiles.md) made for the facade):
17
-
18
- | Source brief's colour | Corrected (Mediterranean) |
19
- |---|---|
20
- | Saturated Poke-Mart **blue** wave floor | **Aqua / turquoise** wave-scale, from the palette's turquoise-sea steps — the *pattern* is the nostalgia anchor; the *hue* matches the sea just outside the glass |
21
- | **Orange-red** container walls | **Weathered charcoal** corrugated (blocker [#7](https://example.com/builders#/blocker/7)) — warmth comes from the *lighting*, not the paint |
22
- | Lush **forest-green** featured rug | Deep **olive / sage** rug (the palette's darkest greens) — still "rich and set apart," in-palette |
23
- | Pure-**white** shelving | **Cream / pale-stone** (warm-neutral steps); no white edge rim, 1-px darker object outline (style rules) |
24
-
25
- > **[#642](https://example.com/builders#/task/642) / [#644](https://example.com/builders#/task/644) note:** both still say "blue" floor; [#644](https://example.com/builders#/task/644)'s title already hedges to "aqua-tinted." Read their "blue" as this brief's Mediterranean turquoise/aqua — this doc is the colour authority for the interior, the way `container-exterior.md` is for the facade.
26
-
27
- ## The unified interior — a layered space
28
-
29
- Three layers on three surfaces, none fighting because each owns a different plane:
30
-
31
- 1. **Shell (walls):** raw weathered-charcoal corrugated container — the industrial bones are never hidden. Double-height: tall wall columns, a second-floor balcony railing along the top of the map, pendant lamps on cables from a high ceiling.
32
- 2. **Floor (the centerpiece):** aqua/turquoise wave-scale tile — the nostalgia geometry underfoot, in Mediterranean hue.
33
- 3. **Retail layer (objects + light):** boutique principles — items displayed like sculpture with as much negative space as product; warm directional track lighting makes the illuminated patches the brightest things in the room.
34
-
35
- You walk into a space that *feels* like a game (the floor pattern), *looks* like a container (the charcoal shell), and *shops* like a boutique (the sculptural, warm-lit display). That triple recognition in the first two seconds is the emotional design goal — unchanged by the palette correction, because it lives in **pattern, material, and light**, not in saturation.
36
-
37
- ## Architecture extracted from the references (geometry — all survives)
38
-
39
- The source brief studied four interior reference images (idea_inbox [#164](https://example.com/builders#/idea/164)); the *geometry* below is the durable takeaway, independent of hue.
40
-
41
- - **Double-height shell** (Interior1/2): two storeys of open vertical space → taller wall columns, a balcony-railing band at the top of the map, pendant lamps on cables from a high ceiling.
42
- - **Symmetrical industrial staircases** (Interior1): black-metal flights on the left/right walls — key navigation + vertical drama. Visual/blocking in V2; walkable upper floor is a later version.
43
- - **Warm pooled light** (Interior2): tall warm lamps and track lights pool on the floor — 2–3 shade steps radiating from each source. This is where *all* the warmth now comes from, since the walls are charcoal.
44
- - **Sculptural display** (interior3): freestanding display tables (gallery plinths, not Poke-Mart shelves), few items, generous spacing. Negative space ≈ product — the single biggest luxury signal.
45
- - **Featured "room within a room"** (interiorShelving1): an architecturally framed VIP zone with a deep olive/sage rug and warm ochre/gold fixtures — the inner sanctum / featured-product zone.
46
- - **Rear glass panel** (Interior1): full-width north-wall glass showing the coast beyond — ties the interior back to the Mediterranean exterior (and reuses the facade's glass language).
47
-
48
- ## Key tile assets (priority order)
49
-
50
- 1. **Aqua wave-scale floor tile** — the foundational tile; pixel spec is [#644](https://example.com/builders#/task/644).
51
- 2. **Interior container wall tile** — weathered charcoal corrugated (interior-facing variant of the exterior wall family).
52
- 3. **Cream shelving unit** — wall-mounted, pale-stone, items displayed; no white rim, 1-px outline.
53
- 4. **Track-light tile** — small dark ceiling/beam fixture + a warm cone splashing onto the surface below.
54
- 5. **Display-table object overlay** — freestanding wood top, few items, spaced.
55
- 6. **Deep olive/sage featured rug** — the premium-zone floor (alpha-tile overlay over the wave floor).
56
- 7. **Industrial pendant lamp** — hung from above, warm light source.
57
- 8. **Balcony railing** — dark metal, top of the map (second-storey read).
58
- 9. **Polished-concrete floor** — grey, smooth, for back-of-house / secondary areas.
59
- 10. **Rear glass panel** — full-width north wall, coast visible through it.
60
-
61
- ## Pipeline mapping (generate after [#640](https://example.com/builders#/task/640) / ADR 0044)
62
-
63
- Per [ADR 0008](../adr/<redacted>.md) (families, not per-tile calls):
64
-
65
- 1. **`interior_container_shell`** family — wall · balcony railing · staircase · pendant lamp.
66
- 2. **`interior_floor`** family — aqua wave-scale + polished-concrete, with the olive/sage rug as an alpha-tile overlay (the `marble_path` technique, [ADR 0013](../adr/<redacted>.md)).
67
- 3. **Display singletons** — table, shelving, track light, glass panel — per-tile.
68
-
69
- **Nothing generates until the palette swap ([#640](https://example.com/builders#/task/640) / ADR 0044) lands** — every tile would re-quantize against the old palette and be redone, the same sequencing rule the exterior brief states.
70
-
71
- ## Mood target
72
-
73
- Walk in and feel like you discovered something — not a supermarket, not an unaffordable boutique, not a video-game shop, but all three at once. The charcoal shell says *industrial secret*; the aqua wave floor says *I know this place*; the spaced, warm-lit display says *this is worth something*. The floor now reads as a **sun-bleached aqua wave** rather than an arcade-saturated blue, which is the point: it belongs to the same coast you can see through the rear glass.
74
-
75
- ## Engineering follow-up (not this task)
76
-
77
- A walkable interior needs a second world map / room and a door transition (the `container-exterior.md` entrance door leads here), plus interior OBJECT ids in **both** `map.js`/`terrain.js` twins (server + client). File as an engineering task once the interior layout ([#642](https://example.com/builders#/task/642)) and floor tile ([#644](https://example.com/builders#/task/644)) are final.
@@ -1,41 +0,0 @@
1
- # World map — the "Sealed Ruins Chart" (diegetic artifact, V2)
2
-
3
- - **Task:** [#648](https://example.com/builders#/task/648) (V2 · product). Promoted from idea_inbox [#169](https://example.com/builders#/idea/169) (builder 25 art/design brief, 2026-06-04).
4
- - **Date:** 2026-06-18
5
- - **Status:** Art + design spec ready. Two follow-ups it implies are separate tasks: the **diegetic-item mechanic** (engineering) and **tile generation** (waits on the palette swap, [#640](https://example.com/builders#/task/640)/[ADR 0044](../adr/0044-mediterranean-palette-replacement.md)). See *Follow-ups* below.
6
-
7
- ## Concept
8
-
9
- The world map is **not a UI overlay or menu** — it is a **diegetic in-game artifact**: an ancient sealed chart the player *discovers*. This reframes navigation from a game mechanic into a story beat. The in-world item name is the **"Sealed Ruins Chart"** — "sealed" is load-bearing: the map is a mystery; not everything is visible from the start.
10
-
11
- > Reference concept art (builder-25 planning input): `art/references/concept-art-original/world-map/Map Item Pop Up Concept.webp` + `Shipping container Concept.png`. **NB:** these planning images are not committed to the repo (the `art/references/concept-art-original/` tree is empty here); this written spec is the durable, self-contained source.
12
-
13
- ## Behaviour (the "diegetic" part — engineering follow-up, not this art task)
14
-
15
- - The player does **not** open a menu. They find / are given the scroll — an inventory item, like the FireRed Town Map received from a character.
16
- - Examining it plays an **unroll animation**.
17
- - It is **progressive / fog-of-war**: sealed areas begin as blank parchment and fill in as the player explores. (This is the "sealed" mystery made mechanical.)
18
-
19
- ## Scroll art spec (pixel art)
20
-
21
- - **Rollers (top + bottom):** dark mahogany brown, visible wood grain (2–3 horizontal pixel lines per plank); cylindrical implied by a lighter highlight strip along the top edge.
22
- - **Corner accents:** round emerald-green gemstone insets, 8×8px each, single light highlight pixel upper-left.
23
- - **Parchment:** aged warm tan, **not uniform** — subtle darker age/wear patches; slight edge vignette.
24
- - **Title banner:** a rectangular banner at the top, slightly darker parchment, **"SEALED RUINS CHART"** in a serif pixel font (dark brown, not black).
25
- - **Footer:** bottom roller repeats the title left + right with a centred **emblem** between — a 32×32 pixel creature (dragon / sea-monster), dark-outline style.
26
-
27
- ### Map surface — the swirling currents (what makes it feel ancient)
28
- The parchment is densely covered in hand-drawn-style arrows + curved flowing lines — **ocean-current / wind notations**, the language of old nautical charts (NOT paths):
29
- - thin 1px dark-brown curved, directional lines; arrow tips = simple 2–3px chevrons;
30
- - density ≈ one arrow/curve segment per 16×16 area over **sea**; **land** areas sparser;
31
- - this texture — organic, dense, directional — is what reads as *ancient + discovered*, not *designed*.
32
-
33
- ## Colour note (palette)
34
- The hexes in the source brief (mahogany, emerald, #D4A96A parchment) are **targets, not literals**: when generated, every pixel quantizes to the locked 64-colour palette — and that palette is being **replaced** ([#640](https://example.com/builders#/task/640) / [ADR 0044](../adr/0044-mediterranean-palette-replacement.md)). The replacement must carry warm-tan / mahogany / emerald steps for this artifact; flag if it doesn't. This is a UI/item artifact (not a world ground tile), so it is less palette-constrained than terrain, but it should still read in-world.
35
-
36
- ## Mood target
37
- The player should feel they found something they were **not supposed to find** — a real object from another time, not a game menu. First unroll = one of the most memorable moments in the game.
38
-
39
- ## Follow-ups (separate tasks, not this art spec)
40
- 1. **Diegetic-item mechanic** (engineering): inventory item + examine→unroll animation + progressive fog-of-war reveal keyed to discovered areas. File when the inventory/item system exists (relates to the V3 purchase-loop / bag work, e.g. [#635](https://example.com/builders#/task/635)).
41
- 2. **Scroll tile/sprite generation** (art pipeline): produce the scroll frame + parchment + emblem assets — **after** the palette swap ([#640](https://example.com/builders#/task/640)) so it isn't re-quantized + redone.
@@ -1,155 +0,0 @@
1
- # Recipe — stand up claudebongos.com + geminibongos.com (vanity doors → cloudbongos.com)
2
-
3
- > Brings **claudebongos.com** and **geminibongos.com** live as public "vanity
4
- > doors": each shows the exact Cloud Bongos landing **gate** (`brand/cloud-bongos/gate`),
5
- > rebranded to **"Claude Bongos" / `> claude`** (resp. **"Gemini Bongos" / `> gemini`**)
6
- > at runtime — the visitor still types `bongos`, and on enter the gate redirects
7
- > to **cloudbongos.com**, landing straight on the orbs. Same task also **promotes
8
- > the gate onto the cloudbongos.com apex** (it replaces the buddha placeholder,
9
- > which is what makes the redirect land "on the orbs"). Task
10
- > [#1746](https://example.com/builders#/task/1746).
11
-
12
- ## Architecture (what we're building)
13
-
14
- One gate bundle, three domains, a runtime switch — **no forked copies**. `space.js`
15
- reads `location.hostname`; a door rebrands the entry + redirects on enter, and the
16
- default (cloudbongos.com) reveals the orbs in place. Any future change to the gate
17
- shows up on all three automatically.
18
-
19
- | Domain | Serves | On typing `bongos` + enter |
20
- |---|---|---|
21
- | **claudebongos.com** | the gate, rebranded "Claude Bongos" / `> claude` | `→ https://cloudbongos.com/?entered=1` (orbs) |
22
- | **geminibongos.com** | the gate, rebranded "Gemini Bongos" / `> gemini` | `→ https://cloudbongos.com/?entered=1` (orbs) |
23
- | **cloudbongos.com** | the gate, "Cloud Bongos" (**was** the buddha page) | reveals the orbs in place |
24
-
25
- Hosting is the **cheapest possible**, same as the staging gate:
26
-
27
- | Thing | Value |
28
- |---|---|
29
- | Served by | Caddy `file_server`, web root `/home/lars/example/brand/cloud-bongos` |
30
- | The two doors | **pure static** — no app process, no port, no password |
31
- | cloudbongos.com apex | static gate for `/` + gate assets; **still proxies** `/api`, `/healthz`, etc. to the platform app (`:3002`) so sign-in keeps working |
32
- | TLS | **one new Cloudflare Origin Cert per new domain** — the cloudbongos cert's SANs do NOT cover claudebongos.com / geminibongos.com |
33
- | Cost | **$0/mo** — same droplet, no new process/port |
34
-
35
- Why static (not a reverse-proxy block): the gate is plain HTML/CSS/JS + PNGs.
36
- There is nothing to run. The `?entered=1` query flag is how a door hands off — the
37
- gate honors it (only on non-door hosts) to skip its own entry and open the orbs.
38
-
39
- Everything in the repo (`space.js`, the `infra/Caddyfile` blocks, the gate art)
40
- auto-deploys to the droplet checkout on merge. What is **NOT** automatic — and is
41
- this recipe — is the per-domain **DNS + Origin Cert** and the **live Caddyfile
42
- merge + reload**. Until those are done, the two domains show a TLS error (expected).
43
-
44
- ## Phase ① — DNS (Cloudflare, ~1 min per domain)
45
-
46
- Both domains are already on Cloudflare. For **each** zone (claudebongos.com,
47
- geminibongos.com):
48
-
49
- 1. Cloudflare → pick the zone → **DNS** → **Add record**.
50
- 2. Type **A**, Name **`@`**, IPv4 **`REDACTED_IP`**, Proxy status **Proxied**
51
- (orange cloud ON), TTL Auto. Save.
52
- 3. Cloudflare → **SSL/TLS** → **Overview** → set encryption mode to **Full (strict)**.
53
-
54
- (Optional: add a **`www`** A record → same IP, proxied, if you want `www.` to work.
55
- The Caddyfile blocks are apex-only today; add `www.claudebongos.com` to the site
56
- line if you add the record.)
57
-
58
- ## Phase ② — Origin Certificate (own domain → own cert)
59
-
60
- Each new domain needs its **own** Origin Cert. For **each** zone:
61
-
62
- Cloudflare → **SSL/TLS → Origin Server → Create Certificate**:
63
- - Hostnames: `claudebongos.com` **and** `*.claudebongos.com` (resp. the gemini pair)
64
- - Let Cloudflare generate the key; **copy both** the certificate and the private key.
65
-
66
- Install on the droplet (operator). Caddy runs as user `caddy` and **must be able to
67
- READ the key** — match the existing `origin.key` ownership (`640 root:caddy`). A
68
- root-only `600` key makes Caddy's reload fail (learned during the cloudbongos
69
- bring-up). The `.crt` can be world-readable.
70
-
71
- ```bash
72
- # claudebongos
73
- sudo install -m 644 -g caddy /dev/stdin /etc/caddy/claudebongos-origin.crt # paste cert, Ctrl-D
74
- sudo install -m 640 -g caddy /dev/stdin /etc/caddy/claudebongos-origin.key # paste key, Ctrl-D
75
- # geminibongos
76
- sudo install -m 644 -g caddy /dev/stdin /etc/caddy/geminibongos-origin.crt # paste cert, Ctrl-D
77
- sudo install -m 640 -g caddy /dev/stdin /etc/caddy/geminibongos-origin.key # paste key, Ctrl-D
78
- ```
79
-
80
- ## Phase ③ — apply the Caddy blocks + reload
81
-
82
- Three blocks ship in `infra/Caddyfile`: the two new **door** blocks
83
- (`claudebongos.com`, `geminibongos.com`) and the **modified `cloudbongos.com`
84
- apex** block (now serves the gate + proxies functional paths). The live
85
- `/etc/caddy/Caddyfile` has drifted slightly from the repo copy, so **do not blindly
86
- `cp` the whole file** — apply the two pieces by hand:
87
-
88
- ```bash
89
- cd /home/lars/example
90
- git pull # or wait for the auto-deploy; the brand/ bundle + infra/Caddyfile land here
91
-
92
- # 1. Append the two door blocks (idempotent: skip if already present).
93
- grep -q '^claudebongos.com {' /etc/caddy/Caddyfile || \
94
- awk '/^# ── Claude Bongos \+ Gemini Bongos — vanity DOORS/{p=1} p' infra/Caddyfile \
95
- | sed '/^# ── Cloud Bongos — STAGING/,$d' >> /etc/caddy/Caddyfile
96
- # (or just open the file and paste the two blocks from infra/Caddyfile — the
97
- # awk above copies from the DOORS comment to end-of-file.)
98
-
99
- # 2. Modify the LIVE cloudbongos.com apex block. Open the file, find the
100
- # `cloudbongos.com {` block, and replace its body — the single
101
- # `reverse_proxy 127.0.0.1:3002 { … }` — with the gate-serving body from
102
- # infra/Caddyfile (root + @hidden + @gate handle + the catch-all reverse_proxy).
103
- # Leave builders.cloudbongos.com and status.cloudbongos.com UNTOUCHED.
104
- sudo nano /etc/caddy/Caddyfile
105
-
106
- # 3. Validate (as root — it must read the 640 origin keys), then reload.
107
- sudo caddy validate --config /etc/caddy/Caddyfile --adapter caddyfile
108
- sudo systemctl reload caddy # fails closed on a bad config
109
- ```
110
-
111
- > A bad config makes `reload` error out and **keep serving the old config** — the
112
- > live sites never go down. If a cert path is wrong, that one site fails to come
113
- > up while the others keep serving.
114
-
115
- ## Phase ④ — verify
116
-
117
- ```bash
118
- # from anywhere — the two doors:
119
- curl -sSL https://claudebongos.com/ | grep -o 'space.css\|gate' # gate HTML served ✓
120
- curl -sSL https://geminibongos.com/ | grep -o 'space.css\|gate' # gate HTML served ✓
121
- curl -sS -o /dev/null -w '%{http_code}\n' https://claudebongos.com/ # 200 (via the /gate/ redirect) ✓
122
-
123
- # cloudbongos.com apex now serves the gate, and the app still answers:
124
- curl -sSL https://cloudbongos.com/ | grep -o 'space.css\|gate' # gate served at apex ✓
125
- curl -sS https://cloudbongos.com/healthz # "ok" — app still reachable ✓
126
- ```
127
-
128
- Then open each in **Chrome or Edge** (the live-glass refraction needs Chromium
129
- `backdrop-filter` + SVG displacement; Safari falls back to a plain frost, still
130
- fine):
131
-
132
- - `https://claudebongos.com` → gate reads **"Claude Bongos" / `> claude`**. Type
133
- `bongos`, press enter → you land on **cloudbongos.com showing the orbs**.
134
- - `https://geminibongos.com` → same, **"Gemini Bongos" / `> gemini`**.
135
- - `https://cloudbongos.com` → gate reads "Cloud Bongos"; type `bongos` → orbs in place.
136
- - **Sign-in still works:** `https://builders.cloudbongos.com` → sign in with GitHub
137
- (the OAuth callback is on the apex, `/api/gds/auth/web/callback`, which the
138
- catch-all still proxies to `:3002`). If sign-in breaks, the apex `@gate`/`handle`
139
- split is wrong — recheck step ③.2.
140
-
141
- ## Rollback
142
-
143
- - **Take a door down:** delete its block from `/etc/caddy/Caddyfile`,
144
- `sudo systemctl reload caddy`, remove the Cloudflare `@` A record.
145
- - **Revert cloudbongos.com to the buddha page:** restore the apex block to the
146
- single `reverse_proxy 127.0.0.1:3002 { … }` body, reload. The buddha landing
147
- (`public-landing/index.html`) is untouched in the repo and served again
148
- immediately.
149
-
150
- ## Files this recipe uses
151
-
152
- - `infra/Caddyfile` — the two door blocks + the modified `cloudbongos.com` apex block.
153
- - `brand/cloud-bongos/gate/space.js` — the runtime hostname switch (branding +
154
- redirect + `?entered` auto-open).
155
- - `brand/cloud-bongos/` — the gate bundle + the world art it refracts (shared by all three).
@@ -1,139 +0,0 @@
1
- # Recipe — stand up staging.cloudbongos.com (password-locked landing-page preview)
2
-
3
- > Brings up **staging.cloudbongos.com** — a password-locked preview of the Cloud Bongos
4
- > landing **gate** (`brand/cloud-bongos/gate`, the cel-glass + `> cloud bongos` terminal
5
- > prototype from tasks [#1532](https://example.com/builders#/task/1532) +
6
- > [#1534](https://example.com/builders#/task/1534)). This is the future
7
- > cloudbongos.com landing page, parked behind a shared password so it can be shared for
8
- > review before it goes public. Task [#1540](https://example.com/builders#/task/1540).
9
-
10
- ## Architecture (what we're building)
11
-
12
- The **cheapest possible** host: no app process, no new port, no new certificate. Caddy
13
- **static-serves** the brand-art folder that already ships in the checkout, behind HTTP
14
- basic auth (the password lock).
15
-
16
- | Thing | Value |
17
- |---|---|
18
- | URL | `https://staging.cloudbongos.com` (redirects to `/gate/`, where the gate is served) |
19
- | Served by | Caddy `file_server`, web root `/home/lars/example/brand/cloud-bongos` |
20
- | Lock | HTTP basic auth — username `bongos` + a shared password |
21
- | TLS | the existing `*.cloudbongos.com` origin + Cloudflare edge certs (already cover it) |
22
- | Cost | **$0/mo** — same droplet, no process, no port |
23
-
24
- Why static (not a reverse-proxy block like the others): the gate is plain HTML/CSS/JS +
25
- PNGs. There is nothing to run. Caddy serves the files directly.
26
-
27
- ## Why the web root is the brand folder, not `gate/`
28
-
29
- The gate page lives at `gate/index.html` but references its art **relative to `/gate/`** —
30
- sprites **beside it** (`sprites/…`, `dispmaps/…`) and the world **one level up**
31
- (`../world/…`). So the folder that must be the web root is `brand/cloud-bongos/` (the
32
- parent), and the page must actually be served **at `/gate/`** for those relative paths to
33
- resolve. The Caddy block therefore **redirects the apex `/` → `/gate/`** (`redir / /gate/`)
34
- rather than serving the gate's HTML at the apex.
35
-
36
- > History (task 1545): the block first tried to serve the gate AT the apex via
37
- > `rewrite / /gate/index.html` plus per-folder rewrites (`/dispmaps/*` → `/gate/dispmaps/*`).
38
- > That was fragile — when the gate gained `sprites/` (task 1541) those 404'd at the apex
39
- > (the browser asked `/sprites/*`, not `/gate/sprites/*`). The redirect serves the gate at
40
- > its native path so **all** relative folders resolve, with nothing to keep in sync.
41
-
42
- ## Phase ① — DNS (Cloudflare dashboard, ~30 s)
43
-
44
- cloudbongos.com is on Cloudflare. Add **one** record:
45
-
46
- 1. Cloudflare → pick the **cloudbongos.com** zone → **DNS** → **Add record**.
47
- 2. Type **A**, Name **`staging`**, IPv4 **`REDACTED_IP`**, Proxy status **Proxied** (orange cloud ON), TTL Auto.
48
- 3. Save.
49
-
50
- No new certificate is needed — the cloudbongos origin cert is already
51
- `cloudbongos.com, *.cloudbongos.com`, and Cloudflare's edge cert covers
52
- `staging.cloudbongos.com` too.
53
-
54
- ## Phase ② — the password (droplet, one-time)
55
-
56
- The `staging.cloudbongos.com` block ships in `infra/Caddyfile` with a **dummy** bcrypt hash,
57
- so no real secret is in git. Generate the real hash and keep it handy for Phase ③:
58
-
59
- ```bash
60
- caddy hash-password --plaintext 'YOUR_PASSWORD'
61
- # → prints $2a$14$……… (copy the whole thing)
62
- ```
63
-
64
- Username is `bongos`; the password is whatever you hashed. The real hash lives **only** in
65
- the live `/etc/caddy/Caddyfile` — never committed.
66
-
67
- ## Phase ③ — apply the Caddy block + reload
68
-
69
- The `staging.cloudbongos.com` block is already in `infra/Caddyfile`. The live
70
- `/etc/caddy/Caddyfile` has drifted slightly from the repo copy (comment wording + block
71
- order), so **do not blindly `cp` the whole file** — append just the new block, swap the
72
- dummy hash for the real one, then reload:
73
-
74
- ```bash
75
- cd /home/lars/example
76
- # 1. append only the staging.cloudbongos.com block (idempotent: skip if present)
77
- grep -q 'staging.cloudbongos.com {' /etc/caddy/Caddyfile || \
78
- awk '/^# ── Cloud Bongos — STAGING/{p=1} p' infra/Caddyfile >> /etc/caddy/Caddyfile
79
-
80
- # 2. replace the dummy hash: open the file, find the staging.cloudbongos.com
81
- # block's `basic_auth { bongos … }` line, and paste the real $2a$… hash from
82
- # Phase ② in place of the dummy one.
83
- sudo nano /etc/caddy/Caddyfile # or any editor
84
-
85
- # 3. let the caddy user READ the static files. Caddy runs as user `caddy`, which
86
- # cannot traverse the 750 home dir — without this, authenticated requests get
87
- # 403. Everything under /home/lars/example is already world-readable, so
88
- # ONLY the home dir itself needs to be traversable (one-time, persists):
89
- sudo setfacl -m u:caddy:x /home/lars 2>/dev/null \
90
- || sudo chmod o+x /home/lars # fallback if the `acl` package is absent
91
- # o+x allows traverse, not listing. For strict least-privilege instead of the
92
- # broad o+x, `sudo apt-get install -y acl` first so the setfacl path is taken.
93
-
94
- # 4. validate (as root — it must read the 640 root:caddy origin key), then reload
95
- sudo caddy validate --config /etc/caddy/Caddyfile --adapter caddyfile
96
- sudo systemctl reload caddy # fails closed on a bad config
97
- ```
98
-
99
- > A bad config makes `reload` error out and **keep serving the old config** — the live
100
- > sites never go down. Leaving the dummy hash in place is also safe: it just means no one
101
- > can log in (fail-closed) until you paste the real one.
102
-
103
- ## Phase ④ — verify
104
-
105
- ```bash
106
- # from anywhere:
107
- curl -sS -o /dev/null -w '%{http_code}\n' https://staging.cloudbongos.com/ # 401 (locked) ✓
108
- curl -sS -u bongos:YOUR_PASSWORD https://staging.cloudbongos.com/ | grep -o 'cloud bongos' # matches ✓
109
- curl -sS -o /dev/null -w '%{http_code}\n' -u bongos:YOUR_PASSWORD \
110
- https://staging.cloudbongos.com/world/assets/plates/scene-establishing.png # 200 (art loads) ✓
111
- ```
112
-
113
- Then open `https://staging.cloudbongos.com` in **Chrome or Edge** (the live-glass
114
- refraction needs Chromium `backdrop-filter` + SVG displacement; Safari falls back to a
115
- plain frost, which still looks fine). The browser shows a username/password box — enter
116
- `bongos` + the password — and the gate appears.
117
-
118
- ## Rotating / removing the password
119
-
120
- - **Change the password:** regenerate the hash (Phase ②), edit the `bongos …` line in the
121
- `staging.cloudbongos.com` block of `/etc/caddy/Caddyfile`, `sudo systemctl reload caddy`.
122
- - **Take the site down:** delete the `staging.cloudbongos.com` block from
123
- `/etc/caddy/Caddyfile`, `sudo systemctl reload caddy`, and remove the Cloudflare
124
- `staging` A record.
125
-
126
- ## Later — promote to the real public landing page
127
-
128
- When the gate is approved as the public landing page, that is a **separate task**: fold the
129
- gate into `public-landing/index.html` (served at the cloudbongos.com apex by
130
- `platform-server.js`), bring the world art it needs under a served path, and drop the basic
131
- auth. Until then, this staging block is the review surface and `public-landing/index.html`
132
- stays the minimal buddha placeholder.
133
-
134
- ## Files this recipe uses
135
-
136
- - `infra/Caddyfile` — the `staging.cloudbongos.com` block (ships with a dummy hash; the
137
- live file carries the real one).
138
- - `brand/cloud-bongos/gate/` — the gate prototype (`index.html` + `dispmaps/`).
139
- - `brand/cloud-bongos/world/assets/` — the art the gate refracts.
@@ -1,111 +0,0 @@
1
- # Recipe — stand up cloudbongos.com (Customer 0, the vanilla dogfood instance)
2
-
3
- > Brings **cloudbongos.com** live as a real Cloud Bongos instance: the vanilla brand, its own database, no game code — Cloud Bongos running on Cloud Bongos (the C8 proof, [ADR 0062](../adr/<redacted>.md) §2, task [#1374](https://example.com/builders#/task/1374) + the proof ship [#1206](https://example.com/builders#/task/1206)). Owner-only inputs are tracked in blocker [#40](https://example.com/builders#/blocker/40).
4
-
5
- ## Architecture (what we're building)
6
-
7
- Same droplet as OTB (`REDACTED_IP`), **separate process + separate database**, no new monthly cost. One checkout (`/home/lars/example`) runs **two** processes:
8
-
9
- | Instance | Entrypoint | Brand | Port | DB | Domain |
10
- |---|---|---|---|---|---|
11
- | OTB (game) | `server.js` | `config/branding.json` (default) | 3000 | `example` | example.com |
12
- | **Cloud Bongos** | `src/platform-server.js` | `config/branding.cloudbongos.json` (via `GDS_BRANDING_FILE`) | **3002** | **`cloudbongos`** | **cloudbongos.com** |
13
-
14
- The only difference between the two processes is **environment** — no divergent clone. The brand switch is `src/branding.js`'s `GDS_BRANDING_FILE` knob (task 1374). The landing page is served at the apex by `platform-server.js`; the Hall + status ride `builders.`/`status.` (host routing in `src/bongos/serve-internal.js`).
15
-
16
- ## Phase ① — DNS ✅ done
17
-
18
- Domain on Cloudflare (Free plan), nameservers switched, **SSL/TLS = Full (strict)**, three **proxied** A records → `REDACTED_IP`:
19
- `cloudbongos.com` (`@`), `builders`, `status`. (Until the origin cert + process are up, the site shows a TLS error — expected.)
20
-
21
- ## Phase ② — GitHub OAuth App (builder sign-in)
22
-
23
- GitHub → Settings → Developer settings → **OAuth Apps** → **New OAuth App**:
24
- - **Application name:** `Cloud Bongos`
25
- - **Homepage URL:** `https://cloudbongos.com`
26
- - **Authorization callback URL:** `https://cloudbongos.com/api/gds/auth/web/callback` (PINNED — `auth.js` `WEB_REDIRECT_ORIGIN`; sign-in begun on `builders.` still calls back to the apex)
27
- - **Enable Device Flow:** ✅ (the CLI login uses it)
28
-
29
- Copy the **Client ID** + generate a **Client Secret** → they go in `/etc/cloudbongos/web.env` as `CLOUDBONGOS_GITHUB_CLIENT_ID` / `CLOUDBONGOS_GITHUB_CLIENT_SECRET` (resolved as `<ENVPREFIX>_GITHUB_*`; vanilla envPrefix = `CLOUDBONGOS`).
30
-
31
- ## Phase ③ — Cloudflare Origin Certificate (own domain → own cert)
32
-
33
- cloudbongos.com is a different domain, so it needs its **own** Origin Cert (the example cert's SANs don't cover it). Cloudflare → **SSL/TLS → Origin Server → Create Certificate**:
34
- - Hostnames: `cloudbongos.com` **and** `*.cloudbongos.com`
35
- - Let Cloudflare generate the key; **copy both** the certificate and the private key.
36
-
37
- Install on the droplet (operator). **Caddy runs as user `caddy` and must be able to READ the key** — match the existing `origin.key` ownership (`640 root:caddy`). A root-only `600` key makes Caddy's reload fail and loop (the OTB site keeps serving the old config, but the new site never comes up — seen during the first bring-up). The `.crt` can be world-readable.
38
- ```bash
39
- sudo install -m 644 -g caddy /dev/stdin /etc/caddy/cloudbongos-origin.crt # paste cert, Ctrl-D
40
- sudo install -m 640 -g caddy /dev/stdin /etc/caddy/cloudbongos-origin.key # paste key, Ctrl-D
41
- ```
42
-
43
- ## Phase ④ — Droplet bring-up (operator)
44
-
45
- ```bash
46
- # 1. The instance database (peer auth via the local socket, same as OTB)
47
- createdb cloudbongos
48
-
49
- # 2. GDS-only schema (skips migrations/game/*.sql — no game tables on this instance)
50
- cd /home/lars/example
51
- PGDATABASE=cloudbongos MEDUSA_GDS_ONLY=1 ./scripts/migrate.sh
52
-
53
- # 3. Secrets file (from infra/cloudbongos.env.example)
54
- sudo mkdir -p /etc/cloudbongos
55
- sudo cp infra/cloudbongos.env.example /etc/cloudbongos/web.env
56
- sudo $EDITOR /etc/cloudbongos/web.env # fill the OAuth client id + secret
57
- sudo chmod 600 /etc/cloudbongos/web.env && sudo chown lars:lars /etc/cloudbongos/web.env
58
-
59
- # 4. systemd service (from infra/cloudbongos.service)
60
- sudo cp infra/cloudbongos.service /etc/systemd/system/cloudbongos.service
61
- sudo systemctl daemon-reload
62
- sudo systemctl enable --now cloudbongos
63
- curl -sS http://127.0.0.1:3002/healthz # → "ok"
64
-
65
- # 5. Caddy — the three cloudbongos blocks are in infra/Caddyfile; apply + reload
66
- sudo cp infra/Caddyfile /etc/caddy/Caddyfile # or merge the cloudbongos blocks in
67
- sudo systemctl reload caddy
68
- ```
69
-
70
- ## Phase ⑤ — Verify + first sign-in
71
-
72
- ```bash
73
- curl -sS https://cloudbongos.com/healthz # "ok" (through Cloudflare now)
74
- curl -sS https://cloudbongos.com/ | grep -o 'cloud bongos' # the landing
75
- ```
76
- - `https://cloudbongos.com` → the buddha landing.
77
- - `https://builders.cloudbongos.com` → the Hall. **Sign in with GitHub.** Because the pack sets `firstAdmin: "example-owner"`, that first sign-in auto-seats example-owner as **Owner/Archon** on the (empty) cloudbongos DB — no manual step (`auth.js` `maybeSeatFirstAdmin`).
78
- - `https://status.cloudbongos.com` → the dashboard.
79
-
80
- ## Deploy automation (important — updated for the dedicated box)
81
-
82
- > **This changed with [ADR 0126](../adr/<redacted>.md).** cloudbongos.com no longer runs co-tenant on the OTB box. It runs on its **own dedicated droplet** (`REDACTED_IP`, checkout `/home/lars/cloudbongos`) and tracks `main` via a **pull-based `systemd` timer** — [ADR 0129](../adr/<redacted>.md), task [#2064](https://example.com/builders#/task/2064). The co-hosting box's CI deploy no longer touches it. (The historical "wire `sudo systemctl restart cloudbongos` into `~/deploy.sh`" note applied only while it was a co-tenant.)
83
-
84
- Once the box exists, the deploy is [`scripts/deploy/deploy-cloudbongos.sh`](../../scripts/deploy/deploy-cloudbongos.sh) fired by [`infra/cloudbongos-deploy.timer`](../../infra/cloudbongos-deploy.timer) every ~2 min: `git fetch` + reset `origin/main` → `npm ci` (if the lockfile changed) → `PGDATABASE=cloudbongos MEDUSA_GDS_ONLY=1 ./scripts/migrate.sh` → `sudo systemctl restart cloudbongos` → 3002 healthz. After the first install it **self-heals** (each run refreshes `/home/lars/deploy.sh` + the units from the checkout).
85
-
86
- **One-time bootstrap** (operator, over SSH to the box — the box can't install its own first deploy automation). Run once the deploy files are on `main`:
87
- ```bash
88
- # Executed as a STABLE copy outside the checkout (so `git reset --hard` can't
89
- # corrupt the running script — see ADR 0129):
90
- sudo -u lars install -m 0755 /home/lars/cloudbongos/scripts/deploy/deploy-cloudbongos.sh /home/lars/deploy.sh
91
- sudo install -m 0644 /home/lars/cloudbongos/infra/cloudbongos-deploy.service /etc/systemd/system/
92
- sudo install -m 0644 /home/lars/cloudbongos/infra/cloudbongos-deploy.timer /etc/systemd/system/
93
- sudo systemctl daemon-reload
94
- sudo systemctl enable --now cloudbongos-deploy.timer
95
- # Verify: one forced run brings the box current + smokes the chain
96
- sudo -u lars FORCE_DEPLOY=1 /home/lars/deploy.sh
97
- systemctl list-timers cloudbongos-deploy.timer --no-pager
98
- curl -sS https://cloudbongos.com/healthz # → "ok"
99
- ```
100
-
101
- ## The C8 proof (task 1206) — ✅ DONE 2026-06-21
102
-
103
- With the instance live, **one real task** went through the full lifecycle **on cloudbongos.com** — claim → grade (the real 4-worker panel, PASS) → merge → ship, credits awarded — carrying zero game code: **cloudbongos task [#1](https://example.com/builders#/task/1)** (under version CB-V1), the `GDS_MODULES_FILE` fix that made Customer-0 advertise its modules correctly all-off. That shipped task is the C8 acceptance ("the GDS builds itself"). Full record + the operational mechanics for driving cloudbongos.com's GDS from the CLI: [2026-06-21 — C8 self-build proof](../session-logs/<redacted>.md). Closes task [#1206](https://example.com/builders#/task/1206).
104
-
105
- ## Files this recipe uses
106
-
107
- - `config/branding.cloudbongos.json` — the customer-0 brand pack (deltas over `config/branding.neutral.json`).
108
- - `src/branding.js` — the `GDS_BRANDING_FILE` selector.
109
- - `src/platform-server.js` — GDS-only entrypoint + the apex landing mount.
110
- - `public-landing/index.html` — the landing page (mascot at `public-landing/buddha.png`).
111
- - `infra/cloudbongos.service`, `infra/cloudbongos.env.example`, `infra/Caddyfile` (the cloudbongos blocks).
@@ -1,63 +0,0 @@
1
- # Parallel-load test results — 2026-05-30 ([#272](https://example.com/builders#/task/272), criterion C6)
2
-
3
- > **Harness:** [`tests/parallel-load.mjs`](../../tests/parallel-load.mjs) · **Procedure:** [parallel-load-test.md](parallel-load-test.md) · **Target:** seeded staging ([#262](https://example.com/builders#/task/262)) at `http://127.0.0.1:3001` on the droplet (direct, bypassing Cloudflare).
4
-
5
- This is the live run that closes **[#272](https://example.com/builders#/task/272) (V3.R53)** and satisfies criterion **C6 — parallel-safe-under-load**. It exercises the deterministic claim concurrency audited in [#269](https://example.com/builders#/task/269) (the `claimTask()` transaction + `uniq_active_claim_per_task` index) against a real running server under concurrent load.
6
-
7
- ## Bottom line
8
-
9
- **Across both runs: ZERO `server_error` (5xx), ZERO races, ZERO anomalies.** Every claim resolved to exactly one of: `success`, `collision` (409 — exactly-one-wins), or `throttled` (429 — the rate limiter cleanly shedding excess writes). Staging's journal logged **0** 5xx responses over the full window. C6's bar — "all claims succeed or 409 cleanly; no 500s, no race conditions" — holds, with the added finding that excess write load is shed cleanly via 429 rather than ever degrading into a race or crash.
10
-
11
- ## Run A — sustained 30-minute run (the [#272](https://example.com/builders#/task/272) acceptance run)
12
-
13
- `--workers 5 --duration 1800 --work-ms 150` (natural claim→work→release→repeat loop across distinct tasks):
14
-
15
- | metric | value |
16
- |---|---|
17
- | wall | 1800.1s (30 min) |
18
- | claim attempts | 144 |
19
- | success | 13 |
20
- | collision (409, exactly-one-wins) | 8 (5.6%) |
21
- | throttled (429) | 123 |
22
- | **server_error (5xx)** | **0** |
23
- | client_error / other | 0 / 0 |
24
- | anomalies | 0 |
25
- | staging 5xx in journal | 0 |
26
-
27
- The system stayed healthy (`/healthz` 200) for the entire 30 minutes with no degradation. Low attempt volume is expected: the per-IP rate limiter (below) caps writes, and releases that get throttled leave tasks `active`, draining the claimable pool — so workers spend most cycles idling on an empty queue rather than hammering. That's the system behaving correctly under its own backpressure.
28
-
29
- ## Run B — high-intensity contention burst (forces collisions)
30
-
31
- `--workers 5 --duration 25 --work-ms 100 --contend` (all 5 workers race the SAME task — the canonical "two builders, one task" case):
32
-
33
- | metric | value |
34
- |---|---|
35
- | claim attempts | 2733 |
36
- | success | 8 |
37
- | collision (409, exactly-one-wins) | 17 |
38
- | throttled (429) | 2708 |
39
- | **server_error (5xx)** | **0** |
40
- | anomalies | 0 |
41
-
42
- This is the direct demonstration of the C6 invariant: when multiple workers race the same task row, **exactly one wins and the rest get a clean 409 `ALREADY_CLAIMED`** — never two winners, never a 5xx.
43
-
44
- ## The rate limiter (a relevant, healthy finding)
45
-
46
- `src/bongos/middleware/rate-limit.js` enforces **30 writes / 60s per-IP** and **60 writes / 60s per-session** on write methods (GET is unlimited). Because all simulated workers run from the droplet's single IP, the per-IP limit binds first, so most write attempts under load return **429** (`throttled` above). For a parallel-safety test this is a *positive* result: under sustained overload the server sheds excess writes cleanly (429 + `Retry-After`) instead of racing or 500ing. To exercise the claim logic at higher volume in a future run, raise the limit on staging or distribute workers across IPs.
47
-
48
- ## Harness fixes made while running this (committed under [#272](https://example.com/builders#/task/272))
49
-
50
- Running against the real server surfaced live-path bugs the self-test (mock) couldn't:
51
-
52
- - **claimable parsing:** the harness read `.data.tasks`, but `GET /tasks/claimable` returns `{ claimable: [...] }` → it saw 0 tasks and never claimed. Fixed to read `.claimable` (with `.tasks` fallback).
53
- - **429 classification:** 429 was bucketed as `other` / flagged as a release anomaly. Added a dedicated `throttled` outcome (clean) so the report and the pass/fail conclusion are accurate.
54
- - **`--contend` mode:** added so workers can be pointed at one task to demonstrate exactly-one-wins collisions on demand (Run B).
55
-
56
- ## Reproduce
57
-
58
- ```bash
59
- # On the droplet (staging seeded per #262, service running, alphanumeric token minted):
60
- cd /home/lars/example-staging
61
- BASE_URL=http://127.0.0.1:3001 TOKEN=<redacted> node tests/parallel-load.mjs --workers 5 --duration 1800 --work-ms 150
62
- # add --contend to force same-task collisions
63
- ```
@@ -1,32 +0,0 @@
1
- # Parallel-load test — exercising claim concurrency under N builders
2
-
3
- > **Harness:** [`tests/parallel-load.mjs`](../../tests/parallel-load.mjs) (built under prep task [#472](https://example.com/builders#/task/472)).
4
- > **Live run:** criterion **C6** task **[#272](https://example.com/builders#/task/272)** (V3.R53) — a 30-min, 5-builder run against **staging**, gated on **[#262](https://example.com/builders#/task/262)** (staging data seeding).
5
-
6
- The harness spawns N concurrent simulated "builder" loops that hammer the GDS claim lifecycle (claim → simulated work → release → repeat) and classifies every claim outcome into `success` / `collision` (clean 409/refusal) / `server_error` (the anomaly we hunt) / `client_error` / `other`. It validates the deterministic claim concurrency C6 requires — the same `claimTask()` transaction + `uniq_active_claim_per_task` index audited in [multi-builder-merge.md](multi-builder-merge.md).
7
-
8
- ## Self-test (CI-safe, no network)
9
-
10
- ```bash
11
- node tests/parallel-load.mjs # defaults: 5 workers, 60s
12
- node tests/parallel-load.mjs --workers 5 --duration 3 --work-ms 50
13
- ```
14
-
15
- With no `BASE_URL`, the harness drives an **in-process mock server** whose claim semantics mirror the DB (one active claim per task; touches-overlap → conflict). The board deliberately has two tasks sharing a path so collisions are *forced*, then asserts: ≥1 success, ≥1 collision, **zero** server_error, no invariant violation, and that attempts == sum of classes. Exit 0 = harness accounting proven correct. This runs in CI without a droplet.
16
-
17
- ## Live run against staging ([#272](https://example.com/builders#/task/272) — once [#262](https://example.com/builders#/task/262) has seeded staging)
18
-
19
- ```bash
20
- # Get builder tokens (one or more simulated builders share the load).
21
- # NEVER point this at production — it mutates real claim/task state. The
22
- # harness refuses a prod example.com host unless you pass --i-know.
23
- BASE_URL=https://staging.example.com \
24
- TOKENS=$tokA,$tokB,$tokC,$tokD,$tokE \
25
- node tests/parallel-load.mjs --workers 5 --duration 1800
26
- ```
27
-
28
- `#272` acceptance: run 30 min (`--duration 1800`); **every claim resolves to `success` or a clean `collision` — zero `server_error`, no races**; the report's throughput + collision-rate are the deliverable. A single anomaly (any 5xx, thrown error, or invariant violation) exits 1 and fails the run.
29
-
30
- ## Why the split (prep [#472](https://example.com/builders#/task/472) vs live [#272](https://example.com/builders#/task/272))
31
-
32
- `#272`'s done-when explicitly requires a seeded staging environment, which `#262` (backlog) provides. Rather than block the runnable harness behind infra, [#472](https://example.com/builders#/task/472) built + self-validated the harness now; [#272](https://example.com/builders#/task/272) stays the live-staging run. When [#262](https://example.com/builders#/task/262) ships, point the harness at staging per above to close [#272](https://example.com/builders#/task/272) and satisfy C6.