Document failed refresh persistence edge case

This commit is contained in:
Eric Bailey
2026-08-31 17:30:20 -05:00
parent c951542dec
commit 4be7352143
2 changed files with 27 additions and 0 deletions
+2
View File
@@ -54,6 +54,8 @@ If a replacement bundle has already been built before bailing, dispose it. This
**Severity: Medium-Low**
**Status: Accepted and documented.** The missing-lineage-link failure mode and its recovery limits are now explicit in the main plan under “Edge case: a failed write leaves a missing lineage link.” Conditional persistence continues to prefer the authoritative stored generation.
On `main`, a failed session write left storage stale, but the next dispatch rewrote the complete session unconditionally. Storage therefore healed on the next refresh. On this branch, the `jti`-chained merge (`session-merge.ts:171-200`) cannot distinguish another tab advancing the chain from this tab losing its own previous chain-link write.
Concrete sequence:
+25
View File
@@ -238,6 +238,31 @@ Tab A action: broadcast update notification
Broadcasting after a failed write would tell Tab B to reread localStorage while it still contains `(7, A)`, spreading the stale generation instead of the new one.
### Edge case: a failed write leaves a missing lineage link
If the process survives a failed write, its live `PasswordSession` may advance while authoritative storage remains behind:
```text
t0 memory/storage: generation A
t1 server refresh: A -> B
t1 reducer/session: generation B
t1 storage write: fails; localStorage remains A
t2 server refresh: B -> C
t2 conditional write:
base generation: B
localStorage: A
action: reject C because the persisted base is not B
```
The committed reconciliation then rebuilds the live session from persisted generation `A`, discarding the valid in-memory generation `C`. If `A` is still inside the PDS reuse window, subsequent refreshes can walk back through the server lineage and recover, potentially requiring extra round trips. If `A`'s fixed grace period has expired, the account may be logged out.
This is a deliberate safety tradeoff. The conditional merge cannot prove that the missing `A -> B` write belonged to this process; the in-memory `B` could instead be stale credentials from another tab or an older login lineage. Accepting `B -> C` without a persisted `B` would weaken the central invariant and could overwrite a different authoritative session. The implementation therefore prefers the persisted generation even though an isolated failed write can turn a healthy in-memory session into a later logout.
A retry of the original `A -> B` write before another refresh closes the gap. Native retries the same queued root snapshot once. Web reports the storage failure without updating its persisted cache or broadcasting; automatic refresh paths log the failure, while explicit refresh operations report it to their caller.
* * *
After successfully writing localStorage, notify other tabs:
```ts