| … | … | @@ -78,30 +78,30 @@ Read in source on 2026-09-05. Nothing outside this table is verified. |
| 78 | 78 | - **subject** — a name (§3). The thing acted upon. |
| 79 | 79 | - **time** — a value, **not** part of identity (§4.2). |
| 80 | 80 | |
| 81 | | ### 2.1 An edge is a record with an empty body |
| 81 | ### 2.1 A bodiless edge is a LOG ENTRY; an edge with a body is a RECORD |
| 82 | 82 | |
| 83 | | **Decision to take, not yet taken — see §9 #1.** The recommendation is that an edge is an ordinary |
| 84 | | `favela-markdown` record: JSON frontmatter, empty body. |
| 83 | **Decided 2026-09-05.** An earlier draft recommended making every edge a markdown record with an |
| 84 | empty body, and left the choice open pending a file-count measurement. The room model settles it, and |
| 85 | in the other direction. |
| 85 | 86 | |
| 86 | | Why that is the simple answer rather than the silly one: |
| 87 | **The rule already exists in the substrate.** A tombstone carries no bytes, so it is stored *as the |
| 88 | pointer itself* and no file is written (`pointer-substrate.js:60-63`). Generalise it: |
| 87 | 89 | |
| 88 | | - **One codec.** One parse, one serialize, one round-trip law (`library.md` §7). No second format, |
| 89 | | no second `EXT`, no second decode path in `getRaw`. |
| 90 | | - **A reply stops being a special case.** A reply is an edge *with* a body. Edge and post become the |
| 91 | | same kind, differing only in whether the body is empty — which resolves `design.md` §8 #2 (the |
| 92 | | markdown-native spelling of threading) by making it not a separate question. |
| 93 | | - **Human-readable on disk**, which is the property the whole pivot exists to buy. |
| 90 | > **If it has bytes, it gets a file. If it does not, the log entry is the whole thing.** |
| 94 | 91 | |
| 95 | | ⚠ **The cost is file count, not bytes.** Records average ~132 bytes (`record-vocabulary.md` §7), so |
| 96 | | the autobase pointer set stays trivial; but likes are far more numerous than posts, and each one is |
| 97 | | a file plus an overlay registration. A heavy account is tens of thousands of tiny files. That is a |
| 98 | | real operational number and it should be measured before this is locked, not after. |
| 92 | - **`like`, `repost`, `follow`, `block`, `flag`, `alias`, `variant-of`** — no body. A small structured |
| 93 | value in the room's autobase (~50 bytes), no file, no overlay registration. |
| 94 | - **`reply`** — has a body. An ordinary markdown record, which is why it is *"the edge that is also a |
| 95 | document"*. |
| 99 | 96 | |
| 100 | | The alternative — an edge as a pointer-valued log entry with no bytes — is already expressible |
| 101 | | (a tombstone is exactly that, `pointer-substrate.js:60-63`). It is cheaper and it costs the |
| 102 | | reply-is-an-edge unification. §9 #1. |
| 97 | **Why the room model forces this.** An interaction is written in **the room where its subject lives** |
| 98 | (§4.5), so a popular public room accumulates *everyone's* likes. That is correct — the room holds the |
| 99 | conversation — but it means edge volume is unbounded in a way post volume is not. A file plus an |
| 100 | overlay registration per like does not survive that; a 50-byte log entry does. |
| 103 | 101 | |
| 104 | | --- |
| 102 | What is given up is the tidiness of one representation for everything. What is kept is the thing that |
| 103 | actually mattered: **`favela-markdown` still owns every edge's vocabulary, projections and loss |
| 104 | report**, whether or not the edge has bytes. The codec's surface is unchanged. |
| 105 | 105 | |
| 106 | 106 | ## 3. ⛔ Naming the subject — the one irreversible decision |
| 107 | 107 | |
| … | … | @@ -389,28 +389,52 @@ a silence, and §5.3's honesty requirement forbids silences. |
| 389 | 389 | |
| 390 | 390 | --- |
| 391 | 391 | |
| 392 | | ## 6. Inbound — a claim is not a record |
| 392 | ## 6. Inbound — admission is ROOM MEMBERSHIP |
| 393 | |
| 394 | **Revised 2026-09-05.** This section previously designed an inbox: unverified claims, retention |
| 395 | windows, rate limiting, a verification boundary. **The room model deletes most of it.** |
| 396 | |
| 397 | **You cannot write into a room you are not a member of.** So there is no unsolicited inbound for |
| 398 | interactions at all — the admission question is *room joining*, which keet already answers three ways |
| 399 | (open policy, invite, request/accept). An edge that reaches you is one written in a room you are both |
| 400 | in, and it is authored by an admitted writer, which is the authorship proof (`design.md` §4). |
| 401 | |
| 402 | ### 6.1 Cold contact is already solved |
| 403 | |
| 404 | `ROOMS-ARE-THE-COMPUTER.md` "Resolved Design Decisions" #1: `dm.fromContact(memberId)` opens a DM |
| 405 | **without a shared room** — only a memberId is needed — and both paths go through blind pairing and |
| 406 | produce a request the target must accept. So reaching a stranger works, and the request/accept step is |
| 407 | the spam gate. It exists, it is battle-tested, and it should be used rather than reimplemented. |
| 408 | |
| 409 | ### 6.2 What genuinely remains: the open public room |
| 410 | |
| 411 | The one surface with real exposure is a **public room anyone may write to** — an open comment section. |
| 412 | That is where spam lands, and the answers are all existing: |
| 393 | 413 | |
| 394 | | An edge authored elsewhere arrives as a **claim**. It becomes a record in *this* node's view only |
| 395 | | after verification. This is the boundary that "everything is a record" glosses over, and it is also |
| 396 | | the spam story. |
| 414 | | lever | what it is | |
| 415 | |---|---| |
| 416 | | **roles** | demote to read-only; keet roles, no new mechanism | |
| 417 | | **rate limit** | per-writer admission throttling — `bucket-rate-limit` exists in the ecosystem | |
| 418 | | **mod signals** | `flag` edges, subscribed to by choice (`SOCIAL-MODEL.md` §6.7) | |
| 419 | | **delisting** | a community stops listing the room; the room is unchanged | |
| 420 | |
| 421 | ⚠ **A rate limit is a local policy, not consensus.** Two peers may admit different amounts of the same |
| 422 | writer's traffic without diverging, because admission is a writer-set decision and the writer set is |
| 423 | already replicated. Do not put a throttle inside `apply()` — it would make the view non-deterministic, |
| 424 | which `multi-writer-autobase-pattern`'s rule 5 forbids. |
| 425 | |
| 426 | ### 6.3 Foreign inbound still crosses a boundary |
| 427 | |
| 428 | Bridged content is the exception, because it does not arrive as an admitted writer's append: |
| 397 | 429 | |
| 398 | 430 | | source | arrives as | verified by | |
| 399 | 431 | |---|---|---| |
| 400 | | | a favela peer | a push on the subject's swarm topic | dereferencing the author's log — the pointer is in an attested writer's core | |
| 401 | | | the fediverse | an HTTP inbox POST | HTTP Signatures, already implemented (`http-signature.js`, `remote-key.js`) | |
| 432 | | the fediverse | an HTTP inbox POST | HTTP Signatures (`http-signature.js`, `remote-key.js`) | |
| 402 | 433 | | bsky | a firehose frame | the signed commit | |
| 403 | 434 | |
| 404 | | **The native path is the AP path's shape.** A DID is already a swarm topic and `atproto-did-peer` |
| 405 | | already does announce-push discovery, so A pushes to B's topic and B verifies against A's log — a |
| 406 | | claim plus dereference, which is precisely AS2's inbox model. Designing the native mechanism to look |
| 407 | | like AP's makes `bridge:fediverse` close to a pass-through and leaves `bridge:bsky` as the lossy one. |
| 408 | | |
| 409 | | ⚠ **Verification is not free.** Dereferencing means replicating enough of the author's log to check |
| 410 | | the pointer. An unverified claim must be storable-but-not-a-record, or the inbox is a write amplifier |
| 411 | | for anyone who can find the topic. Rate limiting and the storable-claim lifetime are **open** (§9 #3). |
| 412 | | |
| 413 | | --- |
| 435 | Neither becomes a native record by arriving. Both become an **`ImportedView`** |
| 436 | (`library.md` §8) — a distinct type that can never be serialized as canonical, because §8 records what |
| 437 | re-encoding someone else's record already cost. |
| 414 | 438 | |
| 415 | 439 | ## 7. Notifications are a projection |
| 416 | 440 | |
| … | … | @@ -442,12 +466,15 @@ content one shape, and an inbound edge is the same idea applied to relationships |
| 442 | 466 | |
| 443 | 467 | ## 9. Open |
| 444 | 468 | |
| 445 | | 1. **Edge as empty-body record vs. pointer-valued log entry** (§2.1). Measure the file count for a |
| 446 | | realistic like volume before locking it. |
| 469 | 1. ~~**Edge as empty-body record vs. pointer-valued log entry**~~ **DECIDED 2026-09-05** — bodiless |
| 470 | edges are log entries, `reply` is a record (§2.1). No measurement needed: the room model makes edge |
| 471 | volume unbounded in a popular room, which settles it. |
| 447 | 472 | 2. ~~**The native URI scheme**~~ **CLOSED 2026-09-05** — `favela://<roomKey>/<collection>/<rkey>`, |
| 448 | 473 | with the identity root as the personal-room case (§3.4). Remaining work is the fail-closed minting |
| 449 | 474 | rule (§3.4.1), not a decision. |
| 450 | | 3. **Inbox admission** (§6) — rate limiting, and how long an unverified claim is retained. |
| 475 | 3. ~~**Inbox admission**~~ **LARGELY DISSOLVED 2026-09-05** (§6) — admission is room membership, and |
| 476 | cold contact is `dm.fromContact` + request/accept. What remains is the open public room (§6.2), and |
| 477 | the levers there are all existing. |
| 451 | 478 | 4. ~~**Where an edge's `reach` defaults from.**~~ **CLOSED 2026-09-05, then RESOLVED STRUCTURALLY** — |
| 452 | 479 | an edge's audience is the room it was written in (§4.5); no computed bound is needed. `reach` (which |
| 453 | 480 | networks) remains a field. What remains open is *recall* after a room's membership shrinks. |