Skip to main content

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}