fix(rules): allow completedByUsers on session update so finished games close (B-001 P1)

The sessions allow-update rule required affectedKeys().hasOnly(['status','completedAt']),
but the async-game completion path (markUserComplete) always writes completedByUsers, so
every 'I reached results' write was denied and the session stayed active forever -> the
couple was locked out of starting any new game (only the destructive 'End their game'
worked, since abandonSession only diffs status/completedAt). Rule now permits
['status','completedAt','completedByUsers'], lets any couple member record completion
progress, keeps startedByUserId immutable and status monotonic (active->completed).
Deployed + verified live: both finish a game -> session auto-completes (completedByUsers
=[both]) -> next game starts immediately (no 'Waiting for partner' block).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
null 2026-06-25 09:37:37 -05:00
parent ebd3b2ed1f
commit a82c43ad90
1 changed files with 12 additions and 9 deletions

View File

@ -309,17 +309,20 @@ service cloud.firestore {
allow create: if isCouplesMember(coupleId)
&& request.resource.data.startedByUserId == request.auth.uid;
// Update: only the user who started the session can update it, OR valid status transitions.
// startedByUserId is immutable for direct client writes.
// Update: any couple member may record session progress/completion.
// (Async two-device games mark each player done via `completedByUsers`; the
// session flips active→completed once both are in. The previous rule only
// allowed `status`/`completedAt`, so every `completedByUsers` write was denied
// and finished games never closed — locking the couple out of new games. B-001.)
allow update: if isCouplesMember(coupleId)
// Either the original starter can update
&& (resource.data.startedByUserId == request.auth.uid
// Or status transition is valid: active → completed
|| (resource.data.status == 'active' && request.resource.data.status == 'completed'))
// startedByUserId cannot be changed by clients
// startedByUserId is immutable for direct client writes.
&& request.resource.data.startedByUserId == resource.data.startedByUserId
// Only a fixed set of fields may change
&& request.resource.data.diff(resource.data).affectedKeys().hasOnly(['status', 'completedAt']);
// Only session progress/completion fields may change.
&& request.resource.data.diff(resource.data).affectedKeys()
.hasOnly(['status', 'completedAt', 'completedByUsers'])
// status is monotonic: stay the same, or transition active → completed (never revert).
&& (request.resource.data.status == resource.data.status
|| (resource.data.status == 'active' && request.resource.data.status == 'completed'));
// Delete: server-only (admin SDK). Admin SDK bypasses rules.
allow delete: if false;