Namespace Resolution Protocol v0.1
neurons.me / suiGn
Status: Draft — Working Document
License: CC0 1.0 Universal — Public Domain
Preamble
This document specifies the Namespace Resolution Protocol (NRP) for the me:// URI scheme.
The NRP defines how a me:// URI is resolved from a symbolic address into a concrete semantic value, across a distributed mesh of surfaces — without a central registry, without a central server, and without requiring persistent connectivity.
It is the protocol that closes the gap between:
Semantic resolution — already implemented in
this.me: local, mathematical, derivation-based. The kernel resolvesme.get("wallet.balance")entirely offline.Topological resolution — the new work: how a requesting surface finds and reaches the surface that holds the target namespace, path, and key material.
These two layers are separate concerns, implemented separately, but always composed in that order:
me://jabellae.cleaker.me[surface:iphone]/wallet.balance
│ │ │
topological topological semantic
(find the (find which (resolve the
namespace) surface) path locally)
The NRP specifies the topological layer. The semantic layer is already specified by this.me.
1. Definitions
Namespace — A named semantic domain. Represented as a human-readable label (e.g., jabellae.cleaker.me). A namespace is owned by whoever holds its root key material. There is no central authority that grants or revokes a namespace.
Surface — A physical or logical runtime context that can hold a .me kernel instance and participate in the mesh. Examples: an iPhone app, a MacBook daemon, a server process, a browser tab. A surface has a name within a namespace.
Surface identity — The cryptographic identifier of a surface within a namespace:
surface_id = hash(namespace_key, surface_name)
This is never the cleartext surface_name. Only holders of namespace_key can derive or verify a surface_id.
Namespace key — The root secret from which all key material in a namespace is derived. Equivalent to the seed in the derivation-based identity model. Never transmitted; always local.
Endpoint descriptor — A transport-layer locator for a surface (IP:port, relay address, onion address, etc.). Stored encrypted inside the surface index. Not the surface's identity — just how to reach it right now.
Surface index — A .me secret space, scoped under the namespace key, that maps surface identities to their current endpoint descriptors. It is the mesh's contact book. Every surface that holds the namespace key can read and write it.
Claim token — A one-time, time-limited token that authorizes a new surface to join a namespace. Generated by an existing surface. Expires. Consumed on first use.
Stealth root — A secret scope root that returns undefined on resolution. This is an honest absence, not an error. The NRP must preserve this distinction at the network level.
Semantic island — A self-contained .me kernel instance holding a fragment of a namespace. Islands do not need to be constantly connected. They interact by exchanging mathematical hashes, not cleartext data.
2. The Two-Layer Model
Resolution of a me:// URI proceeds in two phases, always in this order.
Phase 1 — Topological resolution
Input: namespace + selector
Output: a reachable surface endpoint
The requesting surface must find the surface that can serve the requested path. This involves:
Deriving the target
surface_idfrom the local namespace key and the selector's surface name.Looking up the endpoint descriptor for that
surface_idin the local surface index.Establishing a connection to that endpoint.
If the surface index does not contain the target surface_id, or if the target surface is unreachable, topological resolution fails. The request does not proceed to Phase 2.
Phase 2 — Semantic resolution
Input: path + key material (if the path is within a secret scope)
Output: the resolved value, or undefined, or a closed failure
Once the requesting surface has a connection to the target surface, it sends a read request for the path. The target surface resolves the path locally using its .me kernel, and returns a disclosure envelope (see Section 6).
Semantic resolution is entirely local to the target surface. The requesting surface never sees the target's kernel state directly — only the disclosure envelope for the specific path requested.
3. Surface Identity and the Surface Index
3.1 Surface identity derivation
A surface's identity within a namespace is:
surface_id = HMAC-SHA256(namespace_key, "surface:" + surface_name)
This produces a 32-byte opaque identifier. It is the surface's public identity on the mesh. Parties without the namespace_key cannot derive it, verify it, or enumerate surfaces in the namespace.
The surface_name is a human-readable label chosen by the surface operator (e.g., "iphone", "macbook", "daemon-home"). It is meaningful only to namespace holders.
3.2 Surface index structure
The surface index is a .me secret space:
me.surfaces["_"]("namespace_key");
// For each known surface:
me.surfaces[surface_id].name("iphone");
me.surfaces[surface_id].endpoint("encrypted_endpoint_descriptor");
me.surfaces[surface_id].last_seen(timestamp);
me.surfaces[surface_id].public_key("surface_ephemeral_pubkey");
The index is stored locally on every surface that holds the namespace key. There is no canonical remote copy. Surfaces sync the index through the mesh on reconnection (see Section 7).
The endpoint_descriptor field is doubly encrypted: first under the namespace key (so only namespace members can read it), and second it may use an ephemeral surface key for transport security. The endpoint is volatile and may change. The surface_id is stable.
3.3 Reading from the surface index
A surface resolves me://[surface:iphone]/some.path as follows:
1. Compute: target_id = HMAC-SHA256(namespace_key, "surface:iphone")
2. Read: endpoint = me("surfaces." + target_id + ".endpoint")
3. If endpoint is undefined → surface not found → fail with NRP_ERROR_SURFACE_NOT_FOUND
4. Decrypt endpoint descriptor → get transport address
5. Proceed to Phase 2 (semantic resolution)
4. The Claim Ceremony
The claim ceremony is how a new surface joins a namespace. It replaces the need for a central authority to "add" a surface.
The current implementation has a claim token as a temporary in-memory value. This section formalizes and hardens it.
4.1 Token generation
An existing surface in the namespace (the inviting surface) generates a claim token:
nonce = random_bytes(16)
expiry = now() + TTL // recommended TTL: 300 seconds
claim_token = HMAC-SHA256(namespace_key, nonce + expiry)
claim_payload = base64url(nonce + expiry + claim_token)
The claim payload is encoded into the URI:
me://jabellae.cleaker.me[claim:CLAIM_PAYLOAD]/new-surface
And optionally rendered as a QR code for physical proximity pairing (already implemented in Cleaker).
4.2 Token verification
The new surface presents the claim payload. The inviting surface (or any namespace surface that receives the pairing request) verifies:
1. Decode claim_payload → extract nonce, expiry, presented_token
2. Check: now() < expiry → if expired, reject with NRP_ERROR_CLAIM_EXPIRED
3. Recompute: expected_token = HMAC-SHA256(namespace_key, nonce + expiry)
4. Check: presented_token == expected_token → if not, reject with NRP_ERROR_CLAIM_INVALID
5. Check: token has not been used before (nonce registry, local) → if used, reject with NRP_ERROR_CLAIM_CONSUMED
4.3 Key handshake
After verification, the inviting surface and new surface perform a key handshake:
1. New surface generates an ephemeral keypair: (eph_pub, eph_priv)
2. New surface sends eph_pub to inviting surface
3. Inviting surface encrypts namespace_key with eph_pub:
encrypted_namespace_key = ECIES(eph_pub, namespace_key)
4. Inviting surface sends encrypted_namespace_key + its own surface_id + current surface index snapshot
5. New surface decrypts with eph_priv → receives namespace_key and surface index
6. New surface registers itself in the surface index:
surface_id_new = HMAC-SHA256(namespace_key, "surface:" + new_surface_name)
me.surfaces[surface_id_new].endpoint(...)
me.surfaces[surface_id_new].public_key(eph_pub)
7. New surface broadcasts its registration to all online surfaces (see Section 5.3)
4.4 Token consumption
The nonce is added to a local consumed-nonce registry after a successful claim. The registry is pruned of entries older than the maximum TTL. This prevents replay attacks.
me.system.consumed_nonces[nonce].at(timestamp);
5. Selector Resolution Rules
The me:// URI selector determines how topological resolution proceeds.
5.1 [current] — current surface
me://jabellae.cleaker.me[current]/profile.name
// or equivalently:
me://profile.name
Resolves entirely locally on the surface receiving the request. No topological resolution needed. Phase 1 is a no-op.
5.2 [surface:name] — specific surface
me://jabellae.cleaker.me[surface:iphone]/runtime.battery
- Derive
target_id = HMAC-SHA256(namespace_key, "surface:iphone") - Look up endpoint in surface index
- If not found:
NRP_ERROR_SURFACE_NOT_FOUND - If found but unreachable:
NRP_ERROR_SURFACE_UNREACHABLE - If found and reachable: proceed to semantic resolution
5.3 [] — broadcast to all surfaces
me://jabellae.cleaker.me[]/chat
Read the full surface index
Iterate all known
surface_identriesFor each entry, attempt to reach the endpoint
Send the request to all reachable surfaces
Collect responses (or timeout)
Offline surfaces are skipped — broadcast is best-effort
The requesting surface may optionally queue undelivered messages for retry (implementation-defined)
Broadcast does not wait for all surfaces to respond. It fires and collects what it can within a configurable timeout window.
5.4 [claim:token] — pairing handshake
me://jabellae.cleaker.me[claim:CLAIM_PAYLOAD]/new-surface
Triggers the claim ceremony (Section 4). Does not proceed to semantic resolution — the claim selector is a control-plane operation, not a data-plane operation.
6. The Disclosure Model
This is what a target surface returns for each category of path request. This model must be implemented consistently across all surfaces. A surface that leaks structural information in error responses breaks the security model.
6.1 Public path
The path exists and is not within any secret scope.
Returns: the resolved value.
{
"status": "ok",
"path": "profile.name",
"value": "José Abella",
"origin": "public"
}
6.2 Stealth root
The path points to the root of a secret scope (e.g., wallet when wallet is declared with ["_"]).
Returns: undefined. Explicitly. Not a 404, not an error, not a "path not found." The absence is honest and intentional.
{
"status": "ok",
"path": "wallet",
"value": null,
"origin": "stealth"
}
The distinction between null (stealth root) and NRP_ERROR_PATH_NOT_FOUND (path does not exist at all) is architectural. Callers must not be able to distinguish "this path is secret" from "this path does not exist" — both return a form of undefined. The implementation choice of null vs. omitting the field is left to the transport binding, but the semantics must be indistinguishable to an observer without the secret key.
6.3 Secret leaf — correct key
The path is within a secret scope, and the requesting surface presents the correct key (via the secret:key@ prefix in the URI, or through a pre-established shared secret).
Returns: the resolved value.
{
"status": "ok",
"path": "wallet.balance",
"value": 12480,
"origin": "stealth"
}
6.4 Secret leaf — wrong or absent key
The path is within a secret scope, and the requesting surface does not present the correct key, or presents no key.
Returns: same envelope as stealth root. The response must be indistinguishable from case 6.2. A wrong key must not produce a different error than no key.
{
"status": "ok",
"path": "wallet.balance",
"value": null,
"origin": "stealth"
}
This is the "fail closed, leak nothing" invariant. It directly mirrors the ["_"] axiom in the .me kernel (A0 + A2).
6.5 Path does not exist
The path is not declared in the kernel and is not within any secret scope.
Returns:
{
"status": "not_found",
"path": "does.not.exist"
}
This is the only case where a not_found status is returned. It must only be returned for genuinely absent paths that are not near any secret scope. If there is any ambiguity about whether a path might be secret, return the stealth envelope (6.2) instead.
7. Surface Index Synchronization
The surface index is local on each surface. When surfaces reconnect after being offline, they may have divergent views of the index. The protocol uses a simple Last-Write-Wins (LWW) strategy, consistent with the .me kernel's A9 axiom (deterministic conflict resolution).
7.1 Sync on reconnect
When surface A reconnects to surface B:
- A sends its surface index version vector (a map of
surface_id → last_seen_timestamp) - B compares against its own version vector
- Each sends the other the entries that are newer on its side
- Both apply the updates using LWW on
last_seentimestamp
7.2 Surface expiry
A surface entry that has not updated its last_seen timestamp within a configurable window (e.g., 30 days) may be marked stale. Stale surfaces are kept in the index but deprioritized in routing. They are not deleted automatically — deletion requires an explicit ["-"] operation by a namespace holder.
7.3 Endpoint volatility
Endpoint descriptors are volatile. A surface's IP address or relay address may change frequently. Each surface is responsible for updating its own endpoint descriptor in the index whenever its transport address changes, and broadcasting the update to all online surfaces.
8. Error Reference
| Code | Meaning |
|---|---|
NRP_ERROR_SURFACE_NOT_FOUND |
surface_id not in local index |
NRP_ERROR_SURFACE_UNREACHABLE |
surface_id found but endpoint not reachable |
NRP_ERROR_CLAIM_EXPIRED |
claim token TTL exceeded |
NRP_ERROR_CLAIM_INVALID |
HMAC verification failed |
NRP_ERROR_CLAIM_CONSUMED |
nonce already used |
NRP_ERROR_NAMESPACE_UNKNOWN |
no namespace key available for this namespace |
NRP_ERROR_PATH_NOT_FOUND |
path does not exist and is not near any secret scope |
NRP_ERROR_TRANSPORT |
network-level failure (connection refused, timeout, etc.) |
9. What Is Not Specified Here (v0.1 scope)
The following are intentionally deferred to later versions:
Transport binding — This document specifies the protocol semantics, not the wire format. A v0.2 transport binding document will specify the exact message encoding (JSON-over-WebSocket, protobuf, etc.) and the TLS/noise handshake for the surface-to-surface connection.
Relay infrastructure — When two surfaces cannot reach each other directly (NAT, firewall, different networks), a relay is needed. The relay protocol is not specified here. The relay must not be able to read message content — it is a blind forwarder. Relay discovery and selection are deferred.
Namespace key distribution across surfaces — This document assumes the namespace key is already present on the requesting surface. The initial key bootstrap (how the first surface gets the namespace key from the derivation seed) is specified by the .me kernel, not the NRP. Subsequent key sharing is handled by the claim ceremony (Section 4).
Multi-namespace resolution — A surface may hold keys for multiple namespaces. How a surface selects the correct namespace key when a URI omits the namespace qualifier is implementation-defined in v0.1.
Revocation — How to revoke a surface from a namespace (e.g., a lost device) without a central authority is an open problem. A candidate mechanism is a signed revocation entry in the surface index, broadcast to all surfaces. Deferred to v0.2.
10. Design Principles
These are the invariants that any implementation must preserve. They are not implementation details — they are the protocol's soul.
1. No central registry. The surface index is local to each namespace holder. Discovery requires holding the namespace key. There is nothing to hack that reveals a complete list of surfaces or identities.
2. Identity is computation, not storage. A surface's identity is derived from a key and a name. It is not assigned by an authority and not stored in a global directory.
3. Honest absence. A stealth root returns undefined, not an error. The protocol does not reveal the existence of secret scopes to parties without the key. Absence and secrecy are indistinguishable from the outside.
4. Fail closed. Wrong keys produce the same response as absent keys. The protocol leaks no information about why a resolution returned undefined.
5. Minimum surface area. Surfaces exchange only what is necessary. A broadcast sends a path read request — not the full kernel state, not the surface index, not any metadata beyond what the request requires.
6. Mathematical fragmentation. No single surface holds a complete picture of any namespace. The identity is distributed across surfaces by design. Stealing one surface does not compromise the namespace.
7. Offline-capable. Local resolution ([current] or no selector) works with no network access. The protocol degrades gracefully: offline surfaces are skipped in broadcast, missing surfaces return NRP_ERROR_SURFACE_UNREACHABLE. The local kernel is never blocked by network state.
Appendix A — Formal Grammar (ABNF, from URI scheme v1)
me-uri = "me://" [ namespace ] [ selector ] [ "/" path ]
namespace = 1*( ALPHA / DIGIT / "." / "_" / "-" )
selector = "[" ( "current" / "" / "surface:" surface-name / "claim:" token ) "]"
surface-name = 1*( ALPHA / DIGIT / "-" / "_" )
token = 1*( ALPHA / DIGIT / "-" / "_" )
path = *( VCHAR / "/" )
The secret:key@namespace prefix for authenticated access is parsed as:
me-uri-auth = "me://" "secret:" secret-key "@" namespace [ selector ] [ "/" path ]
secret-key = 1*( ALPHA / DIGIT / "-" / "_" )
Appendix B — Open Questions for v0.2
These are the questions this document intentionally leaves open, in priority order:
Relay protocol — How do two surfaces on different networks find a common relay, and how does the relay stay blind?
Surface revocation — How does a namespace holder revoke a lost or compromised surface without a central authority?
Namespace discovery — Can a surface discover other namespaces it does not already hold keys for? (Current answer: no. Is this the right answer forever?)
Index consistency under partition — If the mesh is partitioned for a long time and surfaces update their own entries, what is the merge strategy beyond LWW?
Derivation-based namespace bootstrap — Should the namespace key be derivable from a BIP-39 mnemonic or similar standard seed phrase, so it can be reconstructed without any network access?
Cross-namespace pointers — The
["->"]operator in.mecan create semantic links between namespaces. How does the NRP resolve a path that crosses namespace boundaries?
∴ Witness our seal
suiGn / neurons.me
v0.1 — Draft
Top comments (0)