Something went wrong. Try again.
atproto git client
Something went wrong. Try again.
Rust
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240//! Rules that hold for any Tangled record carrying a state, whoever wrote it.
use anyhow::Result;
/// Which of two state records is the newer, ordered the way the indexers/// order them.////// The appview's `stateWinner` is `order by created_micros desc, at_uri desc/// limit 1`: the timestamp as an instant, ties broken by the whole at-uri —/// which contains the writer's DID, and so is the half that differs when the/// two accounts Tangled honours both write at the same moment.////// **This existed three times among the readers that decide state, and the/// three disagreed**, while two of them carried doc comments asserting they/// agreed. One broke the tie on the raw `createdAt` *string* first, a step/// the appview has no equivalent of, so `+03:00` beat `Z`. Another broke it/// on the record key alone, ignoring the DID. A pull could read `closed` from/// the command that closes it and `merged` from the command that lists it.////// Takes the two fields rather than a record type, because its callers hold/// different shapes and the rule is about neither of them.pub(crate) fn newer_state(a: (&str, &str), b: (&str, &str)) -> std::cmp::Ordering { use jacquard::types::string::Datetime; use std::str::FromStr; Datetime::from_str(a.0) .ok() .cmp(&Datetime::from_str(b.0).ok()) .then_with(|| a.1.cmp(b.1))}
/// Whether an account may write a state or status record that Tangled will/// actually honour.////// The record goes in the writer's own PDS, so the PDS will accept anything;/// the question is whether the indexers apply it. They disagree slightly, and/// this takes the intersection:////// - The Go appview honours the record's author, the target repo's owner, any/// collaborator holding `repo:push`, and Tangled's own DID; anyone else's/// is dropped after an ACL check against the knot./// - Bobbin has no ACL and filters at query time instead, accepting only the/// author and the repo owner's DID.////// So author-or-owner is the set both honour, and it is the set atgc can/// establish without asking a knot. A collaborator's close would work on/// tangled.org and be invisible in Bobbin-backed output, which is worse than/// being told to use the web UI — so that case is refused, and the refusal/// says why. `TODO.md` records what closing the gap would take.////// Pulls and issues draw this same line, and drew it in two separate copies/// until this one.#[derive(Debug, Clone, Copy, PartialEq, Eq)]pub(crate) enum Standing { Author, RepoOwner, Neither,}
/// Where `actor_did` stands against a record authored by `author_did` on a/// repo owned by `repo_did`, which is `None` when the repo could not be/// resolved — an unresolved repo settles nothing, so it answers `Neither`/// rather than assuming either way.pub(crate) async fn standing_of( actor_did: &str, author_did: &str, repo_did: Option<&str>,) -> Result<Standing> { if actor_did == author_did { return Ok(Standing::Author); } let Some(repo_did) = repo_did else { return Ok(Standing::Neither); }; Ok( match crate::clients::tangled::ownership::owns_repo(actor_did, repo_did).await? { true => Standing::RepoOwner, false => Standing::Neither, }, )}
#[cfg(test)]mod tests { use super::*; use std::cmp::Ordering;
#[test] fn an_offset_timestamp_is_compared_as_an_instant() { // 15:00+03:00 *is* 12:00Z. The string comparison this replaced said // the offset one was later. assert_eq!( newer_state( ("2026-01-01T12:00:00Z", "at://a"), ("2026-01-01T15:00:00+03:00", "at://a") ), Ordering::Equal, ); }
#[test] fn a_tie_breaks_on_the_whole_at_uri_including_the_did() { let t = "2026-01-01T12:00:00Z"; assert_eq!( newer_state((t, "at://did:plc:aaa/c/k"), (t, "at://did:plc:bbb/c/k")), Ordering::Less, ); }
#[test] fn the_later_instant_wins_regardless_of_uri() { assert_eq!( newer_state( ("2026-01-02T00:00:00Z", "at://a"), ("2026-01-01T00:00:00Z", "at://z") ), Ordering::Greater, ); }
#[test] fn an_unparseable_timestamp_loses_to_a_real_one() { assert_eq!( newer_state(("not a date", "at://a"), ("2026-01-01T00:00:00Z", "at://a")), Ordering::Less, ); }}
/// The account whose repository a record lives in: the authority segment of/// `at://<authority>/<collection>/<rkey>`.////// A record's authority *is* its author — an at-uri names the repository the/// record sits in, and only that account can write there — so this answers/// "whose is it" without touching the record.////// `None` for anything that is not an at-uri, which then matches no account/// rather than matching every one.pub(crate) fn authority_of(uri: &str) -> Option<&str> { uri.strip_prefix("at://")?.split('/').next()}
/// Whether `uri` names a record in `did`'s repository.////// **Split on the delimiter, never `starts_with` the DID.** One copy of this/// tested `rest.starts_with(me)` with no trailing `/`, which makes any/// account whose DID begins with yours read as you. `did:plc` is/// fixed-width so it could not bite there, but a `did:web` is as long as its/// domain: standing as `did:web:example.com`, a record at/// `at://did:web:example.com.evil.net/...` came back as yours — and the one/// caller promotes a record it believes is yours from "state unknown" to/// "open".pub(crate) fn is_authored_by(uri: &str, did: &str) -> bool { authority_of(uri) == Some(did)}
#[cfg(test)]mod absence_tests { use super::absence_is_open;
#[test] fn an_owner_and_a_whole_walk_settle_it_open() { assert!(absence_is_open(true, true)); }
#[test] fn a_truncated_walk_settles_nothing_even_for_the_owner() { // The issue path had exactly this case wrong: it printed `open` off a // walk that had stopped at its page cap, having just warned that the // row would read `?`. assert!(!absence_is_open(true, false)); }
#[test] fn a_whole_walk_settles_nothing_for_an_account_that_does_not_write_here() { assert!(!absence_is_open(false, true)); }
#[test] fn neither_settles_nothing() { assert!(!absence_is_open(false, false)); }}
#[cfg(test)]mod authority_tests { use super::*;
#[test] fn the_authority_is_the_first_segment() { assert_eq!( authority_of("at://did:plc:abc/sh.tangled.repo.pull/3k"), Some("did:plc:abc") ); }
#[test] fn anything_that_is_not_an_at_uri_has_no_authority() { assert_eq!(authority_of("https://example.com/x"), None); assert_eq!(authority_of(""), None); }
#[test] fn an_account_whose_did_merely_begins_with_mine_is_not_me() { // The whole reason this is a function: `starts_with` says yes here. let theirs = "at://did:web:example.com.evil.net/sh.tangled.repo.pull/3k"; assert!(!is_authored_by(theirs, "did:web:example.com")); assert!(is_authored_by( "at://did:web:example.com/sh.tangled.repo.pull/3k", "did:web:example.com" )); }
#[test] fn a_bare_authority_with_no_collection_still_reads() { assert_eq!(authority_of("at://did:plc:abc"), Some("did:plc:abc")); }}
/// Whether a *missing* state record may be read as "open".////// An issue or a pull with no state record is open — that is how Tangled/// spells a new one — but only to a reader who can say it has seen every/// record that could exist. Two things have to hold, and neither is enough/// alone:////// - the account whose absence is being read from writes state records for/// this repo at all (`owner_writes_here`), and/// - the walk that found none of them reached the end of the collection/// rather than stopping at a page cap (`walk_complete`).////// **The second condition existed on the pull path and was missing on the/// issue path**, where the walk computed it, printed a warning promising the/// row would read `?` — and then read the absence as `open` anyway. A repo/// owner with more state records than the cap allows saw an issue they had/// closed listed as open, off evidence the code had already gathered and/// dropped.pub(crate) fn absence_is_open(owner_writes_here: bool, walk_complete: bool) -> bool { owner_writes_here && walk_complete}