An at-least-once lane, on purpose
The capture lane promises that a command arrives. It does not promise once, and it does not promise on time. That is not a bug in the lane; it is the price of the two things you wanted from it. Look at how a phone ends up holding the same command twice, or late:
- Every command normally arrives twice. The ring (
sendMessage) wakes the phone app and hands it the envelope. Then the transfer, the durable record, delivers the very same envelope again. The kit has a name for the second copy: an echo. - The watch re-offers what it still holds. On every session activation, every time the phone comes back into reach, and on every flush, anything still in the watch's outbox goes out again, until its transfer reports it landed. A completion callback lost to a relaunch means one more round.
- The watch waits for an absent phone. A command tapped while the phone is out of range sits in the outbox for minutes or hours and lands when the phone returns.
CwkKitWrist can drop honest echoes with a bounded ledger (WristSeenLedger), but its own header says that ledger is never the only defence: the consumer must be idempotent where it acts. Pathfinder passes no ledger at all and does the job in WatchBridge, where it can also do the second half.
Two checks at the door
WatchBridge.take(_:) runs five short lines before anything is obeyed. A presence (.watching) that came this way is ignored; lesson 3 explains why it should never have come. An id already seen is dropped. The id is remembered (the last 64). And a command that is not fresh is dropped: WristCommand.maxAge is 30 seconds, measured as now - issuedAt on the phone's clock against the watch's stamp, with the same 30 seconds allowed the other way for skew. The comment in PathfinderKit puts the intent better than a spec could: a stale "start a walk home" from this morning is history, never an instruction.
Notice the order. The id is remembered before freshness is judged, so a stale command's echo is dropped as a duplicate and never gets judged a second time. And the memory is small and lives in the process ("command ids obeyed in this run"). Within one run that is enough, because freshness bounds how long a duplicate could matter at all: once a command is older than 30 seconds, the freshness check throws it away whether or not anyone remembers its id, and sixty-four ids is far more than a person taps in 30 seconds.
Across a relaunch it is not enough, and it is worth saying so plainly. If the phone app dies and comes back between the ring and the transfer's echo, inside those 30 seconds (iOS reclaims a suspended app, or it crashes), the new process remembers no ids, the echo is still fresh, and the command runs a second time. A second end does nothing when no trip is left to end (if trip != nil). A second toggleVoice flips the voice straight back, and a second go can start the route again. The code accepts that window rather than persisting its ledger, and no test covers it. So the honest contract is once per id within a run, while fresh: two checks that shrink the duplicate window to 30 seconds and one process, not a guarantee of exactly once.
Why the receiver judges freshness
You might ask why the watch does not simply refuse to send old commands. Because it cannot know. The whole point of a durable outbox is that the sender does not know when its record will land. Only the receiver holds "now" at the moment of acting, so only the receiver can decide whether the record is still an instruction.
Durability and freshness answer different questions. Durability: did the record reach the phone? Freshness: is it still a command? Drop the second check and durability turns a tap into a delayed fuse. Picture Dad tapping "walk home" on a bench with the phone left behind. Hours later the phone comes back into range, the outbox flushes, and the phone ends whatever trip he is on (goFromWrist really does end the current guidance first, "switched from the watch") and routes him home from somewhere else entirely.
Obeyed is not the same as done
A fresh, new command can still meet a no. go names a place by id; if that place is no longer saved on the phone, the wrist is told "That place is not saved on the iPhone." Starting a route also needs a fix to route from, and Pathfinder asks for location When In Use only, never Always: with the phone app not on screen and no segment already measuring, the phone may not start measuring now, so the wrist is told "Open Pathfinder on the iPhone to start." Either sentence rides the next glance's problem field, so the answer appears where the question was asked. And pull changes nothing: the phone publishes its glance at once and touches nothing else (lesson 5).
The one message that did not belong here
The first version of the wrist put one more thing on this lane: every 30 seconds, while its navigation session ran, the watch said so with link.capture(.watching(true)). Run the three questions from lesson 1 over that heartbeat. A duplicate says nothing new. A late one has nothing the newest beat did not already say, and on its own it lies, because it says "live" about a session that may be long gone. A lost one is repaired by the next beat. Every answer says state, and it was riding a lane built for acts. On the bench it piled up exactly as this lesson predicts, and commit fc2558d moved it to a lane of its own (lesson 3). The phone still refuses a presence that arrives this way: "a presence that came the capture way is no command", as its test puts it.