Addresses 5 validated review findings on the Phase 5 trigger wiring:
- #3 Reconnect honored a stale cached capability: doReconnect now awaits
handleCapabilityChange (re-reading the freshly-committed sync_drafts
preference) before requestReconcile, so a persisted sync_drafts=false is
respected on a fresh login.
- #4 Disable did not fence in-flight work: handleCapabilityChange's disable
branch now bumps the lifecycle epoch (like invalidate) and resets the
reconcile single-flight, so a GET/POST/DELETE crossing the disabled
interval cannot apply results or set a baseline after re-enable.
- #5 Team reconcile ran before the switch committed: moved requestReconcile
in handleTeamChange to after the new team is persisted (both the tablet
fast-path and the normal path), so a concurrent DB-reading trigger can't
coalesce the baseline back onto the previous team.
- #6a Reliable reconnect never reconciled: WebsocketManager.onReliableReconnect
now calls DraftSyncManager.onReliableReconnect, which GET-reconciles the
current team so silent (event-less) server-side deletions are caught.
- #7 Overflow could permanently stall event-triggered reconcile: enqueue now
fires the reconcile trigger even on buffer overflow (dropping only the
push), so a full buffer left by a transient current-team lookup failure
cannot wedge reconciliation after the database recovers.
Pushed back (not changed): #1 (fence absent keys — would resurrect deleted
drafts; belongs to Phase 6 deleted_fingerprint) and #2 (drain during pending
GET is local-wins-safe, absence is 2-observation guarded). Deferred: #6b
suspend/resume, #8a Global-Drafts stale-on-open, #8b reconciliation deadline.
tsc + eslint clean; new manager tests for #4/#6a/#7 and a reconnect
capability/ordering assertion for #3. All affected suites green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>