OverviewFilesCommitsIssuesDocsReleasesPeers

favela-markdown commits / 735a9b8

spec/interaction: bodiless edges are log entries; admission is room membership
B1: if it has bytes it gets a file, else the log entry is the whole thing - generalising the tombstone rule. The room model makes edge volume unbounded in a popular room, which decides it without a measurement. B2: you cannot write into a room you are not in, so most of the inbox design deletes. Cold contact is already dm.fromContact + request/accept. Adds flag and variant-of verbs. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
735a9b8a6b237fd2ddb382a03adaf5ed57b72c99
by plankatron <fleeky@gmail.com> · 2026-09-05T20:25:28.000Z

Changed files (1) +64 -37

spec/interaction.md +64 -37
……@@ -78,30 +78,30 @@ Read in source on 2026-09-05. Nothing outside this table is verified.
7878- **subject** — a name (§3). The thing acted upon.
7979- **time** — a value, **not** part of identity (§4.2).
8080
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
8282
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
84empty body, and left the choice open pending a file-count measurement. The room model settles it, and
85in the other direction.
8586
86Why 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
88pointer itself* and no file is written (`pointer-substrate.js:60-63`). Generalise it:
8789
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.**
9491
95⚠ **The cost is file count, not bytes.** Records average ~132 bytes (`record-vocabulary.md` §7), so
96the autobase pointer set stays trivial; but likes are far more numerous than posts, and each one is
97a file plus an overlay registration. A heavy account is tens of thousands of tiny files. That is a
98real 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"*.
9996
100The 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
102reply-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
99conversation — but it means edge volume is unbounded in a way post volume is not. A file plus an
100overlay registration per like does not survive that; a 50-byte log entry does.
103101
104---
102What is given up is the tidiness of one representation for everything. What is kept is the thing that
103actually mattered: **`favela-markdown` still owns every edge's vocabulary, projections and loss
104report**, whether or not the edge has bytes. The codec's surface is unchanged.
105105
106106## 3. ⛔ Naming the subject — the one irreversible decision
107107
……@@ -389,28 +389,52 @@ a silence, and §5.3's honesty requirement forbids silences.
389389
390390---
391391
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
395windows, 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
398interactions 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
400in, 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
406produce a request the target must accept. So reaching a stranger works, and the request/accept step is
407the 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
411The one surface with real exposure is a **public room anyone may write to** — an open comment section.
412That is where spam lands, and the answers are all existing:
393413
394An edge authored elsewhere arrives as a **claim**. It becomes a record in *this* node's view only
395after verification. This is the boundary that "everything is a record" glosses over, and it is also
396the 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
422writer's traffic without diverging, because admission is a writer-set decision and the writer set is
423already replicated. Do not put a throttle inside `apply()` — it would make the view non-deterministic,
424which `multi-writer-autobase-pattern`'s rule 5 forbids.
425
426### 6.3 Foreign inbound still crosses a boundary
427
428Bridged content is the exception, because it does not arrive as an admitted writer's append:
397429
398430| source | arrives as | verified by |
399431|---|---|---|
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`) |
402433| bsky | a firehose frame | the signed commit |
403434
404**The native path is the AP path's shape.** A DID is already a swarm topic and `atproto-did-peer`
405already does announce-push discovery, so A pushes to B's topic and B verifies against A's log — a
406claim plus dereference, which is precisely AS2's inbox model. Designing the native mechanism to look
407like 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
410the pointer. An unverified claim must be storable-but-not-a-record, or the inbox is a write amplifier
411for anyone who can find the topic. Rate limiting and the storable-claim lifetime are **open** (§9 #3).
412
413---
435Neither 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
437re-encoding someone else's record already cost.
414438
415439## 7. Notifications are a projection
416440
……@@ -442,12 +466,15 @@ content one shape, and an inbound edge is the same idea applied to relationships
442466
443467## 9. Open
444468
4451. **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.
4691. ~~**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.
4474722. ~~**The native URI scheme**~~ **CLOSED 2026-09-05** — `favela://<roomKey>/<collection>/<rkey>`,
448473 with the identity root as the personal-room case (§3.4). Remaining work is the fail-closed minting
449474 rule (§3.4.1), not a decision.
4503. **Inbox admission** (§6) — rate limiting, and how long an unverified claim is retained.
4753. ~~**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.
4514784. ~~**Where an edge's `reach` defaults from.**~~ **CLOSED 2026-09-05, then RESOLVED STRUCTURALLY** —
452479 an edge's audience is the room it was written in (§4.5); no computed bound is needed. `reach` (which
453480 networks) remains a field. What remains open is *recall* after a room's membership shrinks.