Skip to content
C.W.K.
Stream
Lesson 06 of 09 · published

The Road Test

~13 min · sync-engine, hardening, review, data-protection, travel, v3

Level 0Cold Draft
0 XP0/43 lessons0/13 achievements
0/100 XP to next level100 XP to go0% complete
"At home the network is a given. On the road it is a guess. Harden for the guess before the plane boards."

Reviewed Until Clean, Never Compiled

Before Dad left for Spain, the office sync engine and the iPhone client had only ever been tested by a family that never left the house. On 2026-09-24 a cloud session ran a comprehensive review of the whole stack — the Mac app, the shared core, the engine and the iOS client — round after round until a round came back clean, and fast-forwarded 32 commits onto main. The commit log reads like a ladder: fixes from the re-review, the third review, the fourth, up to fixes from the ninth review.

Then the honest part of the record: the cloud container had no Swift toolchain and no copy of the sibling kit, so none of those 32 commits had been compiled. Nine rounds of reading had converged on code no compiler had seen. The first real gate ran on the MacBook and office the same morning. The product code compiled as written, with zero warnings — the kit API guesses held — and the repairs were all in tests: seven expectations had put a try inside the right side of &&. That inner try is legal, but && rethrows whatever its right side throws, so the whole expression became a throwing one, and nobody had marked the whole expression with try; Swift refuses that at compile time ("operator can throw but expression is not marked with 'try'"). Each check was split into two expectations, the state first and the read value second, one test created a directory where the previous check had already left a lock file, and one fixture built its store before the sync cache existed. After that: 876 Mac tests green, and the mobile gate's 74 Swift Testing tests plus 5 UI tests with no skips. Reviews found real defects; only the gate could say the result ran.

What the Road Changes

The fixes themselves are a list of things a home network never forces you to think about:

  • Office away is not an error. A 502, 503 or 504 from the route (Tailscale Serve answering for an engine that is down, or a proxy giving up) now reads as "office is away", the same as a lost connection, instead of a failed round. A lost reply to the lock request no longer orphans a lease, and a broken pipe no longer kills the process.
  • Busy is not broken. The engine reports busy only when another holder has the lock or a reservation under 30 minutes old will settle by itself; the client throws PeerSyncEngineBusy for exactly those. An older, unwatched reservation is a refusal that names its replica and the remedy.
  • Leases on the monotonic clock. Leases and client activity moved to an uptime clock, so a wall-clock step — the clock corrected by network time after a sleep, or set by hand — neither extends nor cuts a lease. (A time-zone change alone doesn't move this clock; it only changes how the same instant is displayed.) Watches, which must outlive a restart, stay on wall time; the two clocks are never compared.
  • Bounded ledgers. Completed journals and closed reservations older than 14 days are pruned under the ledger lock; pending and stopped journals, unreadable records and every backup stay.
  • Bigger objects. The wire now carries objects up to 128 MiB (it was 64), because the Mac allows attachments around 100 MB; anything larger is left out of the plan and named in the round's warnings.
  • A write replaces only the version it checked. An upload whose expected hash no longer matches is refused and the device re-plans.

The Lock Screen Was the Last Bug

One fix came from a single hardware press. Build 5 had chosen iOS Data Protection class Complete on the premise that a round started by leaving the app runs unlocked. But the side button backgrounds the app and locks the phone in one press, and Complete keys go away about ten seconds later, so a leave-the-app round died mid-flight into an interrupted sync. The class moved to Complete Until First User Authentication: files stay sealed before the first unlock after a restart (launch maintenance, reloads and capture filing wait for it), and stay readable through a lock after that.

From Seville

The road test had a field report. On 2026-10-12, from Seville, the MacBook's sync sheet still opened on the pre-v4 locator — office's home mounted as a share — which named nothing on the road. The default now follows the machine (track 6's two-doors lesson has the rule): off office, the peer is office's sync engine on its HTTPS twin. And the measurement that shaped the transport: a first sync of 53 pulls, 11 pushes and 2 conflicts is 133 requests; from Seville a fresh TLS handshake cost 0.86 s each, about two minutes in total, while a reused connection cost 0.29 s. The Mac app's engine rounds now ride one kept-alive connection pool per process.

Code

Office away vs office busy — two different answers (shape of the client's step handling)·swift
// After a step's reply arrives (sketch of PeerSyncHTTPRoot):
if [502, 503, 504].contains(reply.statusCode) {
  // Tailscale Serve answering for a stopped engine, or a proxy giving up:
  // office is AWAY. The round ends quietly and resumes later.
  throw OfficeUnreachable(detail: "HTTP \(reply.statusCode)")
}
if failure?.busy == true || (step == "lock" && reply.statusCode == 409) {
  // Another holder has the lock, or a young reservation will settle:
  // office is BUSY, not broken. Say what it is busy with.
  throw PeerSyncEngineBusy(detail: message)
}
// Anything else is a real refusal and stops the round visibly.
The road checklist the 2026-09-24 hardening worked through·text
network     502/503/504 = office away; a lost lock reply keeps no lease; SIGPIPE ignored
busy        only a live holder or a reservation < 30 min; older ones name their replica
time        leases on a monotonic clock; watches on wall time; never compared
ledgers     completed/closed records pruned after 14 days under the lock
size        objects up to 128 MiB; larger ones named in the round's warnings
writes      replace only the version the check saw (hash precondition, 409)
device      Data Protection: Complete -> Complete Until First User Authentication
transport   one kept-alive pool per process (0.86 s vs 0.29 s per request abroad)

gate        9 review rounds read it; 1 compile + 876 Mac tests + the mobile gate ran it

External links

Exercise

Pick a sync feature you rely on (a notes app, a photo library, a game save). Write down how it should behave in four road situations: the server is unreachable, the server is busy with another device, the phone locks itself mid-transfer, and the phone lands in a time zone nine hours away. For each, say whether the user should see anything, and what the client must remember for the next attempt.
Hint
The useful split is between conditions that end a round quietly and resume later (unreachable, busy, a locked device) and conditions that must stop visibly (a real refusal, a conflict). Time-zone travel should be invisible — if it is not, something is doing elapsed-time math on local times, or a timer measures elapsed time on a wall clock that just got corrected.

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.