Record L4 device result and severing alternatives in BULLETPROOFING.md
Variant A refuted on device 2026-07-10 (phone kept its higher count; the win was timing, not a structural guard — the merge remains pure newest-modTime-wins). Step 2 now lists ranked link-severing methods, since phone-side Airplane Mode provably doesn't cut the watch link. Claude-Session: https://claude.ai/code/session_01PVNBVKp5bcq52X722uMjwT
This commit is contained in:
+10
-3
@@ -111,7 +111,9 @@ The schema-mismatch trap (see `DEVICE-COMMUNICATION.md` §6) is production-reach
|
||||
|
||||
> **Fix (2026-07-09):** `WatchConnectivityBridge.schemaMismatch` is set when a push fails to decode (cleared on the next good apply), and `ActiveWorkoutGateView` shows an "Update both apps to resume sync" banner while it's set — the freeze is now labeled instead of silent.
|
||||
|
||||
### L4 — Possible log-progress rollback from a late follower write · open · needs device testing
|
||||
### L4 — Possible log-progress rollback from a late follower write · open · Variant A refuted on device
|
||||
|
||||
> **Device result (2026-07-10, Variant A):** refuted — after the gap, the phone kept its higher completed-set count and the watch corrected *upward*. Read carefully, though: neither `WorkoutMergePlanner.merge` nor `ingestFromWatch` contains any progress guard — the merge is pure newest-`modTime`-wins per log — so what won was **timing**, not structure (most likely the phone's log ended up with the newer stamp by the time the queued watch write delivered). The mechanics remain permissive; the pinning test and the regressive-write detector log below are still worth adding, and **Variant B** (watch rest-expiry auto-advance instead of a manual swipe) is untested.
|
||||
|
||||
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.
|
||||
|
||||
@@ -124,11 +126,16 @@ The merge runs **phone-side** (`SyncEngine.ingestFromWatch`), so the repro is: *
|
||||
**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).
|
||||
2. **Sever the link.** Phone-side Airplane Mode does **not** work (confirmed in practice, 2026-07-10) — modern iOS deliberately keeps Bluetooth (and often Wi-Fi) alive for the watch, and the Control Center BT tile explicitly preserves the watch link. Use one of these, in order of preference:
|
||||
- **A — Watch: Airplane Mode ON** (watch Control Center). Kills the watch's own BT + Wi-Fi radios; the phone stays fully usable.
|
||||
- **B — Phone: Settings → Bluetooth → Off *and* Settings → Wi-Fi → Off.** Must be the real switches in the Settings app, not the CC tiles (the tiles only disconnect, and WC falls back to Wi-Fi if either radio survives).
|
||||
- **C — Range separation:** with Wi-Fi off on the phone (Settings switch), walk the phone well out of Bluetooth range (another floor / far end of the home). Slowest to take effect but needs no settings on the watch.
|
||||
|
||||
Whichever method you use, step 3's sanity check is the actual gate — don't trust the toggle, trust the check.
|
||||
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.
|
||||
6. **Restore the link** (undo whatever you used in step 2 — Airplane Mode off / radios back on / walk back into range). 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.
|
||||
|
||||
Reference in New Issue
Block a user