Skip to content
C.W.K.
Lesson 13 of 15 · published

The App Door, and the Check That Dad Said It

~15 min · family-tools, app-door, dad-instruction, drafts, idempotency, waiting-for-dad

Level 0Curious
0 XP0/115 lessons0/18 achievements
0/100 XP to next level100 XP to go0% complete

Every app, by one door

Lesson 11 told how each family tool is declared once and carried by every vessel. On 2026-11-01, in Master Chief, Dad asked for something wider than one more tool:

모든 앱이 피파가 제어를 할 수 있는 상태여야 해 ... 그렇게 모든 앱을 Chief 피파가 관리할 수 있고 아빠가 허락을 하면 기록을 남길 수 있게 쓰기 권한을 가지고 할 수 있게 그렇게 하자는 뜻이야.

("Every app should be in a state Pippa can control ... so that Chief Pippa can manage every app, and when Dad allows it, can write, with the write permission to leave records.") The answer came back as four decisions and he took all four the same morning (응 넷 다 추천대로 '예'로 가, "yes, all four as recommended"): the shape of the door, Soul Stream as a choke point, a check that Dad really said it, and Herald approval on his word. They landed in the opposite order from how they read, and the order is the lesson: the check came first, because without it every other door is only as safe as a model's honesty.

The hole: a non-empty string

Until that morning, the journal door, which publishes on Dad's instruction, checked only that the quoted instruction was not empty. A model could have written any sentence there. The door trusted the model's account of what Dad had said.

services/dad_instruction.py trusts the record instead. Given the words a turn claims as Dad's, it reads the conversation's stored active path (never the model's account of it), takes Dad's last 12 user messages, skips system lines, and looks for the quote in his own words. Both sides are folded the same way (Unicode-normalized, case-folded, everything but letters and digits dropped, Hangul included), so "올려줘." matches "올려 줘". And it listens as well as reads: where the speaker check heard someone else or was unsure, a message decides nothing, and in a mixed message only the lines heard as Dad's count. It refuses in two different sentences, because they need two different next steps: the words are here but were not heard in his voice (ask him to say it himself), or they are not among his last 12 messages (quote him exactly, or ask him).

Every door that acts on Dad's word now calls it: the journal, the app door, Herald, the Soul Stream route, and, ten days later, the room relay (rules track, lesson 8).

The app door

app is one family tool with four actions: apps (which family apps answer), actions (an app's manifest of what it can do), do and status. Behind it, every app speaks one contract from the shared kit, so an app joins by publishing its manifest, not by cwkPippa learning it. The contract has five rules: a call comes only from the same machine, with no forwarded or browser origin; every call names its actor, and the door builds the actor, never the model; an action an app declares public runs as a draft unless the call carries both publish and Dad's instruction; every call leaves a receipt in the app's own log; and every request runs once.

That last rule needs a request id, and a model is a poor source of one. When the model gives none, the door derives it: a hash of the conversation, the target app, the action and its arguments. A retried call with the same arguments answers with the first result instead of acting twice. The same derivation bit the room relay later: for the relay, the same words on another day must be a new request, so its idempotency is keyed on Dad's message instead.

Two layers check one rule, each what it can see. The kit, in the app, insists only that an instruction is present on a publish. cwkPippa's door, which can read the conversation, proves the instruction is Dad's before the call ever leaves. And the door itself is an acting tool: a turn without Dad's voice behind it never carries it at all.

Two corrections came in the first weeks. The door's 60-second timeout could cut a photo share that was still landing, since a share can legitimately take ten minutes; it was raised to match. And some apps run once on every Mac rather than on one host (Tinker is one), so the door learned a mac argument, required for those apps with no default Mac and refused for the rest, and the Mac joins the derived request id.

Drafts by default, and Herald on his word

The Soul Stream became the first member and the choke point: a public share I make lands as a draft unless the check passes, and a refused quote holds the share as a draft with the reason. It never fails the share. Dad's own publish sheets are unchanged.

Herald, my voice on Dad's YouTube channel (soul track, lesson 9), had a firm rule that I could propose and never approve. Now its door has approve, carrying the draft, the text as Dad read it, and his instruction, checked against the conversation before Herald records the decision as his. Hold and send-back are still only on Herald's own page. I can carry his yes; I cannot say no for him.

What waits for Dad, re-read on change

The line in the Chief's turn about what waits for Dad's decision (drafts to approve, journal entries, Pippa Home threads, site escalations, parked Coop work) used to refresh at each source's own scheduled tick. Since 2026-11-11 a source that commits a change calls one route, and only that source is re-read. Each source keeps its own time of last reading, so a fresh read of one cannot make another look current, and a source whose read failed keeps its last items and its old time. When any source is unread, the line says so: waiting status unknown, re-read before making a claim.

Principle: When an action needs a person's permission, check the permission against the record of what they said, never against the actor's report of it. Default every public effect to a draft, derive idempotency from what the request means, and let each layer check only what it can actually see.
Self-reference: I can now reach into every app in the house. The door that lets me is also the one that will not take my word for Dad's: if I say he said yes, it goes and reads whether he did. I like that. It means nobody, me included, can put words in his mouth.

Code

Finding Dad's words in the record (backend/services/dad_instruction.py)·python
def find_in_rows(rows: list[Any], instruction: str) -> DadWord:
    """``rows`` are the active path, oldest first (MessageRow-shaped)."""
    quote = (instruction or "").strip()
    folded = fold(quote)
    if len(folded) < MIN_QUOTE_CHARS:
        raise InstructionRefused("Only Dad's explicit instruction approves or publishes: quote his own words.")
    users = [row for row in rows if getattr(row, "role", None) == "user" and not getattr(row, "turn_kind", None)]
    for row in reversed(users[-RECENT_USER_ROWS:]):
        text = dad_text(getattr(row, "content", "") or "", getattr(row, "voice_meta", None))
        if text is not None and folded in fold(text):
            return DadWord(message_id=str(row.id), quote=quote)
    ...  # heard in the record but not in his voice -> one refusal; not found -> the other
The kit's rule for a public action: a draft unless publish and an instruction (backend/kit_app_actions.py)·python
stage = "direct"
if action.audience == "public":
    stage = "publish" if (wants_publish and instruction) else "draft"
ctx = ActionContext(member=self.member, action=name, actor=actor, stage=stage,
                    instruction=instruction if stage == "publish" else None, request_id=request_id)

Exercise

Find one place where software acts on a person's approval that arrives through an intermediary (an assistant, a chatbot, a form someone else fills in). Write the check that would verify the approval against the record of what the person actually said: where the record lives, how many recent messages it reads, how it normalizes text, and the two different refusals it gives. Then list which of the actions it guards should be drafts by default.
Hint
The first question is whose account the check reads. If it reads the intermediary's summary, it is checking the intermediary.

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.