Skip to main content

Crate didbot_policy_count

Crate didbot_policy_count 

Source
Expand description

The counting engine, behind the same [Evaluator] contract as didbot-policy-regex and didbot-policy-cedar.

A document is bot.did.policy#count: statements, each naming the writes it counts, whether it counts each account on its own or the deployment as a whole, the window, the limit, and what a refusal says. The engine holds the counts; the numbers are the operator’s, or the floor’s, and they live in the document.

{
  "collections": ["app.bsky.graph.follow"],
  "naming": "anotherAccount",
  "scope": "account",
  "window": "hour",
  "limit": 50,
  "reason": "this account has reached this server's limit of 50 follows in an hour."
}

§The count judged is the count before this write

The write path calls judge and then observe for every attempt, admitted or refused, so the limit is the last write admitted: with a limit of fifty, the fiftieth follow inside the window lands and the fifty-first is refused. A write another policy refused still counts, which is the point of counting attempts rather than commits.

A statement counting the deployment as a whole counts only the writes the policy set allowed. An account retrying refused writes then spends its own count and nobody else’s: once its own limit refuses it, its attempts stop reaching the deployment’s count.

§Windows are whole hours

Counts are kept in a ring of hourly buckets. The hour window is the current bucket and the day window is the ring, so the hour advances in steps rather than sliding: a burst may spend the hour’s budget in a second.

§A count outlives the tree that judged it

The tree is rebuilt on every poll tick, and this engine is constructed fresh with it. The counts are not: they live in one Counts per process, held under the statement as written, so a tick is not a way around a limit and an edit to a statement’s limit keeps the count the old one was keeping. Two statements written differently count for themselves, whoever wrote them, so an operator’s own counting policy can only ever refuse more.

§Time is an input

Nothing here calls a clock. The hour comes off [didbot_policy::Universal::now], which the write path supplies and the evaluation log records.

Modules§

counts 🔒
The counts, and the ring each one is kept in.
interaction 🔒
Which records name an account other than the one writing them, which is what a statement’s naming narrows a count to.
statement 🔒
What a statement says, and how it is read.

Structs§

CountEvaluator
The engine: the counts it judges against, and every statement compiled into it.
Counts
Every count this process holds.
Statement
One statement: what it counts, over what, and how much it allows.

Enums§

Scope
Whose writes one statement counts together.
Window
How far back one statement counts.

Constants§

BUCKETS
Hours in the ring, which is how long a day window is.
HELD
How many counts one Counts holds before it drops the ones that have aged out. A deployment holds far fewer accounts than this; the number is here so a deployment whose accounts come and go cannot grow the map without bound between deletions.

Functions§

counts
The counts this process holds, shared by every document it compiles. See the crate doc comment for why they are the process’s and not the tree’s.
hour_of
The hour now falls in, in whole hours since the epoch.
names_another_account
Whether this record names an account other than the one writing it.