A removed device is told so, instead of being refused for ever (401 to 410) #141

Merged
paulhitt merged 1 commits from fix/retired-device-gets-410 into main 2026-09-07 14:19:33 +00:00
paulhitt commented 2026-09-07 13:52:55 +00:00 (Migrated from gitlab.com)

This is why an iPhone stops receiving anything and says nothing about it. Found from production logs while the user was reporting, for the third or fourth time, that messages do not arrive, calls do not connect, and an incoming call shows no screen. All one cause.

The evidence

refused GET /api/flamenet/v1/e2e/messages: unauthorized
  this device was removed from the account          × 8 in half an hour

Meanwhile the relay is fanning envelopes out to all three of that account's devices and every one sits delivered = f.

What was wrong

The design was right and written down in two places — a removed device must learn that rather than poll an empty mailbox for ever, so /e2e/messages answers 410, deliberately not 401, because "sign in again" is the wrong instruction for a device somebody has removed.

But the retirement check runs earlier, in authenticated(), and every AuthError collapsed into WireError.unauthorized. A revoked device threw deviceRetired, got a generic 401, and never reached the 410 the route would have given it. The route's own comment says "the retirement case threw during authentication" — it did, into the wrong status.

The client only acts on 410. AppEnvironment.deregistrationWatch on iOS signs out and explains what happened; it is wired to deviceDeregistered, which fires on 410 only. On a 401 the app keeps polling, keeps failing, and says nothing.

The only path that behaved correctly was the one nobody records

how the device went away key log entry status client learns?
row vanished, nothing recorded it no 410 yes
revoked from the Devices screen yes (revoke) 401 no
expired by the retention sweep yes (expire) 401 no

The accident worked; both real doors did not.

And the existing test proved the accident

revokedDeviceLearnsItIsGone deletes the device row directly, so it exercised the branch that worked and stayed green the whole time. The new test revokes through revokeDevice — the door the Devices screen uses — and asserts KeyLog.isRetired first, so it cannot silently drift back onto the row-deleted path.

Falsified: with the new catch removed, the old test still passes and the new one fails on both assertions.

Note for whoever deploys this

A phone already in this state recovers by signing in again — the fix lets the app find out and say so, it does not un-revoke anything. FN_MAX_DEVICES is 3 and the account under test is at 3, so a re-registration will evict its oldest device.

**This is why an iPhone stops receiving anything and says nothing about it.** Found from production logs while the user was reporting, for the third or fourth time, that messages do not arrive, calls do not connect, and an incoming call shows no screen. All one cause. ## The evidence ``` refused GET /api/flamenet/v1/e2e/messages: unauthorized this device was removed from the account × 8 in half an hour ``` Meanwhile the relay is fanning envelopes out to all three of that account's devices and every one sits `delivered = f`. ## What was wrong The design was right and written down in two places — a removed device must **learn** that rather than poll an empty mailbox for ever, so `/e2e/messages` answers **410**, deliberately not 401, because *"sign in again" is the wrong instruction for a device somebody has removed.* But the retirement check runs earlier, in `authenticated()`, and every `AuthError` collapsed into `WireError.unauthorized`. A revoked device threw `deviceRetired`, got a generic **401**, and never reached the 410 the route would have given it. The route's own comment says "the retirement case threw during authentication" — it did, into the wrong status. The client only acts on 410. `AppEnvironment.deregistrationWatch` on iOS signs out and explains what happened; it is wired to `deviceDeregistered`, which fires on 410 only. On a 401 the app keeps polling, keeps failing, and says nothing. ## The only path that behaved correctly was the one nobody records | how the device went away | key log entry | status | client learns? | |---|---|---|---| | row vanished, nothing recorded it | no | **410** | yes | | revoked from the Devices screen | yes (`revoke`) | **401** | no | | expired by the retention sweep | yes (`expire`) | **401** | no | The accident worked; both real doors did not. ## And the existing test proved the accident `revokedDeviceLearnsItIsGone` deletes the device row *directly*, so it exercised the branch that worked and stayed green the whole time. The new test revokes through `revokeDevice` — the door the Devices screen uses — and asserts `KeyLog.isRetired` first, so it cannot silently drift back onto the row-deleted path. **Falsified:** with the new catch removed, the old test still passes and the new one fails on both assertions. ## Note for whoever deploys this A phone already in this state recovers by signing in again — the fix lets the app find out and say so, it does not un-revoke anything. `FN_MAX_DEVICES` is 3 and the account under test is at 3, so a re-registration will evict its oldest device.
paulhitt commented 2026-09-07 14:16:37 +00:00 (Migrated from gitlab.com)

added 3 commits

  • 83f1ef5a...bd466827 - 2 commits from branch main
  • 1384f27e - A removed device is told so, instead of being refused for ever (401 → 410)

Compare with previous version

added 3 commits <ul><li>83f1ef5a...bd466827 - 2 commits from branch <code>main</code></li><li>1384f27e - A removed device is told so, instead of being refused for ever (401 → 410)</li></ul> [Compare with previous version](/paulhitt/flamenet-relay/-/merge_requests/78/diffs?diff_id=2009341247&start_sha=83f1ef5a50fe8a3450d371e111b64afe536575e4)
paulhitt (Migrated from gitlab.com) merged commit 82485da949 into main 2026-09-07 14:19:33 +00:00
paulhitt commented 2026-09-07 14:19:34 +00:00 (Migrated from gitlab.com)

mentioned in commit 82485da949

mentioned in commit 82485da94913c5d44025f5587a10d63bd6003a90
paulhitt commented 2026-09-09 18:58:46 +00:00 (Migrated from gitlab.com)

changed the description

changed the description
Sign in to join this conversation.