didbot_config/sections.rs
1//! One struct per `[section]`, read from the real flag set and from
2//! `didbot-serve/src/routes.rs`'s constants — see the crate root for which
3//! of these a caller actually applies today.
4//!
5//! Every struct here carries `#[serde(deny_unknown_fields)]` and every field
6//! is optional: a section an operator has not written is a section this
7//! binary decides on its own, the same as a flag nobody passed. What a
8//! section may **not** hold is a scope, a permission, or an allowlist of
9//! apps — see the crate root's doc for why.
10
11use serde::{Deserialize, Serialize};
12
13/// Where the server binds and listens.
14///
15/// Reload: needs a restart. A listener is bound once, at startup.
16#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
17#[serde(deny_unknown_fields)]
18pub struct ServerSection {
19 /// The address to bind, as an IP literal. No flag mirrors this, only
20 /// this key.
21 ///
22 /// Unset binds `::`, the unspecified IPv6 address, which accepts IPv4
23 /// callers too through a dual-stack socket: one listener answers both
24 /// families, and `127.0.0.1` and `[::1]` both reach it. Naming an
25 /// address narrows that. `127.0.0.1` reaches only callers on this host,
26 /// which is what a deployment behind a reverse proxy on the same
27 /// machine wants; a host's own public address pins the listener to one
28 /// interface on a machine with several.
29 ///
30 /// An address this host does not hold refuses startup, from the bind
31 /// itself, naming the address.
32 pub bind: Option<String>,
33 /// The port to listen on. Mirrors `--port`.
34 ///
35 /// **Reasonable range: 1024 to 65_535.** Below 1024 a Unix host wants
36 /// privileges this process should not be started with; a deployment
37 /// that answers the internet on 443 gets there through a proxy or a
38 /// redirect, not by running as root. The built-in default is 3000.
39 pub port: Option<u16>,
40 /// How long a stop waits for requests in flight once it has stopped
41 /// accepting, in seconds. No flag mirrors this, only this key.
42 ///
43 /// **Reasonable range: 1 to 60.** A request that outlives it is cut
44 /// off. The deployment's unit allows 200 seconds from `SIGTERM` to
45 /// `SIGKILL`, which this and `stop_checkpoint_secs` share. The built-in
46 /// default is 10.
47 pub drain_secs: Option<u64>,
48 /// How long a stop waits for a checkpoint taken while the server still
49 /// serves, in seconds, before it stops accepting. The next boot then
50 /// replays only what was written after it. No flag mirrors this, only
51 /// this key.
52 ///
53 /// **Reasonable range: 0 to 120.** 0 takes no checkpoint on a stop. A
54 /// checkpoint still running when this runs out is abandoned, which
55 /// costs nothing: the next boot replays from the one before. The
56 /// built-in default is 30.
57 pub stop_checkpoint_secs: Option<u64>,
58}
59
60/// The zones accounts are minted under, and who answers for them.
61///
62/// Reload: needs a restart. The zone is load-bearing for every DID this
63/// process has already minted; changing it under a running server would
64/// make existing accounts unresolvable against the new value.
65#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
66#[serde(deny_unknown_fields)]
67pub struct ZoneSection {
68 /// Mirrors `--zone`, and holds the same list. The first is the primary:
69 /// the server's own hostname. Every zone mints accounts one label under
70 /// it, and gets its own DNS backend and, under ACME, its own certificate
71 /// pair. Unset leaves `agents.localhost`, which is what a local run
72 /// wants.
73 ///
74 /// **Reasonable range: 1 to 20 zones.** Each one is a certificate pair
75 /// renewed on its own schedule, an apex and a wildcard record, and a
76 /// Route53 hosted zone id. A deployment with one zone writes a list of
77 /// one.
78 pub zones: Option<Vec<String>>,
79 /// Mirrors `--operator`. The DID answerable for every account this run
80 /// mints — never a scope or a permission, only an identifier.
81 ///
82 /// Outside `.localhost` the server polls this DID's repository for its
83 /// `bot.did.operator` claim and its policy records, so it is the DID
84 /// whose records govern this deployment as well as the one a stranger
85 /// reads to find someone accountable.
86 pub operator: Option<String>,
87 /// The URL agents are told to use as their PDS endpoint, and the one
88 /// written into every DID document this server publishes. No flag
89 /// mirrors this, only this key.
90 ///
91 /// Unset, it is derived from the primary zone: `https://<zone>`, plus
92 /// the bound port when this run did not take 443. Set it when the
93 /// address agents reach is not the address this process binds — a
94 /// deployment behind a proxy that terminates TLS on a different
95 /// hostname, for instance.
96 ///
97 /// It is published, not just used: an endpoint that does not reach this
98 /// server makes every account this run mints unreachable, and the DID
99 /// documents already carrying it do not change when this does.
100 pub pds_endpoint: Option<String>,
101}
102
103/// DNS provider selection, once [`dns-providers`](../../../plan/dns-providers.md)
104/// lands a second backend.
105///
106/// Reload: needs a restart. A provider swap changes which credential the
107/// process holds open.
108#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
109#[serde(deny_unknown_fields)]
110pub struct DnsSection {
111 /// Which `DnsProvider` to construct: `loopback`, `wildcard`, `route53`,
112 /// or a name a later backend adds.
113 pub provider: Option<String>,
114 /// The zone id the provider writes, when the provider needs one beyond
115 /// `zone.zone` itself (Route53's hosted zone id, for instance).
116 pub zone_id: Option<String>,
117 /// Whether this deployment may create new zones by writing `NS` records
118 /// into the zone above the one it is creating.
119 ///
120 /// Two postures exist. *Create* needs write access above the zone it
121 /// delegates, and this is what turns that on. *Use handed zones* — an
122 /// operator creates the delegation out of band and hands over an
123 /// already-hosted zone — needs nothing above it, and is the recommended
124 /// posture; it is also what `None` (the default) means, so a deployment
125 /// gets it without an operator having to opt in. Left unset, a create
126 /// attempt is refused at the point of decision rather than surfacing
127 /// later as an access-denied error from the DNS provider itself.
128 pub may_create_zones: Option<bool>,
129}
130
131/// TLS certificate sourcing, mirroring `didbot-tls`'s configuration surface.
132///
133/// Reload: the certificate itself hot-reloads once issued (`didbot-tls`'s
134/// `SwappableCert`); the ACME environment and contact address do not — they
135/// govern how the *next* certificate is obtained, not the one already
136/// served.
137#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
138#[serde(deny_unknown_fields)]
139pub struct TlsSection {
140 /// `staging` or `production` ACME directory. Mirrors
141 /// `--acme-environment`.
142 ///
143 /// Staging (the default) issues untrusted certificates against generous
144 /// rate limits, which is what iterating on a deployment wants.
145 /// Production has hard weekly rate limits, and a run that burns them
146 /// waits a week.
147 pub acme_environment: Option<String>,
148 /// Contact URIs (`mailto:ops@example.com`) for the ACME account.
149 /// Mirrors `--acme-contact`, and holds the same list.
150 pub contact: Option<Vec<String>>,
151 /// Where the certificate comes from: `acme` or a path to a fixed pair,
152 /// for development.
153 pub cert_source: Option<String>,
154 /// Which authority issues this deployment's certificate: `acme` or
155 /// `local`. Mirrors `--tls`.
156 ///
157 /// Unset takes the one authority the primary zone may use, which is
158 /// `local` for a zone under `.localhost` and `acme` for any other.
159 pub mode: Option<String>,
160 /// The directory holding the local certificate authority `local` signs
161 /// with. Mirrors `--tls-ca`.
162 ///
163 /// Unset keeps it under `$XDG_STATE_HOME/didbot`, which is one
164 /// authority per machine: a developer trusts it once, and every local
165 /// server on that machine presents leaves it signed. Point two
166 /// checkouts at one directory to share an authority, or at two to keep
167 /// them apart.
168 pub ca_dir: Option<String>,
169 /// The `max-age` of the `Strict-Transport-Security` header, in seconds.
170 /// No flag mirrors this, only this key. Unset is 63_072_000, two years,
171 /// the same as did.bot's own header (`infra/site/`).
172 ///
173 /// Sent only by a run with `mode = "acme"` and
174 /// `acme_environment = "production"`. A browser that has seen the header
175 /// refuses a certificate error under the names it covers for this long,
176 /// with no way to click through, and every other certificate this
177 /// server can serve chains to a root browsers do not trust.
178 ///
179 /// **Reasonable range: 15_768_000 (six months) to 63_072_000 (two
180 /// years).** Below six months, SSL Labs and the HTTP Observatory score
181 /// the header as absent. `0` tells a browser to drop the header it
182 /// already holds, which is how a deployment backs out of one.
183 pub hsts_max_age_secs: Option<u64>,
184 /// Whether the `Strict-Transport-Security` header carries
185 /// `includeSubDomains`. No flag mirrors this, only this key. Unset is
186 /// `false`.
187 ///
188 /// `true` commits every name below the one a browser reached, including
189 /// names this server does not serve. That is right when this server's
190 /// zone is its own domain, so every name below it is an account served
191 /// under this server's wildcard certificate. It is wrong when the server
192 /// sits at a domain that has other hosts.
193 pub hsts_include_subdomains: Option<bool>,
194}
195
196/// What one blob, one account's blobs, and every account's blobs together
197/// may come to.
198///
199/// Reload: needs a restart today. A running quota is enforced against the
200/// value the store was built with; there is no seam to adjust it live.
201#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
202#[serde(deny_unknown_fields)]
203pub struct BlobsSection {
204 /// Longest a single blob may be, in bytes. Mirrors `--max-blob`. The
205 /// built-in default is 5 MiB (5_242_880).
206 ///
207 /// **Reasonable range: 1_000_000 to `account_quota_bytes`.** 1_000_000
208 /// is the largest image `app.bsky`'s lexicons accept, such as an avatar,
209 /// so below it an agent cannot upload one. Above the account quota, no
210 /// blob that long can be stored.
211 pub max_blob_bytes: Option<u64>,
212 /// What one account's blobs may come to, in bytes. Mirrors
213 /// `--blob-quota`. The built-in default is 64 MiB (67_108_864).
214 ///
215 /// **Reasonable range: `max_blob_bytes` to `total_quota_bytes`.** Below
216 /// `max_blob_bytes`, an account cannot store one blob of the size it is
217 /// allowed. Above the total, one account can fill what every account
218 /// shares.
219 pub account_quota_bytes: Option<u64>,
220 /// What every account's blobs may come to together, in bytes. No flag
221 /// mirrors this, only this key. An upload past it is refused with
222 /// `507 StorageFull`, naming this total.
223 ///
224 /// **Reasonable range: `account_quota_bytes` to half the data volume.**
225 /// Below one account's quota, the total refuses an account the quota it
226 /// was promised. Above half, the rest of the volume may not hold what
227 /// shares it: the log up to `[capacity] log_budget_bytes`, two
228 /// checkpoints as large as the state while a new one is written, and the
229 /// record heap, which grows with every record written. Unset, only each
230 /// account's quota bounds the blobs, and that quota times the number of
231 /// accounts can be more than the volume holds.
232 pub total_quota_bytes: Option<u64>,
233 /// How long a deleted blob is kept before collection, in seconds.
234 /// Named `collection_grace` to match
235 /// `didbot_pds::blobs::BlobLimits`'s field of the same name on the
236 /// branch that adds it, rather than inventing a second name for the
237 /// same setting.
238 pub collection_grace_secs: Option<u64>,
239}
240
241/// How many accounts this deployment may hold at once, and other bounds on
242/// what this deployment keeps rather than what an agent may do.
243///
244/// Reload: needs a restart today. Enforced against the value `AppState` was
245/// built with, the same as [`BlobsSection`]'s quotas.
246#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
247#[serde(deny_unknown_fields)]
248pub struct CapacitySection {
249 /// Mirrors `--max-accounts`. See `plan/capacity.md`.
250 ///
251 /// **Reasonable range: 1 to 100_000.** One account is a deployment that
252 /// exists to run a single agent. The high end is where the costs this
253 /// cap is holding back stop being about accounts at all: every account
254 /// is a repository replayed from the log at startup, so a deployment
255 /// near it should be sized against its restart time rather than against
256 /// this number. The built-in default is 1000.
257 pub max_accounts: Option<u64>,
258 /// The most records one account's repository may hold. No flag mirrors
259 /// this, only this key. A caller's write that would add records past it
260 /// is refused with `403 TooManyRecords`. Updates and deletes are always
261 /// accepted, and the server's own records are written past it. An
262 /// account already past it keeps what it has and cannot create more.
263 ///
264 /// **Reasonable range: 1_000 to 100_000.** A write to a repository the
265 /// server holds built costs the same at any size, about 200µs. A write
266 /// that finds it not held builds it from every record: after a restart,
267 /// or once more than 32 repositories have written since. That build took
268 /// 10ms at a thousand records, 107ms at ten thousand, 690ms at fifty
269 /// thousand and 2.6s at a hundred thousand, on a loaded 16-thread laptop.
270 /// Below a thousand, an agent writing a record an hour fills it in six
271 /// weeks. The built-in default is 50_000. Startup refuses a value outside
272 /// the range.
273 pub max_records_per_account: Option<u64>,
274 /// Bytes the write-ahead log may reach before a further write is
275 /// refused with `StorageFull`. Mirrors `--log-budget`. Applied by
276 /// `didbot_pds::wal::Wal::set_capacity`.
277 ///
278 /// **Reasonable range: a tenth of the volume to most of it.** This is a
279 /// budget, not a disk check: it exists so a full volume is a deployment
280 /// that says `StorageFull` at a size the operator chose, rather than
281 /// one that discovers the size of its disk mid-write. Set it below what
282 /// the volume holds, with room for the blobs beside it, which this does
283 /// not count and `[blobs] total_quota_bytes` bounds. Unset, the log grows
284 /// until the filesystem stops it — an append that fails is still refused
285 /// cleanly, just at a size nobody picked.
286 pub log_budget_bytes: Option<u64>,
287 /// Bytes the write-ahead log reaches before the running server takes a
288 /// checkpoint and trims the log against it. No flag mirrors this, only
289 /// this key. Applied by `didbot_pds::Durable::checkpoint_when`.
290 ///
291 /// **Reasonable range: 128 MiB (134_217_728) to three quarters of
292 /// `log_budget_bytes`.** Each checkpoint writes the whole state, so a
293 /// low watermark rewrites the state for every few segments of log it
294 /// reclaims. Near the budget, the writes that land while a checkpoint
295 /// runs can reach the budget first, and those are refused with
296 /// `StorageFull`. Unset, it is half of `log_budget_bytes`, or 1 GiB
297 /// with no budget. Startup refuses a value below 1 MiB or at or above
298 /// the budget.
299 pub checkpoint_log_bytes: Option<u64>,
300 /// How often the running server takes a checkpoint while the log stays
301 /// under `checkpoint_log_bytes`, in seconds, if anything was written
302 /// since the last. No flag mirrors this, only this key.
303 ///
304 /// **Reasonable range: 3_600 to 604_800 (an hour to a week).** This
305 /// bounds how much log a restart replays and how long churn sits in the
306 /// log. An hour costs a write of the whole state every hour; a week
307 /// lets a restart replay a week of writes. The built-in default is
308 /// 86_400, a day. Startup refuses a value outside the range.
309 pub checkpoint_interval_secs: Option<u64>,
310 /// How long a sealed segment of the policy evaluation log is kept before
311 /// it is trimmed, in days. No flag mirrors this yet, only this key.
312 /// Applied by `didbot-serve/src/bin/didbot-pds/`'s `Enforcement::open`
313 /// via `didbot_pds::evaluation_log::FileSink::set_retention`; unset
314 /// leaves that sink's own `DEFAULT_RETENTION`, thirty days.
315 ///
316 /// This log's volume is driven by what gets refused, which an attacker
317 /// controls: a single client retrying a denied write writes a row every
318 /// time. A week (7) is enough to answer "why was this refused" while it
319 /// is still being asked; a quarter (90) keeps a season of denials for
320 /// looking at patterns, and costs disk in proportion to how noisy the
321 /// deployment's callers are. One day is the shortest that survives an
322 /// operator sleeping through an incident.
323 pub evaluation_log_retention_days: Option<u64>,
324 /// How long a sealed segment of the write log is kept before it is
325 /// trimmed, in days. The same shape as
326 /// [`Self::evaluation_log_retention_days`] and applied the same way,
327 /// against `didbot_pds::write_log::FileWriteLog`.
328 ///
329 /// This log grows with real use rather than with refusals: one row per
330 /// record written or deleted, naming the app that presented it. It is
331 /// the only record of which app wrote what, so the window is how far
332 /// back an attribution question can be answered. Thirty days (the
333 /// default) covers "what did this app do last month"; a year (365) makes
334 /// attribution answerable for as long as most operators keep backups,
335 /// at roughly a row per write. The two logs are set separately because
336 /// they fill at unrelated rates.
337 pub write_log_retention_days: Option<u64>,
338 /// The data volume's free space, as a percentage of its size, below
339 /// which `GET /health`'s `disk.alert` reads `warning` and the journal
340 /// writes a `warn` line. The built-in default is 20.
341 ///
342 /// **Reasonable range: 10 to 50.** The log and the blobs refuse at their
343 /// own budgets, but the record heap, a checkpoint and the small files
344 /// beside the log grow until the volume is full. Twenty percent of the
345 /// default 20 GiB volume is 4 GiB, days of ordinary writes to act in.
346 /// Above 50 the alert stays raised on a volume whose budgets are in use.
347 /// Startup refuses a value outside the range.
348 pub disk_warning_free_percent: Option<u8>,
349 /// The same, for `critical`. The built-in default is 10.
350 ///
351 /// **Reasonable range: 2 to 20, and below
352 /// [`Self::disk_warning_free_percent`].** A boot writes a checkpoint as
353 /// large as the state, so this should leave room for one. Startup
354 /// refuses a value outside the range, or at or above the warning.
355 pub disk_critical_free_percent: Option<u8>,
356 /// How many frames `subscribeRepos` keeps in memory for a consumer to
357 /// resume from. No flag mirrors this, only this key. Default 4096,
358 /// `didbot_serve::DEFAULT_REPLAY_FRAMES`.
359 ///
360 /// **Range: 256 to 65_536**, and startup refuses a value outside it.
361 /// This is a relay's reconnect
362 /// window: a cursor older than the frames kept gets `OutdatedCursor` and
363 /// must read the repositories to fill the hole. Below 256 a short relay
364 /// outage on a busy server is already a hole. Each frame kept also costs
365 /// a few dozen bytes that [`Self::firehose_replay_bytes`] does not count,
366 /// which is what this bound limits when frames are small.
367 pub firehose_replay_frames: Option<usize>,
368 /// How many encoded bytes those frames may come to. No flag mirrors
369 /// this, only this key. Default 67_108_864 (64 MiB),
370 /// `didbot_serve::DEFAULT_REPLAY_BYTES`. The oldest frames are evicted
371 /// first, whichever of the two bounds is reached. Frames an open stream
372 /// has not sent yet are kept past both bounds, up to twice them.
373 ///
374 /// **Range: 16_777_216 (16 MiB) to 1_073_741_824 (1 GiB)**, and startup
375 /// refuses a value outside it. Within that, stay at or under a sixteenth
376 /// of the instance's memory. A commit may carry 2,000,000 bytes, so this
377 /// is the bound that holds when records are large. The firehose's memory
378 /// is at most twice this, plus the one frame each open connection is
379 /// writing, up to `subscribeRepos`' connection limit. Below 16 MiB a
380 /// burst of large records evicts frames before a relay has read them.
381 /// Above a sixteenth, twice this competes with the rest of the server
382 /// for memory.
383 pub firehose_replay_bytes: Option<u64>,
384}
385
386/// How fast one account may write, as two token buckets: one on write
387/// operations and one on request bytes. See `didbot_serve::write_meter`.
388///
389/// Every account shares one write-ahead log, bounded by `[capacity]
390/// log_budget_bytes` and trimmed at `[capacity] checkpoint_log_bytes`. These
391/// keys bound how fast one account can take it: a rate, not a total.
392///
393/// A single `createRecord`, `putRecord` or `deleteRecord` costs one operation
394/// and adds about 1 KiB to the log, whatever the record's size; the body goes
395/// to the record heap. `applyWrites` costs one operation per write, and
396/// `uploadBlob` costs one. Each request's body costs its length in bytes.
397///
398/// In any hour one account writes at most `ops_burst + ops_per_hour`
399/// operations and `bytes_burst + bytes_per_hour` bytes. At the defaults that
400/// is 4,600 operations, about 4.5 MiB of log (0.2% of the 2 GiB budget a 20
401/// GiB volume gets), and 72 MiB of request bodies. One account writing flat
402/// out reaches the 1 GiB watermark that budget defaults to in about nine and
403/// a half days, and the server then trims it.
404///
405/// A write over either bucket is refused with `429 RateLimitExceeded` and a
406/// `Retry-After`. The buckets are in memory and start full on every start.
407///
408/// Reload: needs a restart. The meter is built once, into the router.
409#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
410#[serde(deny_unknown_fields)]
411pub struct WritesSection {
412 /// Write operations an account's bucket regains an hour.
413 ///
414 /// **Reasonable range: 600 to 360_000.** 600 is one write every six
415 /// seconds. 360_000 is a hundred a second, about 350 MiB of log an hour
416 /// from one account, which a deployment should choose only with a large
417 /// budget. The built-in default is 3_600, one a second: an agent
418 /// writing every five seconds uses 20% of it.
419 pub ops_per_hour: Option<u32>,
420 /// Write operations an account's bucket holds when full: how many it
421 /// may write at once before the hourly rate applies.
422 ///
423 /// **Reasonable range: 200 to 100_000.** Below 200, the most writes one
424 /// `applyWrites` takes, a full batch is refused however long the caller
425 /// waits, so startup refuses it. The built-in default is 1_000, which
426 /// refills in about 17 minutes at the default rate.
427 pub ops_burst: Option<u32>,
428 /// Request bytes an account's bucket regains an hour.
429 ///
430 /// **Reasonable range: 8 MiB to 4 GiB** (8_388_608 to 4_294_967_296).
431 /// This bounds how fast one account grows the record heap, which shares
432 /// the volume with the log and the blobs. The built-in default is 64 MiB
433 /// (67_108_864): a 1 KiB record every five seconds uses about 1% of it,
434 /// and a loop of full 2 MiB requests gets one every 112 seconds.
435 pub bytes_per_hour: Option<u64>,
436 /// Request bytes an account's bucket holds when full.
437 ///
438 /// **Reasonable range: 2 MiB to 1 GiB** (2_097_152 to 1_073_741_824).
439 /// Below 2 MiB, the largest request body this server reads, a full
440 /// request is refused however long the caller waits, so startup refuses
441 /// it. The built-in default is 8 MiB (8_388_608), four full requests.
442 pub bytes_burst: Option<u64>,
443}
444
445/// One named source of names: the zones and labels it holds, and how it
446/// makes a name. See `plan/pools.md`.
447///
448/// A pool says how names are made, never who may take them: that is the
449/// operator's `account.create` policy. With no `[[pool]]`, each zone in
450/// `[zone] zones` is a pool named after itself, in zone order.
451///
452/// **Reasonable range: 1 to 20 pools.** Each create with no pool of its
453/// own asks policy about each pool's zones in turn, so a long list is a
454/// longer walk before a refusal.
455///
456/// Reload: needs a restart. A changed pool applies to later creates; a DID
457/// already minted keeps its name.
458#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
459#[serde(deny_unknown_fields)]
460pub struct PoolSection {
461 /// What a create's `pool` and a policy's `resource.pool` call this pool.
462 /// One DNS label, and no two pools share one.
463 pub name: String,
464 /// The zones the pool holds names under, each one of `[zone] zones`,
465 /// in the order it fills them: a create lands in the first one policy
466 /// permits.
467 ///
468 /// **Reasonable range: 1 to 20 zones**, and never more than the server
469 /// serves.
470 pub zones: Vec<String>,
471 /// Which labels under those zones the pool holds: one `*`, with
472 /// optional literal text on either side. `ci-*` holds `ci-7` and not
473 /// `kestrel`, and wraps each name the pool makes: `ci-basalt-otter`.
474 /// Unset is `*`, every label.
475 ///
476 /// **Reasonable values: `*`, or a short prefix or suffix** such as
477 /// `ci-*`. Every character of literal text is one fewer for the scheme
478 /// in a 63-byte label.
479 pub labels: Option<String>,
480 /// How the pool makes a name, in the spellings `[names] spec` takes.
481 /// A braced part, such as `{repo}` in `{repo}+n`, is a slot each create
482 /// fills with a fragment. Unset takes `[names] spec`, or
483 /// `mineral+creature` when that is `hostname`.
484 ///
485 /// **Reasonable values:** a word-list spec for names people say aloud,
486 /// or a spec with `n` for a pool that must never run out. `--help`
487 /// says what each spelling discloses.
488 pub names: Option<String>,
489}
490
491/// How agents are named.
492///
493/// Reload: needs a restart. The namer is built once and handed to the
494/// provisioner.
495#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
496#[serde(deny_unknown_fields)]
497pub struct NamesSection {
498 /// A `--names`-shaped spec: `hostname`, a `+`-joined word-list spec,
499 /// `toml:<path>`, `cmd:<program args...>`, or one of the generated
500 /// forms. Mirrors `--names`, and takes exactly the same spellings —
501 /// `--help` lists them and says what each one discloses.
502 pub spec: Option<String>,
503 /// A second naming to fall back on, same shapes as `spec`. Mirrors
504 /// `--names-if-down`.
505 pub fallback: Option<String>,
506 /// How a number is written into a label: the `n` and `t` slots, and
507 /// the `counter` and `timestamp` namings. Mirrors
508 /// `--number-encoding`. One key rather than one per number, because
509 /// a label carrying a count and a time should read in one number
510 /// system rather than two.
511 ///
512 /// **Reasonable values: `decimal` or `base32`.** `decimal` (the
513 /// default) writes the count as anyone would write it, up to twenty
514 /// characters. `base32` is lowercase Crockford, which drops the four
515 /// symbols a person misreads and fits the same range in thirteen
516 /// characters; a deployment past a million accounts, or one whose
517 /// template already spends most of a DNS label on words, gets shorter
518 /// names from it. Both are legal in a label at every count, so the
519 /// choice is about length and readability rather than validity.
520 pub number_encoding: Option<String>,
521 /// Days a released name stays held before it can be taken again.
522 /// Mirrors `--name-hold`.
523 ///
524 /// **Reasonable range: 1 to 365.** The hold is what keeps a name from
525 /// being handed to a second agent while callers still have the first
526 /// one cached, so the low end is however long anything caches a name —
527 /// a day covers a DNS TTL and a restart. The high end holds a name for
528 /// a year, which suits a deployment where a name is a published
529 /// identity somebody wrote down. The cost of a long hold is names: a
530 /// word-list naming with a small pool runs out of unheld names sooner.
531 /// The built-in default is 30 days.
532 pub hold_days: Option<i64>,
533}
534
535/// The operator: how long their sign-in lasts, and how long their claim
536/// outlives a poll that cannot see it.
537///
538/// Reload: needs a restart. The TTL is read once when the sign-in is built,
539/// and a session already issued keeps the lifetime it was issued with; the
540/// grace window is handed to the operator poll when it is spawned.
541#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
542#[serde(deny_unknown_fields)]
543pub struct OperatorSection {
544 /// How long a signed-in operator stays signed in, in seconds. Mirrors
545 /// `--operator-session-ttl`.
546 ///
547 /// **Reasonable range: 300 to 86_400** — five minutes to a day.
548 ///
549 /// Below five minutes an operator is signing in again mid-incident,
550 /// which is the one time the round trip to their own server is least
551 /// welcome. Above a day the window in which a borrowed browser stays
552 /// the operator outlives any sitting a person actually had. The
553 /// built-in default is twelve hours: one working day, so a sign-in in
554 /// the morning does not expire before the evening.
555 ///
556 /// Nothing enforces the range — it is documented for whoever sets this,
557 /// not checked — but a value outside it is worth a second thought.
558 pub session_ttl_secs: Option<u64>,
559 /// Hours an unreachable operator keeps this deployment writing, counted
560 /// from the moment their `bot.did.operator` claim stops standing. No
561 /// flag mirrors this, only this key. Applied to
562 /// `didbot_serve::operator_poll::configure`.
563 ///
564 /// Only a running server counts it. One restarted while the operator's
565 /// PDS is down boots unclaimed, and refuses writes and creates until that
566 /// PDS answers, however long this is.
567 ///
568 /// **Reasonable range: 1 to 168** — an hour to a week.
569 ///
570 /// This is a trade between two ways of being wrong. Short, and an
571 /// operator whose own PDS is down for an afternoon takes this
572 /// deployment's accounts down with it, because the poll cannot see a
573 /// claim that is still there. Long, and an operator who has genuinely
574 /// stood down leaves accounts writing under a vouch nobody is behind.
575 /// An hour is about as short as the poll's own interval makes
576 /// meaningful; a week outlasts most outages and most holidays. The
577 /// built-in default is six hours: long enough to sleep through, short
578 /// enough that a stood-down operator is not a month of writes.
579 pub grace_window_hours: Option<i64>,
580 /// The shortest `idleFor` a `neverWrote` account delete takes, in
581 /// seconds. A shorter one is refused. No flag mirrors this, only this
582 /// key.
583 ///
584 /// **Reasonable range: 3600 to 604_800** — an hour to a week.
585 ///
586 /// An agent's account is created before its session makes its first
587 /// write. Below an hour, a delete can take an account that is about to
588 /// write. The built-in default is an hour. Nothing enforces the range,
589 /// but zero is read as one second, since a delete always names a span.
590 pub never_wrote_min_idle_secs: Option<u64>,
591 /// The DNS-over-HTTPS resolver the operator's handle is checked
592 /// through: it is asked for the `_atproto` TXT record under the handle
593 /// the operator's DID document claims, before the homepage names them
594 /// by it. Default `https://cloudflare-dns.com/dns-query`.
595 ///
596 /// **Reasonable range: an `https://` DNS-over-HTTPS (RFC 8484) endpoint
597 /// that also answers the JSON form**, `?name=&type=TXT` answered as
598 /// `application/dns-json`: Cloudflare's, Google's at
599 /// `https://dns.google/resolve`, or one on this deployment's own network.
600 /// Anything else refuses startup. Whoever runs the resolver sees the
601 /// operator's handle once per operator poll.
602 pub dns_resolver: Option<String>,
603}
604
605/// Which `bot.did.*` routes this deployment has closed to a public,
606/// unauthenticated caller.
607///
608/// Every field defaults to open (`None`, meaning "leave it public") and only
609/// `Some(false)` ever narrows — mirrors `auth::Disclosure` and
610/// `--close-disclosure`. There is no spelling that widens past public,
611/// because there is nothing wider than public to widen into.
612///
613/// Reload: hot. Nothing here is built into another structure at startup; a
614/// caller may re-read this section and assign it directly onto a live
615/// `auth::Disclosure` at any point.
616#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
617#[serde(deny_unknown_fields)]
618pub struct DisclosureSection {
619 /// `bot.did.listAccounts`.
620 pub list_accounts: Option<bool>,
621 /// `bot.did.listAccountLedgers`.
622 pub list_account_ledgers: Option<bool>,
623 /// `bot.did.getAccountLedger`.
624 pub get_account_ledger: Option<bool>,
625 /// `bot.did.stats`.
626 pub stats: Option<bool>,
627}
628
629/// Which relays `didbot announce` and the dashboard's
630/// `POST /dashboard/api/relay/announce` announce this server to, over
631/// `com.atproto.sync.requestCrawl` and `notifyOfUpdate`. Every relay named
632/// by either key is asked, `hostname` first, and a relay named twice is
633/// asked once.
634///
635/// `None` — no `[relay]` table at all — means there is no relay to reach,
636/// the same as never passing `--relay-hostname`; there is no default relay
637/// address, because this project vendors nobody's as one. A `*.localhost`
638/// zone is left unwired regardless of what this section holds, since no
639/// relay can reach a loopback address; see `didbot_serve::relay`'s own doc.
640///
641/// Reload: needs a restart. This is read once at startup and it does not
642/// itself trigger anything, so there is nothing to re-read on a running
643/// process.
644#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
645#[serde(deny_unknown_fields)]
646pub struct RelaySection {
647 /// One relay's hostname, e.g. `bsky.network`. Reached over `https://`
648 /// unconditionally — a relay worth announcing to is on the public
649 /// internet.
650 pub hostname: Option<String>,
651 /// Relay hostnames, in the form `hostname` takes. Mirrors
652 /// `--relay-hostname`, given once per relay. Zero to a few: each is
653 /// asked at once on every announce, with its own ten-second limit.
654 pub hostnames: Option<Vec<String>>,
655}
656
657/// The authorization server's own bounds: the scope ceiling every grant is
658/// narrowed to, how many decisions may sit undecided at once, and the
659/// resolver it finds a permission set's authority through.
660///
661/// `scope_ceiling` is the one place this file names scopes, and it may only
662/// **narrow**. Whatever it holds is intersected with the compiled-in ceiling
663/// (`didbot_serve::oauth::authorize::GrantAnyScope`) before it is applied, so
664/// a configured value can shrink what an agent may hold and can never reach
665/// past what the binary already allows — the hard-blocked capabilities stay
666/// outside it whatever this section says. See the crate root's doc for why
667/// that direction is the only one permitted.
668///
669/// Reload: needs a restart. The ceiling is built once and handed to the
670/// router's `OAuthState`.
671#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
672#[serde(deny_unknown_fields)]
673pub struct OAuthSection {
674 /// The most any agent may be granted, as an enumeration of scope atoms
675 /// — `plan/scope-policy.md`'s "a ceiling is an enumeration, not a
676 /// pattern". Absent leaves the compiled-in ceiling in place.
677 ///
678 /// A **seam**, and the first half of that plan's "from server
679 /// configuration first, from the operator's records second": a ceiling read
680 /// from policy records in the operator's repository replaces this one at
681 /// the same `ScopePolicy` interface, without either side changing. It is
682 /// here so the boundary is testable before that source exists, not
683 /// because a ceiling belongs in a configuration file.
684 pub scope_ceiling: Option<Vec<String>>,
685 /// How many decisions one account may have awaiting an answer before a
686 /// further pushed authorization request is refused. Mirrors
687 /// `didbot_serve::oauth::decision::DEFAULT_PENDING_PER_ACCOUNT`, 8.
688 ///
689 /// **Reasonable range: 1 to 64.** At 1, a second sign-in started before
690 /// the first is answered displaces it. Above 64, one account can hold
691 /// many sign-ins nobody will answer, and a caller with no credential
692 /// can push them.
693 pub pending_per_account: Option<usize>,
694 /// How many decisions this server may hold in total, counting an
695 /// answered one until its pushed request expires.
696 ///
697 /// Left unset, the default is derived rather than fixed: `didbot-pds`
698 /// computes `[capacity] max_accounts * pending_per_account`, floored at
699 /// `didbot_serve::oauth::decision::DEFAULT_PENDING_TOTAL`, so a deployment
700 /// sized above 64 accounts is not left with a total bound tighter than
701 /// `pending_per_account` gives each one; with no `max_accounts` configured
702 /// either, that same fixed default applies outright. A value written here
703 /// is never adjusted against `max_accounts` -- only checked, with a
704 /// startup warning if it comes up short.
705 ///
706 /// **Reasonable range: `max_accounts * pending_per_account` to twice
707 /// that.** Below it, a full deployment refuses a push before every
708 /// account has used its own bound. An answered decision is held until
709 /// its pushed request expires, so some room above it keeps a busy
710 /// deployment from refusing. Each one held is memory that `POST
711 /// /oauth/par`, which needs no credential, can fill.
712 pub pending_total: Option<usize>,
713 /// The DNS-over-HTTPS resolver an `include:` scope's permission set is
714 /// found through: it is asked for the `_lexicon` TXT record under the
715 /// set's authority. `com.atproto.identity.resolveHandle` asks it for the
716 /// `_atproto` TXT record of a handle no account here holds. Default
717 /// `https://cloudflare-dns.com/dns-query`.
718 ///
719 /// **Reasonable range: an `https://` DNS-over-HTTPS (RFC 8484) endpoint
720 /// that also answers the JSON form**, `?name=&type=TXT` answered as
721 /// `application/dns-json`: Cloudflare's, Google's at
722 /// `https://dns.google/resolve`, or one on this deployment's own network.
723 /// Anything else refuses startup. The default is Cloudflare's because it
724 /// answers that form and is the resolver the operator's own onboarding
725 /// checks ask. Whoever runs the resolver sees which permission sets
726 /// sign-ins here name, and which handles callers ask this server to
727 /// resolve.
728 pub dns_resolver: Option<String>,
729 /// How long a browser stays signed in as an account, in seconds, counted
730 /// from the sign-in. Use does not extend it. Default 43_200, twelve
731 /// hours, as an operator's session.
732 ///
733 /// **Reasonable range: 300 to 86_400** — five minutes to a day. Below
734 /// five minutes an agent signs its browser in again for most web
735 /// sign-ins. Above a day a copied cookie outlives any session an agent
736 /// has. Nothing enforces the range.
737 pub browser_session_ttl_secs: Option<u64>,
738}
739
740/// Which DID's records this server reads as policy, once
741/// [`policy-store`](../../../plan/policy-store.md) exists to read anything.
742///
743/// This is the one field this whole file may say about policy: **which**
744/// repository to read, never what that repository is allowed to grant. See
745/// the crate root's doc.
746///
747/// Reload: hot, by design — `policy-store`'s own poll is how a revision
748/// takes effect, and this section only says where the poll points.
749#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
750#[serde(deny_unknown_fields)]
751pub struct PolicySection {
752 /// The operator's DID, whose repository holds ceilings, admitted apps
753 /// and collection permissions this server enforces but never stores here.
754 pub operator_did: Option<String>,
755}
756
757/// How `bot.did.parkKey` holds a key and bounds who may park one.
758///
759/// Reload: needs a restart. The values are read once, into the router's
760/// own state.
761#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
762#[serde(deny_unknown_fields)]
763pub struct ParkSection {
764 /// How long a parked key is held, in seconds. Mirrors `--park-key-ttl`.
765 ///
766 /// **Reasonable range: 60 to 3600.** The key waits for a person to run
767 /// `didbot operate` on another machine, so it has to outlast reading a
768 /// fingerprint off one screen and typing on another; past an hour a
769 /// park nobody admitted is holding a name against the next machine to
770 /// register it. Default 600.
771 pub ttl_seconds: Option<u64>,
772 /// How many parks and lookups one caller address may make per window.
773 /// Mirrors `--park-key-rate-limit`.
774 ///
775 /// **Reasonable range: 1 to 1000.** A machine parks once; six covers
776 /// one retrying through a flaky first boot. Default 6.
777 pub rate_limit: Option<u32>,
778 /// The window the limit is measured over, in seconds. Mirrors
779 /// `--park-key-rate-window`.
780 ///
781 /// **Reasonable range: 1 to 86400.** Default 600, matching the default
782 /// hold, so one address gets as many parks per window as could be live
783 /// at once.
784 pub rate_window_seconds: Option<u64>,
785}
786
787/// The shape one creator may give the ownership tree, enforced by the
788/// server whatever policy says.
789///
790/// Reload: hot for the three bounds below. A running server re-reads this
791/// file every `[intervals] tree_reload_secs` and judges every later create
792/// by what it found; a create already judged keeps its answer. A re-read
793/// that fails — the file gone, the TOML bad, a bound outside its range —
794/// changes nothing and warns, naming the file and the key.
795///
796/// `reserved_labels` is read once, into the router, and needs a restart.
797#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
798#[serde(deny_unknown_fields)]
799pub struct TreeSection {
800 /// The deepest an account may sit, in edges from the operator.
801 ///
802 /// **Reasonable range: 1 to 64.** This bounds how long a chain a reader
803 /// walks to reach the operator, and how many parties it rests on. Ten,
804 /// the default, leaves room above a service, a host beneath it and an
805 /// agent beneath that for agents that register agents of their own.
806 pub max_depth: Option<u32>,
807 /// The most accounts one creator may operate at once.
808 ///
809 /// **Reasonable range: 1 to 1_000_000.** This bounds how many accounts
810 /// one leaked key can be holding when the operator notices. Ten
811 /// thousand, the default, is above the pools this deployment expects
812 /// and under what a loop reaches.
813 pub max_children: Option<u32>,
814 /// The most accounts one creator may create in an hour.
815 ///
816 /// **Reasonable range: 1 to 1_000_000.** This bounds the rate rather
817 /// than the total, and is what stops a loop before `max_children` is
818 /// reached. A hundred, the default, admits a pool of a hundred coming
819 /// up at once.
820 pub creates_per_hour: Option<u32>,
821 /// Labels no account may take directly under a zone: the hostnames the
822 /// deployment answers at itself, and names a browser or resolver asks
823 /// for on its own. Absent, `didbot_serve::tree::RESERVED_LABELS`
824 /// stands; an empty list reserves nothing.
825 ///
826 /// **Reasonable range: 0 to 100 labels.** Each one is a name the
827 /// deployment's own `www`, `xrpc` or `dashboard` would otherwise share
828 /// with an agent through the zone's wildcard.
829 pub reserved_labels: Option<Vec<String>>,
830}
831
832/// Which OpenID Connect issuers this server will fetch keys from, and how
833/// fresh a token has to be.
834///
835/// Reload: needs a restart. The values are read once, into the router.
836#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
837#[serde(deny_unknown_fields)]
838pub struct OidcSection {
839 /// The issuers whose tokens are accepted, as their `iss` spells them.
840 /// Empty admits any issuer reachable over HTTPS at a public address.
841 ///
842 /// **Reasonable range: 0 to 64 entries.** A deployment names the
843 /// platforms it runs on; a list longer than that is an allowlist nobody
844 /// reviews.
845 #[serde(default)]
846 pub issuers: Vec<String>,
847 /// How old a token's `iat` may be, in seconds.
848 ///
849 /// **Reasonable range: 60 to 3600.** Five minutes, the default, is clock
850 /// skew and a slow network; past an hour a token a run left in a log is
851 /// still a session.
852 pub freshness_secs: Option<u64>,
853}
854
855/// The sessions this server issues from a proof.
856///
857/// Reload: needs a restart. The values are read once, into the registry
858/// and its token store.
859#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
860#[serde(deny_unknown_fields)]
861pub struct SessionsSection {
862 /// How long a session minted from a key-signed JWT or an OpenID Connect
863 /// token lasts, in seconds. A session minted at creation keeps its own
864 /// lifetime.
865 ///
866 /// **Reasonable range: 300 to 86_400.** An hour, the default: a run
867 /// that holds a session longer re-proves itself for one round trip.
868 pub proof_ttl_secs: Option<u64>,
869 /// How many live sessions one account may hold; the one expiring
870 /// soonest makes room for the next.
871 ///
872 /// **Reasonable range: 1 to 64.** Eight, the default, covers a
873 /// pipeline whose runs overlap.
874 pub live_per_account: Option<usize>,
875}
876
877/// The bearer credential provisioning mints for a freshly created account.
878///
879/// Reload: needs a restart. The value is read once, into the provisioner
880/// that issues every later token.
881#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
882#[serde(deny_unknown_fields)]
883pub struct CredentialSection {
884 /// How long a freshly issued agent token is good for, in days. Mirrors
885 /// `didbot_pds::credential::DEFAULT_AGENT_TOKEN_TTL`.
886 ///
887 /// **Reasonable range: 1 to 3650** — a day to ten years. There is no
888 /// rotation path for this token (`didbot_pds::credential`'s own doc): an
889 /// account whose token has expired is provisioned again, so the low end
890 /// suits a deployment that already re-provisions often and the high end
891 /// suits one that would rather live with a long-lived credential than
892 /// re-provision at all. The built-in default is 365, long enough that a
893 /// running deployment never meets the expiry in the ordinary course of
894 /// things.
895 pub agent_token_ttl_days: Option<u64>,
896 /// The shortest time between two of an account's own credential
897 /// replaces through `bot.did.updateCredentials`, in seconds. Mirrors
898 /// `didbot_pds::DEFAULT_REPLACE_INTERVAL`.
899 ///
900 /// **Reasonable range: 10 to 3600** — ten lets a test replace twice in
901 /// one run, and an hour is still far below any schedule a host keeps.
902 /// Every replace stays in the account's ledger for good, and this bounds
903 /// how fast an account grows it. The built-in default is 60.
904 pub replace_interval_secs: Option<u64>,
905}
906
907/// How often each background loop runs.
908///
909/// Every one of these trades freshness against load, and they are set apart
910/// because they load different things: one talks to a DNS provider with its
911/// own rate limits, one walks this server's own disk, and one only reads
912/// state already in memory.
913///
914/// Reload: needs a restart. Each loop is handed its period when it is
915/// spawned.
916#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
917#[serde(deny_unknown_fields)]
918pub struct IntervalsSection {
919 /// Seconds between passes of the blob collector, which reclaims upload
920 /// bytes nothing references once their grace window has passed.
921 ///
922 /// **Reasonable range: 300 to 86_400** — five minutes to a day.
923 ///
924 /// This is how promptly deleted bytes come back, and each pass walks
925 /// every account's blobs. Five minutes suits a deployment near its
926 /// quota, where the disk is the scarce thing. A day suits one with room
927 /// to spare, where the walk is the more expensive half. Nothing is lost
928 /// by a long period: a blob past its grace window stays collectable
929 /// until a pass reaches it. The built-in default is an hour.
930 pub blob_collect_secs: Option<u64>,
931 /// Seconds between passes of the confinement poll, which notices an
932 /// account whose hostname no longer resolves the way this server
933 /// published it.
934 ///
935 /// **Reasonable range: 60 to 3600** — a minute to an hour.
936 ///
937 /// Each pass reads records back from the DNS provider, so this is the
938 /// knob that decides how much of a provider's rate limit this
939 /// deployment spends on noticing drift. A minute catches a hijacked or
940 /// clobbered record fast and costs the most requests; an hour is
941 /// cheaper and leaves a wrong record standing longer. The built-in
942 /// default is five minutes.
943 pub confinement_poll_secs: Option<u64>,
944 /// Seconds between health ticks, which refresh what `bot.did.stats` and
945 /// the dashboard report about this server's own state.
946 ///
947 /// **Reasonable range: 5 to 300** — five seconds to five minutes.
948 ///
949 /// This one costs nothing outside the process: it reads state already
950 /// in memory, so the period is how stale a readout may be rather than a
951 /// load question. Match it to whatever scrapes the dashboard. The
952 /// built-in default is ten seconds.
953 pub health_secs: Option<u64>,
954 /// Seconds between re-reads of this configuration file for `[tree]`'s
955 /// three bounds, which take effect without a restart.
956 ///
957 /// **Reasonable range: 5 to 3600** — five seconds to an hour.
958 ///
959 /// Each pass reads and parses one local file, so the period is how long
960 /// an operator waits for an edit to take rather than a load question.
961 /// Five seconds suits raising a cap that is refusing work right now; an
962 /// hour suits a file nobody edits. The built-in default is thirty
963 /// seconds.
964 pub tree_reload_secs: Option<u64>,
965}
966
967/// What one account may spend of `atproto-proxy`, which forwards a call to
968/// whatever service a DID document names, from this server's address.
969///
970/// Reload: needs a restart. The values are read once, into the router.
971#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
972#[serde(deny_unknown_fields)]
973pub struct ProxySection {
974 /// How many proxied calls one account's budget refills by each minute.
975 ///
976 /// **Reasonable range: 1 to 6000.** Sixty, the default, is one a second
977 /// sustained: far above the few calls a minute an agent makes, and the
978 /// most one account can make a third party see from this server's
979 /// address. A call over it is refused `429` with `Retry-After`.
980 pub calls_per_minute: Option<u32>,
981 /// How many proxied calls one account may make at once from a full
982 /// budget.
983 ///
984 /// **Reasonable range: 1 to 1000.** Sixty, the default, covers a client
985 /// that opens a view by fanning out a few dozen calls.
986 pub burst_calls: Option<u32>,
987 /// How many of one account's proxied calls may be open at once.
988 ///
989 /// **Reasonable range: 1 to 64.** Eight, the default, is more than an
990 /// agent opens at once, and a sixteenth of `in_flight_total`.
991 pub in_flight_per_account: Option<usize>,
992 /// How many proxied calls may be open at once across one account tree:
993 /// an account the operator admitted, and every account created beneath
994 /// it.
995 ///
996 /// **Reasonable range: 1 to 512.** 64, the default, is half of
997 /// `in_flight_total`, so one creator's accounts leave half for every
998 /// other tree. A per-tree cap at or above `in_flight_total` has no
999 /// effect.
1000 pub in_flight_per_tree: Option<usize>,
1001 /// How many proxied calls may be open at once across every account.
1002 ///
1003 /// **Reasonable range: 1 to 512.** 128, the default, is a quarter of the
1004 /// 512 requests this server serves at once. One creator may hold
1005 /// thousands of accounts, and this keeps their calls to a service that
1006 /// never answers from holding the rest. A per-account cap above it has
1007 /// no effect.
1008 pub in_flight_total: Option<usize>,
1009 /// How long the endpoint a target's DID document names is kept, in
1010 /// seconds.
1011 ///
1012 /// **Reasonable range: 10 to 3600.** Five minutes, the default, fetches
1013 /// a document once for every few hundred calls to it, and reaches a
1014 /// service that moved within five minutes. Every call still checks the
1015 /// addresses the endpoint resolves to.
1016 pub target_cache_secs: Option<u64>,
1017 /// How many targets' endpoints are kept, the least recently used
1018 /// dropped first.
1019 ///
1020 /// **Reasonable range: 16 to 100_000.** An entry is a few kilobytes at
1021 /// most. 1024, the default, holds the services a deployment's agents
1022 /// call in a few megabytes.
1023 pub target_cache_entries: Option<usize>,
1024 /// Where an `app.bsky.*` call with no `atproto-proxy` header goes, as
1025 /// that header names a service: `did#service`, such as
1026 /// `did:web:api.bsky.app#bsky_appview`. The call is authorized and
1027 /// signed as a proxied call to that service is.
1028 ///
1029 /// Unset, the default, sends such a call nowhere: it is
1030 /// `MethodNotImplemented`.
1031 pub app_view: Option<String>,
1032}