OverviewFilesCommitsIssuesDocsReleasesPeers

favela-markdown commits / d9262b7

spec: the AP object URI is not unique under rooms (interaction.md 10)
objectIdFor derives from rkey alone, which was only unique while one host fronted one DID. Rooms remove the guarantee; actors were already unscoped (records.js:72 flags it). Fix is additive - bare rkey becomes the personal-room shorthand, same shape as the native URI reconciliation - because 22 federated objects must keep resolving. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
d9262b747737e777663d580cb167c9474c5fabfa
by plankatron <fleeky@gmail.com> · 2026-09-05T20:46:41.000Z

Changed files (2) +68 -4

spec/interaction.md +64 -3
……@@ -478,15 +478,76 @@ content one shape, and an inbound edge is the same idea applied to relationships
4784784. ~~**Where an edge's `reach` defaults from.**~~ **CLOSED 2026-09-05, then RESOLVED STRUCTURALLY** —
479479 an edge's audience is the room it was written in (§4.5); no computed bound is needed. `reach` (which
480480 networks) remains a field. What remains open is *recall* after a room's membership shrinks.
4815. **Multi-DID id purity.** `records.js:72` warns the AP id functions stop being pure *"the moment we
482 front more than one DID"*, and favela DIDs are co-owned communities. Edges make this sharper,
483 because an edge names a subject that may belong to another fronted DID.
4815. ~~**Multi-DID id purity.**~~ **DIAGNOSED 2026-09-05 — this is a live addressing defect, not an
482 open question.** See §10.
4844836. **Counts.** Like/repost counts are per-node derived values, never records. They will disagree
485484 between peers. `favela-web` already renders local and network follower counts without summing
486485 them (hyper-social #45 §2) — edges should inherit that honesty rather than inventing a total.
487486
488487---
489488
489## 10. ⛔ The AP object URI is not unique under rooms
490
491`ap-bridge/lib/records.js:38`:
492
493```js
494export const objectIdFor = ({ host, rkey }) => `https://${host}/ap/objects/${rkey}`
495```
496
497**The rkey is the whole identifier.** That was sound while one host fronted one DID and rkeys were
498unique within one repo. The source already flags the first half of the problem at `records.js:72`:
499*"this stops being a function the moment we front more than one DID."* The room model adds the second
500half and makes it immediate.
501
502**Two collisions, one root cause:**
503
5041. **Across rooms.** A record now lives in a room, and rkeys have **no cross-room uniqueness
505 guarantee at all**. Two rooms on one host can mint the same rkey and produce the same AP object
506 URI for two unrelated records.
5072. **Across actors.** `objectIdFor` takes no actor segment, so every actor a host fronts shares one
508 object namespace. (`activityIdFor` at `:27` *does* take `actorId` and is not affected — only
509 `objectIdFor` is unscoped.)
510
511⚠ **This is not hypothetical under the room model.** AP has addressing, so a followers room projects
512to ActivityPub just as a public room does (§5.2 of `record-vocabulary.md`) — multiple rooms per actor
513genuinely emit AP objects. The rkey is a TID (`atproto-store/lib/tid.js`), monotonic **per process**
514via a module-level counter, so two devices minting in the same microsecond with the same clockId
515collide. Improbable, and it is not a guarantee.
516
517### 10.1 The constraint that shapes the fix
518
519**22 objects are already federated, and their URIs must keep resolving.** `identity.md` §4.2 counts
520this as the thing that survives a DID rotation *because* it derives from the rkey alone, and Mastodon
521caches object URIs — changing them orphans the objects rather than moving them.
522
523So the fix must be **additive**: old URIs keep working, new ones are scoped.
524
525### 10.2 The fix — the same shape as the native URI
526
527```
528new https://<host>/ap/objects/<roomKey>/<rkey>
529legacy https://<host>/ap/objects/<rkey> → resolves to the PERSONAL room
530```
531
532**The bare-rkey form becomes the personal-room shorthand**, exactly as `favela://<identity-root>/…`
533became the personal-room case of `favela://<roomKey>/…` (§3.4). That is not a coincidence — it is the
534same reconciliation, and it works here for the same reason: every one of the 22 existing objects came
535from the single DID this host fronted, which *is* a personal room.
536
537**What is preserved:** `objectIdFor` stays a pure function — it simply takes one more input. No
538mapping table, so `record-vocabulary.md` §3.1's central property survives. The rkey pairing across
539representations is unchanged.
540
541**What must be checked before implementing:** whether anything besides `records.js:78` and `:154`
542constructs or parses an object URI, and whether inbound routing (`router.js`) matches on the bare
543path in a way that would shadow the new form.
544
545⚠ **Room-scoping fixes the URI, not rkey collision itself.** Two writers in one room minting the same
546rkey is a *different* bug — last-writer-wins on one KV key, i.e. data loss — and it exists today,
547independent of addressing. Do not treat this section as closing it.
548
549---
550
490551## 10. What this does not claim
491552
492553- It does not make edges enforceable on atproto. Nothing can; atproto has no addressing.
spec/record-vocabulary.md +4 -1
……@@ -131,7 +131,10 @@ It resolves four problems at once:
131131
132132- **No mapping table.** Given the canonical, each projection is derivable, and vice versa.
133133- **The AP object URI never changes.** ap-bridge keeps deriving from the rkey, so the 22 already-
134 federated URIs keep resolving. Unaffected by the room change — it never used the DID.
134 federated URIs keep resolving. It never used the DID, so a rotation cannot break it.
135 ⛔ **But it is not unique across rooms** — see `interaction.md` §10. The bare-rkey form must become
136 the personal-room shorthand and new objects must carry the room, or two rooms on one host can
137 collide on one object URI.
135138- **ap-bridge's pure-function id-map survives** — it never fronts a second collection.
136139- **Delete and edit cascade mechanically** by rkey.
137140