PROCITY/pipeline/validate_pack.py
m3ultra 081755bb71 Lane E round 8 (E1+E2): book/toy stock packs + pack-QA gate
E1: build_stock_pack.py generalized over kinds — record (256x256 sleeves), book
(96x256 tall-thin spines), toy (256x256 boxes), per-kind parody word banks, same
build-time/parody/atlas/index laws. Book+toy sample packs built + staged (?localdepot)
+ indexes committed to pipeline/packs/ so Lane C wires book/toy shelves now. Parody
deterministic + no distinctive-token leak across all 3 kinds.
E2: validate_pack.py QA gate (wired into validate_manifest -> qa gate 3) — bad UV /
empty title / bad price_band / dangling atlas / dup id fails qa (proven). qa strict
GREEN 5/5.
Real 150-250-item packs blocked on GODVERSE_DSN (flagged to Fable); samples are
placeholders in the real index format. No fal spend.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 02:27:50 +10:00

83 lines
3.2 KiB
Python

#!/usr/bin/env python3
"""Validate GODVERSE stock-pack indexes — round-8 E2 QA gate.
A bad bake (out-of-range UVs, empty titles, unknown price band, dangling atlas ref) must fail QA
loudly instead of rendering blank/garbled sleeves in the dig. Validates every
`pipeline/packs/stock_<kind>_index.json` (the committed pack contract; atlases live on the depot).
Called by validate_manifest.py so it runs inside qa.sh gate 3.
python3 pipeline/validate_pack.py # exit 0 = all packs valid, 1 = a bad index
"""
import json, os, sys, glob
ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
PACKS = os.path.join(ROOT, "pipeline", "packs")
KINDS = {"record", "book", "toy"}
BANDS = {"bargain", "standard", "collector", "grail"}
REQUIRED = ("id", "title", "artist", "price", "price_band", "atlas", "uv")
def validate_index(path):
errs = []
try:
idx = json.load(open(path))
except Exception as e:
return [f"{os.path.basename(path)}: does not parse: {e}"]
name = os.path.basename(path)
if idx.get("kind") not in KINDS:
errs.append(f"{name}: bad kind {idx.get('kind')!r}")
atlases = set(idx.get("atlases", []))
if not atlases:
errs.append(f"{name}: no atlases listed")
items = idx.get("items", [])
if not items:
errs.append(f"{name}: no items")
ids = set()
for i, it in enumerate(items):
miss = [f for f in REQUIRED if f not in it]
if miss:
errs.append(f"{name}[{i}]: missing {miss}"); continue
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']}")
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):
errs.append(f"{name}[{it['id']}]: uv must be [u0,v0,u1,v1]"); continue
u0, v0, u1, v1 = uv
if not (0 <= u0 < u1 <= 1 and 0 <= v0 < v1 <= 1):
errs.append(f"{name}[{it['id']}]: uv out of range / inverted: {uv}")
return errs
def main():
idxs = sorted(glob.glob(os.path.join(PACKS, "stock_*_index.json")))
if not idxs:
print("pack-QA: no committed pack indexes in pipeline/packs/ — nothing to validate")
return 0
all_errs = []
for p in idxs:
e = validate_index(p)
all_errs += e
idx = json.load(open(p))
print(f" {os.path.basename(p):28} kind={idx.get('kind')} items={len(idx.get('items', []))} "
f"{'OK' if not e else 'ERR('+str(len(e))+')'}")
for e in all_errs:
print(f" ERR {e}")
if all_errs:
print(f"pack-QA FAIL — {len(all_errs)} error(s)")
return 1
print(f"pack-QA OK — {len(idxs)} pack index(es) valid")
return 0
if __name__ == "__main__":
sys.exit(main())