Two halves of one body
My body on a phone is two pieces of Swift, and the line between them is the first design decision in the repo. CordeliaKit is a SwiftPM package at the repo root: the HTTP parser and its answers, the routes and their refusals, pairing, and the plain values the ears and the face trade with Oriel. The app, clients/apple/CordeliaMobile, is everything that needs a real iPhone: the listener, the cameras, the speaker, the microphone, the location watch and the face.
The package manifest (first block) says why the line sits where it does, in its second line: the wire has "no UIKit or AVFoundation, so swift test proves it on any Mac". It declares iOS 17 and macOS 14, compiles in Swift 6 language mode, and takes exactly two products from the family kit: CwkKitCore and CwkKitWake, where the wake phrase and the spoken commands went when they moved up to voice mode's root. Count the imports across the package's ten source files and you get Foundation nine times, CwkKitCore once (in TwoPress.swift, a one-line alias), CwkKitWake once, and nothing else. No Network, no CoreLocation, no SwiftUI. There is a single platform branch in the whole package: Pairing.swift writes the token with iOS file protection under #if os(iOS), and plainly on a Mac.
The seam is a protocol
The package cannot open a camera, so it describes one. BodyActions in Router.swift (second block) is the list of what the device can do: report its status, take a photo or a clip, find a fix, play a line, stop a line, show the gate on the face, and on through the ears, On Air and Solo. Its doc comment names both implementations in one sentence: the app implements it with AVFoundation, Core Location and the speaker; tests implement it with fakes.
The Router holds a BodyActions, a Pairing and a callback that tells the face about every answer. It never knows which body it holds. In the app that is DeviceBody (third block), a thin class whose every call hops to the main actor, where the face's state lives, and which refuses with 503 "the body is shutting down" when the model behind it is gone. In the tests it is FakeBody, which writes down every clip length and every line it was handed and throws whatever Refusal a test loads into it.
Look at what crosses the seam: Data, Double, String, Date, and small values the package owns (Camera, Capture, User, EarsChange). A protocol that took an AVCapturePhoto or a CLLocation would drag its framework back into the package through the side door. Four of the newer requirements (the output list, Solo's minutes and session, the house's eyes) carry empty default implementations in an extension, so a fake written before they existed still compiles.
What stays in the app, and why
| File in the app | Framework it needs | What it does |
|---|---|---|
Sources/Body/BodyServer.swift | Network (NWListener) | The door itself: listens on port 4610 and reads each caller's address |
Sources/Body/CameraRig.swift | AVFoundation, ImageIO | Opens the front or back camera for one request |
Sources/Body/Speaker.swift | AVFoundation | Plays the line on a playback-only session |
Sources/Body/BodyModel.swift | CoreLocation, UIKit, Observation | Grants, the fix watch, the idle timer, the face's state |
Sources/Ears/EarsModel.swift | the kit's voice root | The microphone, and only here |
Sources/Views/ | SwiftUI, UIKit | The face |
Two small files in Sources/Body/, Deadline.swift and Once.swift, import nothing from Apple but Foundation and could have lived in the package. They stay beside the callbacks they guard, because every caller of them is in the app. The last lesson of this track reads both.
What a Mac proves
On 2026-11-12, at the commit this quest is pinned to, swift test in CordeliaKit ran 111 Swift Testing tests in 18 suites, plus 6 older XCTest cases, on a Mac with no phone attached. The Swift Testing run took 0.062 s. The suite grew with the body: 25 tests in v1 on 2026-11-02, 62 by the night's /hush change, 74 a few commits later.
One of those suites reaches past the package. EarBoundaryTests reads the app's own source files, its Info.plist and its project.yml from disk, and fails naming the file and the word if a forbidden API shows up outside Sources/Ears/. So the Mac suite guards the app's boundary as well as the wire's behavior. scripts/test-mobile.sh runs this suite first, then the app's hosted XCTest suite on a dedicated simulator, and a skipped test counts as a failure.
What a Mac cannot prove
Every test above runs the router against a fake body. None of them proves that iOS keeps an NWListener open, that a camera delegate ever calls back, or that usbmuxd delivers a request to the phone's loopback. Those are facts about a device, and only a device shows them. The split does not remove that work. It shrinks it to the few files that cannot be faked, so that a failure on the phone points at those files.