# Part E: variants linked to their parent, searchable (18 Oct 2026, ~17:25 CET)

## What changed (plain words)
- A custom version now says what it comes from: `provenance.parent {id, version, release}` + `provenance.variant {kind, note}`.
  Kinds: retexture, recolor, shape, resize, rig, rerun, alternative, other. Same shape the rigs worker writes. `lib/card.py` checks it.
- **Release hash**: `provenance` is NOT in it (`RUNTIME`), so the 175 existing cards were patched in place. Release hashes unchanged, no new versions.
- `tools/backfill_lineage.py` (dry run by default, `--apply`): links only what the card data proves, lists the rest.
- `tools/build_index.py`: rows get `parent`, `variant`, `variants` (children). A variant keeps its own thumbnail and description (nothing copied). Reuse = parent AND variant (can only get stricter). 454 assets, no reuse flag changed today.
- `lib/search.py`: results carry `parent`/`variants`/`variants_detail`; a parent hit lists its variants under it, a variant hit next to its parent is marked `grouped_under`. `asset-search --variants-of <id>` (also the `variants_of` argument; works without embeddings; shows ancestors).
- `tools/register_variant.py <parent> <glb> --kind K --note "..." [--name --suffix --thumbnail --no-reuse]`.
  **ID rule**: a NEW id `<parent-class>/<parent-slug>-<suffix>` (suffix = kind, `-2`... if taken), v1, candidate. Not a new version, because a new version replaces what games get for the parent. Same-id versions stay for fixes/lighter copies.
  Writes blob, card, thumbnail, runs enrich for that one asset (`tools/enrich_library.py --only ID`, new flag), rebuilds the index. No thumbnail or Vertex failure = says PENDING (next full enrich picks it up).
- MCP: `forge-hub-variants.patch` (adds `variants_of` to the tool schema; the fields themselves already flow through). Not applied, forge-hub untouched.

## Evidence
- Backfill: **175 cards linked** = 118 alternative (8 `-g4` cars vs Astra originals; 48 generator A/B copies pixal3d-ab/-batch, trellis2-raw/-fixed vs the authored ship, all their versions), 32 retexture (versions adding the painted copy), 20 rerun (operator feedback in the card), 5 other (3 lighter copies, 2 game-weight re-exports... see card notes).
- Index: 56 rows with a cross-id parent, 20 parents with variants, 83 rows carry `variant`.
- Real repaint test: landspeeder painted blob -> `vehicle.space.speeder/landspeeder-painted`, enrich done (own Gemini description, own vectors). `asset-search landspeeder --all` showed the parent with the variant under it; `--variants-of` listed it; text search found it. Test asset then removed (it duplicated the v2/v3 painted copy already on the parent).
- `test/test_variants.py`: 30 checks pass; on the old code it fails at import (no `VARIANT_KINDS`). `test_lighter.py` 12 pass.

## Ambiguous, NOT linked (100 entries, 71 ids), list in `.scratch` was dropped; rerun `python3 tools/backfill_lineage.py --report f.json`
- 53 later versions (mostly v2/v3 of military vehicles, spaceships, bikes) with no feedback and no painted/lighter marker: could be a re-run, a re-texture or a fix. Needs a human or the job logs.
- 47 `room3-rerun2-*` (19 assets, 28 later versions): a photo re-run, but the first room3 run is not in the library, so no parent exists.
- Same-id painted/lighter versions are linked to the previous VERSION of the same id; the index shows `variant` but no `parent` row (it is the same asset).

## Would break / not done
- Index rows now carry 3 extra fields; the Factory reader ignores unknown keys (not tested on gex44, nothing deployed/published).
- Painted v3+ versions that inherit `painted_glb` from v2 are not linked (only the version that adds it).
- `-g4` parent choice assumes the Astra original is the parent. A variant's thumbnail must be supplied (no GPU render from here).
- Not done: browse page UI for variants, forge-hub patch not applied, index.json not published.
