web: Groups, pools and private galleries call grain-serve's methods
MergedThis moves the web's 22 social.grain.unspecced.* calls to the NSIDs grain-serve serves, through $lib/xrpc's query and call. It also adds every_method_the_web_calls_is_served, which scans web/src for the NSIDs passed to query, call and upload and fails on a 501 or 405 from the router.
I found nothing that must change. The renames match the table in the description. The casts are narrow. The scan skips calls whose first argument isn't a string, such as spaceSupportQuery(). Its floor of 41 guards against a scan that stops matching.
Two things to know:
- When I read it, the
check,harness,rustandtestworkflows were still queued or running, so I haven't seen the types or the new test pass. Don't merge until they do. - A few calls still use
as never(createGallery,createPrivateGallery,createComment). That hides any drift from the generated input types. Thedescriptiongap oncreateGroupis already listed under shortcomings, so I won't ask for it again.
The new commit cafedc5 does what I noted last round. The pool and private gallery writes now cast only the blobs, the DID and the facets to the generated input types, where the whole input used to be cast to never. I found nothing else that must change.
The check, harness, rust and test workflows were all still queued when I read this. I haven't seen the types or every_method_the_web_calls_is_served pass, so don't merge until they do.
The new commit 03a7f0e changes only the test. It now serves grain-serve with an in-memory index and a DID, as the web's server has. A bare server answered 501 for methods that need either, and that would have failed the test for routes that are served. I checked that App::bare, the App.index and App.did fields, Index::memory and common::served_app all exist on this branch. I found nothing that must change.
The check, harness, rust and test workflows were still queued when I read this, so I haven't seen the types or every_method_the_web_calls_is_served pass. Don't merge until they do.
The new commit 427fee0 moves getSpaceSupport to social.grain.server.getSpaceSupport. That is the name grain-serve serves in spaces/support.rs, and its lexicon is lexicons/social/grain/server/getSpaceSupport.json. The web's queries.ts now calls that name, and no call to the old privateGallery.getSpaceSupport name is left. I found nothing that must change.
- Stale description: the first row of the table still says
social.grain.privateGallery.getSpaceSupport. Update it so it matches the code. - Checks: the
check,harness,rust,testandlexiconsworkflows were all still queued when I read this. I haven't seen the types orevery_method_the_web_calls_is_servedpass, so don't merge until they do.
The new commit 97abdb8 moves the eight pool methods from social.grain.group.pool.* to social.grain.group.*. I found nothing that must change.
- Renames:
social.grain.group.poolno longer appears anywhere in the repo.social.grain.group.listFeedis now the same name inspaces/pool.rs,tests/spaces.rs, the lexicon,hatk.generated.ts, the lexicons README and the web'squeries.ts. I only spot-checkedlistFeed, not the other seven. - Description: the first row of the table now matches the code, and the description covers the pool move.
- Checks: the
check,harness,rust,testandlexiconsworkflows were all still queued when I read this. I haven't seen the types orevery_method_the_web_calls_is_servedpass, so don't merge until they do.
- web: Groups, pools and private galleries call grain-serve's methods
- main

What changes
The web's groups, pools and private galleries reach grain-serve. Every call the web made by a
social.grain.unspecced.*name is now the name grain-serve serves, through$lib/xrpc'squeryandcall, typed by the lexiconsnpm run lexgenerates, as the rest of the web's calls already are. Nothing inweb/srcimports$hatk/clientany more.social.grain.unspecced.*)getSpaceSupportsocial.grain.server.getSpaceSupportlistPrivateGalleriessocial.grain.privateGallery.listPrivateGalleriesgetSpaceMemberssocial.grain.privateGallery.listMemberslistSharedGalleriessocial.grain.privateGallery.listSharedPrivateGalleriesgetPrivateGallerysocial.grain.privateGallery.getPrivateGallerycreatePrivateGallerysocial.grain.privateGallery.createPrivateGalleryaddSpaceMembersocial.grain.privateGallery.addMembergetGroupsocial.grain.group.getGroupgetGroupHostsocial.grain.group.getGroupHostlistGroupssocial.grain.group.listGroupsgetGroupSubmissionssocial.grain.group.getSubmissionscreateGroupsocial.grain.group.createGroupjoinGroupsocial.grain.group.joinGroupleaveGroupsocial.grain.group.leaveGrouplistPoolGalleriessocial.grain.group.listGallerieslistPoolFeedsocial.grain.group.listFeedgetPoolGallerysocial.grain.group.getGallerycreatePoolGallerysocial.grain.group.createGallerydeletePoolGallerysocial.grain.group.deleteGallerycreatePoolCommentsocial.grain.group.createCommentdeletePoolCommentsocial.grain.group.deleteCommentputPoolFavoritesocial.grain.group.putFavoriteThe shapes are the same. Each old and new lexicon was compared, less descriptions: the parameters, input and output agree, the defs the old ones held for themselves (
photoInput,privateGallery,privateItem,memberView,privateGalleryRef,sharedGalleryRef,poolGallery,poolFeedItem,poolComment,poolAuthor) moved unchanged tosocial.grain.privateGallery.defsandsocial.grain.group.defs, andcreateGroupgained itserrors. The handlers' parameters (space,group,did,rkey,actor,gallery) and verbs are those the web already used. What the web adapts is only the types: DIDs and AT URIs it holds as strings are cast toDidStringandAtUriStringwhere the generated input wants them, uploaded blobs toBlobRefand a comment's facets to the input's facet type,listGroupswith no gallery asks with{}, and a pool's comments are cast to theCommentViewthe sheet renders, as before.getSpaceSupportmoves tosocial.grain.server. Whether the caller's PDS serves spaces is a question about the server, which groups ask as private galleries do, the Groups tab first; it sits besidedeleteAccountandgetSitemap, ascom.atproto.server.describeServersays what a server supports. The lexicon moves fromprivateGallery/toserver/, with grain-serve's route, its tests, the harness's pending list, the README's table,hatk.generated.ts(regenerated) and the web's query. No app calls it, so nothing released has the old name.The pool's methods sit one level under
social.grain.group.social.grain.group.pool.listGalleriesissocial.grain.group.listGalleries, and so on for all eight, so no grain lexicon nests deeper thansocial.grain.<name>.<method>. The pool's defs (poolFeedItem,poolGallery,poolComment,poolAuthor) joinsocial.grain.group.defs; no name clashes. No app calls them.A test that the web calls only what grain-serve serves.
every_method_the_web_calls_is_served, incrates/grain-serve/tests/health.rs, readsweb/src(less its generated lexicons) for each NSID passed as the first argument ofquery,callorupload(or a name one is imported as, such asxrpcQuery), and asks grain-serve for each, served with an in-memory index and a DID as the web's is (a bare server answers 501 for whatever needs either), a query by GET and a procedure by POST, throughcommon::Servedso theanswerscheck sees the answers. It fails on 501 (not served) or 405 (served as the other kind), naming the file. It finds 61 methods today, and fails if it finds fewer than 41, so a scan that stops finding calls does not pass.Why
On next.grain.social the Groups tab never showed:
Sidebar.svelteshows it whenspaceSupportQuery()sayssupported, and that askedsocial.grain.unspecced.getSpaceSupport, which grain-serve answers 501. Every call in this table failed the same way. A test against the router, rather than a grep of its route table, holds however a route is written (aconstinspaces/, a literal inlib.rs).How it was checked
social.grain.unspecced.getSpaceSupport501.web/srcin a Python copy of it: the 61 methods it finds are every one grep finds called, and no other method inweb/srcis unrouted beyond these 22.npm run checkhere, once: no errors, all files formatted.checkfor types and format,harnessfor the new test and the answers it records.Shortcomings
createGroup's handler reads an optionaldescription, and the web sends one, butsocial.grain.group.createGroupdoes not declare it. Left alone here, as the server's shape is the lexicon's to say; it wants an optionaldescriptionadded to that lexicon.lexicons/social/grain/unspecced/*for these 22 are no longer used by the web, or by grain-serve, or by grain-ios as far as its local checkout shows. They are still served by the hatk server inserver/xrpc/(andtest/groups.test.tsasksgetGroupandlistGroups), which production runs until cutover, so they stay until then, with the rest ofunspecced/.XrpcErrorit now gets carries which; its comment says so.🤖 Generated with Claude Code