atproto git client
README.md

lexicons #

Vendored copies of every lexicon schema atgc reads or writes, from two authorities with two different ways of publishing a schema — ../lexicons.json is the one manifest covering both, under a sources map keyed by which convention applies.

sh.tangled.* and com.atproto.repo.strongRef #

lexicons/sh/tangled/repo/pull.json is sh.tangled.repo.pull, and so on — path mirrors NSID. com.atproto.repo.strongRef (needed by feed.comment's subject/replyTo) lives alongside them.

Tangled's lexicons are not published as com.atproto.lexicon.schema records — models.rs's own header comment names the real source of truth: tangled.org/tangled.org/core/tree/master/lexicons, a git repo. So the tangled entry in ../lexicons.json's sources pins provenance the way that source actually offers it: the commit these files were fetched at, not an at://-uri.

Refresh with ../scripts/vendor-tangled-lexicons.sh, which re-clones the lexicons directory at its current tip, rewrites both the JSON here and the generated Rust in ../vendor/tangled-lexicon/, and updates the pinned commit.

Scoped to exactly the twelve lexicons the tangled source covers — not Tangled's full surface (CI, knot management, and every XRPC endpoint atgc never calls). Widen scripts/vendor-tangled-lexicons.sh's codegen input if a future command needs another one typed, as sh.tangled.label.op and its definition were widened for atgc report bug --agent.

sh.tangled.label.definition is vendored without a reader or a writer for a narrower reason than the paragraph below: atgc writes the op and carries one definition's at-uri as a constant, and the definition is what says what that operand may contain — a null-typed label takes the literal "null" and nothing else. The schema beside the op is what makes that checkable.

sh.tangled.repo.issue.comment is the other one here with neither a reader nor a writer, and it is vendored on purpose. An issue comment is written as sh.tangled.feed.comment — Tangled unified issue, pull and string comments into that collection, and the appview's ingester handles this one as deletes only. That claim is worth being able to check rather than take on trust, and the two schemas side by side are the check: this one names its subject as a bare at-uri and has no room for a CID, which is the shape from before the unification. The pull twin, sh.tangled.repo.pull.comment, is not vendored — it predates this reasoning, and adding it now would be a lexicon bump inside an unrelated change.

app.userinput.* #

lexicons/app/userinput/discussion.json is app.userinput.discussion, and so on for the rest — fetched from the lexicon authority did:plc:uyixj57k6nmxrdj7pjs2ss5s (userinput.app's own PDS) rather than hand-read from its minified web client.

This authority does publish com.atproto.lexicon.schema records, so the userinput entry in ../lexicons.json's sources names the authority DID, and each NSID under lexicons carries its own at://-uri and CID — a copy here is provably what the authority published, not a guess.

Refresh with ../scripts/vendor-userinput-lexicons.sh, which re-resolves every NSID and rewrites both the JSON here and the generated Rust in ../vendor/userinput-lexicon/.

Only discussion and upvote are turned into Rust today (vendor/userinput-lexicon/), because those are the two records atgc report writes — a discussion for the report itself, an upvote for the self-upvote the web client also gives its own posts. discussion's limits (title, body, tag count) are worth enforcing before a write; upvote has none to enforce, but is typed anyway so a vote is built the same way a report is, not as one hand-rolled struct and one generated one. The other 14 are vendored for the same reason headquarters vendors blue.lance.camo: so the shape a client assumes is checked against what the authority actually publishes, not carried only in a comment. See TODO.md for what would need generating next (#image for attachments) if report grows to use it.

Neither generated type's space/subject field is a typed com.atproto.repo.strongRef — that ref is deliberately left out of this crate's codegen input, unlike the tangled-lexicon crate's. See scripts/vendor-userinput-lexicons.sh's comment on why: the generated strongRef type marks cid required, true for a vote's subject but not always for a discussion's space, and jacquard-codegen resolves a ref the same way for every field that names it. Both fields stay the untyped Data<S> a ref resolves to unvendored, built by hand from crate::userinput::StrongRef at the point of use.

Both #

A normal cargo build reads neither the network nor either vendor script — everything both need is already committed. Each script only touches its own source entry and its own NSIDs under lexicons in ../lexicons.json, so running one never disturbs the other's pins.