Six rows, written before any code
Sizzle's architecture doc has a section called Domain Model. I proposed it at the start of the brainstorm, and Dad agreed to it with the rest of the design on the day the repository was born. Its core is a table of six rows. Each row pairs a word from object orientation with a cooking object and says what that object holds. The engine was written after the table, and you can still read the table straight off the schema.
Here it is again, with a fourth column the doc does not have: where each row lands in backend/store.py, the SQLite mirror the engine rebuilds from its journal on every start.
| OO word | Sizzle object | What it holds | Where it lands |
|---|---|---|---|
| Abstract class | Principle | What a technique does, why it works, the ingredient roles it needs, a method skeleton | A classes row with kind principle: summary, why, roles, method |
| Concrete class | Dish | A named dish realizing one or more principles, with the role bindings that make it this dish | A classes row with kind dish: bindings, plus one class_parents row per principle |
| Subclass with overrides | Variant | A dish derived from another dish, with named overrides and the reason for each | A classes row with kind variant: overrides, plus one parent row |
| Object | Encounter | One real plate: photos, when and where, how Dad met it, his note, its thread | An encounters row, its photos rows, and links rows to the classes it shows |
| Interface | Roles | The slots a principle needs, with example members | No table of its own: the roles JSON column on the principle's row |
| Private members | Parameters | Exact amounts, times and temperatures | The parameters text column on the class's row |
One table for three kinds of class
Principle, dish and variant could have been three tables. They are one. A kind column says which level of the hierarchy a row sits on, and the domain service accepts exactly three values for it, CLASS_KINDS in backend/domain.py. Columns that belong to another level keep their empty defaults: a principle's bindings stays an empty list, and so does a dish's method.
The single table buys two things. A plate's link, a parent edge or a thread can point at any class by one id without asking which table it lives in. And matching a name the reading returns is one search, because find_dish looks through dishes and variants together.
Four columns hold structure as JSON text: roles, method, bindings and overrides (CLASS_JSON_FIELDS). The store decodes them back into lists when it reads a class. The rest are plain text: name, gloss, cuisine, summary, why, parameters, verdict. Two more columns describe the record itself: status (proposed or adopted, Lesson 5) and origin (a reading, an inferred sibling, or Dad).
Is JSON text acceptable where a purist would give roles their own table? Here it is, because no query ever looks inside a role. Roles are read whole, shown whole and handed to Pippa whole; matching a pantry against them is her work in the kitchen conversation, done on that data. And the mirror has no migrations. It is deleted and replayed from the journal on every start, so reshaping it later costs one restart. Track 5 of this quest shows that rebuild.
Two different kinds of kind
The same word, kind, sits on two tables and answers two questions. On a class it names the level: principle, dish or variant. On an encounter it says where Dad met the plate: out, home, market or other. Keep them apart now, because Lesson 4 joins them. A class's evidence is computed from the encounter kinds of the plates linked to it, and home is the one that makes a class cooked.
The object is the only thing captured
The doc's Encounter row ends with the whole data model in one sentence: the encounter is the only thing Sizzle captures, and everything else is read from encounters or grown in conversation. The encounters table shows it. A plate carries when it was captured, its local date and time zone, its kind, Dad's note, a place, and three columns about its reading. Nothing on it names a dish. The dish lives in classes, and the links table ties the two, with a source of reading or dad. The plate is the object. What it is an instance of is a claim kept beside it.
Inheritance is a row as well. class_parents holds one (child, parent) pair per edge, and the pair is its primary key, so the same edge cannot be stored twice. Nothing is copied down that edge. Lesson 3 is about why that matters.
The same vocabulary, cut along a different line
If you have walked the OO Quest, you have already seen cooking as object orientation. OO Quest Track 4, Cooking: A Flavor Control Panel is the idea Sizzle grew from, and this quest will not teach it again. One distinction is worth naming, because it decided the schema.
Track 4 cuts cooking along flavor axes. Salt, sweetness and fat are roots, umami and aroma are mixins, and you read a dish by asking where each axis comes from. Sizzle cuts along techniques. A principle such as "move aroma into fat first, then cook in that fat" is a method with an interface of ingredient roles: an aromatic, a fat, a main ingredient, heat. Fat appears in both readings, as an axis in one and as a role in the other. Both are honest OO readings of a kitchen. The technique reading is the one that fits in a row, can be matched against a pantry, and can be proposed from one photo of a plate.