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

grain.social/grain

photography-focused photo sharing platform

grain.social
1 collaborator
0
2

social.grain lexicons for every method the Rust server will serve

Merged
Merged chadtmiller.com asked to merge into feature/rust from grain.social/grain:lexicons/social-grain

What changes

Renamed methods. Each of the 47 social.grain.unspecced.* methods gets a copy under a named namespace (actor, graph, gallery, comment, story, notification, privateGallery, group, group.pool, server). Parameters, input and output keep the same shape, so porting a caller is a rename. The one exception is getNotifications: its countOnly becomes social.grain.notification.getUnreadCount.

Defs a method declared for itself (getMutes#muteItem, getPrivateGallery#privateItem, ...) move to its namespace's defs.json. So does the one inline output object: searchActorsTypeahead's actor, which becomes social.grain.actor.defs#typeaheadItem. Existing views are reused rather than copied.

Names are verb and noun throughout, as Bluesky names its methods, for example:

  • group.createGroup, group.getGroup, group.listGroups, group.joinGroup
  • group.pool.getGallery, group.pool.listFeed
  • privateGallery.createPrivateGallery, privateGallery.listSharedPrivateGalleries, privateGallery.listMembers, privateGallery.getSpaceSupport
  • actor.searchActors, story.getAuthors

Replacements for dev.hatk.

  • social.grain.feed.getFeed takes the feed's at-uri, as app.bsky.feed.getFeed does, and keeps hatk's filters.
  • social.grain.feed.describeFeeds returns {did, feeds: [feedView{uri, name, label}]}, like describeFeedGenerator.
  • social.grain.actor.getPreferences / putPreferences are shaped like Bluesky's: a typed union (pinnedFeedsPref, photoMetadataPref, themePref, notificationsPref), replaced as a whole.
  • social.grain.notification.updateSeen replaces the lastSeenNotifications preference, and getUnreadCount counts from it. Both are named after Bluesky's.
  • social.grain.notification.registerPush / unregisterPush are shaped like Bluesky's: serviceDid, token, platform (ios/android/web) and appId.

New methods.

  • social.grain.sync.indexRecords: called with service auth, takes {uris} (1 to 25), returns {indexed, notFound}. Its errors are RepoMismatch and UnknownCollection.

  • social.grain.admin.*: one method for each hatk admin endpoint:

    • reports and review: listReports, resolveReport, getReviewQueue, getReviewSubject, actOnReview, startScan, getReviewStats
    • labels: applyLabel, negateLabel, listLabelDefinitions
    • repos: addRepos, removeRepos, resyncRepos, getRepo, listRepos (its q covers hatk's account search)
    • server: getInfo, searchRecords

    Takedowns use com.atproto.admin.updateSubjectStatus.

lexicons/README.md maps every old method to its new one. Nothing is removed: hatk serves the unspecced and dev.hatk methods on this branch until cutover.

Why

The Rust server serves social.grain.* only. Once every method has a home, the web app and the apps can be ported to the new names one call at a time.

Records and methods under the same name. social.grain.group and social.grain.privateGallery are space declarations, and their names are also prefixes of methods: social.grain.group.createGroup, social.grain.privateGallery.getPrivateGallery. Nothing forbids this. An NSID's authority is everything before its last segment, so:

  • the declaration resolves at _lexicon.grain.social;
  • the methods resolve at _lexicon.group.grain.social;
  • the pool's methods resolve at _lexicon.pool.group.grain.social.

On disk the files sit side by side (group.json beside group/), as group/defs.json and the group records already do.

hatk and method lexicons without handlers. hatk tolerates them. It loads and validates every lexicon and never asks for a handler. Its view discovery is scoped to each namespace, and none of the new *View defs matches a record in its namespace.

The one catch is codegen. hatk adds a prefix to a type's name only when two lexicons share an NSID's last segment, or two defs share a name. So some old generated names gained prefixes. Two web imports used them, and they now import the new names aliased to the old ones:

  • StoryAuthor now imports GetStoryAuthorsStoryAuthor;
  • GetKnownFollowersFollowerItem now imports GrainUnspeccedGetKnownFollowersFollowerItem.

How it was checked

  • goat lex lint lexicons/ reports no errors in any new file. Its remaining warnings are either in files that already existed or about fields named id or type.
  • A script confirmed that every ref in social.grain.* resolves.
  • After npx hatk generate types, every $hatk / $hatk/client import in server/, web/src/, test/ and seeds/ resolves against the regenerated exports.
  • npx vp fmt --check is clean.
  • check, test and rust run on the runner.

Shortcomings

  • No handlers yet: the Rust server serves none of these.
  • Merging to main publishes schemas under new authorities. Each needs a _lexicon TXT record, which publish-lexicons makes when it has the Cloudflare token. The new authorities, each under .grain.social: feed, sync, admin, server, notification, privateGallery, pool.group, story, comment and graph.
  • Lexicons have no floats, so admin classifier scores are integers in thousandths (0.873 is 873).
  • hatk's labels/reset, labels/rescan and whoami admin endpoints have no counterpart.
  • listNotifications still returns unseenCount, to keep its port a rename.

Review

Settled by the owner:

  • verb+noun names;
  • pools under social.grain.group.pool;
  • actor.searchActors;
  • notification.getUnreadCount split from listNotifications;
  • privateGallery.listMembers;
  • push platforms ios/android/web;
  • an indexRecords cap of 25;
  • story.getAuthors;
  • admin names in Bluesky style;
  • the def renames: suggestedItem becomes actor.defs#suggestedFollow, and getKnownFollowers#followerItem becomes graph.defs#knownFollowerItem.

Left as they are, on purpose:

  • notification preferences stay in the actor preference union;
  • the feed URI is specified only as at-uri. The suggested form is at://<grain service DID>/social.grain.feed.generator/<name>.

Still open: whether listNotifications should drop unseenCount now that getUnreadCount exists. Bluesky's returns seenAt instead.

🤖 Generated with Claude Code

chadtmiller.com added 12 commits
Approved

This pull adds social.grain.* copies of the 47 unspecced methods. It also adds the dev.hatk replacements, sync.indexRecords and 16 admin methods, and a README mapping old names to new. It removes nothing, and it re-points two web imports to the new generated type names.

The full diff is about 400k characters, and I couldn't read it all. I read AGENTS.md, sync/indexRecords.json and notification/registerPush.json, and listed the lexicon tree. Both files are well-formed. The array minLength/maxLength and the declared errors are valid, and the shapes match what the description says. I didn't check the other new lexicons or the web import changes.

The check, test and rust checks were still running or queued when I looked. Please confirm they pass, especially the hatk generate types result and the two aliased web imports, before merging.

The naming questions are yours to settle, so I have no objection to the current choices. Once the checks pass, nothing here blocks the merge.

chadtmiller.com added 1 commit
Approved

This is the same pull as round 2 of the review: social.grain.* copies of the unspecced methods, the dev.hatk replacements, sync.indexRecords, the admin methods, and a README mapping old names to new. It removes nothing. My earlier review asked you to confirm that check, test and rust pass, especially hatk generate types and the two aliased web imports. I can't confirm that yet: all three checks are still queued on commit 11c0ba9. I found nothing wrong in the parts I read. I didn't re-read the full diff, which is too large to cover. The naming questions are yours to settle. Please don't merge until the three checks are green.

Merged as 8d20091
Part of a stack of 2
  1. social.grain lexicons for every method the Rust server will serve Landed as 8d20091
  2. main