id: config title: How the server runs is one file; what an agent may do is never in it status: open crates: [didbot-config, didbot-serve] dependsOn: [] exitCriterion: > didbot-pds starts from a sectioned TOML file covering every setting listed below, refuses to start on an unknown key or a malformed value naming the problem, complains about a loosely permissioned secret file, and states in one place how the file, command-line arguments and the environment resolve against each other. #
config #
didbot-pds takes --zone, --data, --port, --owner, --secret,
--secret-file, --blob-quota, --max-blob, --name-hold, --names,
--names-if-down, --estop-file, --estop-socket, --close-disclosure,
--avatar, --demo, and more — nineteen flags today, and every epic that
touches this binary adds one. --secret already needed a --secret-file twin because process
arguments are world-readable in /proc/<pid>/cmdline, which is
deployment's own finding. A flag list that long is
not a bootstrap interface any more; it is configuration with no schema, no
file, and no way to see the whole shape of a deployment at once.
The tier this file is #
deployment's configuration-tiers table names three tiers by what they hold, not by what stores them, and the table's row for the second tier undersells it: it says "process arguments," and this epic adds a sibling that is reachable the same way. Both are:
| Tier | Holds | Reachable on the host |
|---|---|---|
| The owner's repository | What an agent may hold: ceilings, admitted apps, collection permissions | No |
| Process arguments and this file | Bootstrap and how the server runs: bind address, zone, state directory, which DID's policy to read | Yes |
| Local state | Caches, refusal records, the emergency stop | Yes |
Anyone who holds the host can read this file. That makes it the single most tempting place to let a scope ceiling, an admitted app or a collection permission drift into, because it is right there, it is TOML, and it is already where the operator is looking. It must not become one. Those stay in the owner's repository, read at runtime, per policy-store. This file may say which DID's policy to read; it may never say what that policy is allowed to grant. A reviewer checking a change to this crate's schema should be able to reject anything that adds a scope, a permission, or an allowlist of apps on that question alone.
Sections #
Grouped by what an operator is thinking about, read from the real flag set
and from didbot-serve/src/routes.rs's constants rather than guessed:
[server]— bind address, port.[zone]— the zone agents are minted under, the owner DID, the pds endpoint agents are told to use.[dns]— provider selection and its zone, oncedidbot-dnshas more than one backend (dns-providers).[tls]— ACME environment, contact address, certificate source (didbot-tls).[attestation]— backend selection and the node allowlist.[blobs]—max_blob_bytes,account_quota_bytes, and a name the blob garbage-collection work is free to add a retention setting under (collection_grace, matchingdidbot_pds::blobs::BlobLimits's field, is the name already in use on that branch — this section should accept it under the same name rather than inventing a second one). Wired, exceptcollection_grace_secs, which is wired now, against--blob-collection-grace-hoursto wire it to yet.[capacity]—max_accounts, against--max-accounts(plan/capacity.md). Wired.evaluation_log_retention_days, how long a sealed segment of the policy evaluation log survives beforeEnforcement::openand its sweep trim it (didbot_pds::evaluation_log::FileSink::set_retention). Wired, with no flag mirroring it yet.[names]— the naming spec or template, the fallback namer, the hold period.[limits]— rate limits and their windows, oncedidbot-serve/src/rate_limit.rsandoauth/rate_limit.rsconverge on one shape.[disclosure]— whichbot.did.*routes are closed; mirrorsauth::Disclosure. Wired.[estop]— the file latch path and the admin socket path.[policy]— which DID's records this server reads for policy-store, once that epic exists to read anything.
Every section above is schema from day one — an operator should be able to read one file and see the whole shape of a deployment — but only some of it is wired to anything that runs; see "What this pass wires" below.
Behaviours every section has to satisfy #
What this pass wires #
Four other lines of work are touching didbot-pds.rs, rate_limit.rs,
didbot-tls, and didbot-dns at the same time this epic starts, so it
builds the schema for every section above and wires only a slice that does
not collide: --config reads a file through didbot_config::Config::load,
[blobs] and [disclosure] are applied with --max-blob/--blob-quota/
--close-disclosure taking precedence, and --secret-file is checked for
loose permissions. That proves the
refusal behaviours end to end — unknown key, malformed value, loose
permissions — without touching the bind address, the rate limiter, or
either provider crate.
Left as flags, on purpose, and listed here rather than converted in this pass:
Done #
-
Nothing else is left unguarded by that. Every file this workspace writes a secret into is created `0600` under a `0700` directory by the crate that owns it — `didbot-pds`'s write-ahead log, `didbot-tls`'s certificate store, `didbot-serve`'s e-stop socket — each with a test over the mode of the file it actually wrote, rather than by a check at a read path.