Identities for entities did.bot
agent llm did
didbot plan did-minting.md
6.7 kB
Markdown
at commit 18ba4fe0


id: did-minting title: The server mints the identifier, not its caller status: open crates: [didbot-pds, didbot-identity] dependsOn: [agent-accounts] exitCriterion: > A provisioning request carries no identifier and gets an account whose DID the server chose; two requests carrying the same correlation key get the same account; and a DID whose account has been deleted is never minted again. #

did-minting #

A did:web DID is a hostname, and this server takes that hostname from whoever asked for the account. ProvisionRequest::agent_id is documented as "the DNS label the DID will be minted from", and Provisioner::provision passes it straight to AgentDid::mint. The server checks two things about it: that it is a legal DNS label (validate_label — lowercase ASCII, digits, hyphens, no hyphen at either end) and that the minted host sits at or below the zone. Neither of those is a claim about identity.

Everything else about the identifier is the caller's decision. That is the wrong side of the boundary for the one value in this system that can never be changed afterwards.

What follows from it #

A harness may not have an identifier to give. A top-level session has no agent_id of its own, and a client papers over it by deriving a label out of whatever session bookkeeping it holds, because the harness does not promise the format. That is a good workaround living in the wrong place — it is one client's convention, not a rule the server enforces, and a second client is free to do something else.

Nothing makes the identifier globally unique. derive_agent_id renders 40 bits of an FNV-1a digest, and its own documentation puts the collision odds at roughly one in twenty thousand for ten thousand agents on one server. It argues that is acceptable because a collision is an availability problem: the second provisioning fails against a DID that already exists. That argument holds only for one server and only while both accounts are live.

A deleted DID can be minted again. The duplicate check is self.store.get(&did).is_some() — live accounts only. Names get a hold (DEFAULT_HOLD, thirty days) precisely so a recycled name cannot point at a different agent. DIDs get nothing, even though a DID is the thing at:// URIs and signatures are written against. Provisioner::remove already knows this: it forgets the commit history because "a later account minted at the same DID [would] inherit a head naming blocks nothing holds". It defends the block store against the scenario and leaves the identity alone.

The caller chooses what its permanent public name says. Nothing stops a request minting a label that reads as somebody else's agent, or one that carries harness-internal detail — a ticket id, a path, a username — into a name that is published in DNS, served in a DID document, and never revocable.

These are four symptoms of one thing: the identifier is an input where it should be an output.

The shape of the fix #

The server mints. A request carries whatever correlation the caller has — possibly nothing — and that value is used to answer "does this context already have an account", never to name it.

What this is not #

Not a change to attestation. What authorizes a provisioning is node's and attestation's question, and it stays theirs; this epic is only about who chooses the name once a request is admitted.

Not zone-scale. That epic counts names against a finite pool and records against a provider's quota. This one is about where a single name comes from, and a wider mint space makes zone-scale's arithmetic slightly worse rather than better.

Done #

Nothing yet.