GUIDE-RESOLUTION — how a name becomes bytes

Status: Active The meta-rule: a resolution claim is not validated until a cross-impl conformance test exercises the composed chain. Prose — including this guide — does not catch resolution bugs. The RESOLVE-CHAIN-* vectors (§11) are the validation.


1. The two primitives everything rests on (pointer)

Both normative (V7 §1.4, §1.7; PRIMER):


2. The foundational principle: names are receiver-relative

There is no global namespace. A name has no meaning on its own — it means what your resolver-config says it means. The same string can resolve to different peer_ids for two users running different configs (REGISTRY §1, §4, §5: "the substrate gates no name claims … whether a receiver TRUSTS that binding is the receiver's policy").

Three consequences that govern everything below:

  1. "Trusted" is a property of the path you resolved through, not of the name. alice is trusted for you because your config pinned a registry whose signature you verify. For someone else it may be unknown or resolve elsewhere. (REGISTRY §7.4: asserted vs verified.)
  2. Uniqueness is per-receiver. Two registries may bind the same name to different peers; your config's priority order is the tiebreak, and cross-registry ambiguity fails closed by default (REGISTRY §4.1.1, §8.3).
  3. This is DNS-shaped without a single root. Delegated authorities, TTLs, records — but the "root" is whatever your distribution preloaded and you chose to keep (REGISTRY §7 bootstrap-with-precedes; §7.3 = the OS-root-CA analogy). Global agreement on a name is emergent (many receivers trusting the same registry), not built-in.

This principle is the same fact as §5's "a name is authority-scoped": a name has no self-evident meaning, so only an authority's signature gives it one — which is why its resolution ladder must be trust-ordered, never promiscuous.


3. What do I have → where I enter the ladder

Every entry into the system is a starting reference, and each drops you onto the chain at a different rung. "The different pathways in" is just: which reference do I hold?

  WHAT I HAVE                    ENTERS AT                   NEEDS
  ───────────────────────────────────────────────────────────────────────
  a name      alice          top:    name → peer_id      a registry (Layer A)
  a peer_id   z6Mk… (Base58)     middle: peer_id → transport reachability (Layer B)
  a transport wss://… / http URL lower:  connect directly    nothing — dial it
  a content-hash  b3…            bottom: hash → bytes        substitute / any source
  a URL       name@auth/path     top, composed               the whole chain

The registry's job is the top rung only: name → (peer_id + initial transports + trust_anchor). Everything below is NETWORK / TREE / SUBSTITUTE.


4. The one shape, four ladders

Every resolution is the same three-move shape — try nearest/most-authoritative → fall through a preference-ordered ladder → honest typed dead-end — differing only in target, rungs, and how promiscuous the ladder may be (§5).

ResolvingLadder (near → far)Op / hookSpec home
namepeer_id (+transports)local-name → pinned peer-issued → dns-txt/did → aggregators → not_foundsystem/registry:resolve(name)REGISTRY §2, §6, §7
peer_idreachable transportself → cache → host-vouches → registry transports → Unreachabledispatcher ladder; system/peer/transport/{peer}/{profile}NETWORK §6.5, §10
content-hashbyteslocal store → (pending) → substitute backends → 404transparent miss-hook substitute_consultSUBSTITUTE §3, §5
tree-pathhashlocal tree → (authority: owner's signed snapshot)404system/tree:get(path) (local)TREE §1; V7 §1.7

A full URL is these four composed in sequence — a dead-end at any rung is the honest answer for the whole chain, tagged by rung:

name ─REGISTRY▶ peer_id ─NETWORK▶ transport ─(connect)▶ tree-path ─TREE▶ hash ─SUBSTITUTE▶ bytes ▶ render

There is no separate "browse engine" — browsing is walking this chain and re-walking it on each click.


5. The placement rule: self-verifying vs authority-scoped

This is why substitute is a server-side extension but resolution-orchestration (resolve()) is the SDK — and it is structural, not a style call. Two questions decide where any resolver lives:

Q1 — Is the target self-verifying? A self-verifying target (content-hash → bytes: the bytes hash-match from any source) needs no consumer-side trust state — the hash replaces trust. Its ladder may run server-side, transparently, promiscuously. An authority-scoped target (name, path: value depends on who owns it, P2) requires the consumer's own trust policy at each step, which is consumer-side state.

Q2 — Across how many authority boundaries, and whose capability? Substitute fetches from non-authoritative byte sources (CDNs, dumb HTTP) — it never spends the consumer's authority against a third authority-bearing peer. Resolution crosses multiple authority-bearing peer boundaries, each gated by the consumer's own capability — which lives consumer-side.

ResolverSelf-verifying?Consumer walks it?LivesStatus
substitute (content)yes — hash anchorn/a — transparent hookextensionlanded v1.0
resolve() (the chain)no — authority-scoped rungsyesSDKproposal (Draft)
system/resolve delegationnono — thin client (browser-behind-NAT)extension forwarding the consumer's cap-chaindeferred

They are one family at three points. The delegation handler is resolve-as-an-extension, and it forwards the requester's cap-chain — the same mechanism substitute uses (substitute_consult threads cap_chain). Substitute skips the ceremony only because its target is self-verifying.

The rule: a resolver lives server-side as an extension when its target is self-verifying; client-side in the SDK when it composes across authority-scoped boundaries; and when a thin consumer can't walk it, it delegates to a handler that forwards the consumer's cap-chain — never one that acts with its own authority.


6. Name syntax & semantics — "name at registry"

The mechanism is fixed; the syntax is a convention. Every lookup is one call: system/registry:resolve(name). Which backend answers is decided in two stages: name_format_dispatch (REGISTRY §4.1 step 2) — a list of wildcard-pattern → backend-kinds rules matched against the name string (* is the only metacharacter — REGISTRY §4's closed grammar, not a POSIX/shell glob) — decides which backends are eligible, and then resolver_chain priority decides the order they are tried in, first validated hit winning (REGISTRY §4.1 step 3, §4.1.1). The dispatch list is a filter; it carries no precedence of its own. So the name's shape selects the naming authority, and the substrate does not care what the shape is. This guide standardizes four shapes so apps, links, and registries all speak the same grammar. No substrate change — the grammar is realized by the default name_format_dispatch globs a distribution ships.

6.1 The four shapes (recommended grammar)

Normative home for the default globs that realize these: EXTENSION-REGISTRY §4.1a.

ShapeExampleMeansRoutes via glob (example)Backend
bare namealice"resolve through my default chain"* (catch-all)local-name, self-certifying, out-of-band, peer-issued — no name-transmitting backend (REGISTRY §4.1 step 2, §4.1a row 6)
registry-scoped (email-shaped)alice@entity-church"this name at this registry"*@entity-churchpeer-issued (the named registry)
domain-scopedalice@example.org"resolve via this DNS domain"*@*.* (dotted authority)dns-txt / well-known-url
scheme-typeddid:web:example.org, did:key:z6Mk…, example.etha naming system with its own syntaxdid:web:*, did:key:*, *.ethdid-web / self-certifying / consensus-anchored
self-certifyingz6Mk… (a Base58 peer_id)the name is the keydecodes as V7 §1.5 peer-idnone — self-verifying

This is exactly the user's framing: alice@entity-church reads "Alice at the Entity Church Registry" — the @authority names which registry, the local part is the name within it. Bare alice resolves against your own local handles and pins — the catch-all admits no backend that would transmit the name (REGISTRY §4.1 step 2), so telling a third party about a name is what the @authority form is for. @authority is not decoration: it is the user naming the authority they are willing to tell.

6.2 Why @ (email-shaped) for the primary registry-scoped form

Alternative considered — a reg: scheme (reg:entity-church/alice). Valid and a glob can route it; rejected as the primary form because it's heavier and less familiar than @. Kept available for tooling that prefers explicit schemes. (Design-space: the substrate accepts any convention; we recommend @, document reg: as also-valid.)

6.3 The targeting unification (name@X where X identifies the authority OR pins the peer)

name@X qualifies the name by X. The resolver dispatches on what X is — and this unifies targeting with the link-form's verification pin (PROPOSAL-UNIVERSAL-RESOLUTION §7):

Because peer_ids are Base58 multikey (V7 §1.5) and registry handles/domains are not, the resolver tells them apart by structure. This is the recommended unification and the cleanest answer to "reverse-lookup when clicking around": links carry name@peer_id — name to stay a forward registry resolve through the host-vouches rung, peer_id to verify the answer. Landed: one separator carries both meanings, decided structurally — EXTENSION-REGISTRY §4.1 step 1a makes the peer-id decode of X precede glob dispatch, so a name that pins is never sent to a registry to answer a question the consumer can answer locally.

6.4 Backend status — and where the community comes in

The grammar above names every backend the substrate is designed to carry. Most are not built yet — the substrate contract exists, the concrete backend doesn't. We are shipping the substrate + the conventions and actively want community input on the per-backend mechanics (especially the web-native ones), because how an entity peer maps onto DNS / well-known / DID is a place where existing-web interop matters more than our preferences. Honest status:

BackendStatusWhat's missing
self-certifying (name = peer_id)built (trivial)
local-name (your own handles)built v1.0
pinned / out-of-bandbuilt
peer-issued (a registry vouches; the Entity Church Registry path)built — resolve backend, curated issuance (REGISTRY §6a.8) and live register-request (§6a.9)nothing in the substrate. What remains is a deployment: a registry peer that runs, its bindings, and the pinned key a distribution ships (REGISTRY §7.4)
dns-txtpaperworking out the kinksrecord format; DNSSEC/DoH trust qualification; the resolver backend
well-known-urlpaperworking out the kinksthe .well-known/ path + binding artifact shape; the resolver backend
did-web / did-keypaperworking out the kinksDID-document ↔ binding mapping; inbound resolve + outbound present-as
consensus-anchored (e.g. ENS)paper / futurechain-record format; the resolver backend
aggregator / federation (registries-of-registries, live)deferredRELAY Mode A cross-peer subscription

Open invitation: the web-native backends (dns-txt, well-known-url, did-web) are exactly where we want naming-services / authorities to propose the mapping that fits how they already publish identity. The substrate's contract (REGISTRY §2–§5) is the fixed part; the binding-artifact shape per pathway is open. A backend "works" once it speaks the resolver contract, returns a verifiable ResolutionResult, and a receiver chooses to trust its trust_anchor.

6.4a What the grammar is NOT


7. Trust model — whom you trust, and how registries relate

Trust is receiver-side (REGISTRY §5). You decide which authorities you accept, per resolver-config:

"We trust these registries; community runs their own." This is just multiple backends in your chain, each with its own accepted anchors:

resolver_chain (priority asc):
  0  local-name            (your own handles — free, authoritative for you)
  1  peer-issued: entity-church-registry   accepted: [peer_issued:{ec_peer_id}]   ; the standard entity-native registry
  2  peer-issued: example-registry        accepted: [peer_issued:{ex_peer_id}]   ; a community registry you also trust
  3  did-web / dns-txt      (web-anchored names)         ; when those backends ship

Consulting several registries = "registries of registries", three shapes (EXPLORATION-REGISTRY-NETWORK §4):

  1. Chained config (BUILT today) — the chain above is "consult several registries." Priority order + name_format_dispatch route the query. Most of the felt need is already here.
  2. Static aggregation via precedes (BUILT) — a distribution preloads signed bindings from many registries, each verifiable against its own issuer (REGISTRY §7). A union baked at build time; works offline; only lacks live freshness.
  3. Live federation / aggregator (DEFERRED) — a Mode-A relay subscribes to N registries and serves the live union as one backend (REGISTRY §8.2); deferred on cross-peer subscription. Does not re-sign — receivers verify originals.

7a. Pinning a registry's key does not pin its host

A static registry is two parties, and operators keep reading it as one. The registry signs; an origin — a bucket, a CDN, a mirror — serves. You pin the registry's key, which is exactly right, and that key says nothing about who hands you the bytes.

What the origin can do, even though every artifact it serves is genuinely signed:

It canIt cannot
Choose which signed binding answers which name — serve you the real binding for protocol.example when you asked for foundation.exampleforge a signature
Withhold an artifact indefinitely — most importantly a revocationalter a body
move your clock

The first is closed by the resolver: REGISTRY §6a.4 requires binding.name == norm, so a substituted binding is refused and the chain advances. You get this for free from a conformant resolver; there is nothing to configure.

The second is not closable, and the honest sentence is the one to plan around:

Against a hostile origin, a revocation is bounded by the binding's TTL — not by the revocation's publication.

You cannot detect withholding. A revocation the origin is sitting on and a revocation that was never issued are byte-identical at your end. So the TTL is your only lever, and that is what makes it an operational decision rather than a default to accept. Short TTLs mean faster revocation and more re-fetching; long TTLs mean the reverse. A peer-issued binding may not carry ttl: null at all (REGISTRY §6a.3) — that would make it permanently unrevokable — and if you are running the registry, default_ttl is the field this decision lives in.

If you need to resolve offline indefinitely, use a pin, not a long TTL. Pins are the user's own assertion, are sticky, and override the chain (REGISTRY §4, §4.1.2). Stretching a binding's TTL to approximate one only moves the cliff, and it weakens the revocation bound for everyone using that registry.

Registries vouching for registries (cross-reference / hierarchy) is expressible via issuer_attestation on a peer-issued binding (REGISTRY §3 — "the registry's authority cert"): registry A can attest to registry B's authority, letting a receiver who trusts A extend trust to B. This is the consensus/hierarchy direction; named, not yet specified — lives with the peer-issued backend proposal (§8).


8. Publishing a name — how one gets into a registry

Resolution (§1–§7) is the read side. Getting a name others can resolve is the write side, and it splits sharply by what's built:

The end-to-end the user described works today via precedes: alice@entity-church → peer-issued backend → fetch the registry's signed binding (precedes or http-poll) → verify signature against the pinned Entity Church Registry key → ResolutionResult{peer_id, transports} → connect over http-poll → fetch the lab's content by hash. The registration of that binding (how alice got into the registry) is the gap; the resolution of it is built.


9. The capability model of resolution (pointer)

A resolution chain is also a capability chain — each peer-crossing rung is gated independently by the target peer, presenting the consumer's own cap (PROPOSAL-UNIVERSAL-RESOLUTION §4):

Two structural facts (the typed Outcome must distinguish them):


10. Built vs paper (target work at what's real)

PieceState
Two-hop path→hash→bytes (P1)built / normative
Registry substrate + meta-resolver + binding + resolver-configlanded v1.0
local-name backendlanded v1.0
bootstrap-with-precedes + Entity Church Registry worked examplelanded v1.0 (§7.4) — the demo's registration model
Substitute content ladder (HTTP Mechanism A)landed v1.0
Receiver-relative naming model (§2)implied across REGISTRY §1/§4/§5; named here
Name grammar @/scheme/bare (§6)recommended convention — needs a small proposal to pin default globs
peer-issued backend + registration flow (§8)gap — its own proposal (the real unblock)
dns-txt / did-web / well-known / consensus backendspaper — contract defined, each its own proposal
Live federation (Mode A aggregator)deferred on cross-peer subscription
resolve() SDK seam + typed Outcome + RESOLVE-CHAIN-* vectorsdesigned, not built (Draft proposal; W1)
DID/DNS outbound (present-as did:key / did:web)deferred (REGISTRY §12)

11. Open questions

  1. Name grammar (§6): closed. EXTENSION-REGISTRY §4.1a (the default list), §4.1 step 2 (the catch-all MUST), §4.1 step 1a (one separator, peer-id decode first).
  2. Peer-issued registration (§8): the unblock. Admission policy (registry's own cap surface) + ownership-proof challenge. Its own proposal.
  3. resolve() SDK contract: route the Draft to cohort; is the typed Outcome set (NotFound/PolicyRejected/Unreachable/Denied/Pending) complete + minimal across impls?
  4. Denied vs NotFound confidentiality: the SDK MUST surface the peer's status faithfully, never synthesize a more-informative Denied where the peer chose NotFound (V7 §5.5a). Confirm cross-impl.
  5. Registry-vouches-for-registry (§7): the issuer_attestation cross-registry trust direction — scope with the peer-issued proposal.
  6. Validation: the RESOLVE-CHAIN-* vectors (full name@registry/path → bytes walk) — the only thing that validates this guide per the meta-rule.

Spec homes (canonical — normative text lives here, not in this guide)