PROCITY/pipeline/validate_atlas.py
m3ultra f2989858aa Lane E R26 w4: the mint-id fence — my rule was the stale thing (wave 4, E's one line)
THE RED WAS MINE. My R25 slot-id check demanded `sku_` on every real-stock pack — faithfully implementing
C's R25 line, which was written BEFORE MINT EXISTED. It then failed all 14 of G's mint crates, whose ids
honour its every intent: non-positional, seeded, stable, zero reuse. C's ruling names it exactly: "the rule
was the stale thing, not E's check." Fourth hold of the epoch, and the fourth to trace to a rule meeting a
case written before the case existed.

THE FIX IS A FENCE, AND IT IS STRONGER THAN THE RULE IT REPLACES — that was the thing to prove. The easy
green was to drop the prefix check. Instead the form is sourcing-scoped per C §7.2a (R26 w4):
sourcing="real" => `sku_` (a POS copy on a shelf) · sourcing="mint" => `mint_` (a dealgod listing standing
in for one). A crate wearing the other's prefix FAILS.

WHY THIS IS A MONEY BUG, NOT A NAMING BUG — G's judgment, now law. G refused to file dealgod listing ids
under `sku_` to go green the easy way. They were right: tier-2's sold-means-gone looks up POS skus. R24's
id collision entered the WRONG SHOP; the same collision in the money layer SELLS A REAL RECORD out of a
real shop to satisfy a minted stand-in. The gate's error says that, in those words — the cost, not the
rule. The message also distinguishes a fence-crossing from a merely positional id, because they are
different faults and the reader shouldn't have to guess which one bit.

PROVED 7/7 — that the fence BITES, not merely that the red stopped. G's real crate (sku_...) -> PASS ·
G's mint crate (mint_...) -> PASS · mint filed under `sku_` (the exact shortcut G refused) -> FAIL, naming
the fence and the real cost · real wearing `mint_` (the reverse) -> FAIL · positional ids still FAIL in
BOTH sourcings (the old rule's whole intent, retained) · duplicate ids -> FAIL. Loosening would have passed
the first two and lost the last four; this passes the first two and keeps all four.

BOTH OLDER SUITES BROUGHT FORWARD RATHER THAN LEFT TO ROT. The R26 suite's mint fixture wore sku_ ids, so
the fence correctly rejected it — a fixture that fails for a NEW reason has stopped testing what it was
written for. Moved to the mint_ space: 17/17. R25 licence suite: 8/8, tally still firing VIA THE TALLY.
Third round running where the near-miss was a test going quietly stale around a changing contract; it is
the failure mode I now look for first.

Gates: atlas-QA OK — 15 per-shop atlas(es) valid, 15 licence line(s) shown, 0 warning(s) (1 real + 14
mint, every one showing its flag to a human) · validate_manifest exit 0 · qa.sh --strict --matrix
7 passed / 0 failed / 0 warn — QA GREEN.
-> F: your 6/1/0 is now 7/0/0. The beta is yours to tag.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-17 13:08:40 +10:00

472 lines
30 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env python3
"""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:
the CHECK: lic = prov.get("licence") or prov.get("license") ← permissive: passed either spelling
the PRINT: lic = prov.get("licence") ← strict: British only, and guarded
if lic: print(...) behind a truthiness test
While G's artifact was British both agreed. G's R24 keyed re-emit moved it to US `license` — the check
still passed, the print silently resolved to `{}`, and `FLAG BEFORE ANY PUBLIC/COMMERCIAL RELEASE`
stopped reaching a human. **The fallback WAS the bug**: it let the permissive path hide the strict one's
drift. It is the R21 stale-roads lesson one layer up — *a check applied to one of two symmetric paths is
a trap with a fuse on it* — and the R23 vacuous species again: the gate passed without touching its
subject. Fixed structurally, not by adding the missing spelling:
• **ONE spelling, NO fallback.** C §7.3 retires British `licence`; finding it is now an ERROR that
names the retirement, not a silent accommodation.
• **The evidence line is COUNTED, not conditional.** The gate must print one licence line per atlas it
judges; `printed != judged` is a FAIL. A gate that can say OK while hiding the flag cannot exist by
construction — which is the only fix that survives the next re-emit.
C §7.3 IS THE PROVENANCE AUTHORITY (R24's "runtime is the authority" ruling, applied): the runtime reads
none of these fields, so C ruled the shape to the house convention set by 21 shipped town caches — the
four gate fields **`license`, `attribution`, `generator`, `snapshot` live at TOP LEVEL**, US spelling.
This reverses R24's #2/#3 (which read the artifact of the day); the nested `provenance{}` block stays as
optional colour. **This docstring and the code below now say the same thing** — R24's fourth bug was that
they didn't, and it is not allowed to recur.
THE AUTHORITY IS THE RUNTIME for layout (R24 ruling). `stockpack.js` states it: a per-shop pack lives
under `assets/stock_godverse/<godverseShopId>/` and carries the SAME `stock_<type>_index.json` name (type
picks the stock slot); cache identity is `(type, base)`.
python3 pipeline/validate_atlas.py # 0 = valid (or an honest SKIP), 1 = a bad/misnamed atlas
"""
import json, os, sys, glob, struct
ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
STOCK_G = os.path.join(ROOT, "web", "assets", "stock_godverse") # Lane G's namespace (charter law #5)
GLOB = os.path.join(STOCK_G, "*", "stock_*_index.json") # the RUNTIME path — what the loader fetches
BANDS = {"bargain", "standard", "collector", "grail"}
# The loader fetches `<base>stock_<type>_index.json` where `type` is the shop's registry type — it picks
# the stock slot (stockpack SLOT_FOR: record→sleeve, book→spine, toy→box). Any other `<type>` is a name
# the runtime can never fetch. The glob alone can't say that (`stock_*_index.json` also matches the old
# contract's `stock_shop_<id>_index.json`), so the name is checked, not just matched — presence is not
# the-right-thing (the vacuous-gate law's actual lesson).
STOCK_TYPES = {"record", "book", "toy"}
# C §7.3: the four provenance gate fields, TOP LEVEL, US `license`. The runtime reads none of them, so C
# ruled their shape to the convention 21 shipped town caches already use. `license` is the one that must
# reach a human — it carries the release flag on real product photos.
PROV_FIELDS = ("license", "attribution", "generator", "snapshot")
REQUIRED_ITEM = ("id", "title", "artist", "price", "price_band", "atlas", "uv")
# C §7.2a (R25 #3): slot ids derive from the source's stable key, never position, and are unique across
# ALL packs. A real-stock pack's key is the shop's POS sku → `sku_<POS sku>`; the shipped generic packs own
# the `rec_`/`boo_`/`toy_` positional namespace and a real-stock pack must not emit into it.
# C §7.2a as amended (R26 wave 4) — THE PREFIX IS A NAMESPACE FENCE, and the id form is SOURCING-SCOPED.
# My R25 check demanded `sku_` on every real-stock pack. C wrote that rule before mint existed, so it read
# as one space when there are two — and it failed 14 legitimate mint crates whose ids honour its every
# intent (non-positional, seeded, stable, zero reuse). C's ruling: the rule was the stale thing, not the
# check. The fence is not decoration: `sku_` names a POS copy on a shelf, `mint_` names a dealgod listing
# standing in for one. G refused to file listing ids under `sku_` to go green the easy way, and that
# judgment is the law now — tier-2's sold-means-gone looks up POS skus, so a prefix that lies doesn't fail
# a gate, it SELLS THE WRONG RECORD out of a real shop to satisfy a minted stand-in. Never treat them as
# one space (same standing note as plan-ids vs godverse-ids).
SLOT_ID_PREFIX = {"real": "sku_", "mint": "mint_"}
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²)
FITS_1024 = 64
# charter law #3: no private-individual data, ever — a stock row must never carry a person.
FORBIDDEN_ITEM_KEYS = {"seller", "seller_name", "owner", "owner_name", "customer", "user", "username",
"email", "phone", "mobile", "address", "member", "account", "consignor", "staff"}
_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
prefix says ids SHOULD be disjoint, this measures whether they ARE."""
global _GENERIC_IDS
if _GENERIC_IDS is None:
_GENERIC_IDS = {}
for p in sorted(glob.glob(GENERIC_PACKS)):
try:
d = json.load(open(p))
except Exception:
continue
_GENERIC_IDS[os.path.basename(p)] = {str(i.get("id")) for i in d.get("items", [])
if isinstance(i, dict)}
return _GENERIC_IDS
def image_size(path):
"""(w, h) for PNG/WebP from the header alone — dependency-free, house style (cf. glb_stat.py)."""
with open(path, "rb") as f:
head = f.read(32)
if head[:8] == b"\x89PNG\r\n\x1a\n":
return struct.unpack(">II", head[16:24])
if head[:4] == b"RIFF" and head[8:12] == b"WEBP":
fmt = head[12:16]
if fmt == b"VP8X":
b = head[24:30]
return (b[0] | b[1] << 8 | b[2] << 16) + 1, (b[3] | b[4] << 8 | b[5] << 16) + 1
if fmt == b"VP8 ":
w, h = struct.unpack("<HH", head[26:30])
return w & 0x3FFF, h & 0x3FFF
if fmt == b"VP8L":
b = struct.unpack("<I", head[21:25])[0]
return (b & 0x3FFF) + 1, ((b >> 14) & 0x3FFF) + 1
raise ValueError("unrecognised image header (want PNG or WebP)")
def validate_index(path):
"""→ (errs, warns, evidence). `evidence` is the licence string main() must show a human, or None.
It is returned rather than re-looked-up in main() ON PURPOSE: R24's bug was two paths reading one
field and drifting apart. One lookup, one owner, handed forward."""
errs, warns = [], []
name = os.path.relpath(path, STOCK_G)
try:
idx = json.load(open(path))
except Exception as e:
return [f"{name}: does not parse: {e}"], [], None # present-but-unparseable ⇒ FAIL
d = os.path.dirname(path)
# ── the NAME must be one the runtime can fetch: stock_<type>_index.json, type ∈ STOCK_TYPES ──
fname = os.path.basename(path)
ftype = fname[len("stock_"):-len("_index.json")]
if ftype not in STOCK_TYPES:
errs.append(f"{name}: unloadable name — the runtime fetches `stock_<type>_index.json` for "
f"type ∈ {sorted(STOCK_TYPES)}; {ftype!r} is never fetched (this is the R23 drift)")
elif idx.get("kind") not in (None, ftype):
errs.append(f"{name}: kind {idx.get('kind')!r} != the filename's type {ftype!r} — the filename "
f"picks the stock slot, so they must agree")
# ── identity: the per-shop base IS the godverse shop id (the loader keys on it) ──
shop_dir = os.path.basename(d)
sid = (idx.get("shop") or {}).get("godverse_id")
if sid is None:
errs.append(f"{name}: shop.godverse_id missing — the loader's base is the shop id; the atlas must say who it is")
elif str(sid) != shop_dir:
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
# / determinism detail) is colour per C — good to keep, never the gate.
for f in PROV_FIELDS:
if not str(idx.get(f, "")).strip():
errs.append(f"{name}: top-level `{f}` missing/empty — C §7.3 makes the four provenance gate "
f"fields {list(PROV_FIELDS)} the contract (charter law #3: every atlas carries provenance)")
# British `licence` is RETIRED (C §7.3). NO fallback, deliberately: R24's fallback is exactly what let
# the check and the print drift onto different spellings and silence the flag. One truth, said loudly.
for where, obj in (("top-level", idx), ("provenance", idx.get("provenance") or {})):
if isinstance(obj, dict) and "licence" in obj:
errs.append(f"{name}: British `licence` found at {where} — retired by C §7.3; the contract is "
f"top-level US `license`. (R25: a spelling fork here silently hid the release flag.)")
# ── C §7.2 ceilings ──
atlases = idx.get("atlases")
if not isinstance(atlases, list) or not atlases:
errs.append(f"{name}: no atlases[] listed")
atlases = []
items = idx.get("items")
if not isinstance(items, list) or not items:
errs.append(f"{name}: no items")
items = []
if len(atlases) > MAX_ATLASES:
errs.append(f"{name}: {len(atlases)} atlases — C §7.2 caps a shop at {MAX_ATLASES}")
declared = idx.get("atlas_px")
for a in atlases:
ap = os.path.join(d, a)
if not os.path.isfile(ap):
errs.append(f"{name}: atlas {a!r} listed but missing on disk"); continue
try:
w, h = image_size(ap)
except Exception as e:
errs.append(f"{name}: atlas {a!r} unreadable: {e}"); continue
if declared and (w != declared or h != declared):
errs.append(f"{name}: atlas {a!r} is {w}×{h} but index declares atlas_px={declared}")
if max(w, h) > PX_MAX:
errs.append(f"{name}: atlas {a!r} {w}×{h} exceeds C's ceiling {PX_MAX}²")
elif max(w, h) > PX_TYPICAL and len(items) <= FITS_1024:
warns.append(f"{name}: atlas {a!r} is {w}×{h} for {len(items)} items — C: ≤{PX_TYPICAL}² "
f"whenever the crate fits it (≤{FITS_1024} at 256²)")
# ── the index itself (schema per the loader: it reads items[].uv/atlas; `cell`/`cell_w` are informational) ──
ids = set()
for i, it in enumerate(items):
if not isinstance(it, dict):
errs.append(f"{name}[{i}]: item is not an object"); continue
m = [f for f in REQUIRED_ITEM if f not in it]
if m:
errs.append(f"{name}[{i}]: missing {m}"); continue
priv = sorted(set(k.lower() for k in it) & FORBIDDEN_ITEM_KEYS)
if priv:
errs.append(f"{name}[{it['id']}]: private-individual field(s) {priv} — charter law #3 forbids it")
if not str(it["title"]).strip() or not str(it["artist"]).strip():
errs.append(f"{name}[{it['id']}]: empty title/artist")
if it["id"] in ids:
errs.append(f"{name}: duplicate id {it['id']!r}")
ids.add(it["id"])
if it["price_band"] not in BANDS:
errs.append(f"{name}[{it['id']}]: bad price_band {it['price_band']!r}")
if not isinstance(it["price"], (int, float)) or it["price"] < 0:
errs.append(f"{name}[{it['id']}]: bad price {it['price']!r}")
if it["atlas"] not in atlases:
errs.append(f"{name}[{it['id']}]: atlas {it['atlas']!r} not in atlases[]")
uv = it["uv"]
if not (isinstance(uv, list) and len(uv) == 4 and all(isinstance(v, (int, float)) for v in uv)):
errs.append(f"{name}[{it['id']}]: uv must be [u0,v0,u1,v1] numbers"); continue
u0, v0, u1, v1 = uv
if not (0 <= u0 < u1 <= 1 and 0 <= v0 < v1 <= 1): # origin top-left; stockpack flips V (R7 law)
errs.append(f"{name}[{it['id']}]: uv out of range / inverted: {uv}")
# ── C §7.2a: the slot-id form. Aggregated, not per-item: 120 identical errors bury the one that
# matters. Uniqueness WITHIN the pack is the dup check above; these two are the cross-pack half.
if ids:
# The fence can only be checked once the space is known. An unknown `sourcing` already FAILS above
# (§7.2b), so this doesn't leak a pass — but it must not silently half-run either: no space, no
# verdict on which prefix belongs.
want = SLOT_ID_PREFIX.get(sourcing)
if want:
wrong = sorted(i for i in ids if not str(i).startswith(want))
if wrong:
# Name the fence-crossing specifically when the crate wears the OTHER space's prefix — that
# is a different (and worse) fault than a merely positional id, and the message should say
# which one it is rather than make the reader guess.
other = {p for s, p in SLOT_ID_PREFIX.items() if s != sourcing}
crossed = sorted(i for i in wrong if any(str(i).startswith(p) for p in other))
if crossed:
errs.append(f"{name}: sourcing={sourcing!r} but {len(crossed)}/{len(ids)} slot id(s) wear "
f"another space's prefix (e.g. {crossed[:3]}) — C §7.2a (R26 w4): prefixes are "
f"NAMESPACE FENCES. `sku_` is a POS copy on a shelf; `mint_` is a dealgod "
f"listing standing in for one. A crate wearing the other's prefix is not a "
f"naming slip: tier-2's sold-means-gone looks up POS skus, so this sells a "
f"real record out of a real shop to satisfy a minted stand-in.")
else:
errs.append(f"{name}: sourcing={sourcing!r} ⇒ ids must be `{want}`-prefixed, but "
f"{len(wrong)}/{len(ids)} are not (e.g. {wrong[:3]}) — C §7.2a: an id derives "
f"from its source's stable key, never from position. Positional ids renumber "
f"on every re-bake, so anything holding one across snapshots (a wallet "
f"purchase, tier-2's sold-means-gone) points at a different record.")
gen = generic_pack_ids()
if not gen:
# the vacuous-gate law: if the other side of "unique across ALL packs" isn't here, say so —
# never let an unverifiable check read as a passing one.
warns.append(f"{name}: cross-pack uniqueness (C §7.2a) NOT verified — no generic packs matched "
f"{os.path.relpath(GENERIC_PACKS, ROOT)}")
else:
clash = sorted(ids & set().union(*gen.values()))
if clash:
errs.append(f"{name}: {len(clash)}/{len(ids)} slot id(s) also exist in the generic pack(s) "
f"{sorted(gen)} (e.g. {clash[:3]}) — C §7.2a requires ids unique across ALL "
f"packs. While they collide, an id-equality gate cannot tell which pack a "
f"rendered item came from: that is exactly what made R24's #7a spec vacuous.")
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) 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 `<base>stock_<type>_index.json`; these cannot be loaded:")
for p in stray[:8]:
print(f" unmatched: {os.path.relpath(p, ROOT)}")
return 1
# 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 = 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)
all_errs += e; all_warns += w
try:
idx = json.load(open(p))
except Exception:
idx = {}
name = os.path.relpath(p, STOCK_G)
shop = (idx.get("shop") or {}).get("name", "?")
print(f" {name:44} {shop[:22]:22} items={len(idx.get('items', [])):>3} "
f"atlases={len(idx.get('atlases', []))} px={idx.get('atlas_px')} "
f"{'OK' if not e else 'ERR(' + str(len(e)) + ')'}")
# THE EVIDENCE LINE. Clearing photos for public release is a human call (charter law #3), so the
# gate SURFACES the licence rather than pattern-matching it — but surfacing is mandatory and
# counted. `evidence` comes from validate_index, which also OWNS the missing-`license` error
# (PROV_FIELDS) — main does not second-guess it with a lookup of its own. That is the whole point:
# a missing licence is reported once, by one owner, and the tally below catches the day that owner
# ever goes permissive.
if evidence:
print(f" LICENCE: {evidence}")
att = str(idx.get("attribution", "")).strip()
if att:
print(f" attribution: {att}")
printed += 1
for w in all_warns:
print(f" WARN {w}")
for e in all_errs:
print(f" ERR {e}")
if all_errs:
print(f"atlas-QA FAIL — {len(all_errs)} error(s), {len(all_warns)} warning(s)")
return 1
# THE TALLY — the vacuous-gate law turned on this gate itself, and REACHABLE (R25: my first cut of it
# wasn't). It sits AFTER the `all_errs` return on purpose: while the field check works, a missing
# licence fails above with a precise message and the tally is quiet. The tally exists for the one case
# that check can't report — the day it goes permissive again (R24's `licence or license` fallback was
# exactly that) and passes an atlas whose licence never printed. Judged N ⇒ N lines shown, or FAIL.
# Tested by stubbing validate_index permissive: without this, that atlas ships silently cleared.
if printed != len(idxs):
print(f"atlas-QA FAIL — judged {len(idxs)} atlas(es) but printed {printed} licence line(s). The "
f"flag guards real product photos and MUST reach a human for every atlas judged.")
return 1
print(f"atlas-QA OK — {len(idxs)} per-shop atlas(es) valid, {printed} licence line(s) shown, "
f"{len(all_warns)} warning(s)")
return 0
if __name__ == "__main__":
sys.exit(main())