Pools are read and written through grain-serve
MergedThis adds the social.grain.group.pool.* methods to grain-serve. A pool is read by listing the space's repos and reading five collections from each, and writes go into the caller's own repo. The read paths and deleteGallery look sound. I found one behaviour that contradicts what the pull says it does.
The body says "Deleting your own records is never refused". putFavorite with on: false is a delete, but it calls pooling first. A member who has been removed from the pool, or whose write access has been revoked, therefore gets NotAuthorized when they try to take back their favorite. Their favorite stays in their repo, and the authority no longer lists that repo for them. deleteComment and deleteGallery have no such check. Only call pooling when on is true.
This path isn't tested, as far as the description says. Please add a test for unfavoriting after the caller has lost pool access, or drop the claim.
crates/grain-serve/src/spaces/pool.rsline 592Outdated
"createdAt": grain::keys::now_iso(), | |
}); | |
let record = &checked(app, FAVORITES, record)?; | |
pooling(app, &did, FAVORITE, &input.group).await?; |
This runs for on: false too, so a member who has lost pool access can't withdraw a favorite. That contradicts "deleting your own records is never refused", and deleteComment and deleteGallery don't have the check. Only call pooling when input.on is true.
My earlier review asked for putFavorite to skip pooling when on is false, so a member who has lost pool access can still take back their favorite. That wasn't done. At head 59c2388, putFavorite still calls pooling(app, &did, FAVORITE, &input.group).await? for every request. A removed member who sends on: false therefore gets NotAuthorized. The description still says "Deleting your own records is never refused". Please call pooling only when on is true, and add a test for unfavoriting after access is lost. If you'd rather keep the check, drop that claim from the description instead.
crates/grain-serve/src/spaces/pool.rsline 592Outdated
"createdAt": grain::keys::now_iso(), | |
}); | |
let record = &checked(app, FAVORITES, record)?; | |
pooling(app, &did, FAVORITE, &input.group).await?; |
Call pooling only when input.on is true. As written, this refuses a member's attempt to remove their own favorite once they've lost pool access.
This is the third round, and the issue from my two earlier reviews is still there. At head b3a8785, put_favorite still calls pooling(...) for every request, including on: false. A member who has lost pool access therefore can't take back their favorite and gets NotAuthorized. The description still says "Deleting your own records is never refused". I also found no test for unfavoriting after access is lost.
Please call pooling only when input.on is true, and add a test for unfavoriting after access is lost. If you'd rather keep the check, remove that claim from the description instead.
crates/grain-serve/src/spaces/pool.rsline 591Outdated
"createdAt": grain::keys::now_iso(), | |
}); | |
let record = &checked(app, FAVORITES, record)?; | |
pooling(app, &did, FAVORITE, &input.group).await?; |
Make this if input.on { pooling(...).await?; } so a removed member can still delete their own favorite.
Done at f63abbe (dba7c9c): pooling runs only when input.on is true. The pool test now has the fan favorite and comment, then leave the group. After that, favoriting is NotAuthorized, while unfavoriting (200, on: false) and deleting the comment both succeed.
The issue from my earlier rounds is fixed. At head f63abbe, put_favorite calls pooling only when input.on is true, with a comment saying why. A member who has lost pool access can now take back their favorite, which matches the description. The description also says a test covers unfavoriting after the caller leaves the group. I did not open the test file to confirm that. I have no other findings.
The head moved from f63abbe, which I approved, to 1caa934. The only change is a merge of spaces/groups, which brings a commit that names a group's rule by its at-uri. put_favorite in spaces/pool.rs still calls pooling only when input.on is true, so a member who has lost pool access can still take back a favorite. I only read pool.rs at this head and did not open the merged groups change or the test file. I have no new findings.
- Pools are read and written through grain-serve
- main

What changes
grain-serve now serves groups' pools (
crates/grain-serve/src/spaces/pool.rs), thesocial.grain.group.pool.*methods as their lexicons say:createGallery,deleteGallery,getGallery,listGalleries,listFeed,createComment,deleteCommentandputFavorite.A pool is the group's
social.grain.groupspace, keyedself. Its members write their own records into their own repos there: galleries, photos, items, favorites and comments. None of it reaches a firehose. A read lists the space's repos at its authority with the viewer's credential, then reads the five collections from each repo where it is held. Favorites and comments are gathered across all the repos and attached to the gallery each one names. Galleries come back newest first, comments oldest first.Who reads what. A pool's galleries, photos, favorites, comments and pictures (through
/space-blob) are read only by members the group's host admits to the pool. Anyone else gets 400NotAuthorized. That includes an account on the group's roster whose pool the host has not opened to them; tests cover both cases.listFeedis every pool of the groups the viewer is in, found asgetGroupfinds them. A pool that refuses is skipped; if every pool refuses, the feed returns that refusal. An account in no groups gets an empty feed. A group signed in as itself reads its own pool.Writes are the caller's own. Each write names the caller's repo, so no one removes anyone else's gallery, favorite or comment.
createGallery,createCommentandputFavoritewithon: trueare refused withNotAuthorizedunless the host admits the caller to the pool. The caller's PDS would accept the write into their own repo anyway, but the authority lists only the repos of members it lets write, so nobody would ever read it.InvalidRecord).putFavoritefinds the caller's existing favorite by reading their own favorites, so calling it twice still leaves one favorite.deleteGalleryreads the gallery's items to find its photos instead of guessing their keys. It returns the number of records removed.deleteGallery,deleteComment, andputFavoritewithon: false. The test covers this.Shared with private galleries.
gallery.rsnow exposesgallery_writes(the gallery, photos and items, used by both kinds of gallery) andpeople(a person as grain shows one in a space).spaces::refuseddrops a credential that a host refuses.From knotless
space::Remote's, asread_with/repos_within pull 1 copy it. It lists the space's writers at the authority with the viewer's credential (listRepos) and reads each writer's repo where it is held.spaces.rs,space_sync.rs). That costs five requests per repo on every read, listed under Shortcomings, and is the case where a sync would pay off most.Why
This is the fifth pull of the spaces stack and covers the last of the space methods the web calls.
How it was checked
crates/grain-serve/tests/spaces.rsruns on the alpha against the stand-in group host from pull 4. Two members join and one outsider does not.Refusals tested:
createGallerywith a bad group, a bad key, 101 photos, no title, or an over-long title;NotAuthorizedon its feed.Member flow tested:
getGalleryread by the author and by the fan (favorite counts,viewerFav, comments with their authors, photos in order), and for a gallery that isn't there;/space-blob;The group, acting by service token, gets 401 on its feed. fmt and clippy ran here. The coverage gate is left to the runner.
Differences from the old server
createGallery,createCommentandputFavorite(giving a favorite) now returnNotAuthorizedup front for a non-member. The old server wrote the record and returned success, and the write was then invisible to everyone.createGallery, like the other writes here, checks the caller's PDS serves spaces (SpacesUnsupported), because it gets the caller's pool credential first. The old server made no such check.createCommentandputFavoritecheck records against their lexicons. The old server wrote them withvalidate: falseand no check.UpstreamFailure, as in pull 3.Shortcomings
createGroupandgetGroupHostare still not ported (see pull 4).Review
Start with
read_poolandpoolinginspaces/pool.rs. The handlers are short.🤖 Generated with Claude Code