THE LIVE CRATE READS. godverse_server.py: /health, /shop/<gid>/stock, /shop/<gid>/crates, plus
start|stop|status (F's kill-the-server lifecycle), setup-role, make-fixture. Stdlib + psql. First
server code of the epoch, per the ratified G3.
Live against the real POS: crate 550 -> {baked:120, live:120, gone:0}, real prices, real VG+
grading, etag stable and ?since= -> 304. /crates returns 315 REAL crates with the shop's own
labels: TECHNO (57), $12-$15 TECHNO (46), DRUM N BASS (66).
THE THREE FENCES, each enforced by the machine, each measured:
- The credential: godverse_ro holds SELECT on inventory/crate/disc_cache and nothing else.
UPDATE inventory -> permission denied. SELECT FROM customer -> permission denied. ONE grant
carries both the §7 sandbox and the §9 PII fence — which is why §7 said enforce it at the
credential, not in a code review.
- Write verbs absent: POST /reserve -> 501, POST /buy -> 501, GET either -> 404.
- Metadata only: no cover, no pixels on the wire; a mint shop's /stock is 404 (no POS to be live
about), not an empty crate.
THE FINDING F NEEDS BEFORE IT WRITES THE GATE — `gone[]` has no subject in production. The
mechanism is right; the data has no movement. Monster Robot Party's POS records 5,567 sales ever
(400-800/month Dec->Jun) but 26 in July and NONE SINCE 2026-07-01, and hasn't been written since
2026-07-09 — the 07-16 dump is fresh, its data is stale (restored it to check: crate 550 holds 122
on both 07-15 and 07-16, identical). 0 of the crate's 120 records have ever sold. So `gone` is []
against the real POS, permanently, until the shop trades again — and an assert over it would pass
WITHOUT TOUCHING ITS SUBJECT, the exact species that has held four tags. So: make-fixture --sold 3
clones the POS (createdb -T) and marks 3 of the crate's OWN REAL SKUS sold -> verified
{baked:120, live:117, gone:3}, the three ids absent from items[]. F points at it via
GODVERSE_POS_DB for a deterministic gate; production reads the real POS untouched. A fixture is
not a lie; a green assert over an absent subject is.
LIFECYCLE (ledger #3): stop is a SIGKILL, not a graceful drain — the gate tests a death, not a
goodbye. Verified: stop -> connection refused (curl rc=7) -> status DOWN -> start -> live. No
admin verb was added to the API: a server whose whole point is "no write verbs" shouldn't grow a
remote kill.
THREE CORRECTIONS TO MY OWN G3 §4 (design -> as-built), all measured not reasoned:
1. gone:[sku] -> gone:[id]. §4 AS I RATIFIED IT said bare skus — wording that predates R25's sku
ids and R26's fence law, and a bare number crossing two id spaces is the precise bug that law
exists to stop. My doc was the stale rule meeting a case written after it: the epoch's
signature failure, this time in the doc I wrote.
2. items[] drops release_id and cover — cover edges the API toward serving pixels; release_id is
the pressing, and tier 2 speaks only in copies.
3. Write verbs answer 501, not the 404 my own docstring claimed — caught by running it. 501 is the
STRONGER proof: 404 means a handler declined; 501 means no write handler exists in the process.
-> F: assert 501.
Scoped: pathspec commit. web/assets/towns/index.json is another lane's live edit — left untouched.
Runtime pidfile moved out of the repo (it is not an artifact).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
329 lines
20 KiB
Markdown
329 lines
20 KiB
Markdown
# G3 — THE ECONOMY BACKEND (design doc)
|
||
|
||
*Lane G, R23 draft. Drafted per the v5 charter ([V5_REAL_SHOP.md](V5_REAL_SHOP.md)) ledger #2.*
|
||
|
||
**§7 (THE WRITE-BACK QUESTION) IS RATIFIED — John, 2026-07-17, R24 kickoff: A THEN B.** v5.0 ships
|
||
sandboxed (a game purchase never touches real inventory); reservation is chartered as a v5.x
|
||
upgrade behind a hold budget John sets; full write-back is not chartered. Charter law #4 is
|
||
satisfied. **The rest of this doc remains DESIGN and still needs both Fables' sign-off; no
|
||
server code ships before that.**
|
||
|
||
Status of each section: **[MEASURED]** = verified against the real databases.
|
||
**[DESIGN]** = proposed, not built. **[RULED]** = decided by John under charter law.
|
||
|
||
---
|
||
|
||
## 1. What the data actually is [MEASURED]
|
||
|
||
The charter assumed one join: thriftgod's census of real shops → dealgod's real items. **That join
|
||
does not exist.** The three datasets are three different populations:
|
||
|
||
| dataset | what it really holds | inventory? |
|
||
|---|---|---|
|
||
| `thriftgod.shop` | 2,928 real AU secondhand shops, OSM-anchored (the census) | **no** |
|
||
| `dealgod.stores` | 9,285 online sellers — **8,754 are Discogs accounts**, not physical shops | 1.77M listings |
|
||
| `recordgod` | **one real trading record shop's POS** — crates, slots, prices, grading | 28,624 live |
|
||
|
||
Measured consequences:
|
||
|
||
- **No Newtown record shop has any stock, in any database.** Egg Records, Papa Disquo, Haze
|
||
Guitars, The Vintage Record, Downtown Music: 0 items each. dealgod has zero Newtown stores and
|
||
zero census name matches.
|
||
- `thriftgod.item` (the minted table) holds 2,221 items across **29 shops — all Brisbane/
|
||
Newcastle/Alice charity shops**. Zero record shops. It is a demo seed, not a stock system.
|
||
- This is **not** charter risk #3 ("fuzzy matching"). It is a population mismatch. Fuzzy matching
|
||
cannot fix it, because there is nothing on the other side of the join to match *to*.
|
||
|
||
### 1a. The one shop where all three meet [MEASURED]
|
||
|
||
`monsterrobot` — **147 Musgrave Road, Red Hill, Brisbane QLD** (John, R23) — is the same real shop
|
||
in three forms, and that is the whole reason tier 1 can be honest:
|
||
|
||
```
|
||
recordgod (its POS) ──┐
|
||
monsterrobot.party ├── one real shop: real crates · real slots · real prices · its OWN photos
|
||
dealgod store 3962749 ─┘
|
||
```
|
||
|
||
- 28,624 live vinyl; **24,914 physically placed in 357 real crates** with real labels
|
||
("DEEP HOUSE", "$12–$15 TECHNO") and real slot numbers — with gaps where records actually sold.
|
||
- Covers are the shop's own product shots on the shop's own domain, cached by dealgod
|
||
(23,392 releases). **99.3%** of its crate stock joins to a cached cover by Discogs `release_id`.
|
||
- It is **not in thriftgod's OSM census**, so its godverse id is its dealgod store id `3962749`
|
||
(real, stable, clear of the census space — thriftgod max shop id = 2992).
|
||
|
||
**Shipped this round (G2a):** crate 550 "DEEP HOUSE" → 120 real records → `web/assets/
|
||
stock_godverse/3962749/`. Byte-identical re-bake verified.
|
||
|
||
---
|
||
|
||
## 2. Why `mint()` cannot be the source of any tier [MEASURED]
|
||
|
||
thriftgod's `mint()` is the obvious candidate for stocking shops. It disqualifies itself three ways:
|
||
|
||
1. **It is not deterministic.** `FEED_RECORDS` uses `TABLESAMPLE SYSTEM (0.5)`; `mint()` uses
|
||
`random.random()`, `random.uniform()`, `random.shuffle()`. **`random.seed` appears nowhere in
|
||
`server.py`.** Re-running mint gives different stock at different prices. That is fatal to tier
|
||
1 (charter law #2), and it is *why* tier 1 reads the POS instead: a crate is a table of facts.
|
||
2. **The shop assignment is fictional.** mint samples arbitrary Discogs sellers' listings and
|
||
assigns them to a census shop that never owned them.
|
||
3. **Therefore it breaks two laws downstream.** The licence law's green rests on "the *stores'
|
||
own* product shots" — a Discogs seller's photo shown as Egg Records' stock isn't that. And the
|
||
write-back question (§7) is **meaningless** for minted stock: you cannot mark real inventory
|
||
sold for an item the shop never had.
|
||
|
||
mint stays what it is: a fun demo seeder for parody tier 0. It is not an inventory system.
|
||
*(If a future round wants seeded mint for tier 0 variety, seeding it is a small, safe change —
|
||
`random.Random(shop_id)` + an ordered `ORDER BY id` sample instead of `TABLESAMPLE`.)*
|
||
|
||
---
|
||
|
||
## 3. The tier ladder, as built [MEASURED for 0–1, DESIGN for 2]
|
||
|
||
| tier | source | deterministic | offline |
|
||
|---|---|---|---|
|
||
| 0 · parody | seeded canvas / E's parody packs | yes | yes |
|
||
| 1 · static real | **per-shop atlas baked from the POS snapshot** (G2a, shipped) | **yes — verified** | **yes — files** |
|
||
| 2 · live | thriftgod server over the POS (this doc) | **no, by design** | **enriches only** |
|
||
|
||
**The offline law is structural, not a promise.** `stockpack.js` already returns `null` when an
|
||
index or atlas is missing, and every caller falls back to parody canvas. Tier 2 rides the same
|
||
seam: the server is an *enrichment* that upgrades an already-playable tier-1 room. There is no
|
||
boot path, gate, or tag that may touch the network.
|
||
|
||
---
|
||
|
||
## 4. API surface [AS-BUILT — R27, `pipeline/godverse_server.py`]
|
||
|
||
Read-only, per §7's ruling. **The write verbs are not "gated" — they do not exist.**
|
||
|
||
```
|
||
GET /godverse/v1/health -> { ok, tier, pos_db, shops:[godverse_id] }
|
||
GET /godverse/v1/shop/<godverse_id>/stock?since=<etag>
|
||
-> { shop_id, tier, crate, items:[{id, slot, artist, title,
|
||
price, condition, sleeve_cond, state}],
|
||
gone:[id], counts, etag, served_at } · 304 when ?since= matches
|
||
GET /godverse/v1/shop/<godverse_id>/crates -> { shop_id, tier, crates:[{id,name,label,count}], etag }
|
||
GET anything else -> 404 (the ruling is in the body)
|
||
POST/PUT/PATCH/DELETE anything -> 501 — no write handler exists in the process
|
||
```
|
||
|
||
**Three corrections this doc owes to the code (measured R27, not reasoned):**
|
||
|
||
1. **`gone:[sku]` → `gone:[id]`.** As ratified, §4 said bare skus. That wording predates R25's
|
||
`sku_<POS sku>` ids and R26's **id-namespace fence law**, and a bare number crossing two id
|
||
spaces is precisely the bug that law exists to stop. `gone[]` now carries the atlas's own `id`
|
||
verbatim, so the client does `gone.includes(item.id)` with zero transformation. *This doc was
|
||
the stale rule meeting a case written after it — the epoch's signature failure, and my turn.*
|
||
2. **`items[]` drops `release_id` and `cover`.** `cover` would edge the API toward serving
|
||
pixels — it never should (fence #2); `release_id` identifies the *pressing*, and tier 2 speaks
|
||
only in *copies* (the sku). Both were design-time noise.
|
||
3. **Write verbs answer 501, not 404.** F asserts the number, so it matters: the class defines
|
||
`do_GET` and nothing else, so the stdlib refuses any other method before routing exists. **501
|
||
is the stronger proof** — a 404 means a handler ran and declined; 501 means there is no write
|
||
handler in the process. Adding `do_POST` to answer 404 would implement the forbidden thing.
|
||
|
||
Design rules:
|
||
|
||
- **The atlas is never served by the API.** Covers stay static files (tier 1). The server sends
|
||
*facts* (what's still there, what it costs), never pixels. A tier-2 shop with a dead server is
|
||
visually identical to tier 1 — that's the fail-soft.
|
||
- **`gone[]` is the whole point of tier 2.** The client already holds the tier-1 crate; the server
|
||
says only what changed. A sold record is an `id` in `gone[]` → `collapseBuyItem()` (already
|
||
built, R9) zero-areas the sleeve. Sold-means-gone is a *diff*, not a re-fetch.
|
||
|
||
> ### ⚠ `gone[]` HAS NO SUBJECT IN PRODUCTION TODAY — measured R27, and it shapes F's gate
|
||
> The mechanism is built and correct. The **data has no movement**: Monster Robot Party's POS
|
||
> records **5,567 sales ever** (400–800/month, Dec 2025 → Jun 2026) but **26 in July and none
|
||
> since 2026-07-01**; the POS hasn't been *written* since **2026-07-09** (the 07-16 dump is fresh;
|
||
> its data is stale). And **0 of crate 550's 120 records have ever sold**. So against the real POS
|
||
> `gone` is `[]`, permanently, until the shop trades again.
|
||
>
|
||
> **Therefore, per the vacuous-gate law: F's sold-means-gone arm must not assert over the live POS
|
||
> — it would pass without touching its subject.** Two honest routes, and G built the second:
|
||
> 1. Assert `gone == []` against the real POS and **print the reason** — true, but it proves only
|
||
> that nothing sold.
|
||
> 2. **Point the server at a labelled fixture** (`godverse_server.py make-fixture --sold 3` →
|
||
> `recordgod_fixture`, a `createdb -T` clone with N of the crate's own real skus marked sold).
|
||
> Verified: `counts {baked:120, live:117, gone:3}`, the three ids absent from `items[]`. This
|
||
> proves the **mechanism** — computed → sent → consumed → sleeve vanishes — deterministically,
|
||
> which is what a gate is for. Production still reads the real POS, untouched.
|
||
>
|
||
> **A fixture is not a lie; a green assert over an absent subject is.** The epoch has held four
|
||
> tags on exactly that distinction.
|
||
- **`etag`/`since`** so a re-enter is a 304, not a payload.
|
||
- Shop id is the **godverse id** (§1a), never a POS internal id. **R24 naming, as landed:** the
|
||
town cache carries `shop.godverseShopId` (A's schema field, camelCase, read by the JS runtime);
|
||
the atlas index carries `shop.godverse_id` (snake_case, what E's `validate_atlas.py` requires).
|
||
Same id, two artifacts, two house conventions — the API uses `godverse_id` on the wire to match
|
||
the Python/JSON side it is served from. Recorded rather than silently reconciled: if the
|
||
integrators want one spelling, it is a one-line change in each emitter, not a design question.
|
||
|
||
---
|
||
|
||
## 5. Async enrichment + caching (risk #1) [DESIGN]
|
||
|
||
**Law: the dig never blocks on the network.** Not "usually" — never. The mechanism:
|
||
|
||
1. Room builds at tier 1 from static files. Playable. Riffling works. **No await.**
|
||
2. The stock fetch is fired in parallel, never awaited by any build path, with a **hard ~400 ms
|
||
budget** and `AbortController`.
|
||
3. If it lands in time: apply `gone[]` + price deltas before the player reaches the crate.
|
||
4. If it lands late: apply on arrival (a record vanishing mid-riffle is *correct* — that's a real
|
||
shop). If it never lands: the room stays tier 1, silently. **Zero console noise** — a dead
|
||
server is an expected state, not an error.
|
||
5. Cache the response per `(shop, crate)` in memory + `sessionStorage`, keyed by `etag`. One
|
||
fetch per shop per session, not per crate entry.
|
||
6. **One flight at a time per shop**; re-entering a shop reuses the cache. No polling loop.
|
||
|
||
The v1 machinery makes this cheap: the pack is a shared material + per-item UVs, so applying
|
||
`gone[]` is a vertex collapse on an existing merged mesh — no rebuild, no re-upload, no stutter.
|
||
|
||
---
|
||
|
||
## 6. Identity: census ↔ lot ↔ POS (risk #3) [DESIGN, informed by §1]
|
||
|
||
The R22 contract said "atlas per shop keyed by godverse shop id". §1 shows the id must come from
|
||
whichever dataset actually *has* the shop, and mismatches must fail soft, never mis-stock:
|
||
|
||
- **A shop is stocked only by explicit, recorded identity** — a `godverse_id` that resolves to a
|
||
real POS. There is no fuzzy name matching, ever. A shop we cannot identify with certainty gets
|
||
tier 0. **Mis-stocking is worse than no stock**: it puts one shop's records in another's crate,
|
||
which is a lie the whole epoch exists to avoid.
|
||
- **Every unmatched census shop is counted, not guessed** (the drops law). R23's count: 2,928
|
||
census shops, 1 identified real POS.
|
||
- **The census is not a stock oracle.** Being in the census means the shop exists, not that we
|
||
know what's in it. v5.0-beta's "every hero shop stocked" is therefore bounded by *real POS
|
||
access*, not by census coverage — see §9.
|
||
|
||
---
|
||
|
||
## 7. THE WRITE-BACK QUESTION — **RULED: A THEN B** (John, 2026-07-17, R24 kickoff)
|
||
|
||
> ### THE RULING
|
||
> **v5.0 ships on Option A (sandbox).** An in-game purchase **never** touches real
|
||
> monsterrobot/Square inventory. Sold-means-gone is per-session, tier-1 semantics.
|
||
> **Option B (reservation) is CHARTERED as a v5.x upgrade**, behind a hold budget John sets.
|
||
> **Option C is not chartered** — it happens only as a deliberate business launch, if ever.
|
||
>
|
||
> Charter law #4 is hereby satisfied: the write-back question has an explicit, John-ratified
|
||
> answer, and until B ships the standing state is **read-live, sell-sandboxed**.
|
||
|
||
**What this makes law for v5.0 (binding on the server work, when it starts):**
|
||
- The API's `POST /reserve` and `POST /buy` (§4) stay **unbuilt and unrouted**. Not stubbed
|
||
behind a flag — absent. A sandbox that can be flipped on by a config typo is not a sandbox.
|
||
- The game's buy path terminates in the client's wallet. **No code path from a game purchase to
|
||
`inventory.in_stock`, `sold_date`, `hold_expires_at`, `qty`, or Square exists.** The tier-2
|
||
reader connects to the POS **read-only** — enforce it at the credential, not in a code review:
|
||
a role with `SELECT` on `inventory`/`crate`/`disc_cache` and nothing else (this also carries the
|
||
§9 PII fence in the same grant).
|
||
- A gate worth its name here (the vacuous-gate law): assert the write verbs **404**, and that the
|
||
tier-2 role's grants are read-only. A test that "we didn't call buy()" proves nothing.
|
||
|
||
**Option B's charter (v5.x — the preconditions, so it can't slide in quietly):**
|
||
1. **The hold budget — John's number, not yet set.** Max concurrent game-held SKUs, and max hold
|
||
duration. Until both are set, B is not startable. *(Open item, not a blocker for v5.0.)*
|
||
2. **The shop always wins.** If a held record sells in-store via Square, the game loses the hold,
|
||
silently and immediately, and the player is told the truth ("someone bought it in the shop") —
|
||
the one race policy that is never up for negotiation.
|
||
3. **Staff-visible reason code** on every hold (`held: GODVERSE`), so a human at the counter can
|
||
see why a record is out of the crate and clear it by hand.
|
||
4. **Soak first.** B rides plumbing that has soaked, not plumbing that tags a round — Fable's
|
||
framing, and the reason A ships first.
|
||
|
||
*The options as framed for the ruling are preserved below, since the trade-offs are the record of
|
||
why A-then-B was chosen.*
|
||
|
||
---
|
||
|
||
**The question as put:** does an in-game purchase ever touch real thriftgod/recordgod inventory?
|
||
Live, not hypothetical: `monsterrobot` is a **real trading shop** whose POS syncs to **Square**,
|
||
with real customers buying these exact records off the real shelf.
|
||
|
||
Relevant measured facts: the POS already has `in_stock`, `sold_date`, **`hold_expires_at`**, `qty`
|
||
and `square_item_id`. A reservation primitive therefore already exists — the question is whether
|
||
the game is allowed to pull it.
|
||
|
||
### Option A — Sandbox (read-live, sell-local)
|
||
Game reads real stock; a purchase never leaves the game. Sold-means-gone is per-session.
|
||
- **For:** zero real-world risk; no payment, tax, or fulfilment surface; a bug can't cost real
|
||
stock; ships fastest.
|
||
- **Against:** the one-of-a-kind thrill is fiction — the real crate never changes, and two players
|
||
(or the same player, next boot) buy the same record. Tier 2 becomes "live prices" more than
|
||
"live stock".
|
||
|
||
### Option B — Reservation (a real, expiring hold)
|
||
A game purchase places a real hold on the real SKU via the existing `hold_expires_at`. The record
|
||
genuinely leaves the real crate for N hours; unclaimed, it returns.
|
||
- **For:** genuinely one-of-a-kind, and **reversible** — the strongest truth-per-unit-risk; uses a
|
||
primitive the POS already has.
|
||
- **Against:** a game action removes real sellable stock from real in-store customers. Needs a
|
||
hold cap/budget, a staff-visible reason code ("held: GODVERSE"), and a race policy against
|
||
Square in-store sales. Someone must decide what happens when a held record sells in the shop
|
||
anyway (the game must lose, gracefully).
|
||
|
||
### Option C — Full write-back (game sale = real sale)
|
||
A purchase marks the real SKU sold and syncs to Square.
|
||
- **For:** total truth — buy it in the game and it is gone from the real shop, forever.
|
||
- **Against:** irreversible real-world inventory and revenue consequences; the game becomes a
|
||
sales channel (real payment, tax, postage, fulfilment, refunds, chargebacks); a bug destroys
|
||
real stock; a game wallet is not money. This is a business decision, not an engineering one.
|
||
|
||
*(Lane G asked nothing and recommended nothing here — the options above are the record as put to
|
||
John. **Ruled A-then-B; the binding terms are at the top of this section.**)*
|
||
|
||
---
|
||
|
||
## 8. Failure modes [DESIGN]
|
||
|
||
| failure | behaviour | gate class |
|
||
|---|---|---|
|
||
| server unreachable / DNS dead | tier 1, silent, zero console noise | tier-2 smoke |
|
||
| server dies **mid-session** | room keeps last-good state; no re-fetch storm; no stutter | tier-2 smoke |
|
||
| server slow (> budget) | request aborted; tier 1 stands; late arrival applies or is dropped | tier-2 smoke |
|
||
| 5xx / malformed JSON | treated as unreachable — never a partial apply | tier-2 smoke |
|
||
| atlas missing but server up | **tier 0** — no stock without covers; never text-only sleeves | tier-1 gate |
|
||
| `gone[]` names an unknown sku | ignored + counted, never an error | tier-2 smoke |
|
||
| clock skew on holds | server time is authoritative; client never computes expiry | tier-2 smoke |
|
||
|
||
**No tier-2 failure may fail a gate or block a tag** (charter law #1). Tier 2 gets its own smoke
|
||
class: reachability, fail-soft, latency. Kill the server mid-session and the game must not stutter
|
||
— that is v5.0's release gate, and it is a *client* test, not a server test.
|
||
|
||
---
|
||
|
||
## 9. Fences (non-negotiable, both already load-bearing)
|
||
|
||
- **PII.** `recordgod` holds real `customer`, `customer_session`, `mailing_list`, `staff`,
|
||
`sales` — real private-individual data. The pipeline and the server read **only**
|
||
`inventory` / `crate` / `disc_cache`. No exceptions, no joins, no "just for debugging".
|
||
Charter law #3: no private-individual data, ever.
|
||
- **Licence.** Covers are the shop's own product shots → in-house 🟢, **flagged before any
|
||
public/commercial release**. Real titles/artists ship at tier 1 per John's R23 amendment to the
|
||
no-real-trademarks law, under the same flag. Every atlas carries its provenance.
|
||
- **Scope reality check for v5.0-beta.** "Every hero shop stocked" needs a real POS per shop. We
|
||
have exactly one (John's own). Stocking a *second* real shop means a second real shopkeeper's
|
||
data and consent — a licence and relationship question, not a pipeline question. Beta should be
|
||
scoped to **monsterrobot's 357 crates** (24,646 real records — the whole shop, crate rotation
|
||
per risk #2) rather than to more shops, unless John says otherwise.
|
||
|
||
---
|
||
|
||
## 10. Open items for the reviewers
|
||
|
||
1. **[Fable-m3 / C]** Per-shop pack resolution is not wired: `stockpack.js` caches by `type`
|
||
alone (`_packs`/`_resolved`), and `interior_mode.js:192` resolves `getStockPack(shop.type)`
|
||
with `base` fixed at `assets/models/`. R23's single shop works via a `base` override; **two
|
||
stocked shops will collide in that cache.** The seam C/F need: key the cache by
|
||
`type + base` (or `godverse_id`), and preload per-shop on shop entry. G re-emits to whatever
|
||
wording C lands (LANE_C_PUB).
|
||
2. **[Fable-m3 / E]** `redhill_real` doesn't exist — the only Brisbane cache (`westend_real`)
|
||
stops ~2.3 km south of Red Hill. The crate has no street until E's `build_towns.py` gets a
|
||
Red Hill entry (~`center: (-27.4553, 153.0064), span_km: 2.4`) and one Overpass fetch. That's
|
||
E's file and an outward call — **G has not touched either.**
|
||
3. ~~**[John, R24]** §7, the write-back question.~~ **RULED 2026-07-17: A then B** (§7). Carried
|
||
forward: **the hold budget is John's number and is not yet set** — Option B is not startable
|
||
until it is, which is a v5.x item, not a v5.0 blocker.
|
||
4. **[Both Fables]** §9's beta scoping — depth (one real shop, 357 crates) vs breadth (more
|
||
shops, which needs other people's data).
|