Record the L4 device test protocol in BULLETPROOFING.md
The rollback repro (watch-side airplane mode, phone-ahead/watch-behind divergence, queued transferUserInfo delivery), the ready-made L4 Merge Test split in the iCloud container, confirm/refute criteria, and the deferral caveats (monotonic clamp would break the deliberate swipe-back-to-Ready reset). Claude-Session: https://claude.ai/code/session_01PVNBVKp5bcq52X722uMjwT
This commit is contained in:
@@ -115,6 +115,26 @@ The schema-mismatch trap (see `DEVICE-COMMUNICATION.md` §6) is production-reach
|
||||
|
||||
Both the driver and a follower cover persist auto-advance progress via whole-log newest-`updatedAt`-wins merge (not a monotonic max on progress). A follower resuming late from suspend could in theory regress a log if its delayed write's timestamp outranks the driver's more-advanced state before the durable echo catches it up. Unconfirmed on device.
|
||||
|
||||
Two caveats that shaped the deferral: the merge *mechanics* are provable without a device (a `WorkoutMergePlannerTests` case can pin that a stale-content/newer-stamp log wins), but whether real UI timing ever produces such a write is the device question. And the obvious fix — making `currentStateIndex` monotonic — is wrong: swiping back to Ready is a *deliberate* reset to 0 that must propagate, so a real fix has to distinguish "reset" from "stale echo" (per-log reset marker, or comparing set-entry contents).
|
||||
|
||||
#### Device test protocol
|
||||
|
||||
The merge runs **phone-side** (`SyncEngine.ingestFromWatch`), so the repro is: *the watch sends a semantically-behind write stamped newer than the phone's more-advanced one.* Three ingredients: a connectivity gap, divergence on both sides during the gap (phone further ahead; watch one step past its own last-known state), and the watch's write stamped **later**.
|
||||
|
||||
**Test rig** (created 2026-07-09): the **L4 Merge Test** split — orange, `testtube.2` icon, one exercise (Bench Press 5×8, rep-based so nothing but rests advances on its own), rest 45 s, auto-advance off — written directly into the iCloud container as `Splits/01KX513THZZ6RSYQ0ZA558BT5W.json`. It is ordinary user data; swipe-delete it in-app when done.
|
||||
|
||||
1. Start the workout from the phone's split picker; open Bench Press on **both** devices (both on Set 1's work page).
|
||||
2. **Watch: Airplane Mode ON** (watch Control Center). Phone-side Airplane Mode does **not** work — modern iOS deliberately keeps Bluetooth (and often Wi-Fi) alive for the watch, and the Control Center BT tile explicitly preserves the watch link. Phone-side alternative if needed: Settings → Bluetooth → Off *and* Settings → Wi-Fi → Off (the real switches, not the CC tiles — WC falls back to Wi-Fi).
|
||||
3. **Sanity-check the gap is real:** swipe a page on the phone — the watch must *not* follow. If it follows, the link is still up; do not proceed.
|
||||
4. **Phone first — get ahead:** swipe through two full work→rest cycles (~2–3 sets recorded, stamped `t0`). Note the exact completed-set count.
|
||||
5. Wait ~10 s, then **watch — one step:** swipe forward one page (less progress, newer stamp `t1 > t0`; the update queues via `transferUserInfo`). *Variant B (the original hypothesis): don't touch the watch — let its 45 s rest countdown expire on its own; the auto-advance records with a fresh stamp the same way.*
|
||||
6. **Watch: Airplane Mode OFF.** Wait up to a minute — the queued update delivers, the phone merges, the echo pushes back.
|
||||
7. **Read the phone's exercise screen / log list:**
|
||||
- **Confirmed:** the phone's completed-set count **drops** to the watch's lower number (and the echo drags the watch to the same rolled-back state). Phone-recorded set entries for the "lost" sets vanishing from the rest screen is the same signal.
|
||||
- **Refuted (for this path):** the phone keeps its higher count and the watch corrects *upward* — an existing guard won the race.
|
||||
|
||||
Observability: Console.app → iPhone → subsystem `dev.rzen.indie.Workouts` shows `phone-bridge` receiving the update, but there is no merge-outcome logging yet — the verdict is the UI diff. Offered but not yet added: a regressive-write detector log in `ingestFromWatch` (warn when an incoming log wins on `modTime` with a lower `currentStateIndex`) — worth keeping permanently as a grep-able smoking gun — plus the merge-planner pinning test above.
|
||||
|
||||
---
|
||||
|
||||
## Recommended order
|
||||
|
||||
Reference in New Issue
Block a user