Skip to main content

Crate didbot_lookup

Crate didbot_lookup 

Source
Expand description

Looks up one named record in a repository this deployment does not hold, and caches the answer.

The policy gate decides a write from the subject alone: the account, its kind, handle and depth, the client, the collection, the action and the diff. Three rules the floor wants — an author’s app.bsky.feed.threadgate and app.bsky.feed.postgate, an app.bsky.graph.block, and a declared community.lexicon.preference.ai — each need one more fact, and it is the same fact for all three: the value of a public record in somebody else’s repository. This crate is that capability, built once, for the evaluators that will ask for it.

§Why its own crate

It performs network I/O, and the two crates that would otherwise host it cannot. didbot-policy is the policy core: no I/O, no async, no clock, and Evaluator::evaluate takes a subject and nothing else precisely so a predicate cannot reach the network from inside a decision. didbot-pds holds no HTTP client and no async runtime either — the operator poll’s fetch lives in didbot-serve for exactly that reason. Putting the reader in either would give both a reqwest dependency they are deliberately without. So it is a crate of its own, depending on didbot-http and didbot-identity and on neither policy crate, which leaves an evaluator free to depend on it without didbot-serve coming along.

§How an evaluator reaches it

Two phases, because the decision channel is synchronous and the fetch is not. didbot-pds’s write queue calls the gate inside a synchronous critical section, so nothing under it can await.

  1. Prefetch. The write path, which has a runtime, calls ForeignRecords::prefetch with the records this write needs. It resolves and fetches what the cache does not already hold, bounded by Limits::record_deadline per record and Limits::prefetch_deadline for the call.
  2. Read. An evaluator holding an Arc<ForeignRecords> from its own construction calls ForeignRecords::read, which takes a lock, reads the cache and returns. It performs no I/O and does not await, so it is callable from inside a decision.

That split is what keeps the regex and Cedar engines untouched: they hold no reader, ask for no records, and their evaluation is unchanged.

§What an unknown means

Answer has three variants and the third one matters most. Present and Absent are both things the repository said. Answer::Unknown is everything else — not fetched yet, past the deadline, refused, a DID that does not resolve, a server that did not answer — and this reader never turns one into a refusal or into a permit. The evaluator that asked decides what an unknown means for its own rule, because only it knows whether the conservative reading of its rule is to deny or to stand aside.

That leaves Staleness and EvalError to the evaluator too. A reader whose answers may be up to Limits::present_ttl old is a BestEffort view, and an evaluator built on it declares a max_lag it can live with; this crate reports no lag of its own and raises no EvalError::Stale, because a cache entry inside its TTL is not a stale view — it is the view, and the TTL is the propagation delay plan/ai-preference.md asks to have written down rather than discovered.

§Memory and network, declared

  • At most Limits::entries answers are held, each at most MAX_RECORD_BYTES of JSON, so the cache costs at most those multiplied together. At the defaults that is 1024 records of 64 KiB, 64 MiB in the worst case and around a megabyte for records of the size a gate or a preference actually is.
  • One reference costs at most two requests: the DID document, then com.atproto.repo.getRecord for one record, or com.atproto.repo.listRecords for a whole collection. Both read a bounded body, and a collection read takes at most MAX_COLLECTION_RECORDS records inside that same bound.
  • One prefetch fetches at most Limits::per_write records, each bounded by Limits::record_deadline, and the whole call by Limits::prefetch_deadline.
  • The cache is memory only. Nothing here writes to disk, and a restart begins with an empty cache — which costs one fetch per record and can never resurrect an answer somebody has since changed.

Modules§

fetch 🔒
The one round trip behind a cache miss, and what this reader will not make it for.
record 🔒
How one record in somebody else’s repository is addressed.

Structs§

Counts
What this reader has done since the process started, for the operator watching the numbers move.
Entry 🔒
One cached answer and when it stops being usable.
ForeignRecords
The reader: a bounded cache of foreign records, over one Fetch.
Limits
What a ForeignRecords is bounded by. Every field is a parameter a deployment sets on its own, with its default and its reasonable range on the constant it comes from.
Network
The real fetch: resolve the DID to its #atproto_pds endpoint, then com.atproto.repo.getRecord there.
NoLocalRepositories
A deployment that holds nothing, for a caller with no registry to ask — a test, or a reader pointed only at the public network.
RecordRef
What a reader addresses: a DID, a collection, and either one record key or the whole collection.

Enums§

Answer
What this reader knows about one reference.
Fetched
What one round trip established.
RefError
Why a string is not a record this reader can address.
Unknown
Why a record’s value is not known.

Constants§

DEFAULT_ABSENT_TTL
How long “that repository holds nothing at that key” is reused, absent configuration.
DEFAULT_ENTRIES
How many answers are held at once, absent configuration.
DEFAULT_PER_WRITE
How many records one write may need, absent configuration.
DEFAULT_PREFETCH_DEADLINE
How long one prefetch may take in total, absent configuration.
DEFAULT_PRESENT_TTL
How long a record this reader read is reused, absent configuration.
DEFAULT_RECORD_DEADLINE
How long one record’s fetch may take, absent configuration.
DEFAULT_UNKNOWN_TTL
How long an unknown is reused, absent configuration.
MAX_COLLECTION_RECORDS
The most records one collection read takes from a repository.
MAX_NAMED_ACCOUNTS
The most accounts one write may name while a policy that reads something about each of them applies to it.
MAX_RECORD_BYTES
The most of one record’s JSON this reader holds in memory.

Traits§

Fetch
One round trip to a repository this deployment does not hold.
LocalRepositories
What this deployment already holds, so the reader never fetches it over the network.

Functions§

outcome_of 🔒
The word a fetch’s outcome is logged under.

Type Aliases§

FetchFuture
A future a Fetch implementation hands back, boxed so the trait stays object-safe and a reader can be held as Arc<dyn Fetch>.