Take the FindMy.py fix for the peer id the shares are fetched for - #144
Conversation
Pin moves 337381de -> 4f940158, which carries jamorenom's fix for #140 and the three things the review of it turned up. `RecoveredPeer.peer_id` returned the escrow record's label suffix. Cuttlefish addresses peers by the SHA256-prefixed hash on the sealed bottle, and on some accounts those are different strings - so `FetchRecoverableTlkShares` asked for a peer that does not exist, and Apple answered with every view's key set and no shares at all. The export died at `zone_keys()` reporting that no keychain keys are held, which reads as an account or permissions problem and is not one. Nothing in this repo changes but the revision string. `exporter/` never touches `peer_id` - it asks for `recovery_options` and `zone_keys` and takes what it is given - which is why the whole failure arrived here as an empty result. What came in beyond the reported fix: - the peer id now comes from the trust circle, with the bottle's ids as the fallback for a peer the circle no longer lists - `join_trust_circle` refuses a sponsor the circle does not contain. That was the third use of this property and it is the one that writes: a voucher naming an unknown sponsor is signed, sent and permanent - an end-to-end harness over a synthetic account in **both** shapes - label suffix agreeing with the circle id, and diverging. Reintroducing #140 fails the divergent variant and passes the agreeing one, which is what happened in the world That last part is what makes this safe to take. This account is an agreeing one, so it took the branch the fallback still takes, and nothing offline here could have caught the bug in the first place. 508 exporter tests pass against the new pin. **The fix itself is unverified from here**: it needs an account whose escrow label diverges, and neither this side nor the FindMy.py side has one. jamorenom reports 0/22 shares before and 21/21 after on two unrelated accounts, and has offered to re-run; that is the only evidence it works, and it is theirs. Co-Authored-By: jamorenom <jamorenom@gmail.com> Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The bump missed `app/build.gradle.kts`, so the APK would have shipped FindMy.py 337381de while the exporter and the bridge tests ran 4f940158. The two guard tests in `test_main.py` caught it, which is the whole reason they exist. **It presents as the APK build failing.** The bridge tests run in an earlier step of the same job, so a missed pin skips the APK steps and the actual assertion is several steps up the log, saying nothing about Gradle. Rule 14 lists all four locations, which of them is generated, the two tests that enforce it, and how to run them locally in under a second. There is no single manifest to consult - `opentagviewer_export` runs under both interpreters, so every dependency is declared once per consumer - and nothing about the layout says so. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
Tested Same account, same escrow record, on the pin this PR replaces: 0 of 22, every view reporting The two ids are genuinely different strings on this account — the label suffix is 26 characters The part of your change I could not have got to
sponsor = directory.get(sealed.peer_id) or directory.get(inner.peer_id)
cuttlefish_peer_id = sealed.peer_id or inner.peer_id— so on an account where the circle lists the inner id and not the envelope's, it would have The Two smaller notes from using itNeither is a blocker; both are about the failure being legible when it happens rather than about
Thanks for turning a one-line report into something with a test harness behind it — and for |
A 401 leaves FindMy.py as two different exception types, and only one of them was named. Apple refusing the password raises InvalidCredentialsError; a session restored *without* one raises a bare ValueError from the same function several frames earlier, before anything is sent - and the app restores sessions constantly, so that is not the rare half. Untreated it arrived as UNKNOWN and earned a retry that cannot work. Matching a ValueError on its message is unpleasant and is done anyway: account.py raises it in exactly one place, and the alternative is the app's most ordinary auth failure being offered a button that does nothing. The check is narrow, and a test pins that - widening it to any ValueError turns "that is not a valid serial" into a forced sign-out. **UnauthorizedError is deliberately left unclassified.** request_pet raises it when a second factor is being demanded, which the app already answers by asking for a code rather than signing out, while CloudKit raises the same type for a genuine 401. Same class, opposite remedies, and treating a 2FA prompt as a dead session would cost somebody a sign-in they did not need - the mirror image of the bug this branch exists to fix. It stays as it is until the two can be told apart. From tmp/todo-android.md item 3, via jamorenom on #144. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
@jamorenom, both notes are fixed upstream, in 1. The exporter's 2. A 401 on a session with no password now raises And item 3 turned out to be yours as well. The alignment question from #139 was settled while chasing this, and the mechanism was worse than either side had described: Thanks for running it, and for the two notes. The peer-id fix was verifiable because you had the account; these two were findable because you looked at what the library was doing rather than only at whether your export worked. |
…n two lines Pin moves 4f940158 -> ddc7f234, which carries three fixes from jamorenom's review of parawanderer#144 plus the merge of ubrt's `current_keys()` work. **`fetch` built its "not exportable" list by walking what came back.** Anything `decrypt_records` dropped was therefore absent from that list *and* from the candidates at once, and the user got a shorter table with nothing to explain it. The comment two lines below has been saying why that is bad the whole time: Named rather than dropped quietly: "fewer tags than expected" and "some of those were never tags" look identical from outside. Which is exactly right, and the layer above it was dropping quietly. This is the residue of parawanderer#89's fix - that bug was one unreadable item ending the whole export, fixed by skipping - and it was reported by the same person, who had to count raw records by hand to notice. FindMy.py had always computed the tally and logged it; it now returns it, so `Fetched` carries `undecryptable` and `first_miss`. **Counted separately from `skipped`, and not phrased as a failure.** Those are different things: `skipped` is per accessory and names one, because something was read and set aside for a stated reason. These were never read - there is no beacon id to name, the id being inside what would not open - so folding them in would invent rows. And a zone legitimately holds records belonging to other parties, so a non-zero count is usually nothing. Both surfaces say so, and say the one thing that would make it worth a second look: it being close to the number of tags somebody expected. `first_miss` rides along because the count cannot tell "somebody else's records" from "we are comparing keys in the wrong encoding", and those lead in opposite directions. Five tests, verified by breaking three things: dropping the count, counting only the first reason (the old undercount), and folding them into the not-exportable rows - three, one and one red. Driven with `asyncio.run` like the rest of the suite; there is no pytest-asyncio here. Also in the pin, unused by the exporter but now available: a 401 on a session with no password raises `UnauthorizedError` rather than a bare `ValueError` three frames down, and a secondary-key match no longer ratchets alignment forward past the accessory. The app does not reach that second one - it drives `_fetch_key_reports` with its own `index_by_key`, built from `keys_between`, which dedupes ascending and so already holds the lowest index. 560 exporter tests and 227 bridge tests pass. All four pins moved together, per rule 14. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Opening this so @jamorenom has a branch to test. Not for merge until it has been run against an affected account.
Pin moves
337381de→4f940158, carrying the fix for #140 plus the three things reviewing it turned up.The bug
RecoveredPeer.peer_idreturned the escrow record's label suffix. Cuttlefish addresses peers by theSHA256:-prefixed hash on the sealed bottle, and on some accounts those are different strings — soFetchRecoverableTlkSharesasked for a peer that does not exist, and Apple answered with every view's key set and no shares at all rather than with an error. The export died atzone_keys()reporting that no keychain keys are held, which reads as an account or permissions problem and is not one.Nothing in this repo changes but the revision string.
exporter/never touchespeer_id— it asks forrecovery_optionsandzone_keysand takes what it is given, which is why the whole failure arrived here as an empty result.What came in beyond the reported fix
join_trust_circlerefuses a sponsor the circle does not contain. That was the third use of this property and it is the one that writes: a voucher naming an unknown sponsor is signed, sent and permanent.That last part is what makes this safe to take. This project's own account is an agreeing one, so it takes the branch the fallback still takes — which is why nothing offline caught the bug for so long, and why a fix that always preferred the bottle id would have looked fine here and broken it.
Testing
508 exporter tests pass against the new pin.
The fix itself is unverified from here. It needs an account whose escrow label diverges from its circle id, and neither this side nor the FindMy.py side has one. @jamorenom reports 0/22 shares before and 21/21 after on two unrelated accounts and offered to re-run — that is the only evidence it works, and it is theirs.
To test this branch:
The log line to look for is
Addressing the recovered peer as SHA256:…, followed by a non-zero share count.PR description summarised by Claude Code.