@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
@@ -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.