PROCITY/pipeline/validate_atlas.py
m3ultra 33577a9675 Lane E R26 (v5.0-beta): the sourcing arm + the atlas manifest, both ways (ledgers #3, #6)
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. 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 puts 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 demand `tier` while G emits `sourcing` —
R23 exactly, re-run. The contract's author is the authority for the contract's shape.

THE HONESTY SEPARATION, BOTH DIRECTIONS (C §7.2b). `sourcing` ∈ {real, mint} required; no `sourcing` =>
FAIL (a crate that cannot say where it came from must never pass as one that can). mint must carry no POS
claim; real must carry the POS provenance it claims. The licence line prints for both — verified, not
assumed.

-> C: §7.2b says "a mint index MUST NOT carry POS-snapshot fields" but does not enumerate them, so I named
the set — 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). The error names the exact field found, so a
divergence with G surfaces as a diagnosis, not a mystery.

THE MANIFEST (§7.2d, ledger #6), consistency both ways. 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.
-> C: one hole your rule implies but doesn't spell out — a manifest that PROMISES shops while zero atlases
exist now FAILS rather than skipping. "Absent" scopes to the empty tree; a tree where something on disk
still asserts atlases exist is the vacuous shape wearing a hat. Flagging it as my reading, not your words.

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). real -> PASS · mint -> PASS WITH ITS LICENCE LINE
PRINTED (ledger #3's "prints for both") · real+mint side by side -> PASS · mint carrying `crate` -> FAIL ·
mint carrying pos_db -> FAIL · real carrying no POS provenance -> FAIL (the reverse) · 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 while the 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.

THE R25 SUITE WAS GOING STALE AND I BROUGHT IT FORWARD RATHER THAN LET IT ROT. Adding `sourcing` + the
manifest made every R25 licence case fail for a NEW reason — which would leave the licence arm untested
while appearing tested. Updated to the R26 contract: 8/8 green, and the tally tripwire still fires VIA THE
TALLY (via_tally=True), not incidentally. Near-miss worth recording: 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.

The gate is RED until G lands (2 errors: no `sourcing`, no manifest). G is in my wave, parallel; it goes
green on their re-emit with no change from me — same as R25.

-> F: THERE IS STILL NO v5.0-alpha TAG, and C's + B's BETA commits (4f0adf1, 2f4ab59) now sit on main
above 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.

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

445 lines
27 KiB
Python
Raw 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.
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²)
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:
pos = sorted(i for i in ids if not str(i).startswith(SLOT_ID_PREFIX))
if pos:
errs.append(f"{name}: {len(pos)}/{len(ids)} slot id(s) are not `{SLOT_ID_PREFIX}`-prefixed "
f"(e.g. {pos[:3]}) — C §7.2a: a real-stock id derives from the shop's POS sku, never "
f"from position. Positional ids renumber on every re-bake, so anything holding one "
f"across snapshots (a wallet purchase, tier-2's sold-means-gone) points at a "
f"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())