diff --git a/docs/LANES/LANE_E_NOTES.md b/docs/LANES/LANE_E_NOTES.md
index 571e44a..bd34c95 100644
--- a/docs/LANES/LANE_E_NOTES.md
+++ b/docs/LANES/LANE_E_NOTES.md
@@ -1,5 +1,63 @@
# LANE E — cross-lane notes
+## Round 26 — the sourcing arm + the atlas manifest ⟨v5.0-beta⟩
+
+**1. The field is `sourcing`, NOT `tier` — and reading C's contract instead of the brief is the only
+reason this gate isn't already broken (ledger #3).** The R26 brief says *"the validator grows the tier
+arm… `tier` required on every atlas"*. **C ruled it cannot be `tier`** (§7.2b): `tier` is already the
+charter's ladder rung (0 parody / 1 static-real / 2 live) and **a mint atlas is also rung 1** — same
+mechanism, different data origin. Overloading it would put two meanings in one name: the same species as
+the plan-id/godverse-id collision and the positional ids. Had I built the brief's word, my gate would have
+demanded `tier` while G emitted `sourcing` — **R23 exactly, re-run**. The contract's author is the
+authority for the contract's shape. **Built to C §7.2b.**
+
+**2. The honesty separation, asserted both directions.** `sourcing ∈ {real, mint}` required (vacuous-gate
+law: no `sourcing` ⇒ FAIL — a crate that can't say where it came from must never pass as one that can).
+Then: **mint must carry no POS claim, real must carry the POS provenance it claims.**
+
+**→ C: §7.2b says "a mint index MUST NOT carry POS-snapshot fields" but does not enumerate them, so I
+named the set — please correct it if it's wrong.** I assert on `crate`, `provenance.source.pos_db`,
+`provenance.source.pos_dump` — the fields that claim *a specific shop's actual inventory*. I deliberately
+**excluded `snapshot`**: §7.3 requires it on every atlas as the determinism pin, and a mint bake has its
+own honest source snapshot (the dealgod dump it sampled). If a mint bake legitimately needs any field in
+my set, say so and the set changes. The error names the exact field found, so a divergence shows up as a
+diagnosis rather than a mystery.
+
+**3. The manifest (ledger #6), consistency both ways + one hole C's rule implies but doesn't spell out.**
+`stock_godverse/index.json` per §7.2d. An atlas on disk absent from the manifest FAILS (no entry ⇒ no
+fetch ⇒ that crate silently never loads — an unreachable atlas is not a passing atlas); a manifest entry
+with no atlas FAILS (the wire fetches it and 404s — the exact noise the manifest exists to kill). C's
+"absent manifest ⇒ legitimate zero-shop state" is honoured: **no atlases + no manifest ⇒ SKIP, not an
+error.** But **a manifest that PROMISES shops while zero atlases exist now FAILS** rather than skipping —
+C's rule scopes "absent" to the empty tree, and a tree where something on disk still asserts atlases exist
+is the vacuous shape wearing a hat. Flagging it since it's my reading, not C's words.
+
+**4. Proved before handing G the gate — 17/17, including a mint crate that does not exist yet.** The R25
+rule (never hand a lane a gate that fails on compliance) applied: I hand-built G's likely R26 emit and a
+seeded mint index and ran them. Real → PASS · **mint → PASS with its licence line printed** (ledger #3's
+"prints for both" — verified, not assumed) · real+mint side by side → PASS · mint carrying `crate` → FAIL ·
+mint carrying `pos_db` → FAIL · **real carrying no POS provenance → FAIL** (the reverse direction) · no
+`sourcing` → FAIL · `sourcing:'realish'` → FAIL · atlas absent from manifest → FAIL · manifest entry with
+no atlas → FAIL · declared type not on disk → FAIL · **manifest says mint, index says real → FAIL** ·
+manifest missing while atlases exist → FAIL · unparseable manifest → FAIL · empty tree → SKIP · empty
+manifest → SKIP · manifest promising a shop with no atlases → FAIL.
+
+**5. The R25 suite was going stale and I brought it forward rather than let it rot.** Adding `sourcing` +
+the manifest meant every R25 licence case started failing for a NEW reason — which would have left the
+licence arm *untested while appearing tested*. Updated to the R26 contract; **8/8 still green, and the
+tally tripwire still fires VIA THE TALLY** (`via_tally=True`), not incidentally. Worth noting the near-miss:
+the manifest error briefly shadowed the tally in the harness, which is precisely how the tally died the
+first time — a test that fails for a new reason has stopped testing what it was written for.
+
+**6. The gate is RED until G lands, by design (2 errors: no `sourcing`, no manifest).** Same as R25: G is
+in my wave, parallel. It goes green when G's re-emit + manifest land, with no change from me.
+
+**→ F: one thing before the alpha tag.** There is still **no `v5.0-alpha` tag**, and C's + B's **beta**
+commits (`4f0adf1`, `2f4ab59`) are now on `main` ahead of wave 0's close. Tagging `HEAD` would produce a
+`v5.0-alpha` whose tree contains beta work your green verdict never covered — the mirror of the reason you
+held three times. The alpha's tree is **`e7d58ea`** (wave 0 COMPLETE, my `9b1df5c` beneath it). Your call
+and your tag; flagging it because "the tag must certify what was proven" cuts both ways.
+
## Round 25 — the licence line, made unmissable + the sku form ⟨v5.0-alpha⟩
**1. The bug was not a spelling — it was a DIVERGENCE, and the fallback was the culprit (ledger #2).**
diff --git a/pipeline/validate_atlas.py b/pipeline/validate_atlas.py
index 3ec1983..159ad5e 100644
--- a/pipeline/validate_atlas.py
+++ b/pipeline/validate_atlas.py
@@ -1,9 +1,22 @@
#!/usr/bin/env python3
-"""Validate per-shop real-stock atlases — the G2a format gate (v5.0-alpha, R23 #4 · R24 #4 · R25 #2).
+"""Validate per-shop real-stock atlases — the G2a format gate (R23 #4 · R24 #4 · R25 #2 · R26 #3/#6).
Judges Lane G's tier-1 atlases **from the committed files alone** — no dealgod DB, no network — so the
gate runs on m3. Wired into `validate_manifest` (qa gate 3).
+R26 — `sourcing` + the atlas manifest (ledgers #3, #6). Two arms, both C's contract:
+ • **§7.2b `sourcing: real|mint`** — REQUIRED; no `sourcing` ⇒ FAIL. **The brief called this `tier`; C
+ ruled it cannot be** — `tier` is already the charter's ladder rung and a mint atlas is *also* rung 1.
+ This gate reads C's field, not the brief's word (R23's whole lesson: the contract's author is the
+ authority for the contract's shape, and a gate built to a paraphrase diverges from the emitter).
+ The real/mint separation is asserted **both directions**: mint must carry no POS claim, real must
+ carry the POS provenance it claims. The licence line prints for both.
+ • **§7.2d the manifest** — `stock_godverse/index.json` declares which shops carry atlases so the wire
+ never probes blind (that probe is the 404 noise the zero-console-errors law was eating by
+ attribution). Consistency BOTH ways: an atlas on disk absent from the manifest is unreachable and
+ FAILS; a manifest entry with no atlas would 404 and FAILS. An absent manifest is legitimate **only**
+ with zero atlases — a manifest that promises atlases that don't exist is a FAIL, not a SKIP.
+
R25 — THE LICENCE LINE, MADE UNMISSABLE (ledger #2), and it is the sharpest bug this file has had.
The R24 gate reported `OK — 0 warnings` on real product photos while printing **nothing** about their
licence. Not a typo — a DIVERGENCE between two paths reading one field:
@@ -59,6 +72,18 @@ REQUIRED_ITEM = ("id", "title", "artist", "price", "price_band", "atlas", "uv")
# the `rec_`/`boo_`/`toy_` positional namespace and a real-stock pack must not emit into it.
SLOT_ID_PREFIX = "sku_"
GENERIC_PACKS = os.path.join(ROOT, "web", "assets", "models", "stock_*_index.json")
+# C §7.2b (R26 #1): `sourcing` — real (that shop's own POS snapshot) vs mint (a seeded sample standing in
+# for a keyed shop with no POS). NOTE the brief called this `tier`; C ruled it CANNOT be — `tier` is already
+# the charter's ladder rung (0 parody / 1 static-real / 2 live) and a mint atlas is ALSO rung 1. One name,
+# one meaning. This gate reads C's field, not the brief's word.
+SOURCING = {"real", "mint"}
+# C §7.2b: "a mint index MUST NOT carry POS-snapshot fields (the separation IS the honesty)". C states the
+# rule but does not enumerate the fields, so E names them — these are the ones that CLAIM a specific shop's
+# actual inventory. `snapshot` is deliberately NOT here: it pins determinism (§7.3 requires it on every
+# atlas) and a mint bake has its own honest source snapshot. → C/G: if a mint bake needs any of these, say
+# so and this set changes; the error names the exact field found so a divergence is diagnosable, not a mystery.
+POS_CLAIM_FIELDS = (("crate",), ("provenance", "source", "pos_db"), ("provenance", "source", "pos_dump"))
+MANIFEST = os.path.join(ROOT, "web", "assets", "stock_godverse", "index.json") # C §7.2d
MAX_ATLASES = 2 # C §7.2: ≤2 atlases per shop
PX_MAX = 2048 # C §7.2: ≤2048² each
PX_TYPICAL = 1024 # C §7.2: ≤1024² whenever the crate fits it (≤64 items at 256²)
@@ -69,6 +94,15 @@ FORBIDDEN_ITEM_KEYS = {"seller", "seller_name", "owner", "owner_name", "customer
_GENERIC_IDS = None # lazily filled by generic_pack_ids()
+def _dig(obj, path):
+ """obj["a"]["b"]… for path ("a","b",…) — None if any hop is missing or not a dict."""
+ for k in path:
+ if not isinstance(obj, dict):
+ return None
+ obj = obj.get(k)
+ return obj
+
+
def generic_pack_ids():
"""{pack filename: {id, …}} for the shipped town-wide packs — the other side of "unique across ALL
packs". Read-only; lazily cached. This is what makes §7.2a falsifiable rather than a naming rule: the
@@ -138,6 +172,25 @@ def validate_index(path):
errs.append(f"{name}: shop.godverse_id {sid!r} != its directory {shop_dir!r} "
f"(base `stock_godverse/{shop_dir}/` is the cache identity — they must agree)")
+ # ── C §7.2b: `sourcing` — real vs mint, and the separation that makes it honest ──
+ # Vacuous-gate law: no `sourcing` ⇒ FAIL. A crate that cannot say where it came from must never pass
+ # as one that can; "plausible" silently reading as "real" is the whole failure this field prevents.
+ sourcing = idx.get("sourcing")
+ if sourcing not in SOURCING:
+ errs.append(f"{name}: `sourcing` is {sourcing!r} — C §7.2b requires top-level "
+ f"`sourcing` ∈ {sorted(SOURCING)} on every real-stock atlas. Without it a seeded mint "
+ f"crate is indistinguishable from that shop's actual POS inventory.")
+ else:
+ claims = [".".join(p) for p in POS_CLAIM_FIELDS if _dig(idx, p) not in (None, "", {}, [])]
+ if sourcing == "mint" and claims:
+ errs.append(f"{name}: sourcing='mint' but it carries POS-snapshot field(s) {claims} — C §7.2b: "
+ f"a mint crate is a seeded sample and must never claim a shop's real inventory. "
+ f"The separation IS the honesty.")
+ if sourcing == "real" and not claims:
+ errs.append(f"{name}: sourcing='real' but it carries none of the POS-snapshot fields "
+ f"{['.'.join(p) for p in POS_CLAIM_FIELDS]} — C §7.2b asserts both directions: a "
+ f"'real' crate must show the POS provenance it claims.")
+
# ── provenance: the FOUR gate fields, TOP LEVEL, US `license` (C §7.3 + charter law #3) ──
# `snapshot` pins determinism (the atlas is re-bakeable); `license`/`attribution` are the human-facing
# pair every town cache already carries. The nested `provenance{}` block (G's source / snapshot_sha256
@@ -236,21 +289,110 @@ def validate_index(path):
return errs, warns, (str(idx.get("license", "")).strip() or None)
+def load_manifest():
+ """→ (manifest_dict_or_None, errs). C §7.2d: `stock_godverse/index.json` is the authority for which
+ shops carry atlases — no entry, no fetch, so no 404 and no attribution exception. Absent is a legitimate
+ state (a tree with no godverse atlases); absent-while-atlases-exist is not, and main() catches that."""
+ if not os.path.isfile(MANIFEST):
+ return None, []
+ try:
+ man = json.load(open(MANIFEST))
+ except Exception as e:
+ return None, [f"index.json: the atlas manifest does not parse: {e} — C §7.2d makes it the "
+ f"authority for what the runtime may fetch; an unreadable authority is a FAIL, "
+ f"not a fall-back-to-probing"]
+ errs = []
+ if not isinstance(man.get("shops"), list):
+ errs.append("index.json: no `shops[]` — C §7.2d shape is {version, shops:[{godverseShopId, types[], sourcing}]}")
+ man["shops"] = []
+ return man, errs
+
+
+def check_manifest(idxs, man):
+ """manifest ↔ files, BOTH ways (C §7.2d → Lane E). An atlas on disk absent from the manifest FAILS (the
+ runtime would never fetch it — it would silently never load); a manifest entry with no atlas FAILS (the
+ runtime would fetch it and 404, which is the noise the manifest exists to kill)."""
+ errs = []
+ on_disk = {} # {shop_id: {type: index_path}}
+ for p in idxs:
+ sid = os.path.basename(os.path.dirname(p))
+ t = os.path.basename(p)[len("stock_"):-len("_index.json")]
+ on_disk.setdefault(sid, {})[t] = p
+ declared = {}
+ for i, e in enumerate(man.get("shops", [])):
+ if not isinstance(e, dict) or e.get("godverseShopId") is None:
+ errs.append(f"index.json shops[{i}]: no `godverseShopId`"); continue
+ # standing note (R26): godverseShopId lives in the GODVERSE namespace and shares a numeric space
+ # with plan ids — str() for the directory match, never a bare cross-namespace number compare.
+ sid = str(e["godverseShopId"])
+ types = e.get("types")
+ if not isinstance(types, list) or not types:
+ errs.append(f"index.json[{sid}]: no `types[]` — C §7.2d declares which packs exist"); continue
+ declared[sid] = e
+ for t in types:
+ if t not in on_disk.get(sid, {}):
+ errs.append(f"index.json[{sid}]: declares type {t!r} but "
+ f"stock_godverse/{sid}/stock_{t}_index.json is NOT on disk — the wire would "
+ f"fetch it and 404, which is exactly the noise C §7.2d exists to kill")
+ if e.get("sourcing") is not None and e["sourcing"] not in SOURCING:
+ errs.append(f"index.json[{sid}]: sourcing {e['sourcing']!r} ∉ {sorted(SOURCING)}")
+ for sid, types in sorted(on_disk.items()):
+ if sid not in declared:
+ errs.append(f"stock_godverse/{sid}/: {len(types)} atlas index(es) on disk but the shop is ABSENT "
+ f"from index.json — C §7.2d: no entry, no fetch, so this crate would silently never "
+ f"load. An unreachable atlas is not a passing atlas.")
+ continue
+ for t, p in sorted(types.items()):
+ if t not in declared[sid].get("types", []):
+ errs.append(f"stock_godverse/{sid}/stock_{t}_index.json on disk but type {t!r} is not in "
+ f"index.json's types {declared[sid].get('types')} — the runtime would never fetch it")
+ # the manifest's per-shop sourcing must not contradict the index's own (E's addition — flagged to C)
+ want = declared[sid].get("sourcing")
+ for t, p in sorted(types.items()):
+ try:
+ got = json.load(open(p)).get("sourcing")
+ except Exception:
+ continue
+ if want is not None and got is not None and want != got:
+ errs.append(f"index.json[{sid}] says sourcing={want!r} but stock_{t}_index.json says "
+ f"{got!r} — one of them tells a human a mint crate is real")
+ return errs
+
+
def main():
idxs = sorted(glob.glob(GLOB))
+ man, man_errs = load_manifest()
if not idxs:
# THE VACUOUS-GATE LAW (R23, F): absent subject ⇒ SKIP with a printed reason — and prove the set
# really is empty. Files present but unmatched = a misnamed atlas the loader can't fetch ⇒ FAIL.
- stray = [p for p in glob.glob(os.path.join(STOCK_G, "**", "*"), recursive=True) if os.path.isfile(p)]
+ stray = [p for p in glob.glob(os.path.join(STOCK_G, "**", "*"), recursive=True)
+ if os.path.isfile(p) and os.path.abspath(p) != os.path.abspath(MANIFEST)]
if stray:
print(f"atlas-QA FAIL — {len(stray)} file(s) under stock_godverse/ but 0 matched {GLOB}")
print(f" the runtime fetches `stock__index.json`; these cannot be loaded:")
for p in stray[:8]:
print(f" unmatched: {os.path.relpath(p, ROOT)}")
return 1
- print(f"atlas-QA SKIP: no atlases present (0 files matched glob {GLOB})")
+ # A manifest that PROMISES atlases while none exist must not skip quietly — the wire would fetch
+ # every entry and 404. C's "absent manifest ⇒ legitimate zero-shop state" covers the empty tree,
+ # not a manifest whose declarations are all unbacked. (Vacuous-gate law: the subject is absent, but
+ # something on disk still asserts it exists — that is a FAIL, not a SKIP.)
+ if man_errs or (man and man.get("shops")):
+ for e in man_errs + check_manifest([], man or {}):
+ print(f" ERR {e}")
+ print(f"atlas-QA FAIL — the manifest declares atlases that are not on disk (0 matched {GLOB})")
+ return 1
+ print(f"atlas-QA SKIP: no atlases present (0 files matched glob {GLOB})"
+ + ("" if man is None else "; manifest present and empty"))
return 0
- all_errs, all_warns = [], []
+ all_errs, all_warns = list(man_errs), []
+ if man is None:
+ all_errs.append(f"{len(idxs)} atlas index(es) on disk but web/assets/stock_godverse/index.json is "
+ f"MISSING — C §7.2d makes the manifest the authority for what the runtime may "
+ f"fetch. With no manifest the wire either probes blind (the 404 noise §7.2d kills) "
+ f"or never fetches these at all. Absent is only legitimate when there are no atlases.")
+ else:
+ all_errs += check_manifest(idxs, man)
printed = 0 # licence evidence lines actually emitted — see the tally below
for p in idxs:
e, w, evidence = validate_index(p)