id: capacity title: A deployment stops minting before somebody else's quota does status: open crates: [didbot-pds, didbot-dns, didbot-identity, didbot-serve] dependsOn: [zone-scale, account-types] exitCriterion: > A deployment configured with a maximum account count refuses provisioning once that many accounts hold records, names which zone is full, and an operator watching the dashboard saw the number climbing before it refused anything. #
capacity #
Moving from a wildcard record to a real record per host, which agent-accounts and zone-scale already did, is what created this ceiling. A wildcard record covers every hostname underneath it for the cost of one record; the PDS managing its own zone records per agent, so that per-agent DNS-01 issuance and precise withdrawal are possible, means every agent now has a cost measured in a hosted zone's own finite record count. This epic is that decision's bill, not a surprise found later — the tradeoff was worth making and the bill still has to be paid somewhere.
What an account costs, verified #
docs/deployment.md states it plainly: two hostnames
per account, one for the DID and one for the handle, and both must resolve
before the account exists. Route53Dns::publish in
crates/didbot-dns/src/route53.rs
writes one ChangeResourceRecordSets CREATE per host it is given, so an
account is two record sets, not one. The certificate does not add to that per
account: agent-accounts commits to one wildcard
certificate per zone over ACME DNS-01, so the _acme-challenge TXT records
it needs are fixed overhead on the zone, not a cost that scales with how many
agents the zone holds.
Route53's own quotas, checked against AWS's current documentation rather than assumed:
| Quota | Default | Adjustable |
|---|---|---|
Records per hosted zone (MAX_RRSETS_BY_ZONE) |
10,000 | Yes, via Service Quotas or a support request; AWS charges extra above 10,000 |
| Hosted zones per AWS account | 500 | Yes, via Service Quotas |
ResourceRecord elements per ChangeResourceRecordSets request |
1,000 (doubled for UPSERT) |
No |
Source: Amazon Route 53 quotas, "Quotas on records" and "Quotas on hosted zones". Both the per-zone record quota and the per-account zone quota are soft — raisable by a support request — which matters for what "the wall" means below: it moves, but not on this server's schedule, and not for free above 10,000 records.
At two records per account and the unmodified 10,000-record default, one hosted zone holds five thousand accounts before Route53's own quota refuses a write mid-provisioning. That is the raw ceiling this epic exists to stay under, not the number a deployment should configure — see the default below.
Historical accounts count, not just live ones #
account-types is relaxing the sweep-on-session-end
default and making a name permanent once issued rather than freed at the end
of a session. The record a DID publishes has to keep resolving for as long as
a reader might hold a reference to it — an at:// URI copied out of a log,
a citation, a mention — which is longer than the session that minted the
account. An account whose DID document must still resolve still holds its two
records, whether or not anything is presently running as that account.
So the population this quota is checked against is not "live sessions right now." It is every account whose records are still published:
- Live (provisioned, in use or pinned): two records held.
- Retained past session end (soft-deleted, inside its retention window, still resolving because something might still reference it): two records held. This is the state account-types is making the common case rather than the exception, and it is why "historical" is the right word for the population that matters here, not "active."
- Withdrawn (retention window closed,
Registry::delete's DNS withdrawal has actually run): zero records held. The name itself moves toNameRegistry'sReleasedhold, which is a name-pool cost tracked in zone-scale, not a DNS-record cost tracked here. - Reserved (a zone apex, or an operational block in
NameRegistry): zero DNS records. A reservation is a name held out of the naming pool; it never had a DNS record to begin with.
A cap that only ever counted the first bucket would let a deployment mint past what its own zone can hold, because the retained bucket is exactly the one growing while account-types relaxes the default that used to shrink it.