OverviewFilesCommitsIssuesDocsReleasesPeers

favela-markdown commits / 071ab5e

spec/interaction: attestation binds the URI, and only at minting and admission
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
071ab5e7e6374a3ab1f3719b4233064c77119cf1
by plankatron <fleeky@gmail.com> · 2026-09-05T19:13:57.000Z

Changed files (1) +59 -12

spec/interaction.md +59 -12
……@@ -147,22 +147,69 @@ primitive it is built on — a 32-byte identity pubkey as the authority — is t
147147
148148`at://` remains correct for **foreign** subjects (§3.2); that is genuinely where they live.
149149
150#### 3.4.1 ⚠ Precondition — never mint a URI from the device-pubkey fallback
150#### 3.4.1 Minting — a mechanical check, not a discipline
151151
152Verified: a restore re-derives from the mnemonic (`identity-manager.js:174`,
153`IdentityKey.from({mnemonic})`) and SLIP-48 derivation is deterministic, so **a vault restore
154reproduces the same root** and previously-authored URIs keep resolving. This was the open worry from
155#48 and it is answered.
152A proof is `{ version, epoch, identity, chain[] }` where **`identity` is the root pubkey**, and each
153chain link signs `{epoch, identity, device}` with the parent key
154(`keet-identity-key/index.js:56-92`). `verify()` walks the chain and returns
155`{ receipt, identityPublicKey, devicePublicKey }` (`:133-186`), and `validateProof` accepts
156**`opts.expectedIdentity`**, rejecting a mismatch outright (`:198-206`).
156157
157⛔ But `identityRootHex` falls back to the **device pubkey** when an identity has no root at all —
158the raw/legacy identities, *"which are their own root"* (`identity.js:95-113`). A URI minted from
159that fallback would change the moment that identity gained a real root. **Minting must refuse the
160fallback and fail closed**, in the same direction as every other gate here.
158So the URI's authority segment is verifiable with an existing call, offline and with no directory —
159strictly better than `at://`, which needs a PLC lookup. The pattern is already implemented here:
160`packages/hyper-identity/lib/hyperident-vault.js:242-258` verifies the chain, compares
161`verified.identityPublicKey` to the expected root and returns `chain-root-mismatch`.
161162
162A linked device carries `rootPubkeyHex` in its join payload (`identity.js:116-127`), so a device
163that does not hold the seed can still name correctly.
163**Minting rule.** A `favela://` authority may be minted only from a proof that
164164
165#### 3.4.2 What follows
1651. verifies (`IdentityKey.verify` returns non-null), **and**
1662. carries a **non-empty chain** whose `identity` differs from the signing device key.
167
168A raw/legacy identity that is *"its own root"* (`pgh/lib/identity.js:95-113`, the device-pubkey
169fallback in `identityRootHex`) fails both, so the fallback is refused **mechanically** rather than by
170discipline. Fail-closed, in the same direction as every other gate here.
171
172**A vault restore is safe.** Restore re-derives from the mnemonic
173(`hyperident/lib/identity-manager.js:174`, `IdentityKey.from({ mnemonic })`) and SLIP-48 derivation is
174deterministic, so the same root comes back and previously-authored URIs keep resolving. This was the
175open worry carried from hyper-social #48 and it is answered. A linked device that does not hold the
176seed still carries `rootPubkeyHex` in its join payload (`pgh/lib/identity.js:116-127`), so it can
177name correctly without being able to re-derive.
178
179#### 3.4.2 ⛔ Attestation is for minting and admission — NEVER per-record
180
181`validateProof` enforces a **monotonic epoch floor** (`keet-identity-key/index.js:205-208`):
182
183```js
184if (opts.receipt) {
185 const { epoch } = c.decode(ReceiptEncoding, opts.receipt)
186 if (proof.epoch < epoch) return false
187}
188```
189
190and `bootstrap()` stamps `epoch: Date.now()` (`:63`). **Re-bootstrapping after a lost device mints a
191new epoch, and every proof ever made falls below the floor.** A verifier holding the new receipt
192rejects all of them.
193
194That is correct for Keet, where a proof authorises a **live session** and old device proofs should
195die at once. It is wrong for archival records: "this was validly authored in 2026" must stay true
196after a 2027 revocation.
197
198**So a record — post or edge — MUST NOT carry a live attestation proof.** Attestation is used at
199exactly two moments:
200
2011. **minting the URI / creating the `User`** — proving this device speaks for this root (§3.4.1)
2022. **writer admission** — the `DeviceLinker` path (`identity.md` §10 item 2)
203
204After admission, the autobase's own writer signatures carry authorship permanently, which is what
205`design.md` §4 already resolves. Revocation then behaves correctly: bump the epoch, no new device
206joins under an old proof, and nothing already written is disturbed.
207
208⚠ This is a **negative** requirement, so nothing will fail if it is violated — a per-record proof
209would work perfectly until the first device loss, then retroactively unauthor the archive. It is
210called out here because that is the only moment it becomes visible.
211
212#### 3.4.3 What follows
166213
167214`record-vocabulary.md` §3.1's pairing extends from three names to four — canonical `favela://`,
168215`at://`, the AP object URI, and a display name — all still pure functions of one rkey.