A web login seeds a grain profile from the account's Bluesky profile
ClosedThis pull seeds a grain profile from the account's Bluesky profile in a background task after a web login. It does the write through the web session with a new write::create, factored out of createRecord the way put was out of putRecord.
I found nothing that must change.
- Refactor:
create_recordnow delegates tocreatewith the same body, so the XRPC behaviour is unchanged. - Seeding logic: it reads both profiles from the PDS, skips an account whose grain profile already has a name, and keeps
createdAtwhen there was a profile. It puts over an existing profile withswapRecordset to the CID it read and creates atselfotherwise. - Failure handling: failures are only logged, so the redirect never waits on them.
- Tests: the integration test covers the made, put-over-unnamed, named and no-Bluesky cases. The unit test covers field filtering and
createdAt.
I couldn't confirm the tests pass: the rust, harness, test and check runs were still queued when I looked. Please make sure they come back green before it merges, especially the coverage gate on the harness.
If an account logs in twice at nearly the same moment, both background tasks could read "no grain profile" and race to create it. The loser's create fails and is only logged, so this is harmless.
Since my earlier approval, the pull has one new commit. seed now returns its JoinHandle, and callback drops it, so the redirect still doesn't wait. The test now also covers a DID with no web session. seeded returns an AuthenticationRequired error for it, and the awaited seed handle finishes cleanly because the failure is only logged. I have nothing to add.
The rust, harness, test and check runs were still queued when I looked. Make sure they pass before this merges, especially the coverage gate on the harness.
Landed on feature/rust in batch/four (…/3mzbjho3qgd2b, 785dbe4), with its head fc53a26 unchanged.
What changes
A web login seeds a grain profile. Once the callback has kept the session,
login::profile::seedruns in the background (tokio::spawn), so the redirect never waits on it and a failure is only logged (seeding a profile from Bluesky). If the account'ssocial.grain.actor.profile/selfis missing or has nodisplayName, it readsapp.bsky.actor.profile/selfand writes a grain profile with itsdisplayName,descriptionandavatar(the blob ref as is), and acreatedAtkept from the grain profile there was, or now. An existing one is put over (putRecord, swapped on the CID read); otherwise one is created at rkeyself.The write goes through the web session with
write::putand a newwrite::create, factored out ofcreateRecordasputalready was ofputRecord. So it is checked against the lexicon, refreshed and retried as other web writes are, and indexed before it returns.sync::get_recordis shared for the two reads.Why
The old server's on-login hook did this, so a new grain account shows the name and picture it has on Bluesky. Ported as it was, with two differences:
createdAtfrom.Mirrored from knotless: knotless-serve's login callback sets off
spaces::discover(&app, &did)once the session is kept, atokio::spawnthat never holds up the redirect and whose failure is only logged.profile::seedhas the same shape and sits at the same point incallback. knotless itself makes no profile at login (its pages fall back on the Bluesky profile when reading), so the seeding is ported from the old server.The apps' sign-in (server: The apps sign in at their own PDS, as grain-serve's public client) has no hook for this. The apps sign in at their own PDS and call grain-serve with service tokens through
atproto-proxy. grain-serve never sees the login, and it holds no session to write to their repo with:viewer::vieweronly verifies a token per call. So no first call could do the write. The apps do need the same, since they used to sign in through the old server and got its hook. It has to be done in each app after sign-in: read both profiles from its PDS, write the grain profile there, then callsocial.grain.sync.indexRecords. Not built here.How it was checked
check,harness,rust,testandlexiconspass atfc53a26. Added:a_login_seeds_a_grain_profile_from_bluesky(tests/login.rs, reference PDS). An account with a Bluesky profile and avatar and no grain profile gets one, in the index and at its PDS. One with an unnamed grain profile the index never saw is put over and keeps itscreatedAt. One with a named grain profile is left alone. One with no Bluesky profile gets nothing. Every login still answers 303.seededfails withAuthenticationRequired, and the background task ends quietly.a_bluesky_profile_gives_what_it_has(unit): empty strings and banner/pronouns are left out, and createdAt is kept.Shortcomings
login::provisionedare not seeded, as they have no Bluesky profile.Review
Start at
crates/grain-serve/src/login/profile.rs, then the one line inlogin::callback.🤖 Generated with Claude Code