| … | … | @@ -0,0 +1,153 @@ |
| 1 | # Naming — hashes are unique, names are not global |
| 2 | |
| 3 | **Status:** design. Nothing built. |
| 4 | **Date:** 2026-09-05 |
| 5 | **Sits beside:** `SOCIAL-MODEL.md`, which specifies **addressing** and says nothing about **naming**. |
| 6 | This is that half. |
| 7 | **Grounded in:** keet's key derivation (`keet-core/lib/core/invitations/index.js`), pgh's |
| 8 | `NameRegistry` (`pgh/lib/name-registry.js`), and `ROOMS-ARE-THE-COMPUTER.md`'s discovery model. |
| 9 | |
| 10 | --- |
| 11 | |
| 12 | ## 0. The claim |
| 13 | |
| 14 | > **Hashes are unique. Names are not global. There are global namespaces you can subscribe to.** |
| 15 | |
| 16 | That is the whole design. Everything below follows from it. |
| 17 | |
| 18 | A namespace is not an authority — it is **a subscription**. You join it the way you join a room, and |
| 19 | its names become resolvable *to you*. Two people subscribed to different namespaces resolve the same |
| 20 | string to different things, and that is correct rather than a conflict. |
| 21 | |
| 22 | ## 1. Why there is no third option |
| 23 | |
| 24 | This is Zooko's triangle: a naming system can be **human-meaningful**, **globally unique**, or |
| 25 | **decentralised** — any two. Every escape attempt pays somewhere: |
| 26 | |
| 27 | | system | gives up | |
| 28 | |---|---| |
| 29 | | DNS | decentralised — it has a root | |
| 30 | | content hashes, pubkeys | human-meaningful | |
| 31 | | petnames | globally unique — each person's namespace differs | |
| 32 | |
| 33 | `yourname.topicname` composability is the DNS escape, and it recurses: delegating names requires |
| 34 | someone to name the delegators. **The recursion only bites if the namespace must resolve through |
| 35 | something new and global.** It does not — a namespace is a room, and rooms are already resolved |
| 36 | socially (registry rooms, DM shares, DHT topics, an invite). If you cannot resolve `rust-p2p`, you |
| 37 | have no social path to that community, which is the correct answer and not a failure. |
| 38 | |
| 39 | ## 2. Four layers, and only one of them is in the bytes |
| 40 | |
| 41 | ``` |
| 42 | layer 3 petnames purely local, yours, override everything never leaves your machine |
| 43 | layer 2 namespaces rooms you subscribe to which "alice" you mean |
| 44 | layer 1 names unique WITHIN a namespace, by its own rule alice, rust-p2p |
| 45 | layer 0 keys unique by construction ⇦ THE ONLY LAYER IN BYTES |
| 46 | ``` |
| 47 | |
| 48 | **Layer 0 is the only thing a record ever contains.** keet does the same and derives everything from |
| 49 | one seed through seven domain-separated namespaces; a room key there is a hypercore *manifest* key |
| 50 | over its first signer (`getWriterKey`), never a chosen string. |
| 51 | |
| 52 | ⛔ **A name must never appear in a record.** pgh's `NameRegistry` is first-claim-wins and a released |
| 53 | name is **reclaimable** (`name-registry.js`), so a name embedded in immutable bytes could later denote |
| 54 | a different person. This is a correctness rule, not an aesthetic one. |
| 55 | |
| 56 | ## 3. Resolution |
| 57 | |
| 58 | ``` |
| 59 | alice.rust-p2p |
| 60 | │ └── a namespace — a room you subscribed to, found in a registry room, or were handed |
| 61 | └──────── a name claimed within it → an identity root → a room key |
| 62 | ``` |
| 63 | |
| 64 | **"Global" is just the default namespace.** `alice` bare means *alice in whatever namespace you have |
| 65 | made default*. Same syntax; the bare form is the elided default. Which means **several roots can |
| 66 | coexist and nobody has to agree** — shipping a default registry makes us a convenient root, never an |
| 67 | authoritative one, and it must remain replaceable. |
| 68 | |
| 69 | **Resolution is local.** Because a namespace is a room you have joined, its contents are already on |
| 70 | disk. Names never become a liveness dependency for rendering, and a name resolves the same offline. |
| 71 | |
| 72 | **The hash never surfaces.** You type `alice.rust-p2p`, not `alice.k7x9f2…`. The key stays in the |
| 73 | bytes; the name stays on screen — the same rule as `SOCIAL-MODEL.md`, applied one level up, and it |
| 74 | **recurses**: names resolve through rooms, rooms resolve through rooms, bottoming out at a key someone |
| 75 | handed you (an invite, a DM share, a QR code) rather than at an authority. |
| 76 | |
| 77 | ### 3.1 `alice.rust-p2p` is a RESOLUTION PATH, not a container |
| 78 | |
| 79 | It means *"alice, as named in the rust-p2p namespace"* — resolving to her identity root and thus her |
| 80 | personal room. It does **not** mean "alice's stuff inside rust-p2p". Her records in that community |
| 81 | live at `favela://<rust-p2p-roomKey>/…` like every other member's. The dotted form reads as both, so |
| 82 | it has to be stated. |
| 83 | |
| 84 | ### 3.2 Unresolvable names fail VISIBLY |
| 85 | |
| 86 | If a namespace is not subscribed, or holds no such name, show the key and say so. **Never guess a |
| 87 | namespace and never silently pick one.** Fail-closed, the same direction as everything else in this |
| 88 | model. |
| 89 | |
| 90 | ## 4. Namespace governance is room governance |
| 91 | |
| 92 | Who may claim a name in a namespace = **who may write to that room**. So an open, invite-only or |
| 93 | curated namespace is a matter of keet roles, and needs no new permission system — the same result |
| 94 | `SOCIAL-MODEL.md` §6.7 reaches for moderation. |
| 95 | |
| 96 | ⚠ **A subscription's names can change meaning over time**, because first-claim-wins plus reclaimable |
| 97 | tombstones means a name can change hands. A client **must surface a reassignment** of a name it has |
| 98 | previously displayed — the same duty as a key-change warning in a messenger. Silently re-pointing a |
| 99 | familiar name at a new person is the one failure this design can actually cause. |
| 100 | |
| 101 | ## 5. Conflicts are legal and legible |
| 102 | |
| 103 | Two namespaces may both name `alice`. If you subscribe to both, the client qualifies: |
| 104 | `alice.rust-p2p` and `alice.berlin`. That is disambiguation, not an error, and it is the visible form |
| 105 | of the property we chose to give up. |
| 106 | |
| 107 | **What we give up, deliberately: global uniqueness.** The compensation is that a name is always |
| 108 | qualified by a namespace you can point at, and an unqualified name is never authoritative — only |
| 109 | defaulted. |
| 110 | |
| 111 | ## 6. Subscription is a pattern this stack already uses twice |
| 112 | |
| 113 | - **Moderation signals** — subscribe to a community's flags, apply your own context; the signal is |
| 114 | information, never enforcement (`COMMUNITY-MODERATION.md`). |
| 115 | - **Labelers** — atproto's equivalent, opt-in per consumer. |
| 116 | |
| 117 | A namespace is the third. That consistency is the strongest argument that it is the right shape: in |
| 118 | each case, **what is shared is an offer, and what is authoritative is what you chose to subscribe to.** |
| 119 | |
| 120 | ## 7. Translating outward |
| 121 | |
| 122 | Both target networks already work this way, which is a good sign: |
| 123 | |
| 124 | | network | naming | |
| 125 | |---|---| |
| 126 | | **ActivityPub** | `@user@domain` — the domain **is** the namespace. Maps directly; a bridge supplies its own domain | |
| 127 | | **atproto** | handles are DNS names — the same, with DNS as the root | |
| 128 | | **IRC / Matrix** | server- or homeserver-scoped nicks; namespace-per-server | |
| 129 | |
| 130 | Ours differs in one respect only: **the namespace is a room rather than a domain**, so it needs no |
| 131 | registrar and no DNS. The shape is the same, which is why the projection is uninteresting — and that |
| 132 | is the point. |
| 133 | |
| 134 | ## 8. Open |
| 135 | |
| 136 | 1. **Delimiter and grammar.** `.` reads like DNS but collides with NSID-ish strings; and whether |
| 137 | namespaces nest (`alice.rust-p2p.berlin`) or are flat is unsettled. Flat is probably right. |
| 138 | 2. **What we ship as the default namespace**, and how a user replaces it. A default that cannot be |
| 139 | changed is a root. |
| 140 | 3. **Namespace discovery.** A namespace is a room, so it inherits room discovery — but "which |
| 141 | namespace should I subscribe to" is the same cold-start problem as "which community", unsolved |
| 142 | there too (`SOCIAL-MODEL.md` §10). |
| 143 | 4. **Rendering a reassignment** (§4) — what the UI actually shows, and for how long. |
| 144 | 5. **Whether a room itself can hold a name in a namespace**, or only identities. pgh's `NameRegistry` |
| 145 | anchors a claim to a `publisher` identity and has no notion of naming a room. |
| 146 | |
| 147 | ## 9. What this does not claim |
| 148 | |
| 149 | - It does not make names globally unique. It says they are not, and builds on that. |
| 150 | - It does not remove the need for a default namespace; it makes the default **configuration** rather |
| 151 | than protocol. |
| 152 | - It does not solve cold-start discovery of namespaces (§8 #3). |
| 153 | - It does not put a single name in a single record, ever. |