---
type: plan
version: 1
status: a briefing, not a plan. Written 2026-09-15 from a long working session with rian in which nothing was designed and no code was changed. Intended to be handed to a fresh session with deep reasoning enabled, whose job is to THINK IT THROUGH AND PROPOSE DECISIONS. Every proposal here is rian's or was put to rian; none is accepted.
supersedes: nothing; sits above .logs/planning/places-and-shops-2026-09-15.md and .logs/planning/variations-and-line-pages-2026-09-15.md, which hold the detail for two of the areas
---
# The catalogue model: what we have, what rian wants, and what is unresolved

## 0. What this document is for

Rian spent a session trying to understand the catalogue half of the database well enough to feel
safe changing it. He ended it clearer but not settled, and said so: *"I'm still not feeling secure
in my understanding of it all which makes me hesitant to proceed. It's a bit overwhelming."*

This is the record of that session, written so a later session can reason from it without redoing
the archaeology. It is deliberately long. It carries rian's own words, because his mental model is
the thing being served; it carries every measurement taken, because several of the arguments turned
on numbers that contradicted intuition; and it carries the tensions unresolved rather than papering
them over.

**What the next session is being asked to do**, in rian's words: *"have it really think this
through and make some decisions that will help me get closer to a database that I understand, can
grow beyond the categories and airport model that we have now, and can have human checkers helping
to confirm, correct, and merge things."*

**Three properties rian is optimising for**, in his order:

1. **He can understand it.** He is the owner and the only person who will maintain the vocabulary.
   A model he cannot hold in his head is a model he will not trust.
2. **It grows past airports and past the current categories.** Cruise ports are certain; outlet
   malls are possible; food is an expected category.
3. **Humans can confirm, correct and merge.** The system must assume a person is checking the
   machine, at scale, forever.

To which this document adds a fourth that the evidence forced into view:

4. **It must tolerate being re-derived.** Rules will change repeatedly. Anything that cannot be
   recomputed from what was collected becomes permanent debt.

## 1. Constraints the next session inherits

- **Soft launch 18 Sep 2026; Cannes 27 Sep.** Any refactor competes directly with that. The
  session should say explicitly what is safe before launch, what must wait, and what merely *gets
  more expensive* if deferred.
- **Migrations are schema-only** (create tables, add nullable or server-defaulted columns). Every
  backfill is a separate idempotent CLI command. No data repair inside a migration.
- **Two decisions were settled with the client's reviewers, Mark and Adam, on 9 Sep** and cannot be
  reversed unilaterally: the product address `/products/<name>-<id>`, and the rule that only
  comparable products plus travel exclusives are published.
- **The legal posture constrains collection, not modelling**, but it bounds the AI discussion in
  §8: facts only, never expression; never log in; a block is a refusal; robots re-read every run.
- **Nothing in the catalogue has ever been decided by a person.** See §5.6. This is the single most
  important fact for sequencing: the cost of changing the human-decision mechanism is near zero
  today and rises the moment the first merge session runs.

## 2. Rian's mental model, and where it was corrected

He offered his understanding and asked to be checked. It was mostly right. Recording both the model
and the corrections, because the corrections are where the design pressure is.

> *"listings are the raw data we collect from online sources. They have a url at a specific shop
> online."*

**Correct, with a sharpening.** A listing is **one SKU at one shop**, not one URL. The unique
constraint is `(location_id, source_sku)`. The URL is where it was seen and is not unique.

> *"what about variations listed on the same url? Eg: different sizes? Are those two listings with
> the same url, or one listing?"*

**Separate listings, same URL.** 1,393 URLs carry more than one listing. One Lancôme page at
Panama produces 13 listings and 13 products, one per shade; a Clarins foundation page produces 18.
A Lancôme page mixes 3.4 g and 3.6 g items. This is required by the collection rule that a
multi-size tile shows only the family's cheapest price, which belongs to exactly one member.

> *"what about global sites?"*

**Collected, never compared.** The Heinemann online catalogue is a shop row flagged catalogue-only
with 1,035 priced listings. Every comparison query excludes it. It earns its place on identity:
660 of its products are also sold at a real airport shop, so it supplies names, sizes and barcodes
that help the real shops match each other.

> *"product lines: these are what I tend to be referring to as 'products'. The product line is
> 'Coke Zero'. product: this is the very specific version of the product with it's own barcode.
> 'Coke Zero 355ml Can'. Brand: this is 'Coke'."*

**Right, with one amendment and one missing layer.**

- **Amendment:** a product is *not* defined by having a barcode. 6,896 of 16,761 products have no
  barcode. Identity is the GTIN *where one exists*, and falls back to a computed key otherwise.
- **Missing layer:** **variation** sits between line and product. The identity key is
  `house | line | variation | quantity`. The full ladder in his own terms is Coke, then Coke Zero
  (line), then Coke Zero Cherry (variation), then the 355ml can (product).

Later in the session he arrived at the right formulation himself: *"Products are variations of
product lines."* That is exactly the model's intent.

## 3. The model as it actually is

### 3.1 The identity chain

```
retailers ──< locations (a shop; iata is an OPTIONAL column, not a place row)
                  │
brands ──< product_lines ──< products ──< listings ──< price_observations
  │                              │            │
  │                              │            └── raw_records (the fragment collected)
  │                              └── attributes: variation, variation_kind, concentration
  └── canonical_id → brands (an alias points at its house)
```

- **`products.brand`** is the shop's text, kept as collected.
- **`brands`** is one row per *spelling family*, folded by a slug: "Moët & Chandon" and
  "MOET CHANDON" land on one row automatically.
- **The "house"** is that row after following `canonical_id`. It is a role, not a table and not a
  column.
- **`product_lines`** is one row per house and line key, the key a pure function of the name.
- **`products.attributes.variation`** is a canonical string with a `variation_kind` beside it.
- **`listings`** carries the listed layer: the shop's own words, written once from the stored
  fragment, never re-derived from Product fields.

### 3.2 The three layers rian already approved

On 14 Sep he settled the vocabulary **Listed / Standard / Decided**: listed is the shop's words,
standard is what the rules made of them, decided is what a person set. It is a good vocabulary and
most of this document's naming proposals are attempts to extend it rather than replace it.

### 3.3 What is automatic and what is human

This distinction caused real confusion and is worth stating flatly.

| Act | Who |
|---|---|
| Fold spellings that produce the same slug onto one brand row | rule |
| Compute the line key, variation, quantity, ABV, category | rule |
| Fold two products that share a computed key | rule |
| Point one brand row at another as its house (`canonical_id`) | **person only** |
| Point one line at another | **person only** |
| Map a variation wording to a different canonical | **person only** |
| Set a preferred display name | **person only** |

There is **no rule anywhere that maps "Paco Rabanne" to "Rabanne"**. Rules only *suggest* the pair
into a queue. Rian assumed the opposite, and the assumption matters: it means the machine never
invents a grouping, it only proposes one.

### 3.4 The parser, demonstrated

Run on rian's own example, live:

```
input     "1800 Anejo Triple Cask 40% 1L", brand "1800"
category  Tequila & Mezcal        (derived; the name never says "tequila")
line key  'anejo cask'
variation ''                       (drinks have no variation, by design)
quantity  1000 ml, form single, state stated
abv       40.0
match key '1800|anejo-cask||1000ml'
```

Rian expected to need an AI pass for this. He does not; it already runs on every listing. What it
does *badly* is §5.7.

## 4. The vocabulary problem

Rian: *"you're right to be thinking that I dont like the term ['house']. The term is confusing and
seems more suited to alcohol than broadly applicable. I'd like to explore another name for it."*

**The word is cheap to change.** It is never a database column. It is roughly 250 mentions across
code, tests and docs, a handful of them unrelated ("fashion house"). It is visible to rian on the
internal Listings table, which has a column group labelled House. It appears nowhere on the public
site, so no address, page or SEO artefact is touched. **No migration.**

**Recommendation put to him: "standard brand".** It reuses the Listed/Standard/Decided vocabulary
he already chose, and generalises without inventing anything: standard brand, standard line,
standard variation, standard quantity. Rejected alternatives: *canonical* (accurate, already the
column name, but jargon he would translate every time); *parent brand* and *master brand* (both
imply corporate ownership, which is a genuinely different relationship we may want later for
"Unilever owns Dove", and burning the words now would cost us); *primary brand* (says nothing).

**The deeper naming question he actually asked**, and which is unresolved:

> *"what is the equivalent to 'house' in products? ... I feel like there's probably a similar
> mechanism for any kind of alias for several properties. So maybe we find a standard language for
> that."*

He is right that the mechanism recurs, and wrong that it is one mechanism. It is **three
implementations of one idea plus a fourth of a different idea**:

| Level | What it does | Column | Called |
|---|---|---|---|
| Brands | a row points at the row it belongs to, both survive | `canonical_id` | alias in code, merge in the UI |
| Lines | the same | `canonical_id` | alias in code, merge in the UI |
| Variations | a *string* maps to a standard string plus a display form | `variation_aliases` | alias |
| Products | one row wins, the other becomes a forwarding tombstone | `merged_into_id` | merge |

The first three are the same idea. The fourth is different in kind: a product merge removes a
record from circulation; a brand alias leaves both standing and redirects one. **Calling both
"merge" in the interface is a real source of confusion**, and it is the confusion rian hit.

**A proposed rule, not yet accepted:** a column that points a record at the record it resolves to is
always `canonical_id`; a value holding a resolved form is always `standard_<field>`; a human's
correction to any single field always lands in `overrides` and never in a bespoke column.

## 5. The measurements

Every number below was read from staging on 15 Sep 2026, read-only.

### 5.1 Shape

| | |
|---|---|
| Tables in the database | 47 |
| Columns | 465 |
| Foreign keys | 83 |
| Live products | 16,761 |
| With a barcode | 9,865 |
| Without a barcode, keyed on the computed key | 6,896 |
| Merged-away tombstones | 612 |
| Product lines | 13,772 |
| Brand rows | 2,365 |
| Listings | 23,771 |
| Distinct listing URLs | 21,412 |

### 5.2 The line layer is thin, and that is the symptom

| | |
|---|---|
| Lines holding exactly 1 product | 12,129 |
| Lines holding 2 to 3 | 1,247 |
| Lines holding 4 or more | 330 |
| Largest line | 24 products |
| Mean products per line | 1.22 |

88% of lines hold one product. **This was first read as an argument against line pages and is
better read as the defect rian diagnosed:** those lines hold one product because the variation
split them.

### 5.3 Publication today

| | |
|---|---|
| Products qualifying to publish (comparable or exclusive) | 3,130 |
| Distinct lines behind them | 2,638 |
| Those lines holding exactly one published product | 2,364 |

### 5.4 Rian's price test

| Case | Evidence | Verdict |
|---|---|---|
| Shade | one Clarins lipstick page at Panama: 18 shades, exactly 2 prices (34.00 and 36.00 USD; 2 shades at 34, 16 at 36) | cosmetic |
| Concentration | Bogotá: 1 Million EDT 100ml = 109 USD, 1 Million Elixir 100ml = 130 USD | **price-bearing, 19% apart** |
| Size | Bogotá: same line 50/100/200ml at 84 / 109 / 149 USD | price-bearing |

His test was *"does it change the price"*. It classifies shade correctly and **misclassifies
concentration**, which the code also files with shade. A sharper test was proposed and not yet
accepted: **would a shopper type this word when searching for the product?** That gets
"1 Million Elixir", "Macallan 18" and "Absolut Raspberri" onto the determinant side and
"771V Dahlia Red" onto the variation side, and it aligns with the reason line pages are wanted at
all, which is search intent.

### 5.5 Comparability by variation kind

Share of products carried at more than one airport:

| Variation kind | Products | Comparable | Share |
|---|---|---|---|
| (none) | 14,434 | 2,000 | 13.9% |
| concentration | 1,770 | 744 | 42.0% |
| color | 399 | 249 | 62.4% |
| flavor | 158 | 1 | 0.6% |

**Read this carefully; it was over-read once already.** Colour looks like the most comparable class
in the catalogue, which appeared to refute the objection that shades fragment comparison. But a
one-shade line matches *itself* across one operator's three shops, which is what most of those 249
are. Of the 249 colour products at two airports, **two** reach a third. Panama lists 18 shades of a
lipstick; Paris lists one of the same line. **Shades barely spread. The 62.4% is weaker evidence
than it appears.**

Flavour's 0.6% is a different problem: the kind is misfiring on Paris confectionery gift boxes,
where the flavour word is part of a one-off product's identity ("15 Fine Chocolates Almond Crispy",
flavour "almond praline", no parsed quantity, CDG only). The one genuine case works perfectly: a
Mint Chocolate cream at 1L compares across eight airports — which, note, means flavour there
behaves like a **determinant**, contradicting rian's placement of flavour on the variation side.

### 5.6 Nobody has decided anything yet

| | |
|---|---|
| Rows in `overrides` | 0 |
| Brands with a human decision | 0 |
| Lines with a human decision | 0 |
| Variation aliases set by a person | 0 |
| Brand aliases confirmed | 0 of 2,365 rows |

Every value in the catalogue is machine-derived. Paco Rabanne (54 products) and Rabanne (76
products) are still two separate houses. **The decided layer is built and has never been used.**
Unifying the two mechanisms in §7.2 costs almost nothing today.

### 5.7 Two defects found while testing

**Pack words are stripped from spirit names.** `lines.py` removes pack tokens (pack, twin, trio,
duo, triple, tri, kit, giftset) before the line key is computed. Correct for "Ballantine's Triple
Pack 3x1L"; wrong whenever the word belongs to the expression:

| Product | Line key |
|---|---|
| 1800 Anejo Triple Cask 40% 1L | `anejo cask` |
| Artisian Triple Oak VSOP Cognac 1L | `artisian oak vsop` |
| Bellevoye Prune, Whisky triple malt | `prune` |
| 10 Year Old Triple Distilled Single Pot Still Irish Whiskey 70cl | `10 distilled pot still` |
| Cutty Sark Blended Whisky Triple Pack 3x1L | *(empty)* |

272 live products carry one of those words. The risk is a silent wrong merge: "Macallan Triple Cask
12" keys as `cask 12` while "Macallan Double Cask 12" keys as `double cask 12`, so a plain "Cask 12"
would fold into the Triple Cask.

**Lines are over-split, concentrated in beauty.** Collapsing lines onto the first two words of the
key, per brand, as a crude proxy for stripping a shade tail:

| Family | Lines today | After collapse | Reduction |
|---|---|---|---|
| beauty | 5,432 | 3,583 | 34.0% |
| confectionery | 377 | 327 | 13.3% |
| liquor | 7,896 | 7,224 | 8.5% |

Concrete cases, each one line split many ways today: CHANEL Rouge Allure **59 lines**; Lancôme
Teint Idole 45; Shiseido Synchro Skin 43; Clarins Skin Illusion 40; Clarins Joli Rouge 38; Lancôme
L'Absolu Rouge 29.

**The caveat that stops this being mechanical:** a shared prefix is not a shade. Among the 40 "Skin
Illusion" lines sit `Skin Illusion Full Coverage Foundation` and `Skin Illusion Tinted Moisturizer`,
genuinely different products; `Joli Rouge Velvet` is a different lipstick from `Joli Rouge`. The
rule to build is **strip the shade the collector actually published** (already read and labelled as
`variation_kind = color`), not **collapse lines with similar names**.

### 5.8 Why the shade is in the line key

Deliberately, pending a decision rian deferred on 13 Sep: the Makeup line key keeps the shade tail
until he settles whether makeup shades are one product with variations, with the kind held as
metadata beside it. **That deferral is no longer a loose end. It blocks the line page**, because a
cosmetics line page has nothing to group until the shade leaves the key.

## 6. The proposals on the table

None accepted. Each is recorded with its origin.

### P1. Rename "house" to "standard brand" *(put to rian; he has not answered)*
§4. Cheap, no migration, no public surface.

### P2. The line page becomes the primary page *(rian's)*
A "Coke Zero" page rather than a page each for every size and flavour, with the product selectable
within it. His argument was thin content: *"do we want 10s of 1000s of product pages?"*

**His argument as stated does not survive the numbers** (3,130 product pages become 2,638 line
pages, and 90% of those hold one product) **but a better argument does**: search intent. Nobody
searches "1 Million EDT 100ml duty free". A line page can carry every size and concentration with
the full cross-airport comparison on one screen.

**And on corrected data his original argument may hold after all.** The 16% figure was measured on
the over-split state of §5.7. Once shades fold, line pages both save more pages and get richer.
That is the outcome he wanted, arrived at from the other direction.

**Cost:** reverses the 9 Sep address decision, so Mark and Adam must see it. His sketched URL
`/product/coke-zero?product=12345` is weak as a canonical signal; a fragment or a canonicalised
sub-path is better.

### P3. Determinant versus variation *(rian's, refined twice)*
Final form, in his words:

> *"maybe we do treat all variations and product determinants all as variations and all create new
> 'products'. The barcode argument is what makes me lean that way. But then we have some meta that
> helps us distinguish if the variation should be considered a product determinant or variation for
> the purpose of presentation... flipping flavours would not [regenerate the comparison]. In fact,
> maybe they can't even flip flavors, they're not 'purchasing' anything, they're just viewing
> information."*

- **Determinants** (quantity, concentration, vintage) change the price: selectable, and the
  comparison regenerates.
- **Variations** (shade) do not: **not selectable at all**, listed as information about what the
  shop carries.
- **Everything stays its own product row**, so one product keeps one barcode.

**Assessment.** The strongest part is the non-selectable insight: it removes a whole class of
interface and a whole class of wrong answer, since a shade picker would imply we know what a shop
has in stock today, which we never will. It needs **no schema change** — products stay one-to-one
with barcodes, the identity key is untouched, and which selector splits a comparison becomes a rule
keyed on the variation kind. Rian's own read, which is correct: *"that's actually not going to be a
hard thing to reverse or change, it's more of a presentation thing."*

### P4. Shades leave the line key *(rian's, and the one he is most confident about)*
> *"I think shades are variations of a product line, not new product lines. I also think it's one
> such variation that we've identified that is currently splitting up product lines that should be
> single product lines with variations. I think we just haven't yet found the other examples."*

**Supported by §5.7.** This is the deferred makeup-shades decision, and it gates P2 and P3.

### P5. AI in the parsing path *(rian's, newest, least examined)*
See §8.

### P6. One human-decision mechanism *(put to rian; he has not answered)*
See §7.2.

## 7. The tensions

This is the section the next session should spend its reasoning on. Each is a real conflict between
things we want, not a task list.

### 7.1 Barcode identity versus line consolidation

**The pull.** Every shade carries its own GTIN, and GTIN identity is what makes cross-shop matching
a join instead of guesswork. That argues for keeping every shade a separate product.

**The counter-pull.** Keeping them separate is what produced 59 Chanel Rouge Allure lines and an
88%-single-product line layer.

**Why it resolves, probably.** These operate at different levels: the *product* keeps its barcode
and stays separate; the *line* stops absorbing the shade into its key. Nothing needs to merge. But
this has not been tested against the line-key rules, and §5.7's caveat shows a naive
implementation merges real products.

**Unresolved:** does anything else besides shade belong out of the line key? Rian believes yes and
has not found the cases. A systematic sweep of what the line key absorbs, per vertical, has not
been done.

### 7.2 Two decided layers doing one job

The "decided" layer is built twice, differently:

| | Products | Brands, lines, variations |
|---|---|---|
| Where a human value lands | a row in `overrides` | overwrites the column in place |
| What the rule said | kept in `collected_value` | **destroyed** |
| Disagreement over time | `collector_disagrees_since` | not tracked |
| Fields covered | name, line, variation, quantity | name only |

**The tension.** Routing everything through `overrides` is uniform, inspectable and preserves what
the machine thought, which matters for a system whose rules will change repeatedly. But it is
indirection: reading a brand's display name becomes a join rather than a column read, on a path
that runs for every page.

**Why it is urgent despite being invisible.** §5.6 — zero human decisions exist. This is free now
and permanently expensive after the first merge session, which is queued and waiting for rian.

### 7.3 Rian's price test versus search intent

Two candidate rules for the determinant/variation split (§5.4). The price test is measurable and
already broke once. The search-intent test aligns with why line pages are wanted but is a judgement
call per word with no data behind it.

**Unresolved:** whether the flag is per kind at all, or per (vertical, kind). Concentration is
price-bearing in fragrance. Flavour looks cosmetic in a cream liqueur and identity-bearing in a
chocolate box. A flat kind-to-behaviour map may be too blunt, and the flavour evidence (§5.5)
suggests it is.

### 7.4 What a line page prints as a price

The hardest unresolved question in the session, and rian said so: *"hmm, I dont know what to do."*

Eighteen shades at two prices cannot become one number without a rule. His first idea — group
shades by price and pick a representative per group — broke within two paragraphs of inventing it,
because **price groups are a per-shop fact and cannot be a cross-shop one**: red and pink share a
price at one airport and split at another. They also reshuffle whenever a price moves or a currency
shifts.

**Proposed instead, not accepted:** deduplicate the grid on *identity* (one card per line plus
determinants, cosmetic dimension collapsed), which is stable by construction. That leaves the card
needing a price, with two defensible answers and no third:

1. **Price the specific variation carried at the most airports.** A real like-for-like comparison,
   but it can be absent at one shop, and then the card is silent about that shop.
2. **Print a range with counts** ("most shades $36, two at $34"). Honest, but it is not a
   comparison, and a comparison is the entire product.

**The constraint that rules out the obvious third option:** the project forbids presenting a
family's cheapest as the family's price. "From $34" is that failure exactly, running in reverse.

### 7.5 Deadline versus refactor

Soft launch is 18 Sep. Most of this is not launch-critical; the line-page question is, because it
changes the public address shape and needs the client's reviewers. The next session should
partition the work explicitly rather than treating it as one programme.

### 7.6 Generality versus the categories we actually have

Rian wants a model that grows into food and beyond. Every measurement above is drawn from spirits,
fragrance and cosmetics, plus a little confectionery. **Confectionery is already the second-worst
family for line over-splitting and the worst for variation misclassification**, which is weak
evidence that a food catalogue would inherit these problems rather than escape them. Designing for
generality on three categories' evidence is itself a risk.

## 8. AI in the parsing path

Rian, twice, and newest:

> *"I know we also dont currently have an algorithm that can do the parsing right now, but I feel
> like all it would take is one manual claude pass to do all that parsing for now, and later we
> could either write an algorithm or have a little mini AI workflow that sends off new listings to
> a small local hosted AI to handle the parsing."*

> *"I'm leaning toward involving AI into the collector process so that the collector is just
> focusing on gathering the raw data and the AIs are involved with interpreting and parsing it out
> into brand, product line, product, variation, size, properties, etc."*

**Correct the premise first: the algorithm exists and already runs** (§3.4). It derives category,
line key, variation, quantity, ABV and the match key on every listing, with no human and no model.
The question is not whether to build parsing, but whether to *replace or supplement* rule-based
parsing with a model.

**The architecture already supports this, and this is the most important fact in the section.**

- `raw_records` stores the parsed fragment every listing came from, per run, with a
  `parser_version`.
- `app.cli rederive` re-runs identity over stored rows **without re-fetching anything**.
- Every backfill is an idempotent CLI command.
- `identity_rules_version` records which rules last resolved each row.

So a better parser can be introduced and replayed over everything already collected, with no
re-crawl and no loss. **The hook rian hoped for is already built**, and it is the reason this
decision is reversible.

**What the next session must weigh, none of it examined yet:**

- **Determinism.** Rules are reproducible: same input, same key, forever. A model is not. Identity
  keys that change between runs would churn merges, tombstones and published URLs. A plausible
  mitigation is to let the model *propose* and let a rule or a person *commit*, which is exactly the
  shape the merge queue already has, but nobody has designed it.
- **Where in the pipeline.** At collection (blocking, slow, costly per listing), or as a
  re-derivation pass over `raw_records` (offline, batched, replayable). The second fits the existing
  architecture; the first does not.
- **What it is allowed to invent.** The project's standing rules are that empty beats guessed, a
  barcode is never invented, and a human value is never overwritten by a machine. A model that
  fills a quantity it did not read violates the first. This needs to be a hard constraint on the
  prompt and on ingest, not a hope.
- **Cost and scale.** 23,771 listings today, growing with every airport. A per-listing model call
  at collection time is a different cost structure from a nightly batch over changed fragments only.
- **The runtime rule.** The stack standard says the running container is one Python process and Node
  exists only at build time. A local model sidecar is a new runtime dependency and a deliberate
  architectural change, not an implementation detail.
- **What it would fix that rules cannot.** The honest case for a model is the long tail:
  §5.7's pack-word defect is a rule fighting natural language, and every fix spawns the next
  exception. A model reading "1800 Anejo Triple Cask 40% 1L" would not strip "Triple". That is a
  genuine argument and it should be weighed against determinism, not dismissed.
- **A cheap first step nobody has costed:** run a model over the existing `raw_records` *as an
  auditor*, not a writer — have it disagree with the rules and report the disagreements into the
  existing merge/suggestion queue for a person. That tests the value with no determinism risk and
  no new runtime.

## 9. Unresolved questions, in the order they block things

**Blocking the line page**
1. Do shades leave the line key? (P4; blocks P2 and P3; already a deferred decision)
2. Does the line page replace the product page or sit above it with product pages canonicalised?
3. If it replaces it, what is the address, and do Mark and Adam accept reversing 9 Sep?
4. What price does a line page print for a non-selectable variation? (§7.4)

**Blocking the human-checking workflow**
5. Do brands, lines and variations route human values through `overrides` like products? (§7.2 —
   free now, expensive after the first merge session)
6. Should the interface stop calling a brand alias a "merge", given it is a different operation
   from a product merge?

**Shaping the model's generality**
7. Is "house" renamed, and to what? (§4)
8. Is the determinant flag per kind, or per (vertical, kind)? (§7.3)
9. Is vintage a line distinction, as the line rules have it, or a determinant, as rian has it?
10. What else does the line key absorb that it should not? (§7.1 — unsurveyed)
11. Does a place become a first-class row, so cruise ports and malls can exist?
    (`places-and-shops-2026-09-15.md`)

**Shaping the pipeline**
12. Does a model enter the parsing path, where, and with what authority? (§8)
13. Is the pack-word defect fixed by a better rule or absorbed into the model question? (§5.7)

## 10. What must not break

- **One product, one barcode.** The reason P3 keeps products separate.
- **A price is an observation**, append-only, with its date shown; a family's cheapest is never
  presented as the family's price.
- **Empty beats guessed.** A missing value is never invented, by a rule or by a model.
- **A human value is never overwritten by a machine.**
- **Merges are recorded, never deleted**; a merged row stays as a forwarding tombstone.
- **Everything must stay re-derivable from `raw_records`** without re-fetching. This is what makes
  every decision here reversible, and it is the property most easily lost by accident.
- **Migrations schema-only; backfills idempotent CLI commands.**
- **The 9 Sep decisions** stand until Mark and Adam revisit them.

## 11. What is cheap now and expensive later

Ordered by how fast the cost rises, which is the sequencing argument.

| Change | Cost today | Why it rises |
|---|---|---|
| Unify the decided layer (§7.2) | near zero | zero human decisions exist; the first merge session creates data in the old shape |
| Rename "house" (§4) | ~250 lines, no migration | every new surface that uses the word adds to it |
| Shades out of the line key (P4) | a rule change plus a rederive | every published line URL minted under the old keys is one more redirect |
| Determinant flag (P3) | presentation only | genuinely cheap whenever it is done |
| Line pages (P2) | a new page type plus a client conversation | addresses harden once indexed |
| Places as rows (separate doc) | additive | rises once cruise ports are collected under the airport model |
| A model in the parsing path (§8) | unexamined | rises with every rule exception hand-written in the meantime |

## 12. Related documents

- `.logs/planning/places-and-shops-2026-09-15.md` — the location model: places, shops, chains, and
  the airport code as identity.
- `.logs/planning/variations-and-line-pages-2026-09-15.md` — the detail behind §5.4, §5.5, §5.7 and
  the grid question.
- `main/docs/DATA-MODEL.md` — the generated table reference and relationship diagrams.
- `.memory/database-map-artifact.md` — the published one-page map of every table.
- Running list: `decide-the-line-page-and-the-variation-determinant-split`,
  `decide-makeup-shades-one-product-with-shade-variations-decided`,
  `decide-the-database-structure-changes-you-want-after-the-listin`,
  `issue-pack-words-are-stripped-from-spirit-names-so-triple-cask`,
  `issue-the-flavor-variation-kind-is-landing-on-confectionery-gi`,
  `issue-places-that-are-not-airports-cannot-exist-and-one-is-alr`,
  `issue-the-comparison-counts-shop-rows-not-places-so-a-second-s`.
