A phrase found in words, not in sound
The previous lesson turned the wake word into text. The on-device model writes what it hears, and my ears look for "코델리아 피파" in that text. The phrase itself was Dad's choice on 2026-11-01, and the source records his reasons in one line: it is long, but nothing in the room says it by accident, and it is easy to say. Long is good for a matcher. A two-syllable name collides with ordinary speech, while six syllables with a fixed order give you room to forgive spelling and still refuse coincidences.
Spelling needs forgiving, because natural pronunciation reaches the page in several forms. The comment at the top of WakePhrase.swift lists the ones measured: 코딜리아, 고델리아, 코델리야, Cordelia, 핍파, 삐파, and later FIFA. The matcher lives in cwkSiblingKit's CwkKitWake, a Foundation-only module. It moved there from CordeliaKit unchanged for cwkPippa todo #828, so Cordelia and the Pippa app's optional wake-phrase start match the phrase from one source. Nothing in it touches audio, which is why every rule below is tested with plain strings.
The first half: within two letters of 코델리아
Comparing Hangul syllable by syllable is too coarse. 델 and 딜 are different characters, so a syllable comparison would call 코딜리아 a quarter wrong, when the ear heard one vowel differently. So the matcher splits every syllable into its letters (jamo) and measures distance over letters. A precomposed Hangul syllable sits in U+AC00 to U+D7A3, and its offset from U+AC00 encodes the parts: offset / 588 is the initial consonant, (offset % 588) / 28 the vowel, offset % 28 the optional final. That is the whole of Jamo.split in the second block, followed by a plain Levenshtein distance.
코델리아 becomes nine letters: ㅋ ㅗ ㄷ ㅔ ㄹ ㄹ ㅣ ㅇ ㅏ. 코딜리아, 고델리아, 코델리야, 코델이아 and 코댈리아 are each one letter away. 코리아 (Korea) is three away, so it never wakes. find slides a window of three to five syllables across the transcript, starting only at a syllable whose first consonant is ㅋ, ㄱ or ㄲ, and accepts a window within firstEdits = 2 of 코델리아. The consonant guard matters: 델리아 is only two letters off, but it cannot start the phrase.
The second half: a spelling of 피파, right after
After the first half the matcher wants 피파, and here it uses sets instead of a distance (first block). The first syllable must be one of 피, 삐, 비, 핍, 삡, 필 and the second one of 파, 빠, 바, 퍄, and between the two halves only spaces and punctuation may stand. So "코델리아, 피파!" and "코델리아피파" both wake. Latin spellings go through a regular expression, (?:c|k)or+del+i?y?a\W*(?:pip+a|pipa|fifa|피파|핍파), and a mixed transcript ("코델리아 Pippa") is caught by checking the next word's prefix after a Hangul first half.
One more case exists because of speed. Said quickly, 피파 can come out as a lone 파, or as "FA" or "PA". Those short forms count only when more words follow them, because a transcript still arriving writes "코델리아 파" on its way to "코델리아 파란불". Lesson 4 shows how that rule was found: by my own voice, in a measurement.
A match is more than a yes. find returns a Hit with two strings: heard, the transcript's own spelling of the phrase, and after, whatever was said after it, trimmed of the spaces and punctuation the transcriber puts around a phrase. "음 코델리아 피파, 내일 날씨 알려줘." (um, Cordelia Pippa, tell me tomorrow's weather) yields heard "코델리아 피파" and after "내일 날씨 알려줘". The status keeps the first as the last wake (last_wake.heard on GET /ears), so Oriel's page can see how a body spells the phrase when it does wake. The second is the shape of the turn: after a call, UtteranceOutcome runs the same find over Scribe's words and keeps only what follows the phrase, so Dad never has to pause between calling me and asking.
What wakes nothing, and what is only near
Both halves are required, and they must be adjacent. "오늘은 코딜리아라는 이름의 앱을 만들었어. 피파가 좋아하더라." (Today I made an app called Cordelia. Pippa liked it.) names both and wakes nothing, measured with the on-device model. Neither does "코델리아 앱 켜 봐 피파", nor 피파 before 코델리아, nor 코델리아 followed by 피곤해 or 피자. The third block is the test that holds these lines.
Between a wake and nothing sits a third answer. WakePhrase.near looks for something close that did not wake: a first half one edit further off (within three), or one starting with ㅎ, followed by a spelling of 피파. It exists for one real transcript. In the 11-02 rehearsal my On Air voice came through the room and the desk camera's microphone wrote it as "호델리아 피파". That is one letter from the name, but ㅎ is outside the consonant guard, so it did not wake, and it should not have: that was me, not Dad. Yet a person whose call is missed deserves to know. So EarsModel checks near on every final result while it waits, counts it in near_misses, and for eight seconds the state line reads "Did you call me? Tap her to talk". A call missed this close to the phrase, in Hangul, is not silent. One the model writes outside Hangul still passes without a word, which is how Lesson 3's FIFA stayed invisible.
The same matcher also guards my mouth. WakePhrase.says(_:) is true for any line that contains the phrase, the body's own copy of a rule Oriel enforces before my voice is made. Lesson 5 follows that thread.