e2e_compat_pass.json

pipelines/e2e_compat_pass.json

{
 "lanes": [
  {
   "lane": "dropin-lifecycle",
   "derivation": {
    "derived_from": [
     "docs/ASSET_DROPIN_CONTRACT.md — read whole (335 lines). §1:24 canon-id invariant; §1:40-49 the three named tier axes + the bare-`tier` ban; §3:108-116 the five-state table; §4:135-136 'if complete, ue_asset_path is non-empty and loads'; §7 item 2:206-208 enum closed at five; item 5:218-230 the not_applicable sentinel; item 6.1:236-240 the closed generation_tier enum 'background · region_standard · region_hero · realm · apex' frozen AT FREEZE; 6.2:242-244 tier is a request parameter never a path segment; 6.3:245-247 the tier folds into the hash so a change self-enforces; 6-D D1:271-281 decay_stage is a request parameter + variant-key axis; D3:290-295 'the decay_stage vocabulary FREEZES AT EXACTLY TWO VALUES'; item 7:315-334 presentation_tier_ref = the ability/weapon mastery rail, resolver key (canon id, presentation_tier_ref)",
     "docs/generation_lifecycle.json — read whole (130 lines). :9 the closed enum; :15-16 the sentinel and its no-allowlist rule; :18-32 enrolment + the _enrolment_note 'a live registry that HAS the status column but is not declared FAILs'; :49-52 hash/licence/trigger/timestamp column names; :103-111 licence_record root, path_form 'docs/licence_records/<registry_base>/<canon_id>.json', required_non_null (12 fields incl. generating_model, model_licence, previz_only), licence_class_vocabulary [shippable, previz_only, excluded]; :113-129 prompt_hash algorithm/encoding/required_payload_keys (9 keys) and _key_closure 'The key list is CLOSED'",
     "harness/generation_prompt_hash.py — read whole (238 lines). :75-104 validate_payload: closure checked BOTH directions on KEYS, but the only VALUE vocabulary check is licence_class (:95-99); :115-129 the canonical fixture uses generation_tier='region_standard', decay_stage='', presentation_tier_ref=''; :132-195 the 21 self-test fixtures incl. the C-12 demotion equation",
     "harness/check_art_pointers.py — read whole (463 lines). :90-98 REQUIRED_COLUMNS, SUFFIX12, PATH_DEPTH=3 and the declared ruled form '<class_id>/<canon_id>/<plate_role>__<hash12>.<ext>'; :176-224 teeth P1/P2/P3/P4/P6; :237-245 P7 blind-mode refusal",
     "harness/read_boards.py — read whole (912 lines). :84-85 RIGHTS_VOCAB; :107-129 IMAGE_USE_CLASS_VOCAB + RIGHTS_TO_IMAGE_USE_CLASS; :137 MARKETING_CLEAR_SET; :143-144 GENERATION_TIER_VOCAB; :146-149 DECAY_STAGE_VOCAB ('height','present','n_a'); :151-154 GENERATION_STATUS_VOCAB; :158-168 BOARD_SHAPES; :180-184 PANEL_A/B plates-per-option; :194-199 PLATES_REQUIRED (no generation_tier / decay_stage / regeneration_trigger / timestamp columns); :336-341 T-SHAPE reads only the manifest's own two numbers; :347-351 generation_tier/decay_stage validated on the BOARD row only; :485-510 T-PANELA-REF / T-PANELA-PREVIZ / T-MARKETING all keyed on panel=='A'; :540-546 T-LICENCE requires only NON-EMPTY generation_licence_ref; :604-611 T-MARKETING-COMPUTED; :616-622 T-PLATE-COUNT keyed on panel A/B",
     "harness/check_generation_lifecycle.py — :8-16 the defect class; :31-64 the L1..L10 tooth roster; :97-126 load_registry_tree walks ONLY reg_dir; :160-173 L1 both directions computed over that tree; :360-428 L9 the licence law (ref must resolve to a file under docs/licence_records, record fields non-null, class/flag agreement, orphan check); :431-444 L10",
     "docs/licence_records/RECORD_SCHEMA.json + README.md — read whole. README: 'One shared registry column, generation_licence_ref … It holds the repo-relative path of the record'; layout 'docs/licence_records/<registry_base>/<canon_id>.json'; SCHEMA required[] 12 fields, licence_class enum, allOf previz consistency",
     "docs/art_boards/PLATES.csv — parsed all 91 rows: lane G=76 / R=9 / P=6; status complete=76 pending=15; board_id BRD_0001 on all 91; 19 distinct `panel` slug values on the G rows; option_index 1-4; plate_role='board_candidate' x76; rights generated_local x82; generation_licence_ref 'shippable' x76 / 'n_a_lane_r' x9 / '' x6; model Tongyi-MAI/Z-Image-Turbo; weights_revision z_image_turbo_bf16.safetensors; style_lora_hash empty on all 91; asset_path class segments span D1-01/02/03/11/15/16/22/26",
     "docs/art_boards/MANIFEST.csv — the single BRD_0001 row: density_class_id=D1-01, subject_canon_id=flores_island, board_shape=place_3panel, panel_count=3, options_per_panel=3, generation_class=living, generation_tier=region_hero, decay_stage=present, is_private=true, marketing_eligible=false, approval_state=awaiting_pick",
     "docs/art_boards/README.md — read whole. The board medium ruling; 'previz_only is barred on EVERY PANEL of EVERY BOARD'; 'image_use_class … is not the same field as generation_licence_ref — that one is the C-12 MODEL licence'; 'marketing_eligible is COMPUTED'; 'is_private starts TRUE and only the marketing pass may flip it'",
     "The LIVE hash payload — scratchpad _payload.json (written 09:25 today, the same pass as the 09:29 PLATES.csv): generation_tier 'REVIEW', decay_stage 'none', presentation_tier_ref 'structure', licence_class 'shippable', sampler_config {res_multistep/simple/8/cfg 1.0/1344x1344}",
     "The v1 prompt pack — scratchpad brd0001_pack_final.json: 25 rows, key union ['bar_reason','canon_refs','care_notes','category','generation_barred','negative','prompt','row_id','title']; row_id is a kebab slug ('liang-bua-cave-mouth-approach'); canon_refs is file:line prose",
     "GATES RUN LIVE: check_art_pointers.py -> 'MODE=FULL_INTEGRITY declared=76 complete=76 verified=76 PASS' (self-test 8 teeth / 20 fixtures PASS); read_boards.py -> 'STRUCTURAL: PASS', 1 board / 91 plates, 15 WARNs only; check_generation_lifecycle.py -> 'Gate: PASS (12 registries, 911 enrolled rows)', census complete=0, 'licence refs 0 · records 0', L8+L9 DECLARED NOT-ARMED; check_canon_derivation.py -> 0 findings, BRD_0001 'canon ids 62 · covered 5 · pending 57 · residue 0'",
     "docs/concept_art/art_throughput_measurements.json:56-58 — the render-preset named 'REVIEW' = 1344x1344 s8 b1, which is exactly the payload's width/height/steps",
     "docs/proposals/QA_WATCHING_PROGRAM.md:245-249 — the QA register field: 'structure (any mode) · greybox-feel … · final'; 'Register inflation is a logged defect class'",
     "docs/pipeline_review/tech_research/PIPE_ART_2026-07-29.md:151-155 (the hash owes model id + weights revision + STYLE-LORA HASH + generation_tier + decay_stage + presentation_tier_ref) and :198-199 (the three-state promote/demote gate)",
     "docs/proposals/CONCEPT_ART_PROGRAM.md:387-389 — the same style-LoRA-hash obligation",
     "docs/spine/DECISIONS_PENDING_JOSH.md:5-13 — the 2026-08-03 public-site ruling incl. 'PREVIZ_DIRECTIONAL plates publish WITH their directional label visible'",
     "docs/CONCEPT_ART_PRODUCTION_PROGRAM.md:275 — Z-Image-Turbo Apache-2.0, SHIPPABLE per PIPE_ART",
     "POSITIVE CONTROLS RUN: (a) grep -rIl for the literal hash 40db54e8…2bd9 returns ONLY docs/art_boards/PLATES.csv — controlled by grepping 'generation_prompt_hash' (5+ files) and 'res_multistep' (3 files) with the same tool, so the single-hit is a real absence and not a dead search; (b) ls docs/licence_records -> README.md + RECORD_SCHEMA.json only, zero records — corroborated independently by GATE 36 printing 'records 0'; (c) PLATES.csv header re-read programmatically against the contract's five lifecycle column names: generation_status / generation_licence_ref / generation_prompt_hash PRESENT, regeneration_trigger and last_generated_timestamp ABSENT; (d) GATE 36's own output ('0 rows are complete') set against PLATES.csv's 76 complete rows is the direct proof of the scope hole, not an inference from reading the loader"
    ],
    "not_derived": [
     "The v2/v3 prompt-pack shape (plates[{plate_slug, plate_role, serial_index}] and tier SHIPPABLE_CONCEPT|PREVIZ_DIRECTIONAL) does not exist on disk — only the v1 pack was inspectable. Every statement about what v2/v3 must carry is INFERENCE from the declared contracts, not a read of an artifact.",
     "The public marketing site: git ls-files finds no site builder, no site pages, no publishing script. Finding V-11 is derived from the ruling text plus the board's live is_private/marketing_eligible state, NOT from site code I could read. If the site reads PLATES.csv by a path I have not seen, its filter behaviour is unknown to me.",
     "I did not run the full harness/run_gates.py suite — only gates 36, 39, 43 and boards individually. I make no claim about the other 32 gates' current colour.",
     "CONTENT is out of scope by charter (read_boards.py:10-17): nothing here asserts whether the 76 images are authentic, beautiful, physically coherent or slop. That is the four critics'.",
     "I could not locate any declared closed VOCABULARY for presentation_tier_ref values anywhere in the repo (the fixture uses 'PT_07' illustratively and both live rails are 0-populated per docs/proposals/PRESENTATION_DOCTRINE_BRIEF.md:664). My claim is that 'structure' belongs to a DIFFERENT axis, not that it violates an enumerated list — no such list exists yet.",
     "Whether E:/art/boards or E:/pipe_archive currently holds the superseded outputs was not inspected beyond the 76 declared paths resolving and hashing correctly.",
     "I did not verify the Z-Image-Turbo weights licence against a primary source; I verified only that the repo records it as Apache-2.0 / SHIPPABLE."
    ]
   },
   "violations": [
    {
     "severity": "BLOCKER",
     "what": "generation_tier 'REVIEW' is not a member of the closed generation_tier enum. 'REVIEW' is the ART throughput record's RENDER-PRESET name (1344x1344 s8), i.e. a fourth colliding sense of 'tier' baked into the ratified FIDELITY/BUDGET axis. It is already folded into 76 stored hashes.",
     "contract_cite": "docs/ASSET_DROPIN_CONTRACT.md:236-237 — item 6.1: 'A `generation_tier` column on the assetgen block with a closed enum that contains `realm` AT FREEZE: `background · region_standard · region_hero · realm · apex`'; mirrored as code at harness/read_boards.py:143-144. The collision itself is banned at docs/ASSET_DROPIN_CONTRACT.md:46-49 ('A bare `tier` column or parameter is banned on this contract's schema surface — five live senses already collide').",
     "evidence": "Live payload (scratchpad _payload.json, written 09:25, the same pass that wrote PLATES.csv at 09:29): \"generation_tier\": \"REVIEW\". docs/concept_art/art_throughput_measurements.json:56-58 defines that token: {\"tier\": \"REVIEW\", \"label\": \"1344x1344 s8 b1\", \"width\": 1344, \"height\": 1344, \"steps\": 8} — exactly the payload's sampler_config. Commit 97bbdb00's own message says 'REVIEW 1344'. The board's MANIFEST row meanwhile declares generation_tier=region_hero, so the ONE value that is enum-legal was not the one hashed."
    },
    {
     "severity": "BLOCKER",
     "what": "decay_stage 'none' is not a member of the decay_stage vocabulary. The vocabulary is frozen at the two canon endpoints plus the declared non-bearing token n_a; 'none' is a fourth, undeclared token, already folded into 76 stored hashes.",
     "contract_cite": "docs/ASSET_DROPIN_CONTRACT.md:290-295 — D3: 'The `decay_stage` vocabulary FREEZES AT EXACTLY TWO VALUES, the endpoints canon already pins … Any intermediate stage a later wave wants is a DECLARED post-freeze re-baseline priced in its own commit.' harness/read_boards.py:146-149 names the live three: DECAY_STAGE_VOCAB = ('height', 'present', 'n_a'), with n_a documented as 'the non-decay-bearing case … it is not a third stage'.",
     "evidence": "Live payload: \"decay_stage\": \"none\". The correct non-bearing token already exists and is already in use on the board row — MANIFEST.csv BRD_0001 carries decay_stage=present, and read_boards.py:350-351 would have FAILED that board row had it read 'none'. The plate lane simply invented a synonym below the tooth line."
    },
    {
     "severity": "BLOCKER",
     "what": "presentation_tier_ref 'structure' is a value from an entirely different axis — the QA honesty REGISTER — jammed into the ability/weapon MASTERY-VARIANT key. This is the precise five-senses-of-tier collision DR-2 §1 exists to prevent, and it is now inside 76 immutable content-addressed hashes.",
     "contract_cite": "docs/ASSET_DROPIN_CONTRACT.md:42-43 — '`presentation_tier_ref` is the MASTERY-VARIANT axis: which authored variant of an ability/weapon presentation plays'; :320-324 — 'The resolver key is (canon id, `presentation_tier_ref`). Fallback walks DOWN the ladder to the NEAREST AUTHORED tier.' The banned-collision rule is :46-49.",
     "evidence": "Live payload: \"presentation_tier_ref\": \"structure\". docs/proposals/QA_WATCHING_PROGRAM.md:245-249 defines that exact token on a different axis: 'The register field, keyed to timing mode: `structure` (any mode) · `greybox-feel` … · `final`' — and adds 'Register inflation is a logged defect class'. A place/being/object concept plate has no mastery variant at all; the canonical fixture's own honest value for a non-applicable axis is the empty string (harness/generation_prompt_hash.py:126: \"presentation_tier_ref\": \"\")."
    },
    {
     "severity": "BLOCKER",
     "what": "generation_licence_ref carries the literal token 'shippable' on all 76 complete rows. That column is not a licence-class field — it is the resolvable POINTER to the per-artifact licence record. No record exists for any of the 76 plates, so the answer to 'where does that record live' is: nowhere. C-12's whole licence law is unenforceable for every plate generated so far.",
     "contract_cite": "docs/licence_records/README.md — 'One shared registry column, `generation_licence_ref`, on every enrolled registry. It holds the repo-relative path of the record for the artifact currently sitting at that canon id'; docs/generation_lifecycle.json:104-107 — root 'docs/licence_records', path_form 'docs/licence_records/<registry_base>/<canon_id>.json', required_non_null incl. generating_model, model_revision, model_licence, previz_only, prompt_hash, out_path, sha256, bytes. harness/check_generation_lifecycle.py:383-385 fails any ref that 'does not resolve to a file'.",
     "evidence": "PLATES.csv: generation_licence_ref = 'shippable' x76 (and 'n_a_lane_r' x9 on the Lane-R rows — also not a path). 'shippable' is a member of licence_class_vocabulary (docs/generation_lifecycle.json:108), i.e. the value of a DIFFERENT field. ls docs/licence_records/ returns README.md and RECORD_SCHEMA.json only — zero records; GATE 36 independently prints 'licence refs 0 · records 0'. read_boards.py's T-LICENCE (:544-546) passes these rows because it asserts only NON-EMPTY, never resolution — so the one tooth that looks at this column cannot tell a path from a slogan."
    },
    {
     "severity": "BLOCKER",
     "what": "PLATES.csv is a live, status-bearing, licence-ref-bearing table that is NOT enrolled in docs/generation_lifecycle.json — the exact violation class L1 was minted to make unrepresentable. GATE 36 cannot see it (it walks registries/ only), so the gate is currently reporting a FALSE global state: '0 rows are complete' while 76 complete rows sit on disk.",
     "contract_cite": "docs/generation_lifecycle.json:32 — '_enrolment_note: Checked in BOTH directions … A live registry that HAS the status column but is not declared FAILs (a new generable surface cannot join the tree unenrolled and unqueried).' harness/check_generation_lifecycle.py:170-173 implements it; :8-16 states the defect class ('a starved queue and a finished night are byte-identical from the outside').",
     "evidence": "Scope proof: harness/check_generation_lifecycle.py:97-126 load_registry_tree(reg_dir) walks only the registries/ tree; PLATES.csv lives at docs/art_boards/. Consequence measured live — GATE 36 output: 'census: {\"pending\": 897, … \"complete\": 0 …}' and 'DECLARED NOT-ARMED: (L8) 0 rows are `complete`… (L9) 0 rows carry a licence ref and 0 records exist'. PLATES.csv simultaneously carries generation_status=complete x76 and generation_licence_ref x76. Both statements are true of their own scope; together they are the can't-match-reads-as-clean class. Compounding it: a programmatic header re-read shows PLATES.csv carries generation_status / generation_licence_ref / generation_prompt_hash but has NO regeneration_trigger and NO last_generated_timestamp column — so even once the hash changes, DR-2 §6.3's self-enforcing regeneration has no column on this table to fire through."
    },
    {
     "severity": "MAJOR",
     "what": "The prompt payload that produced each of the 76 hashes is stored nowhere in the repository. The hashes are therefore unfalsifiable — no gate, critic or future session can re-derive or refute a single one, which voids the reproducibility rule the hash exists to serve.",
     "contract_cite": "docs/generation_lifecycle.json:127 — '_ground: PIPE_CONTROL_PLANE §3: generation_prompt_hash = sha256(canonical_json(prompt_payload))'; :128 '_key_closure: … an unhashed field is a field that cannot fire a regeneration.' The declared home for the payload facts is the licence record: docs/licence_records/RECORD_SCHEMA.json properties seed / generation_tier / decay_stage / presentation_tier_ref / prompt_hash / out_path / sha256 / bytes.",
     "evidence": "Positive-controlled grep: grep -rIl for the literal hash '40db54e8173bd85d105e1a80f8e4375e82fca682937bd77eced5c6d5f36f2bd9' across the repo returns exactly one file, docs/art_boards/PLATES.csv. The control: the same tool run for 'generation_prompt_hash' returns 5+ files and for 'res_multistep' returns 3, so the single hit is a genuine absence, not a dead search. The only payload on the machine is an untracked scratchpad temp file (_payload.json) that will not survive the session."
    },
    {
     "severity": "MAJOR",
     "what": "harness/generation_prompt_hash.py closes the payload KEY set but not the VALUE set: only licence_class is checked against a vocabulary. That is why three out-of-vocabulary tokens reached 76 stored hashes through the one function whose stated job is refusing bad payloads.",
     "contract_cite": "harness/generation_prompt_hash.py:19-24 — 'the payload key set is CLOSED … A missing key raises. An undeclared extra key raises. There is no \"best effort\" mode' and, separately, '`licence_class` is one of those required keys, drawn from a closed vocabulary'. The three axis keys have declared enums elsewhere (docs/ASSET_DROPIN_CONTRACT.md:236-237, :290-295) that this function never consults.",
     "evidence": "harness/generation_prompt_hash.py:95-99 is the ONLY membership test in validate_payload — `lc = payload.get(\"licence_class\"); if lc not in vocab: raise`. generation_tier, decay_stage and presentation_tier_ref pass through untouched. Demonstrated end-to-end: the live payload carrying REVIEW / none / structure produced a hash that GATE 43 then byte-verified as valid (declared=76 complete=76 verified=76 PASS)."
    },
    {
     "severity": "MAJOR",
     "what": "style_lora_hash is an owed member of the hash payload per the ART lane's own contract, but it is absent from the CLOSED required_payload_keys — and the hash function REFUSES undeclared keys. The field is therefore unhashable by construction: the first style-LoRA swap will silently fail to fire a regeneration.",
     "contract_cite": "docs/pipeline_review/tech_research/PIPE_ART_2026-07-29.md:151-152 — '`generation_prompt_hash` must include a licence-class token alongside model id + weights revision + style-LoRA hash + `generation_tier` + `decay_stage` + `presentation_tier_ref`'; restated at docs/proposals/CONCEPT_ART_PROGRAM.md:387-389. Against docs/generation_lifecycle.json:116-126, whose nine-key list omits it, and harness/generation_prompt_hash.py:90-94, which raises on any undeclared key.",
     "evidence": "style_lora_hash is a REQUIRED column on PLATES.csv (harness/read_boards.py:196) and is empty on all 91 rows today, so no drift has occurred yet — that is exactly why this is fixable now and not after. Adding the key later re-baselines every stored hash, and because check_art_pointers.py P3 puts hash[:12] in the FILENAME (:96, :198-204), a re-baseline is a physical re-generation of every affected plate, not a CSV edit."
    },
    {
     "severity": "MAJOR",
     "what": "asset_path's middle segment is a prompt-pack kebab row-slug, not a canon id. The declared path form and DR-2's founding invariant both require a canon id there. GATE 43's P2 checks segment COUNT only, so no tooth can see it.",
     "contract_cite": "docs/ASSET_DROPIN_CONTRACT.md:24 — '§1 The invariant: An asset is addressed by its CANON ID, never by a content path.' harness/check_art_pointers.py:95-98 declares the form verbatim: '<class_id>/<canon_id>/<plate_role>__<hash12>.<ext>'.",
     "evidence": "Live rows: 'D1-01/mount-inerie-cone-country/board_candidate__40db54e8173b.png', 'D1-16/the-polo-attested-animal-forms/board_candidate__0b7a61a61f72.png' — 19 such slugs, all sourced from the prompt pack's row_id field (brd0001_pack_final.json rows carry row_id 'liang-bua-cave-mouth-approach' etc., and no canon_id field at all). The tooth that should catch it is inert by construction: check_art_pointers.py:192-195 only counts segments (PATH_DEPTH=3), never resolves segment 2. Consequence measured: GATE 39 reports BRD_0001 at 'canon ids 62 · covered 5 · pending 57' — 76 plates of spend moved that number by nothing, because none of them is addressed at a canon id."
    },
    {
     "severity": "MAJOR",
     "what": "The board-shape contract is broken and structurally invisible: a 3-panel / 3-option board now carries 19 distinct panel values and a 4th option index, with a plate_role outside the charter set. Because `panel` has no vocabulary and every panel-keyed tooth is written against literal 'A'/'B', the entire lane-G set is exempt from the gate's five highest-value teeth.",
     "contract_cite": "docs/art_boards/README.md — 'A DECISION SURFACE, not a mood wall. Three panels, three options per panel, exactly one marked recommendation per panel'; harness/read_boards.py:158-165 BOARD_SHAPES place_3panel = (3, 3); :180-182 'Panel A option is a plate sheet … silhouette and architecture, palette and light, and one signature detail == exactly 3, charter-fixed'.",
     "evidence": "MANIFEST.csv BRD_0001 declares panel_count=3, options_per_panel=3. PLATES.csv holds 19 distinct panel values (mount-inerie-cone-country, komodo-dragon-guardian, the-polo-attested-animal-forms, …) at option_index 1-4 (counts 24/24/24/19) with plate_role='board_candidate' x76. T-SHAPE (read_boards.py:336-341) compares only the manifest's two numbers against the shape table and never looks at a plate; T-PANELA-REF (:485), T-PANELA-PREVIZ (:493), T-MARKETING (:507) and T-PLATE-COUNT (:616-622) are all keyed on panel == 'A' or 'B'. Result: read_boards.py reports STRUCTURAL PASS on a 91-plate board with 15 WARNs and zero FAILs."
    },
    {
     "severity": "MAJOR",
     "what": "The board row and its own plates record contradictory values for the same three facts — generation_tier, decay_stage and density class — and no tooth compares them, even though the repo already has the pattern for exactly this (L7 mirror lock).",
     "contract_cite": "harness/check_generation_lifecycle.py:53-55 — 'L7 MIRROR LOCK. Where two columns hold one lifecycle fact … they must agree per row unless the divergence is declared. Two columns cannot drift into two answers.' docs/generation_lifecycle.json:93-100 declares the mechanism.",
     "evidence": "MANIFEST.csv BRD_0001: generation_tier=region_hero, decay_stage=present, density_class_id=D1-01. The hashed payload for that board's plates: generation_tier='REVIEW', decay_stage='none'. The plates' asset_path class segments span eight density classes — D1-01, D1-02, D1-03, D1-11, D1-15, D1-16, D1-22, D1-26 — while the board's declared class is D1-01 and its subject_canon_id is flores_island."
    },
    {
     "severity": "MAJOR",
     "what": "A PREVIZ_DIRECTIONAL plate has no lawful representation today: the tokens it needs to make C-12's demotion fire (rights_value / image_use_class previz_only) are barred unconditionally from every panel of every board, while the standing ruling requires such plates to publish on the public site. v2/v3 emits this tier, so the contradiction lands tonight.",
     "contract_cite": "harness/read_boards.py:496-510 and docs/art_boards/README.md — '`previz_only` is barred on EVERY PANEL of EVERY BOARD, regardless of class and regardless of what `marketing_eligible` says.' Against docs/spine/DECISIONS_PENDING_JOSH.md:11-12 — 'Tier honesty rides the public surface: PREVIZ_DIRECTIONAL plates publish WITH their directional label visible.'",
     "evidence": "The demotion mechanism requires the token in the hash to move: docs/generation_lifecycle.json:108 closes licence_class at [shippable, previz_only, excluded], and harness/generation_prompt_hash.py:172-178 proves the equation (three classes, three distinct hashes). 'PREVIZ_DIRECTIONAL' and 'SHIPPABLE_CONCEPT' appear nowhere in any closed vocabulary — repo-wide grep finds them only in the ruling prose at DECISIONS_PENDING_JOSH.md:11. So a PREVIZ_DIRECTIONAL plate that keeps licence_class='shippable' hashes identically to a shippable plate and its demotion is undetectable; one that sets licence_class='previz_only' and rights_value='previz_only' is FAILed on sight by T-MARKETING."
    },
    {
     "severity": "MAJOR",
     "what": "The public marketing site publishes plate-grain images from a board that is is_private=true and whose computed marketing_eligible is FALSE, and nothing gates the plate-grain publishing decision — the only eligibility computation in the system is board-grain.",
     "contract_cite": "docs/art_boards/README.md — '`is_private` starts TRUE and only the marketing pass may flip it'; '`marketing_eligible` is COMPUTED from the licence classes of the board's plates … A board computes TRUE only if EVERY plate carries a rights value that clears marketing reuse.' harness/read_boards.py:137 MARKETING_CLEAR_SET; :604-611 T-MARKETING-COMPUTED.",
     "evidence": "MANIFEST.csv BRD_0001: is_private=true, marketing_eligible=false. The computation is correct and blocking: the board holds 4 museum_open_access plates, which read_boards.py:132-137 deliberately excludes ('a museum's open-access grant is not automatically a marketing grant'), so the board computes FALSE. The 76 generated_local plates individually clear, but no tooth expresses per-plate marketing eligibility — the field, the cache and the tooth are all board-grain, and the ruled site architecture (region pages, asset-type subpages) is plate-grain."
    },
    {
     "severity": "MINOR",
     "what": "model_revision is a weights FILE NAME, not a revision. The same filename can hold different bytes, so the reproducibility field does not pin what it claims to pin.",
     "contract_cite": "docs/licence_records/RECORD_SCHEMA.json, model_revision — 'Weights revision / commit / release date. A model id without a revision is not reproducible.'",
     "evidence": "PLATES.csv weights_revision = 'z_image_turbo_bf16.safetensors' on all 76 rows; the same string is the payload's model_revision. A HF revision SHA or release tag is the field's declared content."
    },
    {
     "severity": "MINOR",
     "what": "citation_refs is empty on all 76 lane-G rows while the pack that produced them carries dense per-row canon citations. The derivation trail exists only in an untracked scratchpad file, and the gate cannot notice because it only fails ids that fail to RESOLVE.",
     "contract_cite": "harness/read_boards.py:399-409 T-CITE-RESOLVE fails unresolved ids only; the board-level T-CITE-PANELA (:392-396) is satisfied by the manifest row's own six SRC ids, so a plate with no citation at all passes.",
     "evidence": "PLATES.csv: citation_refs = '' on every lane=G row (the 9 Lane-R rows carry SRC ids). The pack rows each carry a rich canon_refs string, e.g. 'docs/spine/CH_02.md:83-84 … flores_island.md:51 … docs/art_bibles/flores_island.md:22-28' — the evidence exists and was simply not carried onto the row."
    }
   ],
   "required_changes": [
    {
     "change": "Correct the three payload axis tokens to declared members BEFORE any further generation. generation_tier: 'REVIEW' -> the enum member the plate is actually generated against ('region_standard' for board candidates, 'region_hero' if it is to match the board row's own declaration). decay_stage: 'none' -> 'n_a' (the declared non-bearing token). presentation_tier_ref: 'structure' -> '' (the empty string the canonical fixture itself uses for an axis that does not apply to a place/being/object plate). Carry the render preset ('REVIEW 1344') in the sampler_config, where resolution already lives, or in the record's notes — never in generation_tier.",
     "where": "The prompt-pack -> payload composer (the code that wrote scratchpad/_payload.json), and the 76 existing rows in docs/art_boards/PLATES.csv",
     "shape": "Three literal token substitutions in the payload builder. The 76 already-generated plates are the expensive part: their hashes change, and because check_art_pointers.py P3 puts hash[:12] in the filename, every affected file must be renamed to its new hash12 with sha256 and asset_path re-emitted in the same commit (superseded files to E:/pipe_archive/, kept never deleted). Doing this before tonight costs 76 renames; doing it after costs ~376."
    },
    {
     "change": "Make the one canonical hash function close the VALUE space, not only the key space: validate generation_tier, decay_stage and presentation_tier_ref against their declared enums exactly as licence_class is validated, raising PayloadError on a non-member. Publish the three vocabularies from docs/generation_lifecycle.json so there is one source rather than a copy in read_boards.py.",
     "where": "harness/generation_prompt_hash.py validate_payload (beside the licence_class check at :95-99); the vocabularies added to docs/generation_lifecycle.json prompt_hash alongside required_payload_keys",
     "shape": "Roughly the same six lines the licence_class check already occupies, times three, plus a payload_value_vocabularies block in the contract JSON. Add one must-fire fixture per axis to selftest() so the teeth are armed — a tooth without a failing fixture is not armed, and this is the function whose failure let three bad tokens through."
    },
    {
     "change": "Stop putting a licence CLASS in the licence REF column. Write a real per-artifact licence record for every generated plate and point generation_licence_ref at its repo-relative path.",
     "where": "docs/licence_records/<registry_base>/<canon_id>.json (new files, git-tracked) and the generation_licence_ref column of every lane-G row in docs/art_boards/PLATES.csv",
     "shape": "One JSON per plate validating against docs/licence_records/RECORD_SCHEMA.json with all 12 required fields non-null: canon_id, registry, lane='art', generating_model='Tongyi-MAI/Z-Image-Turbo', model_revision (a real HF revision, not the safetensors filename), model_licence='Apache-2.0', licence_class='shippable', previz_only=false, prompt_hash, out_path, sha256, bytes — plus seed, generation_tier, decay_stage, presentation_tier_ref, which is what makes the hash re-derivable and closes the unfalsifiable-hash finding in the same stroke. Because the plate lane has no per-plate registry, the <registry_base> segment and the record-to-row join need a one-line declaration in docs/generation_lifecycle.json rather than an improvised convention."
    },
    {
     "change": "Enrol the plate lane in the generation lifecycle, and widen GATE 36's L1 sweep so an unenrolled status-bearing table outside registries/ can actually be seen. Until the sweep is widened the enrolment is a declaration nobody checks — which is the same defect one level up.",
     "where": "docs/generation_lifecycle.json (enrolment, id_columns, resolved_path_columns, licence_record join) and harness/check_generation_lifecycle.py load_registry_tree / L1 (:97-126, :160-173)",
     "shape": "Either add an `extra_tables` list to the contract that L1 walks alongside the registries tree, or move PLATES.csv's lifecycle columns under a registries/-resolvable path. Add PLATES.csv's missing lifecycle columns — regeneration_trigger and last_generated_timestamp — or the C-12 self-enforcement has no column to fire through on this table. Prove the widening with a must-fire fixture: a status-bearing table outside registries/ that is not declared must FAIL."
    },
    {
     "change": "Address plates by canon id. Give every prompt-pack row a canon_id resolving into a live registry (creature/site/character/scene) or an explicitly declared pending id, and make the path form's middle segment that id rather than the kebab slug. Then arm the tooth that would have caught this.",
     "where": "The prompt pack row schema (add canon_id beside row_id), the asset_path composer, and harness/check_art_pointers.py P2 (:189-195)",
     "shape": "P2 currently counts segments; extend it to resolve segment 2 against the union of live registry id spaces plus a declared pending set, FAILing an unresolvable one. Keep the human-readable slug as a title/caption field for the site — it is good copy, it is just not an address. This is the change that lets the 76 (and the 300) plates count toward GATE 39's 57 pending canon ids instead of running beside them."
    },
    {
     "change": "Decide what a lane-G board IS before 300 more plates inherit the current shape, then make the gate enforce it. Today 76 plates hang off a 3x3 place_3panel board under 19 invented panel names, which exempts them from the five teeth that carry the program's actual rights and care discipline.",
     "where": "docs/art_boards/MANIFEST.csv + PLATES.csv, and harness/read_boards.py (T-SHAPE at :336-341, the panel-keyed teeth at :485-510 and :616-622)",
     "shape": "Either split the 19 subjects into their own boards with correct density_class_id and subject_canon_id, or ratify a new board_shape for a serial candidate sheet and add it to BOARD_SHAPES with its own panel/option rule. Either way: give `panel` a closed vocabulary and make T-SHAPE read the PLATES rows, not only the manifest's self-description, so a board cannot declare 3x3 while holding 19x4. Add plate_role to the closed-vocabulary set too ('board_candidate' is currently unruled)."
    },
    {
     "change": "Rule the PREVIZ_DIRECTIONAL contradiction before v2/v3 emits the tier. As things stand the tier cannot be represented: the tokens that make the C-12 demotion mechanical are the same tokens that are barred unconditionally from every board.",
     "where": "docs/art_boards/README.md + harness/read_boards.py:496-510 (the unconditional bar) against docs/spine/DECISIONS_PENDING_JOSH.md:11-12 (the site ruling); and the v2/v3 pack's tier field",
     "shape": "A director-level ruling with the two options steelmanned, not a code patch. Option A: PREVIZ_DIRECTIONAL plates never enter PLATES.csv — they live in a separate previz manifest with its own gate, and the board bar stays unconditional. Option B: the 4.2a bar is narrowed to promotion-and-bible rather than existence, and a directional_label column becomes mandatory on any previz_only row. Whichever is ruled, the mechanical requirement is fixed and must be written into the pack: licence_class='previz_only' in the hash payload (the only token the demotion equation can read), rights_value='previz_only', image_use_class='PREVIZ-ONLY', and a licence record with previz_only=true — the RECORD_SCHEMA allOf already refuses the inconsistent combination."
    },
    {
     "change": "Give the public site a plate-grain publishing gate before it goes up, and flip is_private through the ruled marketing pass rather than by building a site around it.",
     "where": "docs/art_boards/MANIFEST.csv (is_private, marketing_eligible), harness/read_boards.py T-MARKETING-COMPUTED (:604-611), and the site builder wherever it lands",
     "shape": "Add a computed per-plate marketing_clear derivation (rights_value in own_capture / public_domain / generated_local AND the reveal firewall passed) and make the site read that field rather than iterating PLATES.csv. Keep the board-grain cache as-is. Without it, the first publish puts museum_open_access and reveal-gated rows one loop iteration away from a public page."
    },
    {
     "change": "Carry the pack's canon_refs onto the plate rows.",
     "where": "docs/art_boards/PLATES.csv citation_refs on every lane-G row",
     "shape": "Map each pack row's canon_refs to the SRC ids / spine file:line pointers the row actually used, pipe-separated. The evidence already exists in the pack; it is being dropped at the CSV boundary, and the scratchpad copy will not survive the session."
    }
   ],
   "verified_ok": [
    "BYTE INTEGRITY IS REAL. GATE 43 ran live in FULL_INTEGRITY mode: 'declared=76 complete=76 verified=76 PASS'. Every declared asset_path resolved under E:/art/boards and every file's sha256 matched its row byte-exact. This is the strongest thing in the pass and it is genuinely earned.",
    "CONTENT ADDRESSING HOLDS. All 76 filenames end __<first 12 of generation_prompt_hash>.png and P3 confirmed the suffix equals the stored hash prefix on every row — so a regeneration physically cannot overwrite its predecessor (check_art_pointers.py:40-45). The naming scheme is doing exactly the job it was minted for.",
    "PATH FORM (mechanically). P2 passed on all 76: relative, forward-slashed, no drive letter, no leading slash, no '..', exactly 3 segments. The defect is the CONTENT of segment 2, not its shape.",
    "NO PATH COLLISIONS, NO DUPLICATE CONTENT. P4 and P5 both clean — 76 distinct paths, 76 distinct sha256. At 19 rows x 4 seeds that is a real check on the generator returning one image for four seeds, and it did not.",
    "licence_class = 'shippable' in the hash payload is CORRECT — a member of the closed vocabulary at docs/generation_lifecycle.json:108, and the one payload token that was right. The C-12 demotion equation is therefore live for these plates the moment a record exists to demote.",
    "THE MODEL LICENCE CLAIM IS GROUNDED. Z-Image-Turbo bf16 / Apache-2.0 / SHIPPABLE is recorded at docs/CONCEPT_ART_PRODUCTION_PROGRAM.md:275 and squares with PIPE_ART_2026-07-29.md:198 (SHIPPABLE = MIT / Apache-2.0 / BSD / OpenRAIL++). The licence is right; only its RECORDING is missing.",
    "rights_value / image_use_class pairing is correct on all 82 generated rows: generated_local is one of the ruled six (read_boards.py:84-85) and GENERATED-CLEAN-WEIGHTS is its matching permission token (:114-116). rights_value is not double-gated for this class by design (:124-129), so no identifier is owed.",
    "THE BOARD'S OWN HONESTY DISCLOSURE IS INTACT. docs/art_boards/flores_island/BRD_0001/board.md:178-180 already states that Z-Image-Turbo is NOT the PIPE_ART-ruled primary, that the model-selector benchmark is still open, and that the pass ran outside the ruled night window. That is exactly the register-honesty the QA program demands, recorded before anyone asked.",
    "GATE SELF-TESTS ARE NOT RUSTED. check_art_pointers self-test: 8 teeth, 20 must-fire fixtures + 2 must-not-fire baselines PASS. GATE 36 ran generation_prompt_hash's 21 fixtures 21/21 including the demotion equation. GATE 39 canon_derivation: 0 findings, residue 0, 201/201 proposals declaring a resolving canon parent. The rulers are sound — the findings above are about what the rulers are POINTED AT, not about the rulers.",
    "GATE 39's pending-declaration discipline is working as designed: BRD_0001's 57 uncovered canon ids are explicitly declared pending with resolving destinations and appear on the artifact's face, so residue is honestly 0. The board is not lying about what it realized.",
    "The 15 legacy Lane-R rows remain coherent: pending status, no asset_path, correct reference-set rights on Panel A of a 'living' board, OPEN-ACCESS carrying its granting institution. T-PANELA-REF — the gate's highest-value tooth — is genuinely satisfied where it can still reach."
   ]
  },
  {
   "lane": "assetgen-keying",
   "derivation": {
    "derived_from": [
     "docs/CONCEPT_ART_PRODUCTION_PROGRAM.md:185-192 — the ruled path form `$HUMANITY_ART_ROOT/<class_id>/<canon_id>/<plate_role>__<hash12>.<ext>`; `<canon_id>` = 'the canon row the plate realizes. The path itself carries the derivation. A plate that cannot name a canon id has nowhere to be written.'",
     "docs/CONCEPT_ART_PRODUCTION_PROGRAM.md:165-168 — 'One row = one entity = one decision', the entity's canon id + [SRC:] anchor visible as text, and the residue block naming every pending id WITH ITS OWNING REGISTRY.",
     "docs/CONCEPT_ART_PRODUCTION_PROGRAM.md:232-244 — the 8 landed teeth of harness/check_art_pointers.py (P0-P7); P2 is the only path-form tooth.",
     "docs/concept_art/density_model.csv — all 43 rows read. Column `ruled_source` already NAMES the keying registry per class: D1-01→T0_Region_Index · D1-02→T0_Chapter_Index.emotional_register · D1-03/D1-04→spine 3b site rows · D1-09→T0_Character_Index · D1-11→T0_Antagonist_Network_Registry · D1-13→T0_Boss_Encounter_Registry(main) · D1-14→T0_Boss_Encounter_Registry(sub/env) · D1-15→T0_Creature_Roster · D1-16→T0_Chapter_Index.folklore_entities_in_chapter · D1-22→T0_Weapon_Registry · D1-26→T0_Equipment_Registry · D1-29→T0_Familiar_Registry · D1-37→T0_Language_Script_Registry+T0_Inscription_Spine · D1-40→T0_Vril_Site_Registry.",
     "docs/pipeline_review/tech_research/PIPE_CONTROL_PLANE_2026-07-29.md:115-118 — 'A work order is a registry row, not a ticket. (registry, RowName, canon_id, generation_tier, decay_stage, presentation_tier_ref) is the whole key.'; :160-163 — the artifact record `{canon_id, registry, lane, generating_model, ... out_path, sha256 ...}`.",
     "docs/pipeline_review/tech_research/PIPE_ART_2026-07-29.md:137-145 — '(a) Board plates never enter DR-2. They are decision surfaces.' / '(b) The ratified art bible is the promoted output', consumer = TD_ASSETGEN §4.1 `mesh_tags.style` ('concept decides; geometry executes').",
     "docs/pipeline_review/tech_research/PIPE_3D_2026-07-29.md:128-131 — 'The pipeline is dispatched BY `mesh_id_ref` and writes back TO that same id. It never chooses a path.'; :136-140 — generation_tier closed enum is a REQUEST parameter, never a path segment; :161-162 — the mesh image critic 'reads captures against the concept-art reference'.",
     "docs/pipeline_review/tech_research/LOCAL_3D_ASSET_GEN.md:113 (TRELLIS.2) and :194 (TripoSG) — 'Input: single image'. The AssetGen lane is image-conditioned; it needs a specific PNG per canon row, not a prose art bible.",
     "docs/ASSET_DROPIN_CONTRACT.md:235-236 (§6.1 generation_tier closed enum at freeze: background · region_standard · region_hero · realm · apex); :290-295 (§6-D D3 decay_stage FREEZES AT EXACTLY TWO VALUES — the realm at its height, and its authored present); :317-321 (§7 presentation_tier_ref is the MASTERY-VARIANT axis; resolver key = (canon id, presentation_tier_ref)).",
     "docs/licence_records/RECORD_SCHEMA.json — `canon_id` and `registry` are MANDATORY required fields; canon_id description: 'The registry RowName this artifact fulfils. DR-2 §1: an asset is addressed by its CANON ID, never by a content path.' docs/licence_records/README.md:30-33 — record layout is `docs/licence_records/<registry_base>/<canon_id>.json`, and `generation_licence_ref` 'holds the repo-relative path of the record'.",
     "docs/generation_lifecycle.json — 12-registry enrolment list + `id_columns` map (the PK column per registry). harness/generation_prompt_hash.py:95-98,121-126,173-175,187-188 — licence_class closed vocab {shippable, previz_only, excluded}; self-test payload values generation_tier='region_standard'/'realm', decay_stage=''/'height', presentation_tier_ref=''/'PT_07'.",
     "docs/canon_derivation.json — GATE 39 contract: `derivation_sections` = ['## Asset anchors','## Mechanical anchors']; `id_families` = the 10 canon-id regexes (CR_\\d{4}, CHAR_\\d{4}, A_[A-Z]_\\d{3}, VS_CH\\d{2}_\\d{3}, LINK_\\d{4}, WPN_\\d{3}, BE_\\d{4}(?:_S\\d)?, NW_\\d{4}, INSC_[A-Z0-9_]+, MASTERY_[A-Z0-9_]+); artifact BRD_0001 coverage_file = the board content.json `canon_coverage`.",
     "docs/art_boards/flores_island/BRD_0001/content.json `canon_coverage.rows` — 5 COVERED (CR_0001, CR_0002, INSC_77H_CH02_FRAGMENT, VS_CH02_001, VS_CH03_001), 57 PENDING.",
     "docs/art_boards/PLATES.csv — all 91 rows parsed (9 lane-R + 6 lane-P + 76 lane-G = 91). 19 unique lane-G subject slugs x 4. Header has 17 columns and NO canon_id, NO registry, NO class_id.",
     "docs/art_boards/MANIFEST.csv — BRD_0001: generation_class=living, generation_tier=region_hero, decay_stage=present, care_tier=elevated, marketing_eligible=false, approval_state=awaiting_pick.",
     "harness/read_boards.py:141,460,485-493,507 — LANE_R_ONLY_CLASSES=('living','unsettled'); `panel = (p.get('panel') or '').strip().upper()`; T-PANELA-REF fires only `if panel == 'A'`.",
     "harness/check_art_pointers.py:94-95,189-194,364-374 — P2 checks segment COUNT, absolute paths, backslashes, `..`; its own fixtures use the intended form `D1-15/CR_0001/...`. grep count: 'density_model'=0, 'registries'=0 (positive control 'asset_path'=10).",
     "The live registries, all read directly: T0_Creature_Roster (182 rows; CR_0001-CR_0016 Ch02/Ch03, CR_0151 Polo, CR_0152 Darat, CR_0153 Flores Giant Softshell Turtle) · T0_Boss_Encounter_Registry (281 rows; BE_0002, BE_0002_S1, BE_0002_S2, BE_0003, BE_0003_S1..S3 for CH_02/CH_03) · T0_Character_Index (164; CHAR_0001 The Protagonist, CHAR_0008 The Corrupted Hunter) · T0_Antagonist_Network_Registry (64; NW_0001 Corrupted Hunter, CH_02) · T0_Vril_Site_Registry (42; VS_CH02_001) · T0_Familiar_Registry (22; FAM_01 Shell, slot W1, creature_ref CR_0153) · T0_Environment_Grammar_Registry (112; EG_0003-EG_0021 are the Flores set) · T0_Equipment_Registry (1 row, EQ_0001 Mother's Amulet) · T0_Inscription_Spine (98; INSC_77H_CH02_FRAGMENT exists, spine_name still '[POPULATE→T1_Trades_Master]') · T0_Region_Index (76; flores_island.terrain_feature_class_ids = 'EG_0003;…;EG_0021') · T0_Chapter_Index (CH_02.folklore_entities_in_chapter = 'POLO_soul_eater_witch_nage_lio;TIWU_ATA_POLO_lake_of_the_wicked_dead;EBU_GOGO_wild_folk;KOMODO_PYTHON_dual_serpent').",
     "docs/spine/CH_02.md:77-93 (5 typed site rows), :95-102 (boss structure BE_0002 + subs), :104-162 (11 beats CH02_B01-B11), :226-235 (## Asset anchors), :237-254 (## Mechanical anchors). Lines 234-235 route the realm object and the Kelimutu carved mark as [POPULATE→T0_Equipment_Registry] / [POPULATE→ CH_63 consumer beat] — i.e. NO ROW EXISTS YET.",
     "The live prompt pack C:/Users/joshu/AppData/Local/Temp/claude/C--dev-humanity-forgotten/48d6b62d-e0a5-4853-9e61-c8409c1ea06f/scratchpad/brd0001_pack_final.json — 25 rows, keys ['row_id','title','canon_refs','category','prompt','negative','care_notes','generation_barred','bar_reason']; 6 rows barred to Lane R, 19 generated. Registry ids appear ONLY inside the free-prose `canon_refs` string, never in a structured field.",
     "Live gate runs, this session: `python harness/check_art_pointers.py` → 'MODE=FULL_INTEGRITY declared=76 complete=76 verified=76 PASS'. `python harness/read_boards.py` → '[PASS] BRD_0001 flores_island place_3panel class=living plates=91 state=awaiting_pick'. `python harness/check_generation_lifecycle.py` → all 12 registries green. All three pass WITH the defects below present.",
     "Positive controls run: (a) registry-header scan for plate/concept/board/art_ columns returned 3 unrelated hits while the control token `mesh_id_ref` hit 7 registries — the concept-pointer absence is real, not a failed search. (b) grep for concept/plate/art_board in WS7_WIRING_BUILD_PLAN.md = 0 with control 'registry' = 24. (c) T0_Worldstate_Variables scanned as 46 parsed rows; control ('CH_02' or 'ws.') matched 2 rows; 'kelimutu' and 'ward_recovered' matched 0. (d) `ls docs/licence_records` = README.md + RECORD_SCHEMA.json only — zero records for 76 generated artifacts."
    ],
    "not_derived": [
     "I did not open the ~300 plates queued for tonight — they do not exist yet. Every claim about them is an extrapolation of the 76 that DO exist, whose shape the task states will be reused.",
     "I did not verify the E:/art/boards binaries beyond confirming the 8 class directories exist and that check_art_pointers.py reported FULL_INTEGRITY over 76 sha256s. I did not open any PNG or judge image content.",
     "I did not read the pack v2/v3 `plates[{plate_slug, plate_role, serial_index}]` shape — it is described in the task but not yet on disk. My ruling on it is prescriptive, from the contracts, not a review of authored code.",
     "I did not find the marketing-site generator. `docs/pipeline_review/tech_research/PIPE_WEBSITE_2026-07-29.md` exists but I did not read it; my statement that the site cannot key on canon entities follows from PLATES.csv's column set alone, which is sufficient for that claim but is not a review of the site build.",
     "TD_ASSETGEN §4.1 (`mesh_tags.style`, cited by PIPE_ART as the promoted art bible's declared consumer) — I read PIPE_ART's citation of it, not the document itself. I could not locate a file named TD_ASSETGEN in the tree.",
     "I did not adjudicate whether the four missing Lane-R rows (liang-bua, first-search, ebu-gogo, village-edge x2, ngadhu-bhaga) were correctly barred. Their `bar_reason` cells read sound; the care call is outside this pass."
    ]
   },
   "violations": [
    {
     "severity": "BLOCKER",
     "what": "The `<canon_id>` path segment carries a board slug, not a canon id, on all 76 generated plates. The AssetGen lane cannot find a concept plate for any canon row, in either direction: PLATES.csv has no canon_id column and no registry has a concept-plate column. The join is not hard — it is absent.",
     "contract_cite": "docs/CONCEPT_ART_PRODUCTION_PROGRAM.md:191-192 — '`<canon_id>` — the canon row the plate realizes. The path itself carries the derivation. A plate that cannot name a canon id has nowhere to be written.' PIPE_CONTROL_PLANE:115 — 'A work order is a registry row… (registry, RowName, canon_id, …) is the whole key.' RECORD_SCHEMA.json canon_id — 'an asset is addressed by its CANON ID, never by a content path.'",
     "evidence": "Live paths: `D1-15/komodo-dragon-guardian/board_candidate__605407931294.png`, `D1-01/mount-inerie-cone-country/…`, `D1-22/kelimutu-waterfall-cavern-boss-pocket/…`. The gate's OWN fixtures use the intended form `D1-15/CR_0001/z__aaaaaaaaaaaa.png` (check_art_pointers.py:365-373). PLATES.csv header = plate_id,board_id,panel,option_index,plate_role,rights_value,image_use_class,generation_licence_ref,generation_prompt_hash,model_id,weights_revision,style_lora_hash,asset_path,sha256,generation_status,citation_refs,lane — no canon_id, no registry, no class_id. Reverse direction: a header scan of all 60 registries found ZERO columns matching plate/concept/board/art_ (positive control `mesh_id_ref` hit 7 registries). A dispatcher holding CR_0001 has no query that returns its plates; a plate holding 'komodo-dragon-guardian' has no query that returns CR_0001."
    },
    {
     "severity": "BLOCKER",
     "what": "Three of the nine hash-payload tokens are outside their closed vocabularies. Because hash12 IS the filename, every one of the 76 files is named by an invalid hash and must be regenerated or renamed — and the §6.3 self-enforcement (a tier change fires regeneration_required) is defeated at the root.",
     "contract_cite": "docs/ASSET_DROPIN_CONTRACT.md:235-236 · :290-295 · :317-321; harness/generation_prompt_hash.py:121-126,187-188",
     "evidence": "(1) `generation_tier:'REVIEW'` — the closed enum at freeze is `background · region_standard · region_hero · realm · apex` (DR-2 §6.1). 'REVIEW' is not in it. BRD_0001's own MANIFEST.csv row reads generation_tier=region_hero. (2) `decay_stage:'none'` — §6-D D3 'FREEZES AT EXACTLY TWO VALUES', the realm at its height and its authored present; MANIFEST.csv reads decay_stage=present; the hash script's own base payload uses '' for not-applicable (generation_prompt_hash.py:125), never 'none'. (3) `presentation_tier_ref:'structure'` — this is the MASTERY-VARIANT axis, resolver key (canon id, presentation_tier_ref), self-test value 'PT_07' (generation_prompt_hash.py:188). 'structure' is the honest-tier REPORTING register (structure / greybox-feel / final) from CLAUDE.md — a different vocabulary entirely. Only `licence_class:'shippable'` is valid (closed vocab {shippable, previz_only, excluded}, generation_prompt_hash.py:173-175). Fixing these renames all 76 files; fixing them AFTER tonight renames ~376."
    },
    {
     "severity": "BLOCKER",
     "what": "Four of the 19 generated rows depict a thing that has no canon row anywhere in the repo. Under §3.2 they had nowhere to be written and were written anyway.",
     "contract_cite": "docs/CONCEPT_ART_PRODUCTION_PROGRAM.md:191-192; docs/spine/CH_02.md:234-235",
     "evidence": "`the-recovered-realm-object` — CH_02.md:234 routes it '[POPULATE→T0_Equipment_Registry]'; that registry holds exactly ONE row, EQ_0001 Mother's Amulet. No id minted. `the-kelimutu-carved-mark` — CH_02.md:235 routes it '[POPULATE→ CH_63 continental-revisit consumer beat]'; it is not equipment, and T0_Worldstate_Variables (46 rows, search positive-controlled: 'CH_02'/'ws.' matched 2, 'kelimutu' matched 0) has no row for it. `the-first-fire-night` — beat CH02_B02; beat ids live in the spine only, no registry keys on them. `flores-canopy-agility-course` — HL_0079 is a hard line, and T0_Hard_Lines is in GATE 39's `anchor_registries_excluded` precisely because rules 'are not things an artifact realizes'. All four sit in D1-26 or D1-03 directories under invented slugs."
    },
    {
     "severity": "BLOCKER",
     "what": "`panel` is being overloaded to carry the subject slug on all 76 lane-G rows, which silently disarms T-PANELA-REF — read_boards.py's self-declared 'HIGHEST-VALUE TOOTH' — on a board whose generation_class is `living`.",
     "contract_cite": "harness/read_boards.py:460 `panel = (p.get('panel') or '').strip().upper()`; :484-490 `if panel == 'A' and gen_class in LANE_R_ONLY_CLASSES…`; docs/art_boards/README.md 'the highest-value tooth in the gate'",
     "evidence": "Lane-R rows carry panel A/B/C. Lane-G rows carry `mount-inerie-cone-country` etc., so panel.upper() can never equal 'A' and the tooth cannot fire on 76 of 91 plates. MANIFEST.csv BRD_0001 generation_class=living, i.e. exactly the class the tooth exists for. `python harness/read_boards.py` returns '[PASS] … class=living plates=91'. This is the repo's own documented 'a search that CANNOT match reports zero' class (MEMORY.md), reproduced inside the board gate. Secondary: `option_index` runs 1-4 against a board declaring options_per_panel=3, and no tooth compares them."
    },
    {
     "severity": "MAJOR",
     "what": "Thirteen of the 19 rows (52 of 76 plates) carry a `class_id` whose density-model `ruled_source` registry cannot own the entity depicted. Because §3.2 makes class_id the storage bucket the schedule budgets in ('how much of D1-15 exists is `ls | wc -l`'), every misfiled plate corrupts a budget line.",
     "contract_cite": "docs/CONCEPT_ART_PRODUCTION_PROGRAM.md:189-190; docs/concept_art/density_model.csv `ruled_source`",
     "evidence": "Worst case: `kelimutu-waterfall-cavern-boss-pocket` (pack category 'boss_staging', canon BE_0002) is filed under **D1-22 'Weapons - type silhouette'**, whose ruled_source is T0_Weapon_Registry — `ls E:/art/boards/D1-22 | wc -l` now reports 4 weapon plates that are not weapons. Next: `protagonist-arrival-state` (CHAR_0001, The Protagonist) is filed under **D1-11 'NPCs - antagonist operatives'** (ruled_source T0_Antagonist_Network_Registry). Three region-look rows (mount-inerie, kelimutu-rim, recharge-waterfall) sit in D1-01 'Region key art', whose ruled_source T0_Region_Index has exactly one Flores PK — `flores_island` — so all three collapse to one id and the site distinction is lost. Three artifact_prop rows sit in D1-26, a class the model sizes at 15 plates total from 1 equipment row. `the-polos-offer` (BE_0002_S2, a live sub-boss row) sits in D1-03 'Layouts - climactic sites'."
    },
    {
     "severity": "MAJOR",
     "what": "Zero per-artifact licence records exist for 76 generated artifacts, and `generation_licence_ref` holds a licence CLASS where the ratified contract says it holds a PATH TO THE RECORD. The record schema's two mandatory keying fields are exactly the two the plate rows lack.",
     "contract_cite": "docs/licence_records/README.md:16-18 ('One shared registry column, `generation_licence_ref`… It holds the repo-relative path of the record') and :30-33 (layout `docs/licence_records/<registry_base>/<canon_id>.json`); RECORD_SCHEMA.json `required` = [canon_id, registry, lane, generating_model, model_revision, model_licence, licence_class, previz_only, prompt_hash, out_path, sha256, bytes]",
     "evidence": "`ls docs/licence_records` returns README.md and RECORD_SCHEMA.json only — no records, for 76 `generation_status=complete` artifacts. PLATES.csv `generation_licence_ref` reads 'shippable' on all 76 rows: that is the licence_class token, not a record path. `model_licence` (the WEIGHTS licence — Z-Image-Turbo's actual terms, which the outputs inherit) is recorded nowhere. The lifecycle gate does not catch it because PLATES.csv is not in generation_lifecycle.json's 12-registry `enrolment` list; `python harness/check_generation_lifecycle.py` runs green. Note the record layout `<registry_base>/<canon_id>.json` is ALREADY the two-segment canon key the plate path needs — the answer to this whole pass is ratified and unused."
    },
    {
     "severity": "MAJOR",
     "what": "The board's own ratified GATE-39 coverage block DECLARES PENDING seven ids for which plates were then generated and attached to that same board_id — and five more realized ids are outside the coverage universe entirely, so GATE 39's residue tooth cannot see them.",
     "contract_cite": "docs/canon_derivation.json artifact BRD_0001 (coverage_file = content.json, coverage_key = canon_coverage, coverage_states CLOSED at {covered, pending}); docs/CONCEPT_ART_PRODUCTION_PROGRAM.md:167-168",
     "evidence": "content.json canon_coverage: 5 covered (CR_0001, CR_0002, INSC_77H_CH02_FRAGMENT, VS_CH02_001, VS_CH03_001), 57 pending. Declared PENDING yet plated: CHAR_0001 (reason on file: 'a region establishing board carries no faces by construction (it rules place, not person)'), CHAR_0008, NW_0001, CR_0003, CR_0004, CR_0005, CR_0006. Realized but absent from the universe: BE_0002, BE_0002_S1, BE_0002_S2, CR_0151, CR_0153 — the first three because GATE 39's `derivation_sections` is ['## Asset anchors','## Mechanical anchors'] and the BE_ ids live in CH_02.md:95-102 '## Boss structure'; CR_0151/CR_0153 because the anchors name 'the polo' and 'Shell' in prose without a CR id. `python harness/check_canon_derivation.py` passes."
    },
    {
     "severity": "MAJOR",
     "what": "`plate_role` is `board_candidate` x 76 — one undifferentiated value where the contract rules a per-class role set. This blocks the AssetGen lane at the point of use (image-to-3D takes ONE image and needs the orthographic/silhouette view, not a cinematic wide), collides two same-id rows in one directory, and removes the 'subpages per asset/view type' axis the marketing site is supposed to have.",
     "contract_cite": "docs/proposals/CONCEPT_ART_PROGRAM.md:99-102 — 'A boss sheet is four plates (silhouette, three-quarter orthographic, phase-transform, signature-attack pose with human scale). A creature sheet is three (silhouette, detail, behaviour pose). An NPC sheet is two (portrait, costume full-length).' LOCAL_3D_ASSET_GEN.md:113 (TRELLIS.2) and :194 (TripoSG) — 'Input: single image'.",
     "evidence": "PLATES.csv plate_role by lane: R → {silhouette:3, palette:3, detail:3}, P → {hero:3, law_in_action:3}, G → {board_candidate:76}. The legacy lanes carry real roles; the generated lane carries none. Concretely: `the-polo-vril-hollow-register` and `the-polo-attested-animal-forms` both resolve to CR_0151, so under the ruled path they land in one directory distinguished only by hash — with no role token, nothing says which is the conditioning view. And PIPE_3D:161-162 requires the mesh image critic to read captures 'against the concept-art reference' — it cannot select one."
    },
    {
     "severity": "MAJOR",
     "what": "One plate row carries four peer canon ids, against the contract's own unit and formula.",
     "contract_cite": "docs/CONCEPT_ART_PRODUCTION_PROGRAM.md:165 — 'One row = one entity = one decision'; density_model.csv D1-15 art_unit=SHEET_3, formula `creature_plates = species x 3`",
     "evidence": "`flores-ambient-fauna-study-sheet` names CR_0003 (crab-eating macaque), CR_0004 (wild boar), CR_0005 (Flores monitor), CR_0006 (Timor deer) — four species on one row at 4 plates. The formula asks for 4 species x 3 = 12 plates across 4 rows. Under `<class_id>/<canon_id>/` the row cannot name a single id, which is why it was given a slug instead."
    },
    {
     "severity": "MINOR",
     "what": "Two density classes name a `ruled_source` that has no primary key, so even a correctly-classed plate cannot derive a canon_id from them.",
     "contract_cite": "docs/concept_art/density_model.csv D1-16 ruled_source `T0_Chapter_Index.folklore_entities_in_chapter`; D1-03/D1-04 ruled_source `spine 3b site rows`",
     "evidence": "T0_Chapter_Index CH_02.folklore_entities_in_chapter = 'POLO_soul_eater_witch_nage_lio;TIWU_ATA_POLO_lake_of_the_wicked_dead;EBU_GOGO_wild_folk;KOMODO_PYTHON_dual_serpent' — a token list, not PKs; and T0_Chapter_Index is in GATE 39's `anchor_registries_excluded` as a NODE registry. The real PKs for those beings are CR_0151 (Polo, ontology_class CONDEMNED) and CR_0010 (Ebu Gogo) in T0_Creature_Roster. Likewise spine 3b site rows (`liang_bua_cave`, `kelimutu_crater_lakes`, …) are document keys; a registry-header scan for zone/site_id/beat columns found only T0_Vril_Site_Registry.site_id, a different id space (VS_CH02_001)."
    },
    {
     "severity": "MINOR",
     "what": "P2-PATH-FORM validates segment COUNT and syntax only — neither path segment is checked against anything real. The gate reports FULL_INTEGRITY over data it cannot evaluate.",
     "contract_cite": "docs/CONCEPT_ART_PRODUCTION_PROGRAM.md:238 (P2 tooth description); harness/check_art_pointers.py:94-95,189-194",
     "evidence": "`grep -c density_model harness/check_art_pointers.py` = 0; `grep -c registries harness/check_art_pointers.py` = 0 (positive control `asset_path` = 10). Live run: 'MODE=FULL_INTEGRITY declared=76 complete=76 verified=76 PASS' with `D1-22/kelimutu-waterfall-cavern-boss-pocket/` in the table. The repo's own P7 rationale — 'a check that cannot match reads as a check that passed' — applies to P2 here."
    }
   ],
   "required_changes": [
    {
     "change": "RULE THE KEY: `<class_id>` selects the registry; `<canon_id>` is a LIVE PRIMARY KEY in exactly that registry. Nothing else may occupy segment 2 — not a slug, not a beat id, not a site name. Make the class→registry binding machine-readable instead of prose, so the rule is derivable rather than remembered.",
     "where": "docs/concept_art/density_model.csv (+ .json); docs/CONCEPT_ART_PRODUCTION_PROGRAM.md §3.2",
     "shape": "Add two columns to density_model.csv: `key_registry` (the generation_lifecycle.json enrolment key, e.g. 'T0_Creature_Roster/Sheet1') and `key_column` (the PK column, e.g. 'creature_id') — both already exist as generation_lifecycle.json `id_columns`, so this is a re-point, not a mint. Repoint the two PK-less classes: D1-16 folklore ruled_source T0_Chapter_Index.folklore_entities_in_chapter → T0_Creature_Roster/Sheet1 (creature_id), because CR_0151/CR_0010 are the live rows and the Chapter_Index cell is a token list. For D1-03/D1-04 (spine 3b site rows) either mint a site registry or bind them to T0_Environment_Grammar_Registry/Sheet1 (env_feature_id) — T0_Region_Index.flores_island.terrain_feature_class_ids already carries 'EG_0003;…;EG_0021' as the live region→feature FK. Classes whose subject is a RULE (LAW LEAF: D1-02 material palette/style register, D1-28 Hollow surfaces, D1-34 UI/rarity) declare key_registry='(law)' and key their plates on the art-bible artifact id (BIB_0001), never on a fake canon row."
    },
    {
     "change": "RULE MULTI-ID: three dispositions, no fourth. The path always carries ONE primary id.",
     "where": "docs/CONCEPT_ART_PRODUCTION_PROGRAM.md §3.2 + the pack schema",
     "shape": "(a) SPLIT — N peer entities at the same grain become N rows. `flores-ambient-fauna-study-sheet` becomes D1-15/CR_0003, /CR_0004, /CR_0005, /CR_0006 (which is what density_model's own `species x 3` formula already asks for). (b) PRIMARY + REFS — one entity seen through a relation is filed under the class whose registry owns the VIEW, and the other ids ride a new `canon_id_refs` column (pipe-delimited, the separator the live registries already use). Rangku Wera = D1-11/NW_0001 (operative view) with canon_id_refs 'CHAR_0008|BE_0002'; he separately OWES a D1-09/CHAR_0008 portrait and a D1-13/BE_0002 boss sheet — three classes, one man, three plate sets, never one plate wearing three hats. (c) NO LIVE PK → NOT GENERATED. The row goes to the coverage block as `pending` with its reason and its owning registry, and the generator refuses it. This is §3.2 read literally and it is the tooth that would have stopped the four unmintable rows."
    },
    {
     "change": "THE 19-ROW MAPPING, ruled. Fourteen rows resolve to a live PK today; four have none; one is a law leaf.",
     "where": "docs/art_boards/PLATES.csv (76 lane-G rows) + the pack",
     "shape": "RESOLVE AS-IS (class correct, slug → id): komodo-dragon-guardian → D1-15/CR_0001 (refs BE_0002_S1) · reticulated-python-guardian → D1-15/CR_0002 (refs BE_0002_S1) · rangku-wera-the-quiet-step → D1-11/NW_0001 (refs CHAR_0008|BE_0002). RE-CLASS + RE-KEY: recharge-waterfall-first-water → D1-40/VS_CH02_001 (a real PK, and already COVERED in the board's GATE-39 block) · kelimutu-three-lakes-rim → D1-03/EG_0011 (refs EG_0010) · mount-inerie-cone-country → D1-04/EG_0004 · kelimutu-waterfall-cavern-boss-pocket → D1-13/BE_0002 (refs EG_0009, whose canonical_authority literally cites CH02_B08/CH02_B09) · the-polos-offer → D1-14/BE_0002_S2 · protagonist-arrival-state → D1-09/CHAR_0001 · shell-the-water-familiar-registration → D1-29/FAM_01 (refs CR_0153) · insc-77h-ch02-fragment-carrier → D1-37/INSC_77H_CH02_FRAGMENT · the-polo-vril-hollow-register → D1-16/CR_0151 (plate_role hollow_register) · the-polo-attested-animal-forms → D1-16/CR_0151 (plate_role attested_forms — same id, roles keep them apart). SPLIT: flores-ambient-fauna-study-sheet → D1-15/CR_0003 + /CR_0004 + /CR_0005 + /CR_0006. LAW LEAF: material-palette-and-style-register-plate → keyed on BIB_0001, not on CH_02. NO PK — HOLD OR MINT FIRST: the-recovered-realm-object (mint EQ_0002 in T0_Equipment_Registry; CH_02.md:234 already routes it there) · the-kelimutu-carved-mark (no home anywhere — needs a worldstate/anchor row minted before it can be plated) · the-first-fire-night (beat CH02_B02, no PK) · flores-canopy-agility-course (HL_0079 is a rule, excluded by construction)."
    },
    {
     "change": "ADD THE FORWARD JOIN to PLATES.csv — the columns without which the dispatcher cannot consume a plate.",
     "where": "docs/art_boards/PLATES.csv; harness/read_boards.py REQUIRED_PLATE_COLS (:195)",
     "shape": "New columns: `class_id` (density_model PK, must equal asset_path segment 1) · `canon_id` (the live PK, must equal segment 2) · `canon_registry` (the generation_lifecycle enrolment key — this is what makes the id unambiguous across id spaces) · `canon_id_refs` (pipe-delimited secondaries) · `subject_slug` (the human name — keep it, but ON the row, never in the path) · `view_axis`/`plate_role` from the ruled per-class role set. STOP overloading `panel`: restore it to {A,B,C} for board plates and add a declared lane-G token, or T-PANELA-REF stays disarmed. Backfill all 76 rows; regenerate filenames after the hash fix so the two migrations are one rename, not two."
    },
    {
     "change": "ADD THE REVERSE JOIN to the registries — one column, the WS7 mechanism, so a work order carries its own image input and the AssetGen dispatcher needs no human join.",
     "where": "the 12 registries in docs/generation_lifecycle.json `enrolment`; docs/registry_extensions.json; docs/fidelity_baseline.json",
     "shape": "Add `concept_plate_refs` (pipe-delimited plate_ids) to each enrolled registry, declared in registry_extensions.json under a NEW owner `concept-art` and in fidelity_baseline.json `added_columns`, both in the same commit (the standing --emit-baseline rule; check_registry_extensions.py enforces one-system-one-column). The dispatch loop then becomes: PIPE_CONTROL_PLANE's work order `(registry, RowName, canon_id, generation_tier, decay_stage, presentation_tier_ref)` → read `concept_plate_refs` → PLATES.csv → `asset_path` → the single conditioning image TRELLIS.2/Hunyuan3D require (LOCAL_3D_ASSET_GEN:113,194). WRITE DISCIPLINE: PIPE_ART:137 rules 'Board plates never enter DR-2', so `concept_plate_refs` is written by the PICK APPLY, never at generation time — it carries only picked plates. The full candidate set stays addressable through PLATES.csv by canon_id, which is exactly what the marketing site needs and cannot do today."
    },
    {
     "change": "FIX THE HASH PAYLOAD BEFORE TONIGHT'S ~300. Three tokens are out of vocabulary; hash12 is the filename, so the cost of fixing scales with every plate generated in the wrong shape.",
     "where": "the generation call site + harness/generation_prompt_hash.py --self-test fixtures",
     "shape": "`generation_tier` 'REVIEW' → 'region_hero' (BRD_0001's own MANIFEST value; the closed enum is background|region_standard|region_hero|realm|apex). `decay_stage` 'none' → 'present' (MANIFEST value; D3 freezes at height|present) or '' where genuinely not applicable — 'none' is in neither vocabulary. `presentation_tier_ref` 'structure' → '' (a region plate has no mastery variant); 'structure' is the reporting register and must never enter this field. Keep `licence_class:'shippable'` — it is valid. Then ADD a payload-vocabulary tooth to generation_prompt_hash.py's validator with must-fire fixtures for each of the three, so an out-of-enum tier is refused at hash time rather than discovered at rename time. All 76 existing files rename; do it now at 76 rather than later at ~376."
    },
    {
     "change": "ARM THE GATES. Four new teeth, each with a must-fire fixture that TODAY'S live data would trip.",
     "where": "harness/check_art_pointers.py, harness/read_boards.py",
     "shape": "P8-CLASS-ID — asset_path segment 1 is a live class_id in density_model.csv (would fire 0 times today; the classes are real, but the tooth must exist before class_ids are hand-typed at volume). P9-CANON-ID — segment 2 is a live PK in the registry that class_id's `key_registry` names, AND equals the row's `canon_id` column (would fire 76 times today). P10-ROLE — plate_role is in the class's ruled role set (would fire 76 times today: `board_candidate` is in no set). P11-LICENCE-RECORD — a `complete` row's `generation_licence_ref` resolves to a file under docs/licence_records/<registry_base>/<canon_id>.json that validates against RECORD_SCHEMA.json (would fire 76 times today: the cell holds 'shippable' and no records exist). In read_boards.py, add T-PANEL-VOCAB so a `panel` cell outside the declared vocabulary FAILS instead of silently routing around T-PANELA-REF. Every tooth ships with the must-fire/must-not-fire fixture pair and exit-3 on a rotted ruler, per the landed pattern."
    },
    {
     "change": "CLOSE THE GATE-39 SCOPE HOLE the plates exposed, and reconcile the board's coverage block with what was actually generated.",
     "where": "docs/canon_derivation.json `derivation_sections`; docs/art_boards/flores_island/BRD_0001/content.json `canon_coverage`",
     "shape": "Add '## Boss structure' to `derivation_sections` — BE_0002 / BE_0002_S1 / BE_0002_S2 are realized by plates today and are invisible to the residue tooth because the section is out of scope, while `id_families` already carries the BE_\\d{4}(?:_S\\d)? regex to catch them. Then flip the seven ids now plated from `pending` to `covered` (CHAR_0001, CHAR_0008, NW_0001, CR_0003, CR_0004, CR_0005, CR_0006) — or, if the standing reason holds ('a region establishing board carries no faces by construction'), move those plates off BRD_0001 onto their own leaf. Both are defensible; what is not defensible is a board asserting it did not realize an id while carrying four plates of it. And add CR_0151/CR_0153 to the universe: the anchors name 'the polo' and 'Shell' in prose with no id, which is the same prose-only gap the contract's own _note flags for CR_0003-CR_0006."
    }
   ],
   "verified_ok": [
    "The path FORM is right and should not change: `<class_id>/<canon_id>/<plate_role>__<hash12>.<ext>` is a genuinely good scheme. Content-addressing on the prompt hash means a regeneration physically cannot overwrite its predecessor, which turns 'rejected outputs are kept, never deleted' from a habit into a property of the filesystem. Only the VALUES in segments 1 and 2 are wrong, not the shape.",
    "The class→registry binding the whole fix rests on ALREADY EXISTS: density_model.csv's `ruled_source` column names a keying registry for all 43 classes, and generation_lifecycle.json's `id_columns` names the PK column for the 12 enrolled registries. The rule is a re-point of live data, not an invention — which is why it can land before tonight.",
    "The licence-record layout `docs/licence_records/<registry_base>/<canon_id>.json` is already the exact two-segment canon key the plate path needs, and RECORD_SCHEMA.json already makes canon_id and registry MANDATORY non-null. The contract answered this question before the question was asked; the lane simply did not use it.",
    "check_art_pointers.py's P0-P7 are real and well built. P6-INTEGRITY verified 76 sha256s byte-exact against disk this session; P7-BLIND-MODE correctly distinguishes MANIFEST_ONLY from FULL_INTEGRITY. The mutation-proof discipline (each tooth firing alone, file restored byte-exact) is the right standard — the gap is missing teeth, not weak ones.",
    "The Lane-R bar held. Six of the 25 pack rows were correctly barred from generation with a stated reason (liang_bua_cave elevated care; manggarai village edge protected living heritage; ikat/loom living dress with a DECLARED substrate gap; ngadhu/bhaga protected Ngada heritage with no corpus anchor). No living-tradition subject was routed to Lane G, and the two `bar_reason: false` overrides are argued (neutral-ground macro prop studies depicting no zone).",
    "The Batch-0 backfill that PIPE_CONTROL_PLANE §3 demanded HAS LANDED. That doc reports 796 of 831 rows carrying empty-string generation_status; the live registries now read 'pending' throughout (Creature 182, Boss 281, Character 164, Antagonist 64, Familiar 22, Vril_Site 42, Weapon 72, Equipment 1), and check_generation_lifecycle.py runs green across all 12 enrolled registries. That doc's defect section is stale in the good direction.",
    "The pack rows are genuinely well grounded. `canon_refs` cites file:line into docs/spine/CH_02.md, the region page, and the art bible on every row, and the registry ids ARE present in that prose — CR_0001, CR_0151, CR_0153, BE_0002, CHAR_0001, CHAR_0008, NW_0001, VS_CH02_001, INSC_77H_CH02_FRAGMENT all appear. The derivation is real work; it is simply trapped in a string field where no consumer can read it. Promoting those ids into structured fields is a lift, not a re-derivation.",
    "`licence_class:'shippable'` is the one hash token that IS in its closed vocabulary ({shippable, previz_only, excluded}, generation_prompt_hash.py:173-175), and folding it into the hash is the correct mechanism — a model demoted to previz_only changes every filename and fires regeneration_required automatically.",
    "The rights/image-use posture on the 76 generated rows is coherent: rights_value=generated_local with image_use_class=GENERATED-CLEAN-WEIGHTS is the ruled pairing, and no previz_only plate exists anywhere in the table — so the §4.2a unconditional bar is satisfied in fact, even though T-PANELA-REF could not check it."
   ]
  },
  {
   "lane": "engine-factory",
   "derivation": {
    "derived_from": [
     "docs/art_boards/PLATES.csv — all 91 rows read in full (15 legacy PLT_0001_*, 76 lane-G PLT_G004..PLT_G025); tabulated by lane/class/subject/role/licence-ref with a Python pass",
     "docs/art_boards/MANIFEST.csv — the single BRD_0001 row, every column",
     "docs/art_boards/README.md:1-150 — the interface contract, closed vocabularies, board law, publish/re-publish law",
     "harness/read_boards.py:80-200 (the ruled vocabularies) and :440-640 (the plate-grain teeth); gate executed live, exit 0",
     "harness/generation_prompt_hash.py:1-238 in full; --self-test run live (21/21 PASS)",
     "docs/generation_lifecycle.json — prompt_hash + licence_record blocks in full",
     "harness/check_generation_lifecycle.py — scope greps + the L9 licence-record fixture at :675-705",
     "harness/generation_lifecycle_scorecard.json — including its declared_not_armed block",
     "harness/check_art_pointers.py:1-60 (the eight teeth) + :94 (the ruled path form)",
     "harness/art_pointers_scorecard.json (FULL_INTEGRITY, 76/76 byte-verified)",
     "build/boards/board_scorecard.json (emitted live by the gate run)",
     "harness/gates_config.json — 43 gates; boards + art_pointers + generation_lifecycle entries",
     "docs/concept_art/density_model.csv — header + the 8 class rows the plates touch (D1-01/02/03/11/15/16/22/26)",
     "docs/concept_art/art_throughput_plan.json in full",
     "harness/art_throughput.py:1-45 (the four-stage model) + positive-controlled greps for progress terms",
     "docs/proposals/CONCEPT_ART_PROGRAM.md — §2.4 (register block), §2.5 (board medium / privacy), §4.1-4.5 (lanes, generators, marketing rider, licence constraints, human placement, the gate)",
     "docs/FACTORY_CONTRACT.md — positive-controlled grep for art riders (43 'registry' hits as control); FR-006/040/072/080/081 read in full",
     "docs/factory_contract.json — exhaustive recursive walk for art-board references (0 hits against a 50-hit registry positive control)",
     "docs/art_bibles/flores_island.md:1-45 + flores_island.coverage.json",
     "docs/art_boards/flores_island/BRD_0001/content.json — full structure walk + register_block",
     "harness/boards/render_board.py — input/output greps (:484 PLATES.csv, :488 content.json, :743 board.html, :884 board.md)",
     "docs/spine/CH_02.md and docs/spine/CH_03.md — site-row blocks and protected flags, counted",
     "C:\\dev\\Humanity\\Humanity\\Tools — full listing + positive-controlled greps of import_registry_datatable.py, registry_import_manifest.json, import_audio_assets.py; repo-wide grep of Tools/Source/Config",
     "git log/show for 97bbdb00 (the rebuild commit) — file list and message; mtimes of content.json / board.html / board.md"
    ],
    "not_derived": [
     "The prompt-pack scratchpad JSONs. No path was given and I found none in the repo, so I verified NOTHING about their canon_refs, their care_notes, or the proposed v2/v3 plates[]/tier additions. Every statement I make about canon grounding is about what lands in PLATES.csv, not about what the packs contain.",
     "The 76 PNG binaries. I never opened an image. No claim here touches beauty, authenticity, physics coherence, slop, or reveal leakage — those are the four critics' verdicts and this pass is presence/shape/accounting only.",
     "REALM_DESIGN_PROGRAM.md, PIPE_ART_2026-07-29.md, PIPE_CONTROL_PLANE, ASSET_DROPIN_CONTRACT.md, STACK_CONFLICT_ADJUDICATIONS C-12. I quoted these ONLY through the inline citations in read_boards.py and generation_lifecycle.json. I did not open the source documents, so if any of them was amended after those citations were written, my vocabulary claims inherit that staleness.",
     "docs/CONCEPT_ART_PRODUCTION_PROGRAM.md — referenced by art_throughput.py but not read.",
     "The marketing site. No site generator, template, or build script for it exists in this repo that I could find, so my finding is about the RULE it would violate, not about code I reviewed.",
     "board.html contents beyond grep counts (76 board_candidate/D1-01/PLT_G matches vs 3 legacy matches). I did not read the rendered page.",
     "The full run_gates.py suite. I ran read_boards.py and generation_prompt_hash.py --self-test live and read the committed scorecards for the rest; I did not re-run all 43 gates.",
     "Whether Josh's 'O2' pick exists anywhere. MANIFEST pick_panel_a/pick_panel_b/pick_cards_c are all EMPTY and writeback_status is 'pending', so I found no recorded pick to trace. If an O2 pick was made in conversation, it has not reached disk."
    ]
   },
   "violations": [
    {
     "severity": "BLOCKER",
     "what": "The §4.4 Lane-G refusal rule is UNREPRESENTABLE on a plate row, so nothing can enforce it on the ~300 plates queued for tonight. The board's own register_block states the law verbatim: 'Panel A is Lane R only... The five elevated-care zones can never dispatch to Lane G at all: the composer refuses and emits a Lane-R plate request instead (CONCEPT_ART_PROGRAM 4.4).' PLATES.csv has NO care_tier column, NO protected column, and NO FK to a site row — PLATES_REQUIRED is (plate_id, board_id, panel, option_index, plate_role, rights_value, image_use_class, generation_licence_ref, generation_prompt_hash, model_id, weights_revision, style_lora_hash, asset_path, sha256, generation_status, citation_refs, lane). The commit message for 97bbdb00 says '6 elevated-care rows held as Lane-R acquisition cards', so the lane DID apply the rule by hand — but which rows were held, and why, exists only in a commit message. No pending Lane-R acquisition rows were minted for those 6, so they are invisible to the gate, to the worklist, and to any queue.",
     "contract_cite": "docs/proposals/CONCEPT_ART_PROGRAM.md:715 ('rows whose care_tier is in the living-tradition set can never dispatch to Lane G — the composer refuses and emits a Lane-R plate request instead'); docs/art_boards/flores_island/BRD_0001/content.json register_block.generation_eligible_basis; harness/read_boards.py:194-198 (PLATES_REQUIRED)",
     "evidence": "6 of the 12 Ch2+Ch3 site rows are protected true: docs/spine/CH_02.md:83 liang_bua_cave, :91 manggarai_village_edge ('protected living community referenced not reproduced', :92); docs/spine/CH_03.md:77 wae_rebo_village, :79 compang_lingko_fields, :85 todo_royal_village, :89 liang_bua_cave. Counted: CH_02 5 site rows / 2 protected true; CH_03 7 / 4. All 76 lane-G rows carry lane=G with no field on the row that could ever refuse one. read_boards.py has no lane vocabulary check at all — grep for a LANE_VOCAB constant returns nothing while RIGHTS_VOCAB/DECAY_STAGE_VOCAB/GENERATION_TIER_VOCAB all exist at :84-152."
    },
    {
     "severity": "BLOCKER",
     "what": "The generation_prompt_hash payload carries OUT-OF-VOCABULARY tokens on two of its nine keys and a wrong-axis value on a third. Because the filename is content-addressed on the first 12 hex of that hash, correcting any of them renames every file and invalidates every asset_path. Token by token: (1) generation_tier:'REVIEW' — the closed enum is (background, region_standard, region_hero, realm, apex); REVIEW is a THROUGHPUT-model perf tier (an s_per_plate key in art_throughput_plan.json), a different axis entirely, and BRD_0001's own generation_tier is region_hero. (2) decay_stage:'none' — the closed enum is (height, present, n_a), and read_boards.py's own comment says it 'FREEZES AT EXACTLY TWO VALUES' with n_a as the non-decay case, 'not a third stage'; BRD_0001's decay_stage is present, and the canonical fixture uses ''. (3) presentation_tier_ref:'structure' — the canonical fixture's own value is 'PT_07' and the live column holds PT_NN ids in T0_Ability_Tree_Registry and T0_Weapon_Registry; 'structure' is the report honesty register (structure / greybox-feel / final), not a presentation-tier id. validate_payload vocabulary-checks ONLY licence_class, so all three passed silently.",
     "contract_cite": "harness/read_boards.py:144 GENERATION_TIER_VOCAB, :149 DECAY_STAGE_VOCAB; harness/generation_prompt_hash.py:188 (fixture uses presentation_tier_ref 'PT_07') and :95-99 (only licence_class is vocabulary-gated); docs/generation_lifecycle.json prompt_hash.required_payload_keys",
     "evidence": "docs/concept_art/art_throughput_plan.json s_per_plate keys are exactly {HERO, HERO_FAST, ITERATION, ITERATION_BATCHED, REVIEW} — proving REVIEW is the throughput axis. MANIFEST.csv BRD_0001 declares generation_tier=region_hero, decay_stage=present. presentation_tier_ref is populated in registries/T0_Ability_Tree_Registry [ACTIVE v0.1]/Sheet1.csv and registries/T0_Weapon_Registry [DRAFT v0.1]/Sheet1.csv. Consequence chain: check_art_pointers.py:40-46 P3 requires the filename end __<first 12 of generation_prompt_hash>; every one of the 76 asset_paths encodes the current (wrong-token) hash, e.g. D1-01/mount-inerie-cone-country/board_candidate__40db54e8173b.png against generation_prompt_hash 40db54e8173bd85d... Fixing the tokens changes all 76 hashes, all 76 filenames, and fires regeneration_required on the whole set. Generating ~300 more tonight raises the rework from 76 to ~376."
    },
    {
     "severity": "BLOCKER",
     "what": "generation_licence_ref='shippable' is the wrong VALUE in the wrong COLUMN, and the C-12(1) per-artifact licence record does not exist for any of the 76. The contract resolves generation_licence_ref to a licence-record PATH (docs/licence_records/<registry_base>/<canon_id>.json); read_boards.py documents it as 'the C-12 MODEL licence' which 'reads n_a_lane_r on every Lane-R plate'. 'shippable' is neither — it is the licence_CLASS token, which already has its own home inside the hash payload. The real fact (Z-Image-Turbo is Apache-2.0) is recorded ONLY in the git commit message of 97bbdb00, not on any row and not in any record. Meanwhile docs/licence_records/ contains exactly two files, README.md and RECORD_SCHEMA.json, and zero records — so the twelve required_non_null fields (canon_id, registry, lane, generating_model, model_revision, model_licence, licence_class, previz_only, prompt_hash, out_path, sha256, bytes) are asserted for zero of 76 shipped-class artifacts.",
     "contract_cite": "docs/generation_lifecycle.json licence_record.path_form + required_non_null + _ground ('the per-artifact git-tracked record beside each output carries generating_model + model_licence + previz_only'); harness/check_generation_lifecycle.py:699 (fixture ref = 'docs/licence_records/T0_Fix_Registry/A.json', with model_licence 'MIT' and licence_class 'shippable' as SEPARATE fields); harness/read_boards.py:95-96",
     "evidence": "Tally over PLATES.csv: generation_licence_ref = {'shippable': 76, 'n_a_lane_r': 9, '': 6}. find docs/licence_records -name '*.json' returns exactly 1 path, RECORD_SCHEMA.json (positive control: the find DOES match json under that tree). This is the same defect §3.4 minted image_use_class to stop — 'naming the class without naming the grant' — recurring one column to the left, on the model-licence column."
    },
    {
     "severity": "BLOCKER",
     "what": "The generation_lifecycle gate PRINTS A FALSE STATEMENT ABOUT LIVE STATE and cannot see the art corpus at all. Its committed scorecard declares '(L8) NOT ARMED: 0 rows are complete and 0 are in_progress across 12 enrolled registries... arms on the first generated asset' and '(L9) NOT ARMED: 0 rows carry a licence ref and 0 records exist under docs/licence_records/... arms on the first record written' — while 76 generated assets are byte-verified on disk and 76 rows read generation_status=complete. Both assertions are false as statements about the project; they are true only about registries/, because the gate is invoked with --registries registries and never reads docs/art_boards/PLATES.csv. The entire generated-art corpus is therefore outside the ONLY gate that checks licence-record resolution, previz consistency, orphan records, and hash coherence.",
     "contract_cite": "harness/gates_config.json gate 'generation_lifecycle' -> {'script': 'harness/check_generation_lifecycle.py', 'args': ['--registries','registries','--contract','docs/generation_lifecycle.json',...]}; the noisy-gate-CONCEALS discipline (a gate that cannot match must never read as a gate that looked) — the same rule check_art_pointers.py:23-25 names for an absent volume",
     "evidence": "harness/generation_lifecycle_scorecard.json: gate PASS, rows_enrolled 911, census.complete 0, licence_refs 0, licence_records 0, declared_not_armed carries both L8 and L9 strings verbatim. Against harness/art_pointers_scorecard.json: MODE FULL_INTEGRITY, pointers_declared 76, rows_complete 76, pointers_byte_verified 76. Two gates in the same suite disagree about whether a generated asset exists, and the licence-bearing one is the one that says no."
    },
    {
     "severity": "BLOCKER",
     "what": "A PUBLIC marketing site built from PLATES.csv is barred tonight on three independent grounds, each currently true on disk. (1) The board medium ruling: the pages are private through selection, 'Not published, not indexed, not linked from anything public until every pick is made' — and NO pick has been made anywhere: pick_panel_a, pick_panel_b, pick_cards_c, pick_amendment_a, pick_amendment_b, picked_at, picked_by are ALL empty and writeback_status='pending'. (2) marketing_eligible computes FALSE for BRD_0001 and is gate-enforced as computed-never-asserted; it is blocked by the 5 museum_open_access legacy plates, and §4.2a rules explicitly that 'a museum's open-access grant is not automatically a marketing grant'. (3) is_private='true' on the manifest row, and README rules that only the marketing pass may flip it — after selection.",
     "contract_cite": "docs/proposals/CONCEPT_ART_PROGRAM.md:361-362 ('The pages are PRIVATE through selection. Not published, not indexed, not linked from anything public until every pick is made'); docs/art_boards/README.md:24-28 and :99-108 (marketing_eligible computed; museum_open_access deliberately excluded) and :108 (is_private starts TRUE); harness/read_boards.py:604-611 T-MARKETING-COMPUTED",
     "evidence": "MANIFEST.csv BRD_0001: is_private=true, marketing_eligible=false, approval_state=awaiting_pick, writeback_status=pending, all six pick_* cells empty. docs/art_bibles/flores_island.md:3-6 states it directly: 'Status: AWAITING PICK. Board BRD_0001 is published and unpicked, so nothing in this file is ratified and the asset factory must not read it yet.' Rights tally across the 91 plates: generated_local 76, museum_open_access 5, public_domain 4 — one museum_open_access plate is enough to compute the board FALSE."
    },
    {
     "severity": "MAJOR",
     "what": "The 76 rows route around T-PANELA-REF — which README:72 itself calls 'the highest-value tooth in the gate' — by writing a row-slug into the `panel` column. The tooth fires only on `panel == \"A\"`. All 76 carry rights_value=generated_local, which is NOT in the REFERENCE_SET; on a board whose generation_class is `living` (BRD_0001 is), a Panel-A row carrying that value FAILS. The evasion is not deliberate, it is structural: `panel` has NO closed vocabulary, `plate_role` has NO closed vocabulary, and `lane` has none either. So `panel='mount-inerie-cone-country'` and `plate_role='board_candidate'` (a role that appears in no ruling) pass unremarked, and with them goes the care fence.",
     "contract_cite": "docs/art_boards/README.md:70-72; harness/read_boards.py:485-491 (T-PANELA-REF keys on panel == 'A' and gen_class in LANE_R_ONLY_CLASSES) and :507-511 (T-MARKETING keys on panel != 'A')",
     "evidence": "PLATES.csv panel-column tally: for lane R/P rows {A: 9, B: 6}; for the 76 lane-G rows the column holds 19 distinct kebab slugs (mount-inerie-cone-country, kelimutu-three-lakes-rim, rangku-wera-the-quiet-step, the-polo-attested-animal-forms, ...). plate_role tally: {board_candidate: 76, silhouette: 3, palette: 3, detail: 3, hero: 3, law_in_action: 3}. The gate ran green: exit 0, STRUCTURAL PASS, 15 WARNs all of the harmless 'pending, no asset_path yet' kind."
    },
    {
     "severity": "MAJOR",
     "what": "Board-membership FK abuse: all 76 rows carry board_id=BRD_0001 while spanning EIGHT density classes. BRD_0001 is a single place_3panel board with density_class_id=D1-01 and subject_canon_id=flores_island. Every board-scoped query in the program is now computed over a plate set that does not belong to that board — the plate roster (the gate prints plates=91 for a 15-plate board shape), marketing_eligible, and first_pass_rate, which README:38 defines as boards_with_zero_regenerations / boards_reviewed over MANIFEST.csv and which is the ruled trigger for switching from one-board-at-a-time to all-remaining-at-once.",
     "contract_cite": "docs/art_boards/README.md:46-47 ('MANIFEST.csv one row per BOARD, PLATES.csv one row per PLATE') and :54-57 (the split exists precisely so approval state stays single-sourced per board); README:36-39 (the review cadence and first_pass_rate)",
     "evidence": "asset_path class-prefix tally over the 76: D1-01 12, D1-02 4, D1-03 12, D1-11 8, D1-15 16, D1-16 8, D1-22 4, D1-26 12. MANIFEST.csv holds exactly ONE row, BRD_0001, density_class_id=D1-01, board_shape=place_3panel, panel_count=3, options_per_panel=3. Gate output: 'BRD_0001 flores_island place_3panel class=living plates=91 state=awaiting_pick'."
    },
    {
     "severity": "MAJOR",
     "what": "MANIFEST.csv was NOT touched by the rebuild, so 76 images reached a Josh-facing surface under critic verdicts, an approval_state, and a recommendation set that all predate them. The commit that added the 76 plates changed board.html and PLATES.csv and left MANIFEST.csv untouched — so critic_verdicts (cultural_authenticity:GO_WITH_FIXES | physics_coherence:GO_WITH_FIXES | de_slop:GO_WITH_FIXES | reveal_discipline:GO) were rendered against the OLD three-panel content. The commit message says the PROMPT PACK was critic-gated, which is a different artifact: de-slop's ruled method is 'the unlabeled-relabel test run literally' and physics coherence judges an image — neither is runnable on a prompt. T-CRITICS checks that four verdicts are PRESENT and none is NO_GO; it has no way to check that they are CURRENT.",
     "contract_cite": "docs/art_boards/README.md:138-144 ('The four critics run BEFORE Josh sees anything... A NO-GO regenerates the board; Josh never sees a board that failed a floor'); harness/read_boards.py:171-178 (REQUIRED_CRITICS, STATES_JOSH_CAN_SEE)",
     "evidence": "git show --stat 97bbdb00 file list: docs/START_HERE.md, docs/art_boards/PLATES.csv (+76), docs/art_boards/flores_island/BRD_0001/board.html (1106 changed), and eight harness scorecards. MANIFEST.csv is absent from the list. MANIFEST.csv still reads approval_state=awaiting_pick, regeneration_count=0, recommend_panel_a=A1, recommend_panel_b=B1, recommend_cards_c=C-SQ-1."
    },
    {
     "severity": "MAJOR",
     "what": "The new board shape has NO decision surface, so the board law cannot be satisfied by it. The ruled law is 'exactly one marked recommendation per panel, and one pick line... A board with two marked recommendations is a board that failed to decide, and the deterministic gate rejects it.' A 19-row x 4-candidate board has no representation for 'one marked recommendation per row' and no representation for 19 pick lines. T-ONE-REC validated the STALE three-panel columns (A1 / B1 / C-SQ-1) against an option space of {1,2,3} derived from BOARD_SHAPES['place_3panel'] and passed. Josh is being shown 19 four-way choices on a surface whose recorded decision capacity is three.",
     "contract_cite": "docs/art_boards/README.md:9-11; harness/read_boards.py:365-386 (T-ONE-REC derives the option space from BOARD_SHAPES, panels A and B only, plus a single C card)",
     "evidence": "MANIFEST.csv recommend_panel_a=A1, recommend_panel_b=B1, recommend_cards_c=C-SQ-1 — three recommendations for 19 subjects. content.json still carries panels A/B/C with options len 3, 3 and slots len 5, i.e. the pre-rebuild structure (mtime 2026-08-02T19:36, unchanged by the rebuild commit)."
    },
    {
     "severity": "MAJOR",
     "what": "board.md, the in-repo mirror, is STALE — it shows a board that no longer exists. render_board.py writes board.html and board.md in the same run, but the rebuild changed only board.html. The mirror is the artifact that makes README's rule 'a pick that lives only on a web page is not canon' true of the repo rather than only of the sentence, and it now carries the superseded three-panel board.",
     "contract_cite": "docs/art_boards/README.md:27-28 ('THE PICK LINE STILL LANDS IN A COMMIT. The web page is the review surface; the ratified reading is versioned in-repo') and :51-52 (board.html and board.md are both GENERATED); harness/boards/render_board.py:743 (writes board.html) and :884 (writes board.md)",
     "evidence": "grep counts: board.html matches board_candidate|D1-01/|PLT_G 76 times; board.md matches 0 times (positive control: board.html matches PLT_0001|Panel 3 times, so the pattern class works). mtimes: board.html 2026-08-03T09:25:41, board.md 2026-08-02T19:36:42, content.json 2026-08-02T19:36:42. git show --stat 97bbdb00 lists board.html and not board.md."
    },
    {
     "severity": "MAJOR",
     "what": "NOTHING records D1-class plate progress. There is no artifact anywhere that answers 'how far is D1-01 against its 180-plate slice floor', and no column on a plate row that even names its class — the class is recoverable only by string-splitting asset_path. The four candidate homes each fail for a different reason: density_model.csv carries floors and targets with NO produced column; art_throughput.py is a pure forecast multiplier with no produced term at all; art_throughput_plan.json is its output; board_scorecard.json is board-scoped, not class-scoped; art_pointers_scorecard.json is pointer-scoped. So the schedule in art_throughput_plan.json (4.6 nights to the slice floor) cannot read real state and never will in this shape.",
     "contract_cite": "docs/concept_art/density_model.csv header — class_id, class_name, art_unit, formula, formula_inputs, ruled_source, slice_floor, slice_target, fullgame_floor, fullgame_target, fullgame_basis, missing_inputs, cheap_or_expensive, tier (14 columns, none of them produced/delivered/remaining); harness/art_throughput.py:12-14 ('This tool NEVER re-counts plates; it multiplies')",
     "evidence": "grep -cE 'produced|progress|remaining|delivered|done_count|actual' harness/art_throughput.py returns 0, against a positive control of 17 hits for 'DRAFT' in the same file. build/boards/board_scorecard.json contains exactly {boards: 1, plates: 91, plates_present: 76, structural, warns, critic_verdicts} — no per-class term. harness/art_pointers_scorecard.json contains pointers_declared 76 / rows_complete 76 / pointers_byte_verified 76 — no per-class term. PLATES.csv has no density_class_id column."
    },
    {
     "severity": "MAJOR",
     "what": "The 76 are DRAFT-STAGE candidates recorded as COMPLETE plates, which double-counts them against the plan's own arithmetic and corrupts the measured throughput. The throughput model is explicitly four-stage: DRAFT (k candidate seeds per plate at ITERATION tier — 'You do not ship the first sample'), REVIEW (the ONE selected candidate re-rendered), HERO (only APPROVED plates), REGEN. draft_k is 4 and the commit is '19 rows x 4' — so 76 rows are 19 plates' DRAFT stage, not 76 plates. Recorded as generation_status=complete they read as 76 finished plates to every consumer. Separately, they were rendered at REVIEW resolution (1344, per the commit message) while the model prices DRAFT at ITERATION tier (2.78 s/plate) and REVIEW at 5.35 s/plate — so the measured s_per_plate figures no longer predict this lane's real spend either.",
     "contract_cite": "harness/art_throughput.py:16-21 (the four-stage pipeline, verbatim); docs/concept_art/art_throughput_plan.json parameters.draft_k = 4 and s_per_plate {ITERATION: 2.78, REVIEW: 5.35}",
     "evidence": "19 distinct subject slugs x exactly 4 rows each = 76, verified by tally (D1-01 3 subjects, D1-02 1, D1-03 3, D1-11 2, D1-15 4, D1-16 2, D1-22 1, D1-26 3 = 19). All 76 carry generation_status=complete. Against D1-01's slice_floor of 180 (formula: boarded_region_rows 12 x 15_plate_board), the honest D1-01 credit tonight is at most 3 subjects' worth of draft candidates — and 0 finished plates, since no pick, no REVIEW selection and no HERO pass has occurred."
    },
    {
     "severity": "MAJOR",
     "what": "citation_refs is EMPTY on all 76 rows, so no plate can be shown to derive from the canon it depicts. The prompt packs carry canon_refs as file:line prose (per the task's own description of their shape) but nothing lands on the row. The gate cannot catch this: plate-level citation_refs are FK-checked only when non-empty, and the Panel-A citation tooth is satisfied by the BOARD-level citation_refs cell, which still carries the six legacy SRC ids from the old board. GATE 39 canon_derivation was landed on the principle that 'the artifact must DERIVE from the canon it claims' — on a plate row there is now nothing for it to read.",
     "contract_cite": "harness/read_boards.py:392-395 (board-level citation tooth) and :406 (plate citation_refs resolved against T0_Source_Material_Registry only when present); commit 30ba2469 'GATE 39 canon_derivation: the artifact must DERIVE from the canon it claims'",
     "evidence": "Tally: 76 of 91 rows have an empty citation_refs cell, and they are exactly the 76 lane-G rows (the 15 legacy rows all carry SRC ids such as SRC_00203|SRC_00469). MANIFEST.csv citation_refs still reads SRC_00203|SRC_00457|SRC_00469|SRC_00193|SRC_00483|SRC_00230 — six ids authored for the three-panel board."
    },
    {
     "severity": "MAJOR",
     "what": "ZERO factory-contract riders name concept art, boards, or plates as a data_home or a consumption_point, so the factory contract will never notice whether a chapter's plates exist. The five art-asset class riders all route elsewhere: FR-006 to T0_Ability_Tree_Registry#presentation_tier_ref, FR-040 to T0_Vril_Site_Registry#mesh_pipeline_type, FR-072 to NONE_YET(GAP-142), FR-080 to NONE_YET(GAP-144), FR-081 to T0_Scene_Spec_Registry#chapter. The single occurrence of the string concept_art in factory_contract.json is docs/concept_art/item_economy_sanity_band.csv, an item-economy band with no relation to the plate program.",
     "contract_cite": "docs/FACTORY_CONTRACT.md:138 (FR-006), :172 (FR-040), :204 (FR-072), :212 (FR-080), :213 (FR-081); docs/factory_contract.json",
     "evidence": "Exhaustive recursive walk of every string value in docs/factory_contract.json for art_boards|PLATES.csv|art_bibles|density_model|BRD_|D1-NN returned 0 hits, against a positive control of 50 registry-valued fields in the same walk. Positive control on the markdown too: 43 'registry' hits in docs/FACTORY_CONTRACT.md, so the greps can match."
    },
    {
     "severity": "MAJOR",
     "what": "The engine does not reference concept art AT ALL — there is no previz/greybox consumer for any plate. C:\\dev\\Humanity\\Humanity is reachable and Tools/ holds import_all_registries.py, import_registry_datatable.py, import_audio_assets.py and import_flores_landscape.py, but nothing in Tools/, Source/ or Config/ mentions concept art, art boards, plates or art bibles. The precedent shape exists and is proven on the audio lane: import_audio_assets.py DECLARES ue_sound_asset_path back onto the registry row so the runtime subsystem resolves a real asset. There is no art twin of that write-back, so a plate cannot dress a greybox and a greybox cannot report which plate it realized.",
     "contract_cite": "C:\\dev\\Humanity\\Humanity\\Tools\\import_audio_assets.py:57-59 (the DECLARE(ue_sound_asset_path) write-back pattern — the reusable precedent)",
     "evidence": "grep -rl for concept_art|art_boards|art_bible across Tools/, Source/ and Config/ returns nothing; grep -rn for concept.?art|art_board|PLATES\\.csv|art_bible|plate over import_registry_datatable.py and registry_import_manifest.json returns nothing. Positive control: 15 hits for 'registry' in import_registry_datatable.py, so the greps reach the files. harness/import_registry_datatable.py does not exist in this repo — the importer lives engine-side only."
    },
    {
     "severity": "MINOR",
     "what": "Two different ruled asset-path forms are live at once, and the ~300 plates tonight will cement the one the program document contradicts. CONCEPT_ART_PROGRAM.md §4.5 rules 'Board outputs land at E:\\art\\boards\\<subject>\\<board_id>\\'. check_art_pointers.py calls a different form 'the ruled form': <class_id>/<canon_id>/<plate_role>__<hash12>.<ext>. The plates follow the second and P2-PATH-FORM passes them. The tooth's form is the better one — it is content-addressed and class-keyed — but the program text was never amended, so a future reader has two ruled forms and no supersession note.",
     "contract_cite": "docs/proposals/CONCEPT_ART_PROGRAM.md:~726 (§4.5, 'Board outputs land at E:\\art\\boards\\<subject>\\<board_id>\\'); harness/check_art_pointers.py:94 ('<class_id>/<canon_id>/<plate_role>__<hash12>.<ext> -- the ruled form')",
     "evidence": "Live asset_path values are of the form D1-01/mount-inerie-cone-country/board_candidate__40db54e8173b.png — class-keyed, not <subject>/<board_id>/. art_pointers_scorecard.json reports P2-PATH-FORM PASS."
    }
   ],
   "required_changes": [
    {
     "change": "STOP the ~300-plate run in this shape until the hash payload tokens are corrected. Correcting generation_tier ('REVIEW' -> the row's real board tier from the closed enum, e.g. region_hero for D1-01), decay_stage ('none' -> 'n_a' or 'present') and presentation_tier_ref ('structure' -> a real PT_NN id or '') changes every hash, hence every content-addressed filename, hence every asset_path. Doing it before the run costs 76 regenerations; doing it after costs ~376.",
     "where": "the generation dispatcher that builds the payload passed to harness/generation_prompt_hash.py:prompt_hash; values must be drawn from harness/read_boards.py:144 GENERATION_TIER_VOCAB and :149 DECAY_STAGE_VOCAB",
     "shape": "Arm validate_payload against the value vocabularies, not just licence_class. Add to harness/generation_prompt_hash.py a per-key vocabulary map loaded from docs/generation_lifecycle.json (a new prompt_hash.payload_value_vocabularies block) so generation_tier and decay_stage are refused outside their enums exactly as licence_class already is, with must-fire fixtures for each. The existing self-test harness at :132-195 is the pattern; add one must-fire case per gated key."
    },
    {
     "change": "Give the plate row the four columns that make the care fence and the accounting representable, then arm the teeth that read them. Without density_class_id the accounting is a string-split; without care_tier and protected the §4.4 Lane-G refusal cannot exist as anything but a habit.",
     "where": "docs/art_boards/PLATES.csv (new columns) + harness/read_boards.py PLATES_REQUIRED at :194-198 and the plate loop at :455-587",
     "shape": "Add density_class_id (FK to docs/concept_art/density_model.csv class_id — read_boards.py already loads DENSITY at :73), subject_canon_id (FK to the site/registry row the plate depicts), care_tier and protected (both copied from that row, never hand-typed). Then add: T-LANE-VOCAB (lane in R|P|G); T-LANE-CARE (FAIL any row with lane=G whose care_tier is in the living-tradition set or whose protected is true — §4.4's refusal, stated as arithmetic); T-DENSITY-FK (density_class_id resolves in density_model.csv); T-PLATE-ROLE (closed role vocabulary — the five legacy roles plus whatever board_candidate is renamed to). Every one needs a must-fire fixture in the _CASES table at :764."
    },
    {
     "change": "Split the 76 lane-G rows off BRD_0001. They belong to 8 density classes and one 15-plate place_3panel board cannot own them; leaving them there corrupts the plate roster, marketing_eligible and first_pass_rate for the one board Josh is actually being asked to pick.",
     "where": "docs/art_boards/PLATES.csv board_id column (76 rows) + docs/art_boards/MANIFEST.csv (currently one row)",
     "shape": "Either (a) mint one MANIFEST row per real board — the honest reading, since a 19-subject candidate sweep across D1-01/02/03/11/15/16/22/26 is 8 or 19 boards, not one — each with its own density_class_id, board_shape, care_tier, generation_class, critic_verdicts and pick columns; or (b) if these are explicitly NOT boards but a pre-board candidate pool, move them out of PLATES.csv into a sibling table (CANDIDATES.csv) with its own gate, and let a plate row exist only once a candidate is selected. Option (b) also fixes the DRAFT-counted-as-complete problem below. Whichever is chosen, add T-BOARD-CLASS: every plate's density_class_id must equal its board's density_class_id."
    },
    {
     "change": "Write the C-12(1) licence record for every generated artifact and put a resolvable PATH in generation_licence_ref, not a class token. The Apache-2.0 fact currently lives only in a commit message.",
     "where": "docs/licence_records/<registry_base>/<canon_id>.json (currently empty but for README.md and RECORD_SCHEMA.json); docs/art_boards/PLATES.csv generation_licence_ref column (76 rows reading 'shippable')",
     "shape": "One record per plate carrying the twelve required_non_null fields from docs/generation_lifecycle.json licence_record — with generating_model 'Tongyi-MAI/Z-Image-Turbo', model_licence 'Apache-2.0' and licence_class 'shippable' as three SEPARATE fields, plus prompt_hash / out_path / sha256 / bytes copied from the row. Set generation_licence_ref to the record path. Emit them from the dispatcher at generation time, never by a later sweep — a record written after the fact is a record nobody verified."
    },
    {
     "change": "Extend the generation_lifecycle gate to read PLATES.csv, so its NOT-ARMED declarations stop being false. A gate that says 'arms on the first generated asset' while 76 generated assets sit byte-verified on disk is the conceal-by-scope defect this repo has a standing rule against.",
     "where": "harness/gates_config.json gate 'generation_lifecycle' args; harness/check_generation_lifecycle.py enrolment",
     "shape": "Add docs/art_boards/PLATES.csv as a second enrolled source alongside --registries, mapping plate_id->canon_id, generation_status->the five-state lifecycle, generation_licence_ref->the record path, generation_prompt_hash->the hash. L8 and L9 then arm on the corpus that actually exists. Re-emit the scorecard and confirm declared_not_armed shrinks — if it does not, the enrolment did not take."
    },
    {
     "change": "Refresh MANIFEST.csv for the rebuilt board and re-run the four IMAGE critics against the images, not the prompt pack. Josh must not be shown 76 plates under verdicts authored for a board that no longer exists.",
     "where": "docs/art_boards/MANIFEST.csv BRD_0001 row (critic_verdicts, approval_state, board_shape, panel_count, options_per_panel, recommend_*, citation_refs) — untouched by commit 97bbdb00",
     "shape": "Set approval_state back to critics_running until the four verdicts are re-rendered against the current plate set (de-slop's unlabeled-relabel test is only meaningful on an image). Then add T-CRITICS-CURRENT: bind the verdict cell to a hash of the board's plate_id + generation_prompt_hash set, so a plate set that changes after the critics ran forces the board out of a Josh-visible state automatically. Presence-of-four is not currency-of-four."
    },
    {
     "change": "Design a decision surface for the 19-row x 4-candidate shape before Josh sees it, or the board law has no expression. One recommendation per panel does not map onto 19 subjects.",
     "where": "docs/art_boards/README.md (the board law at :9-11), docs/art_boards/MANIFEST.csv recommend_*/pick_* columns, harness/read_boards.py T-ONE-REC at :365-386",
     "shape": "Per-row recommendation and pick, not per-board: a row-grain table (row_id, recommended_candidate, picked_candidate, picked_at, amendment) with exactly one recommendation per row enforced, and the board's pick line becoming a completeness assertion over that table. T-ONE-REC then reads the row grain for candidate-shaped boards and the panel grain for 3-panel boards, keyed on board_shape."
    },
    {
     "change": "Mint the per-class production ledger that makes the D1 schedule read real state. Right now nothing in the repo can answer 'how far is D1-01 against 180'.",
     "where": "a new emitted artifact, e.g. docs/concept_art/art_production_ledger.csv, emitted by a new harness/art_production_ledger.py; consumed by harness/art_throughput.py",
     "shape": "One row per density class: class_id, slice_floor, slice_target, fullgame_floor, fullgame_target (joined from density_model.csv, never re-typed), then draft_candidates, review_selected, hero_approved, picked, remaining_to_floor, remaining_to_target — each COUNTED from PLATES.csv by density_class_id and generation_status, never asserted. Emitted-never-hand-edited, like art_throughput_plan.json. Then teach art_throughput.py to subtract delivered work from the forecast so nights_at_night_window means time REMAINING rather than time from zero."
    },
    {
     "change": "Stop recording DRAFT-stage candidates as generation_status=complete. Four seeds per subject is the DRAFT stage of one plate, not four plates; recording them as complete makes every downstream count wrong by ~4x and inflates D1 progress against floors that count finished plates.",
     "where": "docs/art_boards/PLATES.csv generation_status on the 76 lane-G rows; the throughput model's stage definitions at harness/art_throughput.py:16-21",
     "shape": "Either carry a stage column on the row (draft | review | hero) so complete means 'complete AT THIS STAGE' and the ledger can count each stage separately, or hold candidates in the separate CANDIDATES.csv proposed above and let a PLATES.csv row exist only from the REVIEW selection onward. Also record the render resolution/tier actually used (1344/REVIEW) on the row, so art_throughput_measurements.json can be reconciled against what the lane really did rather than against what the model assumed."
    },
    {
     "change": "Land canon_refs from the prompt packs onto the plate rows, and make the tooth refuse an empty cell on a generated row. A plate that cannot name the canon it depicts cannot be checked against it by GATE 39 or by any critic.",
     "where": "docs/art_boards/PLATES.csv citation_refs (empty on all 76); harness/read_boards.py:392-406",
     "shape": "Write the pack's canon_refs into citation_refs at dispatch, as SRC ids resolvable in T0_Source_Material_Registry (or a second column canon_refs carrying file:line spine anchors if the pack's refs are spine prose rather than source ids — do not overload one column with two id spaces). Then change the plate citation tooth from resolve-if-present to FAIL-if-empty on any row whose lane is G or P, with a must-fire fixture. Board-level citation_refs must stop being able to satisfy a plate-level obligation."
    },
    {
     "change": "Regenerate board.md, and add a tooth that refuses a stale mirror. The in-repo mirror currently shows the superseded three-panel board while board.html shows the 76 candidates.",
     "where": "docs/art_boards/flores_island/BRD_0001/board.md (mtime 2026-08-02T19:36 against board.html 2026-08-03T09:25); harness/read_boards.py",
     "shape": "Re-run python harness/boards/render_board.py --board BRD_0001, which writes both files from PLATES.csv + content.json in one pass. Then add T-RENDER-CURRENT: a board FAILs if the plate rows it owns are newer than either rendered file, or if the rendered files disagree with each other on plate count. A generated file that can silently fall behind its source is a generated file that will."
    },
    {
     "change": "Add art riders to the factory contract, or the plate program has no per-chapter obligation and will never be measured by the machinery that measures everything else.",
     "where": "docs/FACTORY_CONTRACT.md rider table + docs/factory_contract.json (0 art-board references today)",
     "shape": "One art-asset rider per art-bearing obligation with data_home 'plates:docs/art_boards/PLATES.csv#density_class_id' and consumption_point the art bible, plus a tooth naming the class floor from density_model.csv. Follow the honest-NONE convention the existing riders already use: if the class ledger does not exist yet, the tooth reads NONE(<why>) and the rider stays SEED — recording the obligation is not the same as arming it, and the contract already knows how to say so."
    },
    {
     "change": "Decide and record whether the engine consumes concept art at all. Today 76 plates have no downstream consumer, which makes them orphans under the standing anti-orphan rule.",
     "where": "C:\\dev\\Humanity\\Humanity\\Tools (no art import exists) and docs/DOC_MAP.md",
     "shape": "Either build the art twin of the audio lane — an import that writes a ue_texture_asset_path (or previz_plate_ref) back onto the registry row exactly as import_audio_assets.py:57-59 declares ue_sound_asset_path, so a greybox can be dressed from a picked plate and a plate can report which greybox realized it — or declare in writing that concept art is a HUMAN-facing artifact only, never engine-imported, and record that as a routing fact in the program doc. Either answer is fine; the absence of an answer is what makes the plates orphans."
    },
    {
     "change": "Hold the public marketing site. No pick exists anywhere on disk, marketing_eligible computes FALSE, is_private is true, and the board medium is ruled private through selection.",
     "where": "the proposed marketing site build; gated on docs/art_boards/MANIFEST.csv pick_*/is_private/marketing_eligible",
     "shape": "Make the site a QUERY over MANIFEST.csv that can only emit boards where is_private is false AND marketing_eligible computes true AND writeback_status is applied — never a directory walk of E:/art/boards. Then the ruling enforces itself and the site is structurally incapable of publishing an unpicked or care-gated plate. Until then the correct surface is the existing default-private artifact page, which the program already rules and the manifest already tracks via published_url."
    },
    {
     "change": "Reconcile the two ruled asset-path forms in writing before ~300 more files cement one of them.",
     "where": "docs/proposals/CONCEPT_ART_PROGRAM.md §4.5 vs harness/check_art_pointers.py:94",
     "shape": "Amend §4.5 in place to the class-keyed content-addressed form the tooth enforces (<class_id>/<canon_id>/<plate_role>__<hash12>.<ext>), with a one-line supersession note naming the tooth as the reason — the living-source discipline, so no stale form survives to make a later agent hallucinate the old path."
    }
   ],
   "verified_ok": [
    "The plate BINARIES are genuinely there and genuinely match their rows. harness/art_pointers_scorecard.json reports MODE FULL_INTEGRITY with the art root resolved at E:\\art\\boards, pointers_declared 76, rows_complete 76, pointers_byte_verified 76, and all eight teeth P0-P7 PASS with zero findings and zero warns. The content-addressing discipline is real, not claimed: every filename ends __<first 12 of generation_prompt_hash> and matches.",
    "The hash FUNCTION is correct and armed. harness/generation_prompt_hash.py --self-test passes 21/21 live, including the C-12(3) equation (shippable / previz_only / excluded produce three distinct hashes), key-order independence, and a must-fire case for every required key in both directions (missing AND undeclared). The defect is in the VALUES the dispatcher feeds it, never in the function.",
    "licence_class 'shippable' is a legitimate token — it is one of the three in docs/generation_lifecycle.json licence_record.licence_class_vocabulary (shippable / previz_only / excluded). The error is that it was ALSO written into generation_licence_ref, a column that means something else.",
    "rights_value 'generated_local' paired with image_use_class 'GENERATED-CLEAN-WEIGHTS' is the correct pairing on all 76 rows. generated_local is deliberately absent from RIGHTS_TO_IMAGE_USE_CLASS (read_boards.py:124-131) because it is not double-gated, GENERATED-CLEAN-WEIGHTS is in IMAGE_USE_CLASS_VOCAB, and generated_local is in MARKETING_CLEAR_SET. T-RIGHTS-ONE, T-IMAGE-LICENCE and T-MARKETING all correctly pass on these rows.",
    "Z-Image-Turbo is a RULED model on a clean licence — CONCEPT_ART_PROGRAM.md §4.2 lists it at Apache-2.0. It is ruled for 'Fast iteration and variant sweeps on any class' and as the objects-class fallback, not as the primary for places (HiDream-O1-Image-Dev) or beings (Qwen-Image-2512), and art_throughput_plan.json openly declares the ruled primary 'is not installed' with a per-tier multiplier for when it is. The substitution is honestly recorded in the plan; it is simply not recorded on the plate rows.",
    "marketing_eligible=false is CORRECTLY computed, and T-MARKETING-COMPUTED is genuinely armed. The board carries 5 museum_open_access plates, which §4.2a deliberately excludes from the marketing-clear set, so the board computes FALSE and the asserted cell agrees. Adding 76 generated_local plates did not flip it. This tooth was hardened in commit 9936daad after being found inert, and it held.",
    "The look-law home EXISTS and is correctly built — this was the question I most expected to come back empty, and it does not. docs/art_bibles/flores_island.md is the face, docs/art_bibles/flores_island.coverage.json is the machine sidecar for GATE 39, MANIFEST.csv carries pick_panel_a / pick_panel_b / pick_cards_c / pick_amendment_a / pick_amendment_b / picked_at / picked_by / art_bible_ref / writeback_status, and read_boards.py:440-442 T-WRITEBACK FAILs a board whose approval_state is 'picked' while writeback_status is not 'applied' — so a pick cannot silently fail to reach the owning registry row. The bible correctly declares itself unratified and instructs the factory not to read it.",
    "The bible's own content is the right shape for a look law and already states the constraints the plates should have honored: a fixed palette independent of the pick, a light doctrine SET BY the Panel A pick (three readings, one to be chosen), a fixed material vocabulary, and the line 'Real cultural architecture is reference-built, never raw-generated (§17.1); only fantastical and vril structures are generation-sanctioned, and this region has none.' The document is not the problem; the 76 plates simply predate the pick that would activate it.",
    "The lane's own care JUDGEMENT was right even though it left no enforceable trace. Commit 97bbdb00 records '6 elevated-care rows held as Lane-R acquisition cards per the board's own register' — the lane applied §4.4 rather than dispatching everything. And the three Ch-2 place subjects it DID send to Lane G (mount_inerie_cone_country, kelimutu_crater_lakes, recharge_waterfall) are all protected false in docs/spine/CH_02.md:85-89, so Lane G is defensible for them on the routing facts. The finding above is about enforceability and record, not about a care breach I can demonstrate on those three rows.",
    "The boards gate itself is honest about its own scope: 'It gates PRESENCE and SHAPE only and never CONTENT' (README:134-136), and its 15 WARNs are all the benign 'pending, no asset_path yet -- promised but not produced (WARN, never FAIL)' class on the legacy Lane-R rows. It is not lying about what it checks; the gap is that nobody minted teeth for the new row shape."
   ]
  },
  {
   "lane": "scene-persona-terrain",
   "derivation": {
    "derived_from": [
     "docs/art_boards/PLATES.csv (all 91 rows parsed: lane G 76, lane R 9, lane P 6)",
     "docs/art_boards/MANIFEST.csv (BRD_0001 row)",
     "docs/art_boards/README.md (board/plate split, rights vocabulary, binaries-never-enter rule)",
     "docs/CONCEPT_ART_PRODUCTION_PROGRAM.md:100-215 (leaf unit, §3.1 id scheme, §3.2 the ruled path form) and :263-292 (the measured render-tier table)",
     "docs/ASSET_DROPIN_CONTRACT.md:24 (the invariant), :56-70 (the three named tier axes), :80-89 (the per-asset-class contract table)",
     "docs/translation/T99_Translation_Terrain.md:200-232 (PCG zone binding + the PARAMETERS BLOCK ownership split)",
     "docs/translation/T99_Translation_UE5_Build.md:184-213 (Step 6 PCG, Step 7 MetaHuman, Step 9 Sequencer)",
     "docs/translation/T99_Translation_Cinematic.md:70-90, :185-200 (the seven-column scene shape; scene_anchor_id as asset name + regeneration key)",
     "docs/pipeline_review/tech_research/GEODATA_TERRAIN_CHAIN.md:505-525 (Step 5 POPULATE, zone-polygon→PCG-bounds binding)",
     "docs/spine/CH_02.md:82-92 (§3b site rows + env/hosts lines), :99-101 (boss rows with phases [P1→CH02_B07, P2→CH02_B08]), :106-160 (§3c beat rows, each carrying `site <zone_id>` and `boss BE_0002.Pn`), :199-208 (§13 scene table), :226-254 (Asset + Mechanical anchors)",
     "build/zones/flores_island.csv (11 zone rows: zone_id, zone_bounds_wgs84, meters_per_vertex, area_bounds)",
     "harness/regen_zone_catalog.py:1-40 (column ownership; `zone_id == §3b site_id`)",
     "harness/read_boards.py:137,158,187-200,577,589-628,767-806 (closed sets, required columns, the tooth roster)",
     "harness/check_art_pointers.py:1-110, :192-195 (GATE 43 teeth P0-P7; PATH_DEPTH=3; the `<canon_id>` comment at :94)",
     "harness/gates_config.json (43 gates; `art_pointers` and `boards` both enrolled)",
     "harness/generation_prompt_hash.py:1-60 + docs/generation_lifecycle.json prompt_hash (closed 9-key payload; licence_class vocabulary)",
     "harness/check_generation_lifecycle.py:1-60 (L1-L9, the licence law)",
     "docs/art_boards/flores_island/BRD_0001/content.json (canon_scope + canon_coverage: 62 rows, 5 covered / 57 pending)",
     "docs/canon_derivation.json (GATE 39 artifact registration for BRD_0001 and BIB_0001)",
     "registries/T0_Boss_Encounter_Registry [DRAFT v0.1]/Sheet1.csv (281 rows; BE_0002 / BE_0002_S1 / BE_0002_S2 read in full)",
     "registries/T0_Character_Index [ACTIVE v1.1] (37-column header incl. the assetgen block)",
     "registries/T0_Creature_Roster [DRAFT v0.1] (182 rows; CR_0001-CR_0012 confirmed)",
     "registries/T0_Scene_Spec_Registry [ACTIVE v0.1] (37-column header; scene_id/scene_anchor_id/scene_spec_ref)",
     "registries/T0_Region_Index [ACTIVE v1.2] (region_id = flores_island, confirming subject_canon_id is a valid board-level FK)",
     "build/boards/board_worklist.json (the live generator worklist; the only home of the plate_role values in use)",
     "the prompt pack under review: <scratchpad>/brd0001_pack_final.json (25 rows; row_id/title/canon_refs/category/prompt/negative/care_notes/generation_barred)",
     "C:/dev/Humanity/Humanity/Tools/derive_region_bbox.py:95-120 (the running terrain consumer: reads build/zones/<slug>.csv, keys on zone_id, refuses to emit a guessed bbox)",
     "C:/dev/Humanity/Humanity/Tools/stage_flores_gather.py:116-130 (SITE_ANCHOR_BBOX keyed on zone_id)",
     "C:/dev/Humanity/Humanity/QA/bench/TERRAIN_CHAIN_PROVEN_2026-08-02.md (the chain receipt; 2.0801 m/vertex, EPSG:32751)",
     "E:/art/boards/D1-01/mount-inerie-cone-country/*.png (PNG IHDR read: 1344x1344 — the REVIEW row of the measured tier table)"
    ],
    "not_derived": [
     "Whether the ~300 plates queued for tonight use the same pack shape — I read only brd0001_pack_final.json; the v2/v3 packs with plates[{plate_slug, plate_role, serial_index}] do not exist on disk yet, so every claim about them is a claim about the SHAPE the brief describes, not about a file I read.",
     "The public marketing site — no site source, template, or generator exists in either repo; I found only docs/MARKETING_WEB_PROGRAM.md (a directive record whose own banner reads 'Canon served: NONE FOUND'). I ruled nothing about how the site consumes PLATES.csv because there is nothing to read.",
     "I did NOT run any gate. check_art_pointers.py and read_boards.py both write scorecards; the shared-tree read-only discipline applies to a critic lane. All gate claims are derived from reading the gate source and gates_config.json, not from an execution.",
     "I did not verify sha256 of any plate against its row (that is GATE 43 P6's job and it needs a write).",
     "No MetaHuman/persona lane doc beyond ASSET_DROPIN_CONTRACT + UE5_Build Step 7 + ENTITY_PERSONA_SYSTEM.md exists as a separate contract; there is no 'MetaHuman lane doc' to cite and I did not invent one.",
     "T0_Familiar_Registry was not opened, so Shell's familiar-slot id is unverified; the pack cites CR_0153 and I carried that.",
     "POSITIVE CONTROLS RUN: (1) `layout_plan` returns zero across both repos — control `board_candidate` matches build/boards/board_worklist.json, so the scan can match; (2) zero lane-G path segments match ^(CR|CHAR|BE|VS|SCENE|EQ|INSC|LINK|NW)_ — control: the same scan over the pack's canon_refs prose returns 10 rows with ids, so the pattern works; (3) `art_pointers` returns zero in run_gates.py — that control FAILED (run_gates.py contains no gate names at all), and re-checking gates_config.json showed art_pointers IS enrolled; the enrolment claim below rests on the config, not the failed grep; (4) plate_role has no closed set in read_boards.py — control: BOARD_SHAPES:158 and MARKETING_CLEAR_SET:137 are closed sets in the same file."
    ]
   },
   "violations": [
    {
     "severity": "BLOCKER",
     "what": "No plate in lane G names a canon id anywhere — not in a column, not in the path. The ruled path form's second segment is defined as `<canon_id>` and all 76 rows put a hand-coined kebab slug there. The derivation exists only in a scratchpad JSON and dies with the session.",
     "contract_cite": "docs/CONCEPT_ART_PRODUCTION_PROGRAM.md:191-192 — \"`<canon_id>` — the canon row the plate realizes. The path itself carries the derivation. A plate that cannot name a canon id has nowhere to be written.\" Reinforced by docs/ASSET_DROPIN_CONTRACT.md:24 — \"An asset is addressed by its CANON ID, never by a content path.\"",
     "evidence": "PLATES.csv has 17 columns (plate_id, board_id, panel, option_index, plate_role, rights_value, image_use_class, generation_licence_ref, generation_prompt_hash, model_id, weights_revision, style_lora_hash, asset_path, sha256, generation_status, citation_refs, lane) — none of them is a canon id. Lane G puts the row-slug in `panel`, a column whose declared domain is A/B/C. 19 distinct path segments, 0 matching any id family (CR_|CHAR_|BE_|VS_|SCENE_|EQ_|INSC_|LINK_|NW_); positive control `board_candidate` matches, so the scan can match. Meanwhile the ids DO exist and are known: the pack's canon_refs prose names them for 10 of 25 rows (CR_0001, CR_0002, CR_0003-0006, CR_0010, CR_0151, CR_0153, CHAR_0001, CHAR_0008, CHAR_0009-0012, BE_0002, BE_0002_S2, VS_CH02_001, INSC_77H_CH02_FRAGMENT) and are simply dropped at emit."
    },
    {
     "severity": "BLOCKER",
     "what": "GATE 43 art_pointers names the `<canon_id>` segment in its own source comment and then never checks it. It validates depth and the hash suffix only, and reads no registry at all — so 76 rows violate the form the gate documents and the gate is green.",
     "contract_cite": "harness/check_art_pointers.py:94 — \"# <class_id>/<canon_id>/<plate_role>__<hash12>.<ext> -- the ruled form\"; enforcement is only PATH_DEPTH = 3 (:98) applied at :192-195, plus SUFFIX12 (:96). The gate's own charter (:14-24) is the can't-match-reads-as-clean class it exists to stop.",
     "evidence": "A grep of check_art_pointers.py for `registries|Character_Index|Creature_Roster|zone` returns zero — the file imports argparse/csv/hashlib/io/json/os/re/sys/tempfile/datetime/pathlib and nothing else. The gate IS enrolled (harness/gates_config.json: `art_pointers -> {script: harness/check_art_pointers.py, args: [--out, harness/art_pointers_scorecard]}`, one of 43), so this is a live blind spot on a running gate, not an unwired tool. Every one of tonight's ~300 plates will pass it."
    },
    {
     "severity": "BLOCKER",
     "what": "The 76 plates realize subjects that their own board's canon_coverage block declares PENDING with the reason \"not this board's obligation\" — plus four subjects that are not in the coverage universe at all. GATE 39's residue-zero claim for BRD_0001 is now false in substance.",
     "contract_cite": "docs/art_boards/flores_island/BRD_0001/content.json canon_coverage.note — \"Every id docs/spine/CH_02.md and docs/spine/CH_03.md name … each either COVERED by this board or declared PENDING with the reason it is not here … The residue — a canon-named thing this board neither realized nor admitted skipping — is zero\". Registered as a GATE 39 artifact at docs/canon_derivation.json (artifact_id BRD_0001, coverage_key canon_coverage).",
     "evidence": "canon_coverage.rows = 62 rows: 5 covered (CR_0001, CR_0002, INSC_77H_CH02_FRAGMENT, VS_CH02_001, VS_CH03_001), 57 pending. But lane G shipped 4 plates each for CHAR_0001 and CHAR_0008 — both declared pending, reason \"per-character design is a CHARACTER-SHEET obligation\" — and a fauna study sheet covering CR_0003/0004/0005/0006, all four declared pending, reason \"the ambient-fauna and folklore-being set is a CREATURE-SHEET obligation, not a region establishing board\". BE_0002, BE_0002_S2, CR_0151 and CR_0153 return NOT-IN-COVERAGE. All 76 rows carry board_id=BRD_0001, whose subject_canon_id is `flores_island` (a region-level FK) and whose board_shape is place_3panel."
    },
    {
     "severity": "MAJOR",
     "what": "(a) TERRAIN/LEVEL — no place-class plate carries `zone_id`, the only key the running terrain consumer accepts. Four of five subjects re-slug an existing zone_id into an unjoinable variant; one has no zone key at all.",
     "contract_cite": "C:/dev/Humanity/Humanity/Tools/derive_region_bbox.py:99-117 — reads build/zones/<slug>.csv, collects zone_bounds_wgs84 + meters_per_vertex, records `row.get(\"zone_id\")` as the anchor, and raises SystemExit \"has ZERO geo-anchored zones … Refusing to emit a guessed bbox\". Tools/stage_flores_gather.py:116-130 keys SITE_ANCHOR_BBOX on zone_id. harness/regen_zone_catalog.py:22 — \"zone_id == §3b site_id\". T99_Translation_UE5_Build.md:184 Step 6 — the PCG input is the Zone Catalog row, \"one zone row to one Attribute Set\". GEODATA_TERRAIN_CHAIN.md:512-519 — affordances drive which PCG graph runs \"via Bounds/Volume actors keyed to the zone catalog's own zone polygons\".",
     "evidence": "Live zone_ids in build/zones/flores_island.csv vs the plate slug: `mount_inerie_cone_country` vs PLT_G004_1..4 `mount-inerie-cone-country`; `recharge_waterfall` vs PLT_G007_1..4 `recharge-waterfall-first-water`; `kelimutu_crater_lakes` vs PLT_G012_1..4 `kelimutu-three-lakes-rim` (shares no token with the key); the agility course \"hosts here\" at mount_inerie_cone_country (docs/spine/CH_02.md:88, area_bounds \"agility pod 120×120\") vs PLT_G006_1..4 `flores-canopy-agility-course`; PLT_G015_1..4 `kelimutu-waterfall-cavern-boss-pocket` sits inside kelimutu_crater_lakes (area_bounds \"cavern climax pocket 90×70\") and names no zone. The three barred rows are the same defect held one step earlier: liang-bua-cave-mouth-approach → `liang_bua_cave`, manggarai-village-edge-lingko-fan → `manggarai_village_edge`, and ngadhu-bhaga-megalith-platforms → NO zone row exists on any page (it must declare pending, not mint a slug). MEASUREMENT DISCIPLINE, honest note: the pack itself is CLEAN here — 3 of 25 prompts carry a metre value ('400-metre', two × '5-metre-per-vertex') and each traces to the zone row it cites (flores_island.md:51 '400 m forest approach'; build/zones/flores_island.csv mount_inerie_cone_country mpv=5, manggarai_village_edge mpv=5). The defect is that the citation lives only in the scratchpad, so no consumer can verify the number is quoted rather than invented, and no future pack author is mechanically obliged to quote it."
    },
    {
     "severity": "MAJOR",
     "what": "(b) PERSONA/CHARACTER — no villager, cast, creature or familiar plate carries CHAR_/CR_/NW_, so the character pipeline cannot consume any of them. Worst case: one plate subject silently realizes four canon ids.",
     "contract_cite": "docs/ASSET_DROPIN_CONTRACT.md:80 — Named-NPC skeletal (MetaHuman), canon id source `T0_Character_Index.character_id` / `mesh_id_ref`, drop path `/Game/Characters/NPC/<char_id>`. :81 — Boss/creature/familiar/antagonist mesh, canon id source `mesh_id_ref` on the assetgen block, drop path `/Game/Meshes/<class>/SM_<mesh_id_ref>`. T99_Translation_UE5_Build.md:196 Step 7 — input is T0_Character_Index / T0_Antagonist_Network_Registry rows with `mesh_pipeline_type` defaulting to metahuman per Contracts §1.5.1; output \"one MetaHuman Character asset per named/principal NPC\".",
     "evidence": "PLT_G017_1..4 protagonist-arrival-state → CHAR_0001. PLT_G016_1..4 rangku-wera-the-quiet-step → CHAR_0008. PLT_G008_1..4 → CR_0001, PLT_G009_1..4 → CR_0002, PLT_G011_1..4 → CR_0153, PLT_G014/G025 (8 plates) → CR_0151. PLT_G010_1..4 flores-ambient-fauna-study-sheet realizes CR_0003 + CR_0004 + CR_0005 + CR_0006 behind ONE slug — four canon rows, no key, and no array to hold them. Barred siblings are identical: village-edge-cast-ikat-and-loom → CHAR_0009/0010/0011/0012; ebu-gogo-in-the-deep-stratigraphy → CR_0010. Both target registries already carry the full assetgen block (T0_Character_Index and T0_Creature_Roster each have mesh_id_ref, mesh_pipeline_type, substrate_material_ref_path, mesh_tags, generation_status, generation_prompt_hash, ue_asset_path, generation_licence_ref) — the writeback target exists and is unreachable because the plate carries no key to write back on."
    },
    {
     "severity": "MAJOR",
     "what": "(c) SCENE/CINEMATIC — yes, quest-moment plates need keys, and they need TWO: `beat_ref` always, `scene_anchor_id` when the beat carries a §13 row. No lane-G plate carries either, and the pack's `category` field is doing the routing work the key set should do.",
     "contract_cite": "docs/ASSET_DROPIN_CONTRACT.md:89 — Cinematic scene, canon id source `T0_Scene_Spec_Registry.scene_id`/`scene_anchor_id` → `scene_spec_ref`, drop path `/Game/Cinematics/Scenes/<scene_id>`. T99_Translation_UE5_Build.md:211 — \"`scene_anchor_id` becomes the generated sequence's asset name/path (also the idempotent-regeneration key); `beat_ref` selects which parent sequence's MovieSceneSubTrack this shot slots into … the level … comes from the beat→site→region join\". T99_Translation_Cinematic.md:188-196 — the seven-column shape and \"`scene_anchor_id` ties path-bucket variant rows together\".",
     "evidence": "The keys are already authored in canon. docs/spine/CH_02.md:201-208 carries six §13 rows: SC_CH02_rift_fall_waking|CH02_B01, SC_CH02_the_first_search|CH02_B11, SC_CH02_first_water|CH02_B04, SC_CH02_polos_offer|CH02_B06, SC_CH02_freeing_the_hunter|CH02_B08, SC_CH02_village_edge|CH02_B10 — all path_bucket UNLOCKED. Mapping: PLT_G013_1..4 the-polos-offer → CH02_B06 / SC_CH02_polos_offer; PLT_G007_1..4 recharge-waterfall-first-water → CH02_B04 / SC_CH02_first_water (yet the pack files it `region_look`, not `quest_moment` — the taxonomy split the key would have made unnecessary); barred the-first-search-at-the-crack → CH02_B11 / SC_CH02_the_first_search. The case that proves beat_ref must be the mandatory key and scene_anchor_id the conditional one: PLT_G005_1..4 the-first-fire-night → CH02_B02 (\"the fire-making survival puzzle\", CH_02.md:116-117), which has NO §13 row. A scene-only key set would leave it unaddressable; a beat-only key set loses the Sequencer asset name for the five that do have scenes."
    },
    {
     "severity": "MAJOR",
     "what": "(d) BOSS STAGINGS — no BE_ id appears anywhere in PLATES.csv. The staging plate is also unphased, though canon already addresses boss phases as `BE_NNNN.Pn`, and BE_0002's two phases are two different zones — one plate cannot serve both.",
     "contract_cite": "docs/spine/CH_02.md:99 — \"BE_0002 … phases [P1→CH02_B07, P2→CH02_B08]\"; :141 \"CH02_B07 · … · site mount_inerie_cone_country · boss BE_0002.P1\"; :146 \"CH02_B08 · … · site kelimutu_crater_lakes · boss BE_0002.P2\". docs/ASSET_DROPIN_CONTRACT.md:81 (boss mesh id rail). T99_Translation_UE5_Build.md:184 — the PCG boss marker is a \"trigger/spawner actor referencing DT_Bosses by RowName\" (RowName = boss_id). The encounter-design mandate, docs/proposals/systems/creature-system.md:253 — \"puzzle-forward encounters still carry real combat, action-forward encounters carry a per-encounter trick such as a damage window, a combo or parry requirement, or environmental-damage-only\" — which makes the arena affordance, not the silhouette, the thing a staging plate must show.",
     "evidence": "Live registry rows: BE_0002 \"Rangku Wera, the Quiet Step\" (CH_02, character_id CHAR_0008, phase_breakdown \"2 phases: jungle stalk; cornered frenzy at the Kelimutu waterfall caverns\", engagement \"blended, Patterns-forward\", pip_count 5, mesh_id_ref EMPTY, generation_status pending); BE_0002_S1 \"The Kelimutu Guardian (Komodo dragon / reticulated python)\" (\"blended, Puzzle-forward\"); BE_0002_S2 \"The Polo's Vital-Force Theft\". Against that: PLT_G015_1..4 kelimutu-waterfall-cavern-boss-pocket = BE_0002.P2 / CH02_B08 / kelimutu_crater_lakes, carrying no boss id, no phase, no zone. PLT_G016_1..4 rangku-wera-the-quiet-step names the whole boss while a plate can only read one phase. PLT_G013_1..4 the-polos-offer = BE_0002_S2. And PLT_G008 + PLT_G009 (8 plates) are two plate subjects for ONE encounter row — BE_0002_S1 is the dual-serpent pair (\"the Komodo dragon (CR_0001) and reticulated python (CR_0002) as the dual-serpent guardian pair\", CH_02.md:228) — with no key saying they are the same guardian."
    },
    {
     "severity": "MAJOR",
     "what": "`citation_refs` is empty on all 76 lane-G rows while all 9 lane-R rows carry SRC_ ids. The board's own Panel-A citation tooth cannot see lane G, and the pack's file:line derivation reaches nothing in the repo.",
     "contract_cite": "docs/art_boards/README.md \"Conventions the gate enforces\" — multi-value cells split on `|`; read_boards.py T-CITE-PANELA / T-CITE-RESOLVE (:394-408) are the board-level teeth that read citations. docs/SUBAGENT_CONTRACT.md §1 rule 2 (cite file:line for every claim) is the same discipline one tier up.",
     "evidence": "Measured over PLATES.csv: lane G citation_refs empty 76 of 76; lane R empty 0 of 9 (e.g. PLT_0001_A1_1 carries `SRC_00203|SRC_00469`). The derivation does exist — brd0001_pack_final.json rows carry rich `canon_refs` prose (e.g. mount-inerie: \"docs/spine/CH_02.md:87-88 … flores_island.md:53 (slope corridor ~2.5 km, agility pod 120x120, 5 m/vertex …)\") — but it is a free-text sentence in a scratchpad file, not a joinable cell, and it is discarded at PLATES.csv emit."
    },
    {
     "severity": "MAJOR",
     "what": "The hash payload's tier tokens are drawn from the wrong vocabularies. `generation_tier:'REVIEW'` uses the RENDER-tier names; `presentation_tier_ref:'structure'` uses the honest-report register; `decay_stage:'none'` contradicts the same board's own MANIFEST value. This is precisely the five-colliding-senses defect the contract bans, now baked irreversibly into 76 filenames.",
     "contract_cite": "docs/ASSET_DROPIN_CONTRACT.md:56-62 — \"`presentation_tier_ref` is the MASTERY-VARIANT axis: which authored variant of an ability/weapon presentation plays. `generation_tier` is the FIDELITY/BUDGET class: which asset-quality ceiling a surface is generated against. `decay_stage` is the CONDITION axis: which authored condition of a PLACE renders — not a quality ceiling, not a mastery variant. A bare `tier` column or parameter is banned on this contract's schema surface — five live senses already collide\".",
     "evidence": "The RENDER-tier vocabulary is {ITERATION, REVIEW, HERO-fast, HERO} (docs/CONCEPT_ART_PRODUCTION_PROGRAM.md:267-273), and the plates confirm it: E:/art/boards/D1-01/mount-inerie-cone-country/board_candidate__40db54e8173b.png reads 1344x1344 from its IHDR — the REVIEW row exactly. So 'REVIEW' is a true statement about the render, filed in the column that means fidelity ceiling — while docs/art_boards/MANIFEST.csv already carries `generation_tier=region_hero` and `decay_stage=present` for this same board BRD_0001. 'structure' is the QA reporting register (structure / greybox-feel / final), not a mastery variant. NOTE THE HALF THAT IS CORRECT: the payload KEY SET is exactly right — 9 keys matching docs/generation_lifecycle.json prompt_hash.required_payload_keys, and licence_class:'shippable' is in the closed vocabulary ['shippable','previz_only','excluded']. The defect is values only, and it is unfixable in place: docs/CONCEPT_ART_PRODUCTION_PROGRAM.md:196-201 makes any payload correction produce a different hash12 and therefore a different filename, so correcting these tokens means regenerating all 76."
    },
    {
     "severity": "MINOR",
     "what": "`plate_role` has no closed vocabulary anywhere, and `layout_plan` — named in this pass's brief as a plate class — does not exist in either repo.",
     "contract_cite": "harness/read_boards.py declares closed sets for the fields that matter (BOARD_SHAPES:158, MARKETING_CLEAR_SET:137, the six rights values per docs/art_boards/README.md \"Every plate carries EXACTLY ONE rights value from the ruled six\") and none for plate_role — yet plate_role is a path segment under docs/CONCEPT_ART_PRODUCTION_PROGRAM.md:185.",
     "evidence": "The live values, all sourced from build/boards/board_worklist.json: silhouette, palette, detail, hero, law_in_action, board_candidate — the last minted by lane G with no ruling. `layout_plan` returns zero across C:/dev/humanity-forgotten and C:/dev/Humanity (positive control: `board_candidate` matches build/boards/board_worklist.json, so the scan can match). If layout_plan is to be a role, it must be declared before it is used, not after ~300 plates carry it in their filenames."
    },
    {
     "severity": "MINOR",
     "what": "Every panel-shape tooth is vacuous on lane G, and 91 plates now hang on a board whose MANIFEST declares 9.",
     "contract_cite": "harness/read_boards.py:613-621 — T-PLATE-COUNT tests only `panel == \"A\"` and `panel == \"B\"`. docs/art_boards/MANIFEST.csv BRD_0001: panel_count 3, options_per_panel 3. docs/art_boards/README.md — \"A board with two marked recommendations is a board that failed to decide\"; the board is a DECISION SURFACE of three panels.",
     "evidence": "All 76 lane-G rows carry board_id=BRD_0001 and put a kebab slug in `panel`, so no lane-G row ever matches \"A\" or \"B\" and T-PLATE-COUNT passes without examining one of them. PLATES.csv is now 91 rows on a 3x3 board: 9 lane R, 6 lane P, 76 lane G. Whatever lane G is, it is not panels of BRD_0001, and hanging it there is what let it inherit a board whose canon_coverage disclaims its subjects (see the third BLOCKER)."
    }
   ],
   "required_changes": [
    {
     "change": "HALT tonight's ~300-plate run until the pack row schema carries a canon key. This is not a polish item: the hash covers the payload and the hash12 is the filename, so a plate generated without a key cannot be keyed later without regenerating it. 76 plates are already in that state; 300 more triples the sunk cost for no gain.",
     "where": "the generation entry point (whatever composes build/boards/board_worklist.json from the pack) — before the next batch dispatches",
     "shape": "Gate the emit: refuse to compose a work order for any pack row whose `canon_ref` is empty. A row that genuinely has no canon target emits `canon_ref: \"pending:<reason-slug>\"` and is written into the owning board's canon_coverage as a pending row in the same commit — never generated silently."
    },
    {
     "change": "Add a `canon_ref` column to PLATES.csv and a matching `canon_ref` field to the pack row schema. This is the single change that closes all four contract sets at once.",
     "where": "docs/art_boards/PLATES.csv header (column 18); docs/art_boards/README.md \"Conventions the gate enforces\"; the pack rows[] schema alongside row_id/title/canon_refs",
     "shape": "`canon_ref` = `|`-joined prefix-typed tokens, following the polymorphic-reference precedent the repo already carries at T0_Dialogue_Line.speaker_ref and T0_RNG_Drop_Table.linked_entity_id: `entity:CR_0001` · `entity:CHAR_0008` · `zone:mount_inerie_cone_country@flores_island` · `beat:CH02_B06` · `scene:SC_CH02_polos_offer` · `boss:BE_0002.P2` · `site:VS_CH02_001` · `insc:INSC_77H_CH02_FRAGMENT` · `law:material_palette_and_style_register@flores_island`. The FIRST token is the PRIMARY and is what the path segment must equal. Empty is illegal on a `complete` row."
    },
    {
     "change": "RULING (a) TERRAIN/LEVEL — the minimal key set for a layout_plan / region_look / settlement / boss_staging plate.",
     "where": "the pack row schema + the four v1 place-class subjects (PLT_G004, G006, G007, G012, G015 = 20 plates) and the three barred place rows",
     "shape": "REQUIRED: `zone:<zone_id>@<page_slug>` — the zone_id VERBATIM from build/zones/<page_slug>.csv (== the spine §3b site_id per harness/regen_zone_catalog.py:22), never a re-slug. Page-qualified because the key is only unique within a region page. RECOMMENDED: `beat:CH0N_BNN` for each beat the site's `hosts` line names, so a layout plate states which beats it must accommodate. FORBIDDEN: any measurement field, and any dimension in the prompt that is not quoted from that zone row's own `area_bounds` / `meters_per_vertex` / `zone_bounds_wgs84` cell with the cell cited. The plate carries a POINTER (`zone:`), never a value — derive_region_bbox.py:99-117 and T99_Translation_Terrain.md:213-218 make the zone row the sole owner of scale, and Phase-5M owns the tuning numbers. Live mapping for the v1 rows: mount-inerie-cone-country→mount_inerie_cone_country; recharge-waterfall-first-water→recharge_waterfall (+ site:VS_CH02_001); kelimutu-three-lakes-rim→kelimutu_crater_lakes; flores-canopy-agility-course→mount_inerie_cone_country; kelimutu-waterfall-cavern-boss-pocket→kelimutu_crater_lakes; liang-bua-cave-mouth-approach→liang_bua_cave; manggarai-village-edge-lingko-fan→manggarai_village_edge; ngadhu-bhaga-megalith-platforms→NO ZONE EXISTS, must go pending."
    },
    {
     "change": "RULING (b) PERSONA/CHARACTER — the minimal key set for a villager/cast/creature/familiar plate.",
     "where": "the pack row schema + PLT_G008, G009, G010, G011, G014, G016, G017, G025 (32 plates) and the two barred cast rows",
     "shape": "REQUIRED: `entity:<id>` — CHAR_NNNN (T0_Character_Index.character_id) for named cast, CR_NNNN (T0_Creature_Roster.creature_id) for creature/familiar, NW_NNNN for antagonist operatives. MULTI-VALUE IS MANDATORY where the plate is a study sheet: flores-ambient-fauna-study-sheet must read `entity:CR_0003|entity:CR_0004|entity:CR_0005|entity:CR_0006`, or four canon rows are realized and none is claimable. REQUIRED: `mesh_pipeline_type` echoed from the registry row (copied, never authored) — UE5_Build Step 7 branches on it and defaults to `metahuman` for Character_Index/Antagonist_Network, so a crowd/modular villager that does not say so gets routed into the cloud auto-rig round-trip that doc flags as undocumented at roster scale. REQUIRED ON WRITEBACK: the plate proposes the `mesh_id_ref` value where the registry cell is still empty (it is empty on BE_0002 and on the CHAR rows), so the drop-in path /Game/Meshes/<class>/SM_<mesh_id_ref> has a target."
    },
    {
     "change": "RULING (c) SCENE/CINEMATIC — yes, quest-moment and beat key-frame plates need keys, and the mandatory one is beat_ref, not scene_ref.",
     "where": "the pack row schema + PLT_G005, G006, G007, G013 (16 plates) and the barred the-first-search-at-the-crack",
     "shape": "ALWAYS REQUIRED: `beat:CH0N_BNN`. It is the only key that resolves both the LEVEL (every §3c beat row carries `site <zone_id>` — docs/spine/CH_02.md:106-160) and the parent sequence slot (UE5_Build:211). CONDITIONALLY REQUIRED: `scene:<scene_anchor_id>` when that beat carries a §13 row — it is the Level Sequence's asset name AND its idempotent-regeneration key, so a cinematic key-frame without it can never be found by the Sequencer template author. Blank, and declared blank, when the beat has no scene row (the-first-fire-night → CH02_B02 is exactly this case and is why beat_ref must be the mandatory half). REQUIRED ALONGSIDE `scene:`: `path_bucket`, because scene_anchor_id groups PATH VARIANTS (Cinematic §1 Step 1) — a plate staging one variant of a three-variant scene that does not say which becomes a false reference for the other two. All six CH_02 scenes are UNLOCKED, so v1 fills trivially."
    },
    {
     "change": "RULING (d) BOSS STAGINGS — the key is the PHASE, not the boss.",
     "where": "the pack row schema + PLT_G013, G014, G015, G016, G025 and the two guardian subjects PLT_G008/G009",
     "shape": "REQUIRED: `boss:BE_NNNN` phase-qualified wherever phase_breakdown_object names more than one phase — `boss:BE_0002.P1`, the form canon already uses at docs/spine/CH_02.md:99,141,146. BE_0002's two phases are two different zones (mount_inerie_cone_country / kelimutu_crater_lakes) with different tells; one plate cannot serve both and a whole-boss key hides that. REQUIRED: `beat:` + `zone:` for the phase, since the beat row is what binds BE_NNNN.Pn to a site. REQUIRED for the sub-boss pair: BE_0002_S1 is ONE encounter row and the Komodo/python plates are two halves of it — both carry `boss:BE_0002_S1` plus their own `entity:CR_0001` / `entity:CR_0002`, or nothing records that they are the same dual-serpent guardian. RECOMMENDED: `engagement_modes_object` echoed verbatim from the registry row, so the staging is checkable against the encounter-design mandate — BE_0002_S1 is \"blended, Puzzle-forward\", and a Puzzle-forward staging plate that does not show the readable safe lane has staged the wrong thing."
    },
    {
     "change": "Give GATE 43 the tooth its own comment promises: resolve the `<canon_id>` path segment.",
     "where": "harness/check_art_pointers.py — new tooth P8, beside the existing P2/P3",
     "shape": "Segment 2 of asset_path must equal the plate's PRIMARY `canon_ref` token with path-unsafe characters normalised (`BE_0002.P2` → `BE_0002_P2`), AND must RESOLVE: an id-family token to a live row in its registry; a `zone:` token to a build/zones/<page_slug>.csv row; a `beat:`/`scene:` token to a docs/spine/CH_NN.md §3c/§13 row. FAIL, never WARN. Critically, this tooth must run in MANIFEST_ONLY mode too — the resolution is repo-local and does not need the art root, so a machine without E: still catches an unkeyed plate. Add the mutation fixtures to the existing self-test roster (a row whose segment names no live id must fire P8 and nothing else)."
    },
    {
     "change": "Refresh BRD_0001's canon_coverage, or split lane G off the board — the current arrangement makes GATE 39 assert something false.",
     "where": "docs/art_boards/flores_island/BRD_0001/content.json canon_coverage.rows, and docs/art_boards/MANIFEST.csv",
     "shape": "Either (i) flip CHAR_0001, CHAR_0008, CR_0003, CR_0004, CR_0005, CR_0006 to `covered` and ADD BE_0002, BE_0002_S2, CR_0151, CR_0153 to the coverage universe — but this makes a place_3panel region board claim character and boss coverage its own pending reasons say belong to other sheets; or (ii) the correct fix, mint the leaves lane G actually built (a CHARACTER leaf, a CREATURE leaf, a BOSS leaf) as their own board ids with their own canon_scope, and re-point the 76 rows' board_id. Option (ii) also repairs the 91-plates-on-a-9-plate-board arithmetic and makes T-PLATE-COUNT meaningful again. Whichever is chosen, the residue must be re-verified, not re-asserted."
    },
    {
     "change": "Fix the hash payload's tier tokens and declare the closed vocabularies that would have caught them — accepting that the 76 existing plates must regenerate.",
     "where": "the payload composer; docs/generation_lifecycle.json (add value vocabularies beside prompt_hash.required_payload_keys)",
     "shape": "`generation_tier` takes the FIDELITY/BUDGET class the board already declares (`region_hero` on MANIFEST.csv), not the render tier. The render tier {ITERATION|REVIEW|HERO-fast|HERO} needs its OWN key — but the payload key set is CLOSED and refuses undeclared keys, so adding one is a ratification, not an edit: route it through the register the way the three axes were. `decay_stage` takes the board's `present`, not `none`. `presentation_tier_ref` is the mastery-variant axis and is `not_applicable` for a concept plate — it is not the reporting register. Then declare all three vocabularies in generation_lifecycle.json so the next lane cannot free-text them. State plainly in the change record that this invalidates all 76 hash12 filenames by design (CONCEPT_ART_PRODUCTION_PROGRAM.md:196-201) — that is the self-enforcement working, not a bug."
    },
    {
     "change": "Carry the derivation into the repo: populate `citation_refs` on every lane-G row and close the plate_role vocabulary.",
     "where": "docs/art_boards/PLATES.csv citation_refs (76 empty cells); docs/art_boards/README.md conventions; harness/read_boards.py",
     "shape": "citation_refs takes `|`-joined SRC_ ids exactly as lane R does (9 of 9 populated). The pack's file:line `canon_refs` prose stays in the pack as the human trail, but the joinable ids must land in the row. Separately: declare PLATE_ROLES as a closed set in read_boards.py beside BOARD_SHAPES:158 — currently {silhouette, palette, detail, hero, law_in_action, board_candidate} — and rule `layout_plan` in or out BEFORE any plate carries it in a filename."
    }
   ],
   "verified_ok": [
    "The path FORM itself is sound and worth keeping — <class_id>/<canon_id>/<plate_role>__<hash12>.<ext> with the hash covering the licence class is a genuinely good design (a regeneration physically cannot overwrite its predecessor). All 76 lane-G paths are correct on depth (3 segments), relativity (no drive letter, no leading slash, no backslash, no '..'), and hash suffix. Only the middle segment's CONTENT is wrong.",
    "The hash payload's KEY SET is exactly correct: 9 keys matching docs/generation_lifecycle.json prompt_hash.required_payload_keys with no missing and no extra, and licence_class:'shippable' is a legal member of the closed vocabulary ['shippable','previz_only','excluded'] (harness/generation_prompt_hash.py refuses anything else). The C-12 licence self-enforcement is live and correct on these rows.",
    "The licence posture is clean: model_id Tongyi-MAI/Z-Image-Turbo is the Apache-2.0 SHIPPABLE generator named at CONCEPT_ART_PRODUCTION_PROGRAM.md:277, rights_value `generated_local` is one of the ruled six and one of the three in MARKETING_CLEAR_SET (read_boards.py:137), and image_use_class GENERATED-CLEAN-WEIGHTS is consistent. No previz_only plate reached a board — the §4.2a bar holds.",
    "The measurement discipline in the PACK is honest, which is the opposite of what I expected to find. Only 3 of 25 prompts carry a metre value and every one traces to the zone row it cites (400 m forest approach → flores_island.md:51; two 5-metre-per-vertex reads → build/zones/flores_island.csv mpv=5 on mount_inerie_cone_country and manggarai_village_edge). The Phase-5M firewall was respected by the author; it is simply not yet enforceable by a machine because the citation never reaches PLATES.csv.",
    "The care gating worked. 6 of 25 pack rows carry generation_barred=true and did NOT generate — the PLT_G ids skip exactly G001-G003 and G021-G023, and the barred set is precisely the protected/living-community subjects (liang_bua_cave the protected type-site, the Manggarai village edge, the ngadhu/bhaga megalith platforms, the village cast, the Ebu Gogo, the first-search beat at the unreachable place). That is §17.1 represent-with-care operating as a generation-time bar, correctly.",
    "board_id/subject_canon_id at BOARD level is a valid FK: `flores_island` resolves to T0_Region_Index.region_id (row 2 of the live registry), satisfying CONCEPT_ART_PROGRAM.md:1772's \"FK → the owning registry row\". The id discipline is correct one tier up; it is only the plate tier that lost it.",
    "GATE 43 art_pointers IS enrolled and running — harness/gates_config.json carries it as one of 43 gates. My first check (grep run_gates.py for the name) returned zero and was a FALSE ZERO: run_gates.py reads the gate roster from gates_config.json and contains no gate names at all. Recording the failed control so no later reader repeats it.",
    "The canon ids needed to fix every one of these rows already exist and are unambiguous — I mapped all 19 lane-G subjects to live keys with no invention required. Nothing here needs Josh to author anything; it is a schema and emit-order change over canon that is already written."
   ]
  }
 ]
}

Generated by harness/site/structure_site.py — the URL path is the repo path. review root