# The names an agent has An account is two hostnames, a DID document and a plain-text DID. None of that is a DNS record. This page works one example all the way through: what exists in the zone, what the server answers, and what a stranger reads to get from a name to a repository. [Deployment](deployment.md) states the rules this example obeys; [the shape of the system](architecture.md) draws the hosts around it. ## The example A zone `pds.example`, delegated to one server at `203.0.113.10`. One account, minted with a namer, so it has both hostnames a deployment can issue: the opaque one the DID is minted from, and the issued handle. | | | | --- | --- | | zone | `pds.example` | | DID | `did:web:7f3a9c.pds.example` | | handle | `basalt-kestrel.pds.example` | | PDS endpoint | `https://pds.example` | A deployment without a namer has one hostname per account rather than two: the handle is then the DID's own host, which `Provisioner::issued_handle` reports as no issued handle at all, because there is nothing extra to resolve or withdraw. ## Everything that exists in DNS Three names, and none of them is per-account: ```text pds.example. NS ns-… (in the parent zone, added by hand) pds.example. A 203.0.113.10 *.pds.example. A 203.0.113.10 _acme-challenge.pds.example. TXT "" "" (only while a certificate is being issued) ``` Minting an account writes nothing here. `7f3a9c.pds.example` and `basalt-kestrel.pds.example` resolve because `*.pds.example` synthesizes an answer for them, and they do so the instant the name is chosen — there is no propagation to wait for and no external call on the provisioning path. `didbot-dns`'s `WildcardDns` is the provider for this: it keeps the same per-name bookkeeping every other backend keeps, refuses a host outside its zone, and publishes nothing, because there is nothing left to publish. ## Everything the server answers
DNS ZONE · pds.example · external pds.example A · 203.0.113.10 *.pds.example A · 203.0.113.10 _acme-challenge.pds.example TXT · two values at once, only during issuance three records, whatever the account count provisioning writes none of them the NS delegation lives in the parent, by hand SYNTHESIZED — no node in the zone 7f3a9c.pds.example 203.0.113.10 basalt-kestrel.pds.example 203.0.113.10 any depth, while no closer node exists — publishing one real record here would end that resolves ONE LISTENER · 203.0.113.10 certificate: pds.example + *.pds.example a TLS wildcard matches exactly one label — unlike the DNS one dispatch on the Host header one process, every account; the name is the whole key 7f3a9c.pds.example the DID's own hostname GET /.well-known/did.json application/did+json id · alsoKnownAs · #atproto · #atproto_pds 404 for a name no account holds basalt-kestrel.pds.example the issued handle GET /.well-known/atproto-did text/plain did:web:7f3a9c.pds.example answers only when the document agrees WHAT did.json SAYS · the four fields atproto reads id did:web:7f3a9c.pds.example alsoKnownAs at://basalt-kestrel.pds.example verificationMethod #atproto · Multikey service #atproto_pds · https://pds.example the rest of the W3C surface is not emitted, because nothing reads it points back at the same account WHAT A STRANGER DOES 1 · handle → GET /.well-known/atproto-did → a DID 2 · DID → GET /.well-known/did.json on its own host 3 · check id, and alsoKnownAs back to the handle 4 · #atproto_pds → the XRPC surface no step consults DNS for anything but reachability
Both hostnames land on the same listener, and the `Host` header is the only thing that says which account a request means. `did_json` and `atproto_did` in `didbot-serve`'s routes both read it, and both derive their answer from one registry, so the bidirectional check holds by construction: a resolver following the handle to the DID and the DID to its document always finds the handle again in `alsoKnownAs`. A name no account holds is a 404 rather than an `NXDOMAIN`, because the wildcard resolves it and the TLS handshake completes. That is a conformant answer — a resolver treats any non-200 as unresolvable — and it is the only one that can express a soft-deleted account, which keeps serving its document and its verification method while its repository is empty. ## What the wildcard does not cover **The apex.** `*.pds.example` does not answer for `pds.example`. That is a separate record, and the reason [deployment](deployment.md) keeps agents off the apex in the first place. **Any type that is not at the wildcard node.** A wildcard holding `A` answers `NODATA` for a `TXT` query at a synthesized name. Nothing wildcards the ACME DNS-01 challenge: the authority asks for a literal token at `_acme-challenge.`, so a deployment issuing its own certificates needs a provider that writes real records, and needs two values present at one name simultaneously to cover both the apex and the wildcard in one order. **Anything below a name that exists.** DNS wildcards synthesize at any depth, but only while no closer node exists — publish a real record at `basalt-kestrel.pds.example` and `x.basalt-kestrel.pds.example` stops resolving, because the lookup now wants `*.basalt-kestrel.pds.example` and finds nothing. Per-account records and deep names are mutually exclusive; the TLS certificate rules out the deep names anyway. ## DNS wildcards and TLS wildcards are not the same rule They are easy to conflate and they differ where it matters: | | matches | | --- | --- | | `*.pds.example` in DNS | any depth — `a.pds.example`, `a.b.pds.example` — while no closer node exists | | `*.pds.example` in a certificate | exactly one label, and never the bare parent | So the constraint on how deep an agent hostname may sit is the certificate, not the zone. No authority issues a multi-label wildcard, which is why a deployment with two zones holds two certificates and `didbot_tls::fleet::CertificateFleet` keeps one manager per zone.