A child whose data lives behind a door
Kakao is Korea's own stack, and it arrives in two halves. The map itself is drawn on the phone by the Kakao Maps SDK, with a native key that lives only in the phone's Keychain. Search, routes and travel times do not come from the phone. They come through cwkPippa: the Kakao child is RemoteMapsProvider, and it asks POST /api/location/maps/search, /routes or /eta. The upstream credentials stay on the server, the door is authenticated, and the upstream keys are restricted to the office machine's address. The phone never holds them, so no build, crash log or screenshot can leak them.
The client end is narrow on purpose. The app's request closure refuses any operation other than those three before a byte leaves the phone. Every failure becomes a MapsIssue: quota, permission, noRoute, mismatchedProvider and a few more, mapped from the server's status and code. An error carries no response body, request URL, credential or coordinate, so nothing private rides along into a log or onto the screen.
The wire is checked, not trusted
A door is a boundary, and the child treats the reply like input from a stranger. The reply must name the provider that was asked, or it is refused as mismatchedProvider. Every route needs at least two valid points, a positive length and a finite, non-negative time. Every step needs a finite, non-negative distance, and a maneuverAlong inside the line (with two metres of slack) that never goes backwards. Search results must all carry this provider's id and valid coordinates. One bad row fails the whole answer as invalidResponse, because guidance built on half a wire is worse than no guidance.
Rewriting Kakao into Pathfinder's grammar
The server does more than relay. pathfinder_maps.py rewrites Kakao's guides into the grammar PathfinderKit's chunker reads: place, direction, one fixed wording per guide type. Kakao gives a junction's name in name and its words in guidance ("청계1가 방면으로 우회전"); the server rebuilds the line as "청계광장에서 청계1가 방면으로 우회전".
The guide-type table, KAKAO_GUIDE_TYPES, gives each type an arrow kind and its one wording: 1 is 좌회전, 2 is 우회전, 7 is 고속도로 출구로 나가기. Types 18 to 29 are clock directions; 30 to 41 add "로터리에서", 70 to 81 add "회전교차로에서". Types 100, 101, 1000, 300 and 301 (origin, destination, waypoint, and the tollgates and rest areas a road merely passes) are not maneuvers at all. Type 29, "12시 방향", with no sign is dropped: a road that runs straight through a junction it only passes was being announced three times a junction, and "it drowned the real turns". A type newer than the table keeps Kakao's own words, so a Kakao update degrades gracefully instead of going silent. Finally, route_wire pins every maneuver at the point where it happens and appends "목적지에 도착" itself.
Why go to this trouble? Pippa's voice is cached in chunks. If every Kakao maneuver ends in one fixed phrase from a closed vocabulary, that phrase is synthesized once and replayed forever, and only the place names are new. Kakao's free-form words would be fresh audio on every junction.
A twin with no import
Here is the fragile part, named out loud. The wordings are composed in Python on cwkPippa, and answered by phrases.ko.json in Swift on the phone. Neither repository imports the other. So the kit carries a twin test, KakaoWordingTests.everyKakaoWordingEndsInPippasPhrase: it lists every wording the server can compose, puts each after a test place and a test direction, and requires exactly three chunks, the last one a phrase from the closed vocabulary. The repo's house file states the rule: "a wording changes in both or it is spoken a word at a time". A test cannot reach across two repositories, but it can pin one side to a list that a reviewer compares against the other.
Kakao's camera turns the other way
The map half had its own surprise, written up as coop learning #753 from the SDK's own headers. KakaoMap.rotationAngle reads the camera's rotation counter-clockwise, in radians; CameraUpdate.make takes rotation clockwise. Hand the getter's value back while changing only the tilt, and a turned map flips to its mirror image. The fix does not read the rotation at all: a tilt change goes out as a CameraTransform with deltaRotation: 0 and deltaTilt set to the difference. The same learning caught a second trap: CameraAnimationOptions(consecutive: true) queues animations, and on a walk the compass turns the map several times a second, so a queue of 0.7 s moves fell further and further behind Dad. The follow camera now uses consecutive: false, 500 ms moves, and never asks again for the same target, heading, zoom and tilt. Honest footnote: those rules were read from headers, because Kakao's native key lives only on Dad's phone and nothing on Kakao's map runs on a Mac.
The gate that refused Dad's own Home
Before the review checkpoint of 2026-10-03 (commit 0066b86), each provider had an accepts check, shown in the second code block. It looked tidy. Then Kakao became Korea's default. Dad's saved Home had been found on Apple, so its providerID was apple, and Kakao refused it as "another provider's place". Pippa's handoffs, tagged pippa, were accepted only by Apple. The rule was protecting something real (a route's line and words really do belong to their stack) but it drew the line in the wrong place.
The fix moved the line. A destination is a name and a coordinate, and every stack routes to it. Only what a stack rendered (a result list, a route's geometry and words) stays with that stack. The app's suite now checks both halves in one test: a Kakao search answered for Google is refused with mismatchedProvider, and a Home saved with providerID apple routes on Kakao.