Skip to content

Count the records that never decrypted, instead of losing them between two lines - #152

Merged
parawanderer merged 1 commit into
mainfrom
fix/count-what-could-not-be-decrypted
Aug 23, 2026
Merged

Count the records that never decrypted, instead of losing them between two lines#152
parawanderer merged 1 commit into
mainfrom
fix/count-what-could-not-be-decrypted

Conversation

@parawanderer

Copy link
Copy Markdown
Owner

Pin moves 4f940158ddc7f234, carrying the three fixes from @jamorenom's review of #144, plus the merge of @ubrt's current_keys() work.

The exporter was undercounting its own inventory

fetch() builds its "not exportable" list by walking group_records(decrypted) — so anything decrypt_records dropped was absent from that list and the candidates at once. A shorter table, nothing to explain it.

The comment two lines below has been saying why that's 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 right, and the layer above it was dropping quietly. It's the residue of #89's fix — that bug was one unreadable item ends 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 always computed the tally and logged it. It now returns it, so Fetched carries undecryptable and first_miss.

Counted separately, and not called a failure

skipped is per accessory and names one: something was read, understood, set aside for a stated reason. These were never read — there is no beacon id to name, because the id is inside what would not open. Folding them in would invent rows.

And a non-zero count is usually nothing: a zone legitimately holds records belonging to other parties. 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 you 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.

Also in the pin

Unused by the exporter, but worth knowing:

  • a 401 on a session with no password now raises UnauthorizedError rather than a bare ValueError("No username or password specified") three frames down
  • a secondary-key match no longer ratchets alignment forward past the accessory — measured at up to +191 indices (47.8h), permanent, because alignment never moves back

The app does not reach that second one. It never calls FindMy.py's accessory fetch; it drives _fetch_key_reports with its own index_by_key, built from keys_between, which dedupes ascending and therefore already holds the lowest index — the conservative value the upstream fix now produces.

Testing

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 respectively.

560 exporter tests and 227 bridge tests pass. All four pins moved together, per rule 14.


PR description summarised by Claude Code.

…n two lines

Pin moves 4f940158 -> ddc7f234, which carries three fixes from jamorenom's review
of #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 #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>
@parawanderer
parawanderer merged commit 8af3808 into main Aug 23, 2026
8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant