Identities for entities did.bot
agent llm did
didbot plan zone-scale.md
11 kB
Markdown
at main


id: zone-scale title: The zone runs out of names before it runs out of accounts status: open crates: [didbot-pds, didbot-name, didbot-dns] dependsOn: [agent-accounts] exitCriterion: > A deployment holding a hundred thousand live accounts issues a name and publishes a hostname without failing, and an operator can see how close it is to the next limit before reaching it. #

zone-scale #

An account costs a name and a hostname, and both are finite in ways the rest of the system is not.

The default namer is mineral+creature, which is 128 words against 112: about fourteen thousand names. Naming::issue makes sixteen random attempts before giving up, so the failure rate is occupancy to the sixteenth — negligible at half full, around three per cent at eighty, around nineteen at ninety. The usable ceiling is therefore near eleven thousand simultaneously held names, not fourteen thousand, and provisioning starts failing intermittently rather than stopping.

Every account keeps its name until the operator deletes it, and a deleted account's name returns to the pool thirty days later, so "held" tracks the cumulative population the operator has not deleted. Adding word lists moves this a long way for almost nothing, which is the point of writing the numbers down: the fix is cheap and knowing when to apply it is not free.

Done #

  • | spec | pool size | leaks |
    | --- | --- | --- |
    | `mineral+creature` (word pair) | ~14,000 (usable ceiling ~11,000; see the arithmetic above) | nothing |
    | `Counter` (`counter`) | unbounded (`u64::MAX`) | the deployment's total mint count |
    | `mineral+creature+n` (a word pair and the counter) | unbounded (`u64::MAX`) | the deployment's total mint count |
    | `mineral+creature+t` (a word pair and the clock) | the word product per second | when each account was minted, to the second |
    | `Timestamp` (`timestamp`) | one per second | when each account was minted, to the second |
    | `Uuid` (`uuid` / `uuid-compact`) | 2^122 | nothing |
    | `Random` (`random32:<n>` / `random36:<n>`) | `alphabet_size^n`, e.g. 36^10 ≈ 3.6 × 10^15 | nothing |
    
    `Random::pool_size` and `Fragments::capacity` both exist for the same
    reason: a deployment choosing a spec can read the number rather than
    compute it, and `describe()` folds it into the log line every namer
    already writes at startup.
    
    Every generated label goes through `didbot_name::check`, which no
    longer hand-maintains the DNS-label character rules: it calls
    `didbot_identity::validate_label` — now `pub`, and the same function
    `AccountDid::mint` checks an agent id against before it becomes part of a
    `did:web` identifier — so there is one legality rule for both places a
    label has to be legal, not two that could disagree. A UUID's canonical
    form is emitted with its hyphens in exactly the positions that rule
    allows; no other encoding (base64, uppercase) is offered, on purpose.
    
    `Counter` cannot collide with itself, so it declares
    `Namer::max_useful_attempts() == Some(1)` — a new default method on
    `Namer`, `None` for every other namer — and `Naming::issue` honours it
    rather than spending the usual sixteen-attempt budget on a namer that
    cannot need it.
    
    **A word pair and a count in one name.** A template may name `{n}`
    once, which draws from the same counter instead of a word list:
    `mineral+creature+n` gives `basalt-kestrel-4813`. Such a template has
    no pool to exhaust and cannot collide with itself, so it declares
    `max_useful_attempts() == Some(1)` for the same reason `Counter` does,
    and it discloses the same thing `Counter` does — the mint count, and
    the order. `n` on its own is the `counter` naming rather than a second
    namer beside it. One `DurableCounter` feeds every template a
    deployment configures, including `--names-if-down`'s, so changing the
    spec keeps counting. `[names] counter_encoding` and
    `--counter-encoding` write the count decimal (the default) or in
    lowercase Crockford base32, which fits `u64::MAX` in thirteen
    characters instead of twenty.
    
    **A time in one name too.** A template may also name `{t}` once,
    which stamps the current unix time in whole seconds under that same
    encoding: `mineral+creature+t` gives `basalt-kestrel-1790000000`, and
    `t` on its own is the `timestamp` naming rather than a second namer
    beside it. `Timestamp` reads the one clock this crate has and now
    carries seconds rather than base36 nanoseconds. Every name minted in
    one second carries the same stamp, so a template with `{t}` and no
    `{n}` keeps the ordinary retry budget and numbers from the first
    retry the way `Timestamp` does; with `{n}` beside it the template
    reports one useful attempt as before. `counter_encoding` governs both
    numbers, so a label carrying a count and a time reads in one number
    system rather than two.
    
    The counter survives a restart. `didbot-pds::names::DurableCounter`
    writes the value it is about to hand out into the write-ahead log
    *before* returning it — the same check-append-apply order
    `NameRegistry` already keeps for name claims — and replay resumes the
    counter past every value a previous run promised. That is a new `Entry`
    variant (`CounterAdvanced`), so it bumped `layout::LAYOUT` — which has
    moved several times since and reads 9 today, so do not take a number
    from this entry. `didbot-pds`'s `--names counter`
    shares the durable counter with `--names-if-down counter`, and runs
    in-memory, restarting at zero, without `--data` — the same split every
    other durable-optional store here makes.
    
  • **An open question this closed the easy half of, and left the hard
    half for the owner to overrule.** Two things collide over one
    hostname, and this closed the direction where a zone already exists:
    its apex is reserved, so the namer can never mint an agent there. The
    inverse direction also needed closing — `ZoneManager::add_zone` refuses
    to create a zone at a hostname a live agent already answers at, naming
    the DID so an operator knows what to delete first.
    
    What is left is the question in between: **may a zone be created at a
    name still inside its release hold?** Deleting an agent does not free
    its name immediately — `NameRegistry` holds it `Released` for thirty
    days precisely so an `at://` URI copied out of a log does not later
    point at a different agent. The conservative answer treats a zone apex
    as inheriting that same hold, on the grounds that the hold's whole
    purpose — a stale reference not silently repointing — applies just as
    much to a zone suddenly answering where an agent used to. The
    permissive answer notes that an agent DID and a zone apex are
    different identifiers, so the collision is weaker than agent-vs-agent
    and the wait may be needless caution.
    
    `ZoneManager::add_zone` implements the **conservative** answer: it
    refuses while the apex name is held, and reports when the hold
    expires. This is a judgment call, not a settled one — a deliberate
    operator override (free the name explicitly, then retry) is a
    reasonable follow-up and is not implemented. Documented here, per the
    project's own posture on this kind of decision, so the owner can
    overrule it rather than discover it.