@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.
- package/.bongos-core.json +363 -312
- package/.claude/skills/blocker-review/SKILL.md +1 -1
- package/.claude/skills/builder-claim/SKILL.md +2 -0
- package/.claude/skills/builder-exit/SKILL.md +2 -2
- package/.claude/skills/builder-redteam/SKILL.md +3 -3
- package/.claude/skills/builder-setup/SKILL.md +2 -2
- package/.claude/skills/builder-start/SKILL.md +1 -1
- package/.claude/skills/dev/SKILL.md +1 -1
- package/.claude/skills/idea-triage/SKILL.md +2 -2
- package/.claude/skills/ideate/SKILL.md +1 -1
- package/.claude/skills/merge-mode/SKILL.md +1 -1
- package/.claude/skills/new-project/SKILL.md +3 -0
- package/.claude/skills/paint/SKILL.md +1 -1
- package/CLAUDE.md +11 -238
- package/GOVERNANCE.md +1 -1
- package/README.md +1 -1
- package/bin/bongos.js +1 -0
- package/clients/bongos-client/README.md +4 -4
- package/clients/bongos-client/bongos-client.global.js +14 -1
- package/clients/bongos-client/index.cjs +14 -1
- package/clients/bongos-client/index.d.ts +19 -2
- package/clients/bongos-client/index.mjs +11 -1
- package/clients/bongos-client/package.json +1 -1
- package/docs/adr/0001-stack-choice-single-vps.md +1 -1
- package/docs/adr/0008-google-chat-oauth-user-auth.md +1 -1
- package/docs/adr/0019-red-team-bounty-payout-table.md +3 -3
- package/docs/adr/0020-security-model-public-stub.md +1 -1
- package/docs/adr/0023-kind-multipliers-and-idea-bonus.md +1 -1
- package/docs/adr/0024-cloneable-repo-local-first-memory.md +1 -1
- package/docs/adr/0026-bfg-memory-hygiene-and-cross-builder-learning.md +4 -4
- package/docs/adr/0027-bfg-session-inefficiency-evaluator.md +3 -3
- package/docs/adr/0030-builders-hall-live-updates.md +1 -1
- package/docs/adr/0031-cloud-dev-environments-for-builders.md +4 -4
- package/docs/adr/0032-discord-mirror-of-gds.md +2 -2
- package/docs/adr/0034-thetes-graduated-newcomer-rank.md +1 -1
- package/docs/adr/0044-mediterranean-palette-replacement.md +1 -1
- package/docs/adr/0044-per-box-live-game-preview.md +1 -1
- package/docs/adr/0054-session-token-reward-cost-plus.md +10 -10
- package/docs/adr/0057-container-cost-ledger.md +3 -3
- package/docs/adr/0062-medusa-instance-model-decoupling.md +4 -4
- package/docs/adr/0065-agpl-nonprofit-ai-first-governance.md +2 -2
- package/docs/adr/0085-remove-default-per-builder-spend-cap.md +1 -1
- package/docs/adr/0086-goal-scoped-work-hierarchy.md +1 -1
- package/docs/adr/0096-require-reward-before-workable.md +9 -9
- package/docs/adr/0097-retroactive-reward-backfill.md +3 -3
- package/docs/adr/0100-two-repo-instance-model.md +1 -1
- package/docs/adr/0101-goal-hierarchy-phase-5-judge-membership-reward.md +3 -3
- package/docs/adr/0103-core-first-extraction-cloud-bongos-trunk.md +3 -3
- package/docs/adr/0107-module-upstreaming-contribution-to-core-catalog.md +1 -1
- package/docs/adr/0120-pay-on-land-and-builder-owned-rebase-gate.md +6 -6
- package/docs/adr/0133-github-app-manifest-onboarding-signin.md +2 -0
- package/docs/adr/0134-private-first-npm-distribution.md +20 -6
- package/docs/adr/0135-module-upstream-submission-interim-queue.md +34 -0
- package/docs/adr/0136-update-channel-subscription-policy.md +51 -0
- package/docs/adr/0137-upstream-publish-policy-manual-owner-gated.md +40 -0
- package/docs/adr/0138-bundled-module-deps-declared-per-module.md +45 -0
- package/docs/adr/README.md +14 -10
- package/docs/api/openapi.json +315 -6
- package/docs/api-reference.md +10 -5
- package/docs/architecture.md +3 -3
- package/docs/branding-contract.md +5 -4
- package/docs/canonical-permissions.md +1 -1
- package/docs/design/vanilla-hall-ui-redesign-scope.md +1 -1
- package/docs/file-map.md +20 -19
- package/docs/handoff-template.md +2 -2
- package/docs/modules-contract.md +8 -1
- package/docs/onboarding/diagrams/README.md +7 -7
- package/docs/onboarding/drachmae-and-karma.md +18 -18
- package/docs/onboarding/primer.md +9 -9
- package/docs/recipes/audit-replay.md +2 -2
- package/docs/recipes/builder-box-lifecycle.md +2 -2
- package/docs/recipes/claude-design-loop.md +1 -1
- package/docs/recipes/instance-deploy.md +4 -4
- package/docs/recipes/instance-first-run.md +51 -0
- package/docs/recipes/ops-gotchas.md +1 -1
- package/docs/recipes/packaging-the-core.md +5 -5
- package/docs/recipes/private-npm-distribution.md +39 -12
- package/docs/recipes/self-host.md +1 -1
- package/docs/recipes/staging-environment.md +1 -1
- package/docs/recipes/upgrading-the-core.md +63 -8
- package/docs/research/cursor-competitive-analysis.md +1 -1
- package/docs/research/llm-model-agnostic-and-cheaper-grader.md +1 -1
- package/migrations/core_191_module_submissions.sql +45 -0
- package/modules/autonomy/CLAUDE.md +11 -45
- package/modules/builder-settings/CLAUDE.md +11 -54
- package/modules/dev-box/CLAUDE.md +11 -34
- package/modules/discord/CLAUDE.md +11 -46
- package/modules/discord/module.json +2 -1
- package/modules/economy/CLAUDE.md +11 -44
- package/modules/economy/achievements.js +9 -1
- package/modules/economy/credits.js +10 -1
- package/modules/grading/CLAUDE.md +11 -38
- package/modules/hall-ui/CLAUDE.md +11 -57
- package/modules/hall-ui/public/builders.js +71 -4
- package/modules/hall-ui/public/goal-inbox.js +191 -0
- package/modules/hall-ui/public/goals-page.js +227 -22
- package/modules/hall-ui/public/goals.html +16 -0
- package/modules/hall-ui/public/hall-widgets.js +14 -1
- package/modules/hall-ui/public/index.html +3 -0
- package/modules/hall-ui/public/new-project-wizard.js +6 -4
- package/modules/hall-ui/public/style.css +43 -0
- package/modules/ideas/CLAUDE.md +11 -33
- package/modules/lifecycle/CLAUDE.md +11 -59
- package/modules/lifecycle/routes/goals.js +80 -0
- package/modules/memory/CLAUDE.md +11 -36
- package/modules/onboarding/CLAUDE.md +11 -51
- package/modules/provisioning/CLAUDE.md +15 -0
- package/modules/provisioning/migrations/provisioning_001_tables.sql +160 -0
- package/modules/provisioning/migrations/provisioning_002_standalone_shape.sql +11 -0
- package/modules/provisioning/migrations/provisioning_003_github_app_manifest.sql +95 -0
- package/modules/provisioning/module.json +17 -0
- package/modules/provisioning/onboard-plan.js +37 -0
- package/modules/provisioning/provisioning.js +539 -0
- package/modules/provisioning/rate-limit.js +35 -0
- package/modules/provisioning/routes/provisioning.js +607 -0
- package/modules/provisioning/tests/provisioning.mjs +232 -0
- package/modules/provisioning/ui/.gitkeep +0 -0
- package/modules/security/CLAUDE.md +11 -40
- package/modules/security/secret-scrub.js +10 -0
- package/modules/sessions/CLAUDE.md +11 -35
- package/modules/status-ui/CLAUDE.md +11 -43
- package/package-lock.json +2 -2
- package/package.json +7 -2
- package/public-landing/create.html +58 -12
- package/public-landing/index.html +40 -21
- package/scripts/discord/README.md +1 -1
- package/scripts/gds/CLAUDE.md +11 -140
- package/scripts/gds/claim.js +98 -48
- package/scripts/gds/claude-materialize.js +174 -20
- package/scripts/gds/claude-md-template.js +84 -0
- package/scripts/gds/cli-lib.js +112 -2
- package/scripts/gds/cutover-codemod.js +241 -0
- package/scripts/gds/fitness.js +54 -0
- package/scripts/gds/gen-api-client.js +10 -10
- package/scripts/gds/init.js +112 -16
- package/scripts/gds/lint-infra-exec.js +70 -0
- package/scripts/gds/lint-workflow-exec.js +63 -0
- package/scripts/gds/migration-namespace.js +1 -1
- package/scripts/gds/mirror-lineage-check.js +14 -5
- package/scripts/gds/mirror-redact.js +8 -0
- package/scripts/gds/module.js +118 -4
- package/scripts/gds/npmrc.js +93 -0
- package/scripts/gds/onboard.js +17 -24
- package/scripts/gds/package-core.js +74 -17
- package/scripts/gds/provision.js +102 -17
- package/scripts/gds/publish-manifest.js +60 -2
- package/scripts/gds/run-routine.js +51 -13
- package/scripts/gds/run-unit-tests.js +4 -0
- package/scripts/gds/seed-adopt-tasks.js +1 -1
- package/scripts/gds/status.js +6 -1
- package/scripts/gds/task.js +166 -0
- package/scripts/gds/update-channel.js +186 -0
- package/scripts/gds/upgrade.js +172 -25
- package/src/bongos/CLAUDE.md +11 -60
- package/src/bongos/auth.js +24 -0
- package/src/bongos/module-submissions.js +56 -0
- package/src/bongos/route-rank-check.js +6 -0
- package/src/bongos/routes/CLAUDE.md +11 -39
- package/src/bongos/routes/auth.js +6 -1
- package/src/bongos/routes/modules.js +123 -21
- package/src/bongos/serve-internal.js +10 -1
- package/src/branding.js +1 -0
- package/src/instance-config.js +1 -1
- package/src/module-api.js +24 -1
- package/src/module-loader/loader.js +36 -4
- package/src/module-loader/manifest-schema.js +16 -1
- package/tests/branding.mjs +11 -0
- package/tests/claim_error_surface.mjs +107 -0
- package/tests/claude_materialize.mjs +125 -0
- package/tests/consumer_layout_boot.mjs +302 -0
- package/tests/cutover_codemod.mjs +184 -0
- package/tests/device_flow_start.mjs +96 -0
- package/tests/first_run_engine_check.mjs +79 -0
- package/tests/goal_membership_requests.mjs +33 -3
- package/tests/hook_core_resolve.mjs +125 -0
- package/tests/hook_user_agents.mjs +62 -0
- package/tests/infra_exec_paths.mjs +104 -0
- package/tests/init.mjs +106 -6
- package/tests/instance_mismatch_guard.mjs +101 -0
- package/tests/mirror_lineage_proof.mjs +14 -0
- package/tests/module_cli.mjs +130 -2
- package/tests/module_loader.mjs +42 -0
- package/tests/module_manifest.mjs +19 -0
- package/tests/module_submissions.mjs +135 -0
- package/tests/npmrc.mjs +105 -0
- package/tests/onboard.mjs +62 -0
- package/tests/package_core.mjs +76 -22
- package/tests/platform_boot.mjs +5 -2
- package/tests/provision.mjs +107 -8
- package/tests/provisioning_github_app_routes.mjs +44 -2
- package/tests/publish_manifest.mjs +52 -1
- package/tests/run_routine.mjs +26 -1
- package/tests/run_routine_two_root.mjs +77 -0
- package/tests/scrubber_corpus.mjs +17 -0
- package/tests/served_paths_shipped.mjs +2 -2
- package/tests/task_cli.mjs +99 -0
- package/tests/update_channel.mjs +186 -0
- package/tests/upgrade.mjs +220 -25
- package/tests/workflow_exec_paths.mjs +98 -0
- package/docs/design/container-exterior-tiles.md +0 -49
- package/docs/design/container-exterior.md +0 -81
- package/docs/design/container-interior.md +0 -77
- package/docs/design/world-map-scroll.md +0 -41
- package/docs/recipes/claude-gemini-bongos-doors.md +0 -155
- package/docs/recipes/cloudbongos-staging-gate.md +0 -139
- package/docs/recipes/cloudbongos-standup.md +0 -111
- package/docs/recipes/parallel-load-results-2026-05-30.md +0 -63
- package/docs/recipes/parallel-load-test.md +0 -32
- package/docs/recipes/pixel-art-pipeline.md +0 -411
- package/docs/recipes/staging-data.md +0 -50
- package/migrations/instance/018_pms_v3_planning_seed.sql +0 -131
- package/migrations/instance/080_gds_v4_planning_seed.sql +0 -75
- package/migrations/instance/110_medusa_planning_seed.sql +0 -70
- package/migrations/instance/120_bongos_v1_planning_seed.sql +0 -69
- package/migrations/instance/144_bongos_v1_module_system_criterion.sql +0 -26
- package/migrations/instance/159_bongos_v1_goal_hierarchy_criterion.sql +0 -29
- package/migrations/instance/172_v2_world_planning_seed.sql +0 -163
- package/migrations/instance/173_bongos_v1_c7_core_modularized.sql +0 -33
- package/migrations/instance/182_gdsv4_bongos_consolidation.sql +0 -132
- package/migrations/instance/183_gdsv4_carryover_themed_goals.sql +0 -116
- package/migrations/instance/186_bongos_v1_governance_planning_seed.sql +0 -69
- package/scripts/gds/art-stage.js +0 -152
- package/server.js +0 -212
- package/tests/art_key.mjs +0 -121
- package/tests/day_cycle.mjs +0 -197
- package/tests/snapshot_picker.mjs +0 -233
- package/tests/sprite_facing.mjs +0 -35
- package/tests/terrain_picker.mjs +0 -298
- package/tests/turn_in_place.mjs +0 -280
- 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.
|