Skip to content
C.W.K.
Lesson 05 of 06 · published

Proposed Until Adopted

~13 min · adoption, human-in-the-loop, event-sourcing, ownership

Level 0Mise en Place
0 XP0/43 lessons0/12 achievements
0/100 XP to next level100 XP to go0% complete

The tree grows; it is never imported

Invariant 6 in the architecture doc has two halves. First, no pre-seeded taxonomy: Sizzle starts with an empty tree, and the Tree page says so until the first plate is read. Second, proposals stay proposals until Dad adopts them. Every class I create starts as one. The principles and dishes a reading names and every sibling it infers are written with a status of proposed and an origin of reading or sibling.

The doc names the way out in one sentence: a proposal joins Dad's tree when he adopts it, renames it, or ties a plate to it, and it stays visibly a proposal until then. The code has exactly those three doors.

Three doors in

DoorHow Dad opens itWhat is recorded
AdoptPresses Adopt into my tree, or applies an adopt action I proposedclass.adopted
RenameRenames the class, or applies a rename actionclass.renamed, whose projection also sets the status to adopted
Tie a plateApplies a link action I proposed in that plate's conversationclass.linked with source dad, then class.adopted if the class was still a proposal

Look at where rename's adoption happens. rename in the domain records only the new name. The projection, _on_class_renamed, sets the status to adopted in the same UPDATE. So the rule that renaming is adopting lives where the journal is replayed, and every rebuild applies it again.

Tying is the door that is also evidence. link writes a link with source dad, and when the class was still a proposal, it records class.adopted right after. A tied plate says this plate is that dish. A class Dad vouches for with a plate cannot stay my guess. The engine also has a direct route for it, POST /api/encounters/{id}/links, but neither client calls it today, so on both the tie arrives as an action from a conversation.

What does not adopt

  • A reading linking its own plate. Those links carry source reading and adopt nothing. A dish can be encountered and still a proposal.
  • Dad editing a field. Saving a verdict or exact amounts goes through PATCH /api/classes/{id}, which records class.revised and leaves the status alone.
  • My suggestion by itself. An adopt, rename or link action I propose waits on the turn until Dad applies it. Applying it is what calls the domain method.

One kind of class is born adopted: a variant. derive_variant creates it with status adopted and origin dad, because Dad made it, in the form or by applying a variant action I proposed.

Why the status matters

Adoption is the moment the words change owners. While a class is a proposal it is my current understanding, and a newer reading may rewrite its description, my voice under a new rubric included. Once it is adopted it is Dad's, and readings leave its words alone. Proposal Versus Adoption: Who Owns the Words, in Track 4, shows the guard that enforces it.

The status also shapes what Dad sees. The web's chip reads Proposed or In my tree, the phone's reads proposed or adopted, and both offer Adopt into my tree only while a class is a proposal. The Tree page lists adopted principles first. My context carries it too: the compact tree that every reading and conversation hands me lists each class with its status, so when I talk about the tree I can tell Dad's classes from my own guesses.

A one-way door

No event turns an adopted class back into a proposal, and no event deletes a class. Adoption is final in this version of the record. That suits a tree Dad grows by hand. It also means the adopt button deserves a moment's thought, because after it the only way to change a class is to edit it.

Principle: A model's output joins the record as a proposal, and only a person's act makes it theirs. Name every such act, record it as its own event, and keep the proposal looking like a proposal until one of them happens.

Code

cwkSizzle, backend/domain.py: adopt and rename, each one event (verbatim, lines 171-182)·python
    def adopt(self, class_id: str) -> dict[str, Any]:
        self._require_class(class_id)
        self.record("class.adopted", {"id": class_id})
        return self.store.klass(class_id)

    def rename(self, class_id: str, name: str) -> dict[str, Any]:
        self._require_class(class_id)
        name = _text(name, 200)
        if not name:
            raise DomainError("a name cannot be empty")
        self.record("class.renamed", {"id": class_id, "name": name})
        return self.store.klass(class_id)
cwkSizzle, backend/domain.py: tying a plate also adopts (verbatim, lines 221-227)·python
    def link(self, encounter_id: str, class_id: str) -> None:
        """Dad ties a plate to a class; that also adopts the class."""
        self._require_encounter(encounter_id)
        klass = self._require_class(class_id)
        self.record("class.linked", {"encounter_id": encounter_id, "class_id": class_id, "source": "dad"})
        if klass["status"] != "adopted":
            self.record("class.adopted", {"id": class_id})
cwkSizzle, backend/store.py: the projection, where a rename becomes an adoption (verbatim, lines 275-281)·python
    def _on_class_adopted(self, d: dict[str, Any], at: str) -> None:
        self.conn.execute("UPDATE classes SET status = 'adopted', updated_at = ? WHERE id = ?", (at, d["id"]))

    def _on_class_renamed(self, d: dict[str, Any], at: str) -> None:
        self.conn.execute(
            "UPDATE classes SET name = ?, status = 'adopted', updated_at = ? WHERE id = ?", (d["name"], at, d["id"])
        )

External links

Exercise

Pick a feature in software you build where a model or an import creates records, such as suggested categories, extracted contacts or draft replies. Give those records a proposed status, then list every act by which a person makes one theirs. For each act, name the event you would record, and decide whether a person's act can ever be undone and by which event.
Hint
Separate the acts that adopt from the edits that do not; saving a note on a proposal is not the same as accepting it. Make sure a suggestion from the model can never call the adopting method directly, only queue an action a person applies.

Progress

Progress is local-only — sign in to sync across devices.
Spotted a bug or have feedback on this page?Report an Issue

Comments 0

🔔 Reply notifications (sign in)
Sign in — Please sign in to comment.

No comments yet — be the first.