| … | … | @@ -478,15 +478,76 @@ content one shape, and an inbound edge is the same idea applied to relationships |
| 478 | 478 | 4. ~~**Where an edge's `reach` defaults from.**~~ **CLOSED 2026-09-05, then RESOLVED STRUCTURALLY** — |
| 479 | 479 | an edge's audience is the room it was written in (§4.5); no computed bound is needed. `reach` (which |
| 480 | 480 | networks) remains a field. What remains open is *recall* after a room's membership shrinks. |
| 481 | | 5. **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. |
| 481 | 5. ~~**Multi-DID id purity.**~~ **DIAGNOSED 2026-09-05 — this is a live addressing defect, not an |
| 482 | open question.** See §10. |
| 484 | 483 | 6. **Counts.** Like/repost counts are per-node derived values, never records. They will disagree |
| 485 | 484 | between peers. `favela-web` already renders local and network follower counts without summing |
| 486 | 485 | them (hyper-social #45 §2) — edges should inherit that honesty rather than inventing a total. |
| 487 | 486 | |
| 488 | 487 | --- |
| 489 | 488 | |
| 489 | ## 10. ⛔ The AP object URI is not unique under rooms |
| 490 | |
| 491 | `ap-bridge/lib/records.js:38`: |
| 492 | |
| 493 | ```js |
| 494 | export 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 |
| 498 | unique 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 |
| 500 | half and makes it immediate. |
| 501 | |
| 502 | **Two collisions, one root cause:** |
| 503 | |
| 504 | 1. **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. |
| 507 | 2. **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 |
| 512 | to ActivityPub just as a public room does (§5.2 of `record-vocabulary.md`) — multiple rooms per actor |
| 513 | genuinely emit AP objects. The rkey is a TID (`atproto-store/lib/tid.js`), monotonic **per process** |
| 514 | via a module-level counter, so two devices minting in the same microsecond with the same clockId |
| 515 | collide. 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 |
| 520 | this as the thing that survives a DID rotation *because* it derives from the rkey alone, and Mastodon |
| 521 | caches object URIs — changing them orphans the objects rather than moving them. |
| 522 | |
| 523 | So 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 | ``` |
| 528 | new https://<host>/ap/objects/<roomKey>/<rkey> |
| 529 | legacy 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>/…` |
| 533 | became the personal-room case of `favela://<roomKey>/…` (§3.4). That is not a coincidence — it is the |
| 534 | same reconciliation, and it works here for the same reason: every one of the 22 existing objects came |
| 535 | from 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 |
| 538 | mapping table, so `record-vocabulary.md` §3.1's central property survives. The rkey pairing across |
| 539 | representations is unchanged. |
| 540 | |
| 541 | **What must be checked before implementing:** whether anything besides `records.js:78` and `:154` |
| 542 | constructs or parses an object URI, and whether inbound routing (`router.js`) matches on the bare |
| 543 | path 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 |
| 546 | rkey is a *different* bug — last-writer-wins on one KV key, i.e. data loss — and it exists today, |
| 547 | independent of addressing. Do not treat this section as closing it. |
| 548 | |
| 549 | --- |
| 550 | |
| 490 | 551 | ## 10. What this does not claim |
| 491 | 552 | |
| 492 | 553 | - It does not make edges enforceable on atproto. Nothing can; atproto has no addressing. |