Closer/app
null cabfca5e28 fix(crypto): rotation must advance keyGeneration — a cached read silently stranded the partner (C-ROTATE-001)
Caught live on the fixture couple, second rotation. The first rotation worked;
the second looked identical from the rotating device — dialog confirmed, no
error, keyset swapped locally — and permanently locked the partner out of every
message written afterwards: 🔒 "Couldn't unlock on this device", with no way to
recover on his own, because the one signal that tells a partner to adopt a new
key never fired.

The chain: rotateCoupleKey read the couple through the cache-first
getCoupleById, got the stale keyGeneration=0 (the doc was already at 1), and
computed next = 1 — the value already on the document. Firestore doesn't count
an unchanged value as a change: affectedKeys() omitted keyGeneration, so the
monotonic rule never engaged, onCoupleKeyRotated never fired, and the partner's
device kept seeing "up to date". Meanwhile the write DID replace
wrappedCoupleKey with a wrap of the newer keyset and the rotating device DID
commit it locally — new content under a key only one phone has.

Same disease as C-AUTH-001, new organ: a cache-first read driving a one-shot
decision. Fixed at both layers:

- client: getCoupleByIdFromServer (Source.SERVER) backs the rotation decision;
  offline throws, nothing is written, the user is told nothing changed —
  rotation must fail loudly rather than half-happen.
- rules: a write that CHANGES wrappedCoupleKey must now carry a strictly
  increased keyGeneration. The stranding write is unrepresentable server-side,
  whatever any future client computes. (The old rule only checked monotonicity
  when keyGeneration was itself in affectedKeys — which is exactly the case
  that never arises when the bug happens.)

CoupleKeyRotationRepositoryTest pins it: generation comes from the server never
the cache, the published generation strictly advances, server write precedes the
local commit, offline fails without guessing. Mutation-checked by reinstating
the cached read — the two key tests fail, restored, suite green.

The fixture partner is un-stranded by rotating once more with this build: the
server read yields the true generation, the write is a real change, the trigger
fires, and adoption delivers the latest keyset (which retains every older key,
so the stranded-era messages decrypt too).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 19:33:12 -05:00
..
schemas
src fix(crypto): rotation must advance keyGeneration — a cached read silently stranded the partner (C-ROTATE-001) 2026-07-15 19:33:12 -05:00
build.gradle.kts feat(avatar): frame a picked photo, and fall back to initials not a heart 2026-07-14 23:19:37 -05:00
proguard-rules.pro feat: settings polish, privacy strings, home partner state, proguard rules 2026-06-21 09:49:02 -05:00