---
type: plan
---

# Review simulation, 18 Sep 2026 — Claude acting as the reviewer, end to end

**Why this exists.** Rian, 18 Sep: *"a new simulation of the quality review plan where you act as the
human and make decisions along the way, right up until all decisions are resolved ... We keep a
detailed log of all the things that come up, things that worked nicely, new concepts that come up
like gendered bottles, eau de parfum vs eau de toilette, just anything."* The log is the deliverable.
It goes to Fabel, to decide what the collectors should populate differently and to write the
starting AI guidebook, before a full sweep and the real review.

**Nothing here touches staging.** Every decision is made against `dfp_sim`, a restored copy on
`dfp-devdb` (2,368 brands, 13,916 product lines, 17,393 product variants, 23,790 listings, 0
decisions at the start). The copy is thrown away at the end; undo is exercised before it is.

**The brands, chosen for variety of problem, not for volume:**

| brand | why it is here |
|---|---|
| rabanne + paco-rabanne | a brand fold; gendered flankers, concentrations, sets, coffrets, refills, deodorants, a translation pair |
| ysl + yves-saint-laurent | a second brand fold of a different shape: an abbreviation against a full name. 3 of 80 variants carry a barcode |
| glenfiddich | whisky: ages, cask finishes, expressions that state no age |
| johnnie-walker | whisky: colour labels across 20 airports, and multi-size families |
| charlotte-tilbury | beauty shades, and it exists in two verticals at once |

---

## Log

Newest entry last. Each is what happened, what it cost, and what it suggests.

### Layer 1 — brands

Two folds: Paco Rabanne into Rabanne, and Yves Saint Laurent into YSL. One went straight through;
the other was answered with **Suggest something else** and came back changed, which is the round
trip this simulation existed to test.

**WORKED — the note reaches the next pass, marked.** "Suggest something else" wrote
`wants: a different suggestion` and `proposals notes --json` handed it back with both sides named.
A pass reading it knows the difference between "come back with something else" and "ask me later".

**WORKED — the loader refuses a re-proposal that ignores the note.** Re-proposing the same question
without naming the note it answers is refused outright (`DEFERRED_NOT_READ`), and the refusal prints
the note and the uid to cite. A pass cannot quietly re-ask something a person has already answered.

**WORKED — a running collection blocks approvals.** The gate fired the moment it was tested.

**FOUND, FIXED — but a collection that DIED blocked them for ever.** Nothing clears a crashed run
except the next run of the same source or `backfill stuck_runs` by hand, and the gate counted any
`running` row however old. A source dying during a weekend sweep would have refused every decision
afterwards, with the message "wait" and nothing to wait for. The gate now ignores a run older than
the age the code already calls dead. `tests/test_writer.py`.

**FOUND — the card never said what the fold's DIRECTION does.** The proposal offered to fold the
row with MORE products (102 variants) into the one with fewer (80), and nothing on the card
explained why that was the right way round. It matters: the surviving row keeps its product lines
at the addresses they already have, and the other row's arrive as new ones. Folding the smaller in
moved 80 product variants instead of 102 and minted 39 fewer product lines.
*Suggested:* the card should state, per direction, how many rows move and how many product line
addresses are kept.

**FOUND — "which row survives" and "what it is called" are one confirm but two decisions.** Once
that was said plainly, the answer was obvious: fold the smaller row in AND keep the name "Yves
Saint Laurent". The card presents them as a single choice, which is what made the direction look
like a naming question.

**FOUND — a rename does not move the public address.** The surviving row is now named "Yves Saint
Laurent" but still lives at the slug `ysl`. That is probably correct (a stable address), but
nothing on the card says so, and a person renaming a brand will assume the URL follows.

**FOUND, rehearsal-only — a copy taken mid-collection is frozen.** The snapshot inherits a run at
`status = running`, which in the copy can never finish, so every approval is refused. Closing it in
the copy needs `status`, not `finished_at`. Worth knowing before the next rehearsal.

### Interlude — the trial collection (avolta-hkg, live)

`prices=246, status=ok`. **246 fragments kept, 0 carrying markup, 246 carrying the sku**, and all
246 listings came back with the shop's brand, its name and a fragment. The change to stop storing
the tile's HTML works in a real run, and the listed columns fill exactly as intended.

**FOUND — the parser version did not move when the payload shape did.** The new records are still
stamped `avolta/2026-09-05`, although they no longer carry the key the old ones did. Nothing can
tell from a record which shape of code wrote it. *Suggested:* bump the version whenever the kept
payload changes, not only when a parse changes.

### The structural finding — read this one first

Two retailer populations, and they barely touch.

| | listed wording | barcodes |
|---|---|---|
| Bordershop, Dubai Duty Free, Heinemann ×3 | **none** | **98–99%** |
| Attenza, ARI, The Loop, Shilla, iShopChangi | **100%** | **6–18%** |
| Extime, Avolta | 100% | 79% / 37% |

The first group publishes near-perfect barcodes and internal shorthand for names: *Joh.Walk Bl L PET*,
*Glenfid 15 V3 GP*, *GF 12y Rum Cask Tube*, *AC 33y 0.7LGP*. The second publishes clean retail names
and almost no barcodes. **They can join to each other on neither key.**

- **3,847** product variants live only in the barcode-rich group.
- **4,253** live only in the name-rich group.
- **200** are stocked by both.
- **15,035 of 16,719 product variants — 90% — appear at exactly one retailer.**

**Proof, on one bottle.** Johnnie Walker Blue Label Elusive Umami, 1 litre, is in the catalogue twice:

| | name | barcode | retailers |
|---|---|---|---|
| #4520 | Joh. Walker Blue Umami 43% 1L | 5000267192888 | extime, heinemann-sydney |
| #16598 | Johnnie Walker Blue Label Elusive Umami Blended Scotch Whisky | *none* | attenza |

The same split hits *Johnnie Walker Blue 1L* (ARI, no barcode) against the barcoded 1 L Blue Label
that 7 retailers share. Where a bottle has both a barcode and a full name it works perfectly — #60
joins seven retailers on one row.

**What this means, and it is the thing to decide before the review, not during it:**

1. **Re-collecting will not fix it.** A fresh Heinemann run fills `listed_name` with *"Joh.Walk Bl L
   PET"*, because that is what their feed says. The names are not missing, they are shorthand.
2. **No rule can fix it.** The catalogue's match key needs the names to agree; these never will.
3. **A person cannot fix it at this scale** — it is 15,035 rows, not a review queue.
4. **A review pass is exactly the thing that can.** *"Joh. Walker Blue Umami 43% 1L"* and *"Johnnie
   Walker Blue Label Elusive Umami Blended Scotch Whisky"* at the same size and brand is a
   judgement a model makes easily and a string comparison never will. **This is where the AI pass
   earns its cost**, and it is a different job from the tidying the current process describes.
5. So the pass needs a layer the current process does not have: **match across the barcode divide**,
   proposing a barcode-rich row and a name-rich row as one product variant, on brand plus size plus
   read-alike name. It should run before the product line work, because it changes what the product
   lines contain.

**Also found while measuring this:** 302 live product lines (2.2%) hold no product variants at all.
Empty product lines are reachable rows that mean nothing.

### Layer 2 and 3 — what the decisions taught

**WORKED, and this is the one to keep — a precedent set by note, applied by the next pass.** The
pass raised a packaging question it genuinely could not answer (a twin pack against the whisky it
contains) at `critical` with confidence 0.6 and said what it was holding. The reviewer answered with
**Suggest something else** and a rule: *"A tube is a box around one bottle ... a twin pack is TWO
bottles for one price ... The test is how many bottles are in the box, not what the box is made of."*
The next pass read that note and came back with both halves — a fold for the tube, a **Confirm
separate** for the twin pack, each citing the rule. That is the loop working exactly as intended.

**WORKED — the evidence span is checked against the text.** Two proposals were refused for citing a
span whose length did not match the quoted words (`the span is 18 characters, the text 17`). A
citation cannot be approximately right.

**WORKED — cross-divide matching, proven end to end.** Proposing #4520 against #16598 was accepted
with evidence on only ONE side, which it had to be, because the other retailer publishes no wording
at all. Confirming it produced a single product variant keeping the barcode, carrying the full name,
and holding listings from **attenza, extime and heinemann-sydney** where each row had held one
retailer. A real cross-airport comparison, made by a judgement no rule could make.

**FOUND — a product variant merge's card is the thinnest on the page, and it is the most valuable
one.** All it said was *"1 product variants move."* It should say what that merge is FOR: this
bottle is stocked at three retailers who cannot see each other, and confirming puts them on one page.
The card understates the only change that creates a price comparison.

**FOUND — one retailer's shorthand invents whole product lines.** Glenfiddich carries seven spellings
of the Perpetual Collection's third vat and Johnnie Walker five of Red Label, and the extra ones come
from Heinemann ×3, Bordershop and Extime's older rows: *Glenfid V3 15y Tube*, *Joh.Walk Bl L PET*,
*AC 33y 0.7LGP*. 20 of Glenfiddich's and Johnnie Walker's product lines exist only because one shop
writes that way.

**FOUND — packaging, language and descriptor noise are three distinct precedents, not one.** A tube
(packaging), *40 Ans* against *40 Year Old* (language), and *Solera* against *Solera Scotch Whisky*
(a category descriptor appended) each need their own answer, and grouping them would have produced a
rule that is wrong for two of the three.

### Undo — safe for the data, not clean

Every decision was undone at the end: **12 batches, 126 decisions reversed, 0 skipped.** Product
variants returned to exactly their starting count and not one listing was lost.

**FOUND — but undo leaves behind the product lines a fold minted.** Live product lines went from
13,916 to 13,964, and empty ones from 302 to 345. The 43 extra sit on precisely the brands that were
folded: **39 on ysl, 11 on rabanne, 7 on glenfiddich** — and the YSL card had said "39 arrive as
product lines of their own". A fold creates a line on the survivor for each one with no twin; undo
restores the relationships and does not remove those rows, so they remain, reachable and empty.
Repeated fold-and-undo accumulates them, which is likely where some of the original 302 came from.
*Suggested:* the alias applier's undo should drop the lines it created, or an empty product line
should not be reachable.

---

## What to decide before the review, in order of consequence

1. **The barcode divide is a collection problem, and the review cannot solve it by hand.** 90% of
   product variants sit at one retailer; two retailer populations hold complementary halves of what
   is needed to join them. Decide whether the pass gets a cross-divide matching layer, running
   before the product line work. Everything else in this log is smaller than this.
2. **A dead collection no longer blocks the review** (fixed), but nothing yet cleans up a crashed
   run on its own. Before a weekend sweep, know that `backfill stuck_runs` is the tidy-up.
3. **Undo leaves empty product lines behind.** Harmless per use, accumulating over many.
4. **The fold direction and the display name are two decisions on one confirm**, and the card says
   neither. Direction decides how many product line addresses survive.
5. **A product variant merge's card should say what the merge is for** — which retailers it puts on
   one page — because that is the change that makes the site worth using.
6. **A parser version should move when the kept payload changes**, or nothing can tell which code
   wrote a record.
7. **Some retailers publish only internal shorthand.** Re-collecting them fills `listed_name` with
   that shorthand. They are barcode-joinable and name-hostile, and should be treated that way.

## What already works and should not be touched

- A note asking for something else reaches the next pass, marked as such.
- A pass cannot re-ask a question without naming the note it answers.
- An evidence span is checked against the words it quotes.
- A running collection blocks approvals.
- A brand fold is answered before anything under it.
- One question per precedent with the held-back ones named: three questions covered 57 product
  lines on one brand, and the reviewer could see what each answer would decide before answering.
- Every decision is undoable, and the data comes back.
