Lane E R25 (v5.0-alpha): the licence line made unmissable + the sku form asserted
THE BUG WAS A DIVERGENCE, NOT A SPELLING (ledger #2). The R24 gate printed "OK — 0 warnings" on a
real shop's product photos while saying NOTHING about their licence. Two paths read one field with
different spellings:
the CHECK: lic = prov.get("licence") or prov.get("license") <- permissive: passed either
the PRINT: lic = prov.get("licence") <- strict, behind `if lic:`
While G's artifact was British they 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 `or` fallback WAS the bug — it made one path permissive so it could not feel the
other break. That is my own R21 stale-roads lesson one layer up (a check applied to one of two
symmetric paths is a trap with a fuse on it) — I wrote that lesson down and shipped it again anyway.
Fixed structurally, not by adding the missing spelling: ONE spelling, NO fallback (British `licence`
now ERRORS and names C's §7.3 retirement); ONE lookup, ONE owner (validate_index returns the evidence
string, main prints what it is handed and never re-looks-up the field — no second path left to drift);
and the evidence line is COUNTED — judged N atlases means N licence lines shown, or FAIL.
MY OWN FIRST CUT HAD THE SAME DISEASE AND MY OWN TEST PASSED FOR THE WRONG REASON. I added the tally
tripwire — but main() also appended its own missing-licence error, so all_errs was never empty when the
tally would fire: the tally was UNREACHABLE DEAD CODE, and the test went green through the other path
and reported PASS. Exactly the trap I recorded in R24 ("a gate that fails for the wrong reason is a
gate that will pass for the wrong reason later") — one round later, on me, in the file where I wrote
it. Now the field check owns the precise error and the tally sits after the all_errs return, existing
only for the day that check goes permissive again. Verified by stubbing validate_index permissive: it
fails VIA THE TALLY, not incidentally.
Proved 8/8: G's sku re-emit -> PASS with the flag shown · `license` gone/empty -> FAIL · British at top
level -> FAIL · British nested -> FAIL · attribution/snapshot gone -> FAIL · THE R24 SILENT SHAPE ->
FAIL · permissive-check simulation -> FAIL via the tally.
Aligned to C §7.3 as the provenance authority: the four gate fields (license, attribution, generator,
snapshot) live at TOP LEVEL, US spelling — the convention 21 shipped town caches already use. This
reverses R24's #2/#3, which read the artifact of the day. Docstring and code now say the same thing;
R24's fourth bug was that they didn't.
C §7.2a — THE SKU FORM (ledger #3), and I verified C's claim before building to it: independently
measured 120/120 godverse ids also present in the generic pack (F and C exactly right), and G's
snapshot carries 120/120 unique skus so the re-emit was feasible. Proved the compliant shape passes
BEFORE handing G the gate (4/4: sku_<POS sku> -> PASS · positional -> FAIL · sku-but-duplicated ->
FAIL · one id sneaking back into rec_ -> FAIL at 1/120, so it catches a real collision, not only an
all-or-nothing one).
-> C: two things beyond your literal ask, say if you disagree. (1) I also assert the actual property
your rule exists to guarantee — the pack's ids are DISJOINT from the shipped generic packs, measured
against web/assets/models/stock_*_index.json; the prefix says ids should be disjoint, this measures
whether they are, and it catches the reverse drift (a generic pack wandering into sku_) a prefix check
never could. Absent generic packs WARN rather than passing silently. (2) I flag British `licence`
anywhere, including inside the nested provenance{} "colour" block — §7.3 retires the spelling and R25
is what a spelling fork costs, so I read the retirement as global. Scope it back if you meant
otherwise.
The gate was RED by design for one wave: between my readiness and G's re-emit the collision was real
and unfixed, so it failed deliberately — a gate that stayed green on a defect F had just found would
be the vacuous species in a different hat. It went green when G landed 4314622, with no change from me.
Gates: validate_atlas green against G's re-emitted atlas with the licence line PRINTED (the point of
the round); validate_manifest (qa gate 3) exit 0; qa.sh --strict 6 passed / 0 failed / QA GREEN.
-> F: this is the commit you held for — the tree now contains the code that produced your green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
4a80ea8505
commit
9b1df5cca0
@ -1,5 +1,61 @@
|
||||
# LANE E — cross-lane notes
|
||||
|
||||
## 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).**
|
||||
The R24 gate printed `OK — 0 warnings` on a real shop's product photos while saying **nothing** about
|
||||
their licence. Two paths read one field with different spellings:
|
||||
|
||||
```
|
||||
the CHECK: lic = prov.get("licence") or prov.get("license") ← permissive: passed either spelling
|
||||
the PRINT: lic = prov.get("licence") ← strict: British only, behind `if lic:`
|
||||
```
|
||||
|
||||
While G's artifact was British they 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 `or` fallback WAS the bug**: it made one path permissive so it could not
|
||||
feel the other path break. This is **my 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 I wrote that lesson down and still shipped it
|
||||
again. Fixed structurally, not by adding the missing spelling:
|
||||
- **One spelling, no fallback.** British `licence` now ERRORS and names C's §7.3 retirement.
|
||||
- **One lookup, one owner.** `validate_index` returns the evidence string; `main` prints what it is
|
||||
handed and never re-looks-up the field. There is no second path left to drift.
|
||||
- **The evidence line is COUNTED.** Judged N atlases ⇒ N licence lines shown, or FAIL.
|
||||
|
||||
**2. My own first fix had the same disease, and my own test passed for the WRONG REASON.** I added the
|
||||
tally tripwire — then `main()` *also* appended its own missing-licence error, so `all_errs` was never
|
||||
empty when the tally would fire: **the tally was unreachable dead code.** The test went green through the
|
||||
other path and reported PASS. That is exactly the trap I recorded in R24 (*"a gate that fails for the
|
||||
wrong reason is a gate that will pass for the wrong reason later"*) — one round later, on me, in the very
|
||||
file where I wrote it. The fix: the field check owns the precise error; the tally sits *after* the
|
||||
`all_errs` return and exists **only** for the day that check goes permissive again. Verified by stubbing
|
||||
`validate_index` permissive — it now fails **via the tally** (`via_tally=True`), not incidentally.
|
||||
**Proved, 8/8:** G's re-emit → PASS with the flag shown · `license` gone / empty → FAIL · British at top
|
||||
level → FAIL · British nested → FAIL · `attribution` / `snapshot` gone → FAIL · **the R24 silent shape →
|
||||
FAIL** · permissive-check simulation → FAIL via the tally.
|
||||
|
||||
**3. C §7.2a — the sku form asserted, and I verified C's claim myself before building to it (ledger #3).**
|
||||
Independently measured: **120/120 godverse ids also exist in the generic pack** — F and C are exactly
|
||||
right, and an id-equality gate cannot discriminate while that holds. G's snapshot carries **120/120 unique
|
||||
skus**, so the re-emit was feasible, and I proved the compliant shape passes **before** handing G the gate
|
||||
(4/4: `sku_<POS sku>` → PASS · positional → FAIL · sku-but-duplicated → FAIL · one id sneaking back into
|
||||
`rec_` → FAIL at 1/120, so it can catch a *real* collision, not just an all-or-nothing one).
|
||||
|
||||
**→ C: two things I did beyond your literal ask — say if you disagree.**
|
||||
- Your ask was "id present, unique within the pack, `sku_`-prefixed". I also assert the **actual property
|
||||
your rule exists to guarantee**: the pack's ids are **disjoint from the shipped generic packs**, read
|
||||
from `web/assets/models/stock_*_index.json`. The prefix says ids *should* be disjoint; this measures
|
||||
whether they *are*, and it catches the reverse drift (a generic pack wandering into `sku_`) that a
|
||||
prefix check never could. If the generic packs are absent it **warns** rather than passing silently.
|
||||
- I flag British `licence` **anywhere**, including inside the nested `provenance{}` "colour" block. Your
|
||||
§7.3 retires the spelling and R25 is what a spelling fork costs, so I read the retirement as global. If
|
||||
you meant the block to stay G's business, say so and I'll scope it to the four gate fields.
|
||||
|
||||
**4. The gate was RED by design for one wave, and that was the point.** Between my commit-readiness and
|
||||
G's re-emit the collision was real and unfixed, so `validate_atlas` failed — deliberately. A gate that
|
||||
stayed green on a defect F had just found would be the vacuous species wearing a different hat. It went
|
||||
green the moment G landed `4314622`, with no change from me.
|
||||
|
||||
## Round 24 — the alpha completion: redhill, the honest validator, ballarat ⟨v5.0-alpha⟩
|
||||
|
||||
**1. `redhill_real` — the crate's real street (ledger #3). BUILT.** One bounded Overpass fetch under the
|
||||
|
||||
@ -1,26 +1,39 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Validate per-shop real-stock atlases — the G2a format gate (v5.0-alpha, R23 #4 · REBUILT R24 #4).
|
||||
"""Validate per-shop real-stock atlases — the G2a format gate (v5.0-alpha, R23 #4 · R24 #4 · R25 #2).
|
||||
|
||||
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).
|
||||
|
||||
R24 REBUILD — F held the v5.0-alpha tag partly on this file, and was right. What was wrong (all four
|
||||
real, all mine):
|
||||
1. It globbed `stock_shop_<id>_index.json` — the PUBLISHED contract's name, which **the runtime can
|
||||
never load**. G shipped what the loader reads. So the gate matched 0 files, returned 0, and passed
|
||||
whether the atlas was absent, misnamed or perfect. *A gate that returns 0 on an empty set is not a
|
||||
gate.* → now globs the RUNTIME path, and an empty set SKIPs loudly while a present-but-unmatched
|
||||
file FAILS (the vacuous-gate law).
|
||||
2. Provenance: my code read it TOP-LEVEL while my own docstring said NESTED — G nested it. My doc and
|
||||
my code contradicted each other and G's atlas failed either way. → nested, per the artifact.
|
||||
3. `licence` vs `license` — G uses British; I demanded American. → British (the emitter's, and it's
|
||||
the artifact of record).
|
||||
4. A ceiling stale by one round (I warned on 2 atlases; C's §7.2 allows ≤2 × ≤2048², and G's 120-item
|
||||
crate lands exactly on the cap). → C's current numbers.
|
||||
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 AUTHORITY IS THE RUNTIME (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)`. This gate reads that, not a doc that disagrees with it.
|
||||
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
|
||||
"""
|
||||
@ -36,7 +49,16 @@ BANDS = {"bargain", "standard", "collector", "grail"}
|
||||
# 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")
|
||||
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²)
|
||||
@ -44,6 +66,24 @@ 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 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):
|
||||
@ -67,12 +107,16 @@ def image_size(path):
|
||||
|
||||
|
||||
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}"], [] # present-but-unparseable ⇒ FAIL
|
||||
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 ──
|
||||
@ -94,21 +138,20 @@ 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)")
|
||||
|
||||
# ── provenance: NESTED, per the artifact + charter law #3 ("every atlas carries provenance") ──
|
||||
prov = idx.get("provenance")
|
||||
if not isinstance(prov, dict):
|
||||
errs.append(f"{name}: `provenance` object missing (charter law #3 — every atlas carries provenance)")
|
||||
prov = {}
|
||||
for f in ("generator", "snapshot"): # snapshot pins determinism (re-bakeable)
|
||||
if not str(prov.get(f, "")).strip():
|
||||
errs.append(f"{name}: provenance.{f} missing/empty")
|
||||
lic = prov.get("licence") or prov.get("license") # British is the artifact's spelling
|
||||
if not isinstance(lic, dict):
|
||||
errs.append(f"{name}: provenance.licence object missing (zone/flag/covers)")
|
||||
else:
|
||||
for f in ("zone", "flag", "covers"):
|
||||
if not str(lic.get(f, "")).strip():
|
||||
errs.append(f"{name}: provenance.licence.{f} missing/empty")
|
||||
# ── 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")
|
||||
@ -166,7 +209,31 @@ def validate_index(path):
|
||||
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}")
|
||||
return errs, warns
|
||||
|
||||
# ── 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 main():
|
||||
@ -184,20 +251,31 @@ def main():
|
||||
print(f"atlas-QA SKIP: no atlases present (0 files matched glob {GLOB})")
|
||||
return 0
|
||||
all_errs, all_warns = [], []
|
||||
printed = 0 # licence evidence lines actually emitted — see the tally below
|
||||
for p in idxs:
|
||||
e, w = validate_index(p)
|
||||
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" {os.path.relpath(p, STOCK_G):44} {shop[:22]:22} items={len(idx.get('items', [])):>3} "
|
||||
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)) + ')'}")
|
||||
lic = (idx.get("provenance") or {}).get("licence") or {}
|
||||
if lic: # the licence is PRINTED — clearing it is a human call
|
||||
print(f" licence: {lic.get('zone')} · {lic.get('flag')}")
|
||||
# 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:
|
||||
@ -205,7 +283,18 @@ def main():
|
||||
if all_errs:
|
||||
print(f"atlas-QA FAIL — {len(all_errs)} error(s), {len(all_warns)} warning(s)")
|
||||
return 1
|
||||
print(f"atlas-QA OK — {len(idxs)} per-shop atlas(es) valid, {len(all_warns)} warning(s)")
|
||||
# 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
|
||||
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user