Two parents is normal
The concept table says it in the Dish row: multiple inheritance is normal. Its example is gambas al ajillo, which is aroma-in-fat plus a fast-cooking protein served in its own sauce. Those are two techniques, and a dish that realizes both should sit under both. In the record that needs nothing beyond the join table from Lesson 1. class_parents holds one row per edge, so a dish with two principles has two rows, and a principle with five dishes under it has five.
Each direction starts from one join. parents joins from child to parent and is a single query. children joins from parent to child, then loads every child whole through klass, which reads the child's row and runs its evidence query, so N children cost 1 + 2N SELECTs. That is cheap at the size of one cook's tree, and it is still a loop, not one query. The web's class page prints the first as a lineage line, Inherits followed by every parent (Derived from, for a variant), and the second as Dishes that inherit it, or as Versions of it under a dish. The phone prints one Inherits link per parent.
Where the edges come from
Most edges are written by integrate, the function that grows the tree from one reading (Track 4 walks through it whole). For each dish on the plate it takes at most two of the principles the reading named, resolves each against Dad's tree or proposes a new one, resolves the dish the same way, and calls ensure_parent once per principle. Siblings get edges too. Each inferred sibling is parented under the principle it says it shares, or under the dish's first principle when that name matches nothing.
ensure_parent refuses two things before it writes: a class as its own parent, and an edge that already exists. Only then does it record a class.parented event. The projection inserts with INSERT OR IGNORE, so even a duplicate event replayed from the journal cannot add a second row. The composite primary key is the last line of defense, and the domain check keeps the journal free of events that would change nothing.
The cap belongs to one dish entry
The rubric tells me one root principle per dish, two at most, and the code holds me to it with a slice, dish.principles[:2]. A third principle in my answer is ignored. That bound is per dish entry, not per reading and not per class. The join table has no limit, and nothing counts a dish's parents before adding one. Three paths can take a dish past two:
- Inside one reading. A tapas board can hold several dishes, and the reading lists each as its own entry. A second entry that names the same dish folds onto the same class and brings its own two principles. An entry that lists this dish as a sibling parents it under the principle that entry names for it. Either way the third edge appears before the reading is done.
- A later plate of the same dish is read, its name folds onto the existing class, and this reading names a principle the first one did not. A third edge appears.
- A reading of some other plate lists this dish as a sibling under that plate's principle. The dish already exists, so it is parented there as well.
And no event removes an edge. The catalogue has class.parented and nothing that undoes it, so an edge a past reading added stays after the plate is re-read under a newer rubric. Compare the links between plates and classes: the current_links view counts a reading's links only while that reading is the plate's current one. Evidence forgets an old reading. Inheritance keeps it.
Is that a bug? It is a property to know. The cap stops one dish entry from scattering a dish across every technique it touches, and the growing parent list remembers every principle any reading has seen in the dish. For a cooking record that is a reasonable memory. If you reuse the design somewhere a wrong parent must be retractable, you need the event Sizzle does not have. OO Quest Track 5 is about what a wrong parent class costs.
Nothing is looked up through the edge
Multiple inheritance in a programming language comes with a famous problem. If two parents define the same method, which one does the child get? Python answers with its method resolution order. Sizzle never has to answer, because its edges carry no behavior. klass returns a class's own columns and nothing from its parents. A dish's bindings are its own, a principle's roles stay on the principle, and no field is inherited by lookup.
The edge is used to show and to count. It feeds the lineage line, the children lists, the Tree page, the parents and children a class conversation hands me as context, and a principle's evidence, which Lesson 4 computes from its direct children. That keeps the diamond harmless. A variant under a dish under two principles is a path you can walk. It is never a conflict to resolve.
Variants derive once
A variant starts with exactly one parent, the dish or variant it was derived from. It can be derived from in turn, so a lineage can run as a chain of home versions. What it cannot do is derive from a principle. The domain refuses that in derive_variant and again when it validates a variant action I proposed, with the same message both times. That guard covers deriving, not parenting. A reading can still resolve a dish entry to a variant and parent it under a principle, so a variant can end up with a principle among its parents, and Lesson 4 shows what that does to evidence. For the vocabulary behind all of this, multiple inheritance included, see OO Quest Track 7.