Pairing survives a project whose rules are older than the app - #41
Merged
Merged
Conversation
Claiming a pairing code wrote three documents in one transaction: the child profile, the code itself, and the kid device's linkedDevices doc (#39). That third write is the one a deployed firestore.rules only started permitting in the same commit that added it, so on any project whose rules hadn't been re-published since, Firestore refused the whole transaction and every claim failed with "the code was found, but linking this phone was refused" - a live code, an unpairable phone, and nothing a parent could do about it. Only the two writes that ARE the pairing stay in the transaction. The linkedDevices doc is written straight after, best effort: if it's refused the phone is paired and works, it just can't read the parent's self-tracked stats. The #39 binding is unchanged - the rule's getAfter still proves this device claimed that parent's code, which holds just as well outside the transaction. Two more ways a claim could be refused, both invisible from the kid's side: - A missing field is an evaluation error in the security rules, not null, so `resource.data.deviceUid == null` denied any child written without the key at all. Now get('deviceUid', null). - The iOS parent app wrote exactly that shape: assigning nil to a Swift dictionary subscript removes the key, so every child created there was born unpairable. It writes NSNull now, like the rest of that file already did. The claim error also no longer marks the code used while working out which step was refused - diagnosing a failed attempt shouldn't burn a good code. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes the "That code isn't active ... the code was found, but linking this phone was refused" failure that made pairing impossible on a Firebase project whose
firestore.ruleshadn't been re-published since #39.What was wrong
Claiming a code wrote three documents in one transaction: the child profile, the code doc, and the kid device's
linkedDevicesdoc. That third write only became legal in the rules in the same commit that added it — so if the deployed rules were older than the app, Firestore refused the entire transaction. The code doc itself stayed readable, which is why the error said the code was found.Reproduced in the Firestore emulator
Running the repo's real rules, with and without the
linkedDevicesblock:linkedDevicesChanges
PairingRepository— the transaction holds only the two writes that are the pairing. ThelinkedDevicesdoc is written straight after, best effort. The Residual security and reliability items from the #38 review #39 security binding is unchanged:getAfterstill proves this device claimed that parent's code.firestore.rules—resource.data.get('deviceUid', null), because reading a field that isn't there is an evaluation error, not null, and denied the claim outright.ChildProfile.swift— assigningnilto a Swift dictionary subscript deletes the key, so every child created in the iOS parent app was written withoutdeviceUidand could never be paired. WritesNSNull()now, matching the rest of that file.deviceUidfield, and a README note to re-publish the rules whenever that file changes.Verification
:shared:testDebugUnitTestand:shared:compileDebugAndroidTestKotlinpass locally; the emulator matrix above is the substantive check. Confirmed working on a real phone against the live project after re-publishing the rules.🤖 Generated with Claude Code