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
namingnarrows a count to. - statement 🔒
- What a statement says, and how it is read.
Structs§
- Count
Evaluator - 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§
Constants§
- BUCKETS
- Hours in the ring, which is how long a day window is.
- HELD
- How many counts one
Countsholds 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
nowfalls in, in whole hours since the epoch. - names_
another_ account - Whether this record names an account other than the one writing it.