~ knotless Search/
↑↓ move↵ open⇧↵ all results

grain.social/grain

photography-focused photo sharing platform

grain.social
1 collaborator
0
2

An account Jetstream says was deleted goes only when an answer says so

Closed
Closed chadtmiller.com asked to merge into feature/rust from grain.social/grain:gaps/gone

What changes

Three answers, not two. When Jetstream says an account was deleted, accounts::ask now asks where its identity points and answers one of three things (crates/grain-serve/src/accounts.rs):

  • Here: the host's com.atproto.sync.getRepoStatus says active: true. The account moved there, so it stays.
  • Gone: the host says active: false, or refuses with RepoNotFound, or PLC (or the did:web host) answers 410 (a tombstone) or 404 (nothing there). Only these purge.
  • Unsure: anything else. The DID document can't be read (transport error, a 5xx), it names no PDS, the PDS can't be reached or answers with an error other than RepoNotFound, or it answers with no active.

Asked again, then kept. accounts::settle asks until an answer settles it, doubling the wait each time. Patience::default() is six asks, starting at 30 s and capped at 10 min a wait, about half an hour in all. If it is still Unsure after that, it logs an error and keeps everything. deletions gives each account its own task (a JoinSet), so one account waiting holds none of the others up, and it ends once the stream's channel closes and every task has settled. jetstream::follow passes the default patience.

Why

A passing PLC or network failure met while handling a Jetstream deletion of an account that had moved used to count as gone. That purged state nothing rebuilds: preferences, push tokens, mutes, story views and invites. Purging is the one answer that can't be undone, so it now waits for a host or directory to actually say so.

knotless does not do this. Its knotless-serve/src/accounts.rs deleted() takes an identity that fails to resolve, or a status call that errors, as deleted (active != Some(true)), which is the shape grain had. So this is grain's own, and the right change is to make it in knotless too. What knotless loses to a false purge is less (it keeps less per account), which is likely why it hasn't mattered there.

How it was checked

crates/grain-serve/tests/gone.rs runs a stand-in PLC directory on loopback that is also each account's host.

  • ask answers Here for an active repo.
  • It answers Gone for active: false, RepoNotFound, a 410 tombstone and a 404.
  • It answers Unsure for PLC answering 503, the host answering 502, a host nobody listens on, a status with no active, and a document that names no PDS.
  • settle keeps an active account, purges one whose PLC fails once and then says it is inactive, and gives up on one PLC never answers. Its preferences and host status are left as they were.
  • deletions purges a tombstone and keeps an account it couldn't ask, both sent through the channel at once.

tests/deletes.rs's guard test now checks ask against the reference PDS: an account there is Here, and a DID its PLC never heard of is Gone. Those tests ran here; the rest is the runner's.

Shortcomings

  • active: false counts as gone, as asked for, even when its status says deactivated at the new host. Deactivated accounts already sit out of reads until they're back, so this is the stricter of the two readings.
  • An account whose PLC stays down for the whole half hour is never purged by this event. A later deleted event, or an admin, has to do it.
  • The waits live in memory. A restart part way through drops the accounts still waiting, and they're kept.

🤖 Generated with Claude Code

Approved

This makes a Jetstream "account deleted" event purge only when an answer says the account is gone. ask returns Here, Gone or Unsure. settle re-asks with doubling waits and keeps the account if it is still Unsure after six asks. deletions runs one task per account. I read the new logic in accounts.rs and the identity::document it relies on, and it matches the description. The tests are reported as run on the runner. I did not read them or the checks, and did not read the diff for tests/gone.rs or tests/deletes.rs.

I found nothing that has to change. Two points to be aware of, both minor:

  • did:web 404: a did:web host that answers 404 counts as Gone and purges at once. A host that misconfigures its /.well-known/did.json for a moment would purge an account. That is what the description says it wants, and PLC 404 and 410 are unambiguous. If you want to be stricter, make a did:web 404 Unsure.
  • Repeat events: two deletion events for one DID spawn two independent settle tasks. They are idempotent, so this is harmless, but each waits out the full schedule.

Closed: the coordinator moved this fix onto batch/two (The web proxy, deletes and the cross-post test land together, checked once), at 48472f4, as its reviewer asked.