OverviewFilesCommitsIssuesDocsReleasesPeers

favela-markdown commits / 99e7f18

spec/record-vocabulary: reconcile 3 and 5 to the room; reverse the stale 3.3
3.1 room-scoped canonical name; 3.2 the record is a file, audience deleted (the room is the audience), reach kept; 3.3 REVERSES the pre-08-31 position that text+facets are canonical; 5.1/5.2 room as audience. 5.4 survives. Flags the open mention-embeds-a-DID defect in 3.4. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
99e7f18b527524fa08b8fab0d459217c3dab3822
by plankatron <fleeky@gmail.com> · 2026-09-05T20:01:47.000Z

Changed files (1) +69 -38

spec/record-vocabulary.md +69 -38
……@@ -110,63 +110,80 @@ verified.
110110
111111### 3.1 The rkey pairing — the mechanism everything rests on
112112
113**A canonical record and its atproto projection share one rkey, differing only by collection.**
113**Revised 2026-09-05.** The room is the container (`hyper-social/docs/SOCIAL-MODEL.md`), so the
114canonical name is room-scoped. The pairing itself is unchanged and is still what everything rests on:
115**one rkey, N names, each a pure function of the others.**
114116
115117```
116canonical at://<did>/net.favela.post/3msdnle65lk22
117projection at://<did>/app.bsky.feed.post/3msdnle65lk22
118canonical favela://<roomKey>/net.favela.post/3msdnle65lk22
119projection at://<did>/app.bsky.feed.post/3msdnle65lk22 (public rooms only)
118120AP object https://ap.7rnx.net/ap/objects/3msdnle65lk22
119121```
120122
121This is not a convenience. It resolves four problems at once:
123It resolves four problems at once:
122124
123- **No mapping table.** Given the canonical, the projection is derivable, and vice versa.
125- **No mapping table.** Given the canonical, each projection is derivable, and vice versa.
124126- **The AP object URI never changes.** ap-bridge keeps deriving from the rkey, so the 22 already-
125 federated URIs keep resolving. The collection-in-path change discussed earlier is **not needed**.
127 federated URIs keep resolving. Unaffected by the room change — it never used the DID.
126128- **ap-bridge's pure-function id-map survives** — it never fronts a second collection.
127129- **Delete and edit cascade mechanically** by rkey.
128130
131⚠ The `at://` projection exists **only for public rooms**, because atproto cannot express addressing
132(§5.2). A record in a followers or community room has a canonical name and an AP object and no
133atproto name at all — that is not a gap, it is the ceiling of the target.
134
129135### 3.2 The core fields
130136
131The intersection of what atproto and AS2 both express, checked field by field against both specs:
137**Revised 2026-09-05.** A record is a **file** — JSON frontmatter plus a markdown body
138(`design.md` §5) — not a JSON object with a stored body. What follows is the frontmatter.
132139
133140```json
134141{
135 "$type": "net.favela.post",
136142 "createdAt": "2026-08-05T13:38:23.294Z",
137143 "langs": ["en"],
138
139 "source": { "content": "hello **world**", "mediaType": "text/markdown" },
140 "text": "hello world",
141 "facets": [ /* byte offsets into `text` */ ],
142
143 "reply": { "root": {…}, "parent": {…} },
144 "embed": { /* images / quote / external */ },
145
146 "audience": ["https://www.w3.org/ns/activitystreams#Public"]
144 "reply": { "root": {…}, "parent": {…} },
145 "reach": ["favela", "bsky", "fediverse"]
147146}
148147```
149148
150`audience` is the only genuinely new field. Everything else is what is already written today,
151because those parts of bsky's model are sound and reinventing them would be churn.
149Everything the old version of this section listed as a stored field — `text`, `facets`, `source`,
150`embed` — is **derived at projection time** from the body (§3.3). `$type` is carried by the
151collection in the record's name, not repeated in the bytes.
152
153⛔ **`audience` is gone, and it is not renamed — it is deleted.** Who can see a record is **the room
154it was written in**; membership is the audience, enforced by replication rather than honoured by
155convention (`design.md` §5.1). `reach` — which networks to syndicate to — remains, because a room
156cannot express that.
152157
153### 3.3 Two representation decisions, settled on recoverability
158### 3.3 Two representation decisions — one reversed
154159
155**Body: plain text + byte-offset facets is canonical; markdown is the source; HTML is derived.**
160**Body: the MARKDOWN FILE is canonical; `text` + `facets` are DERIVED.**
156161
157Markdown → HTML is deterministic. HTML → byte-offset facets is not reliably recoverable. So the
158canonical machine-readable body is `text` + `facets`, and `source` carries what the human typed.
159This is not "atproto won" — it is the direction that loses nothing.
162⛔ **Reversed 2026-09-05.** This section previously said *"plain text + byte-offset facets is
163canonical; markdown is the source"* — the pre-08-31 position, contradicting §0 ever since. §0 is
164correct: the markdown document is the canonical record, and `text`/`facets` are computed by
165`strip()` at projection time (`markdown-strip.md`), never stored.
160166
161**Threading: `{root, parent}` is canonical, `inReplyTo` is derived.** atproto's pair carries strictly
162more than AS2's single URI, and the projection down is trivial.
167The old reasoning was that HTML → byte-offset facets is not reliably recoverable. True, and no longer
168relevant: nothing recovers facets from HTML, because facets are produced from the **markdown parse**,
169which yields exact source ranges (§3.4). Storing them was solving a problem that the parse removes.
170
171**Threading: `{root, parent}` is canonical, `inReplyTo` is derived.** Unchanged — atproto's pair
172carries strictly more than AS2's single URI, and the projection down is trivial. Its markdown-native
173spelling is still open (`design.md` §8 #2).
163174
164175### 3.4 Markdown links *are* facets
165176
166177`[@alice](did:plc:xyz)` carries display text plus resolved target — exactly what a facet is. A
167178CommonMark parser yields exact source ranges, so facet offsets are **derived from the parse**, never
168179hand-maintained. This preserves the existing rule (favela-web 0.3.11) that a mention resolves to a
169DID or is omitted, never guessed.
180target or is omitted, never guessed.
181
182⛔ **OPEN — a mention embeds a DID in canonical bytes.** `[@alice](did:plc:xyz)` writes a *target's*
183DID into the record, which is the same defect §8 and `identity.md` §5 forbid for the *author's* DID:
184when alice rotates, every mention of her everywhere points at a dead identifier, and §8 forbids
185rewriting them. The mention target should be a `favela://` name resolved to a DID at projection time
186— the treatment the author already gets. Not settled here.
170187
171188---
172189
……@@ -240,24 +257,35 @@ The AP projection is **never** truncated; AS2 has no length limit.
240257
241258## 5. Audience
242259
243### 5.1 Shape
260### 5.1 The room is the audience; `reach` is the field
261
262**Revised 2026-09-05.** This section previously specified `audience` as a list of URIs in the record.
263Half of that is now structural and half survives.
244264
245A list of URIs. `https://www.w3.org/ns/activitystreams#Public` means everyone. A community is its
246room key as a URI; a list is its `at://` URI.
265- **Who — the room.** You do not list an audience; you write in a room whose members *are* the
266 audience. A public room, a followers room, a community room, a two-member DM room. Enforced by
267 replication rather than by every projection remembering to honour a field
268 (`hyper-social/docs/SOCIAL-MODEL.md` §4).
269- **Where — `reach`.** Which networks to syndicate to, within what the room permits. Stays a field,
270 because a room cannot express it.
247271
248URIs rather than protocol names (`["bsky","fediverse"]`) because a URI names *who*, not *where*.
249Adding a protocol later adds no field — it is another audience that either includes a given
250recipient or does not. Protocol names as flags would need a new field per protocol forever, and
251would put knowledge of projections inside the record, which is the thing this design exists to
252avoid.
272**What the old reasoning got right, and keep.** It argued for URIs over protocol names
273(`["bsky","fediverse"]`) because *"a URI names who, not where"*, and because protocol flags would
274put knowledge of projections inside the record. Both correct — and the room model is that argument
275carried to its conclusion: **the strongest way to keep projection knowledge out of the record is for
276"who" not to be in the record at all.** `reach` is explicitly the "where" half, and is named so.
253277
254278### 5.2 What each projection does with it
255279
256280| projection | enforcement |
257281|---|---|
258| **P2P (favela-web and any other client)** | not a projection at all — it is the substrate (§5.4). It renders everything it holds, and `audience` never withholds anything from it. |
259| **ActivityPub** | **native.** `audience` maps onto `to`/`cc`. ap-bridge already emits these — hardcoded to Public. The change is to read the value instead of assuming it. |
260| **atproto / bsky** | **none.** atproto has no addressing. The only lever is whether the `app.bsky.feed.post` projection is written at all. |
282| **P2P (favela-web and any other client)** | not a projection at all — it is the substrate (§5.4). It renders every room it is a member of. |
283| **ActivityPub** | **native.** Room membership maps onto `to`/`cc`; a followers room onto the followers collection. ap-bridge already emits these, hardcoded to Public — the change is to derive them from the room. |
284| **atproto / bsky** | **none.** atproto has no addressing. Only a **public room** can project at all; for any other room the `app.bsky.feed.post` projection is simply not written. |
285
286⚠ The asymmetry is structural, not an implementation gap: AP maps well **because AP has addressing**
287and atproto maps badly **because it has none**. That is the same reason ap-bridge is a fifth the size
288of the atproto path.
261289
262290### 5.3 The honesty requirement — non-negotiable
263291
……@@ -274,6 +302,9 @@ remains "community, not secret".
274302
275303### 5.4 ★ Local is the SUBSTRATE, not a scope — reach is N independent toggles
276304
305✅ **Survives the room change unchanged (checked 2026-09-05)** — and reads more naturally under it:
306"local" is the room you wrote in, always on because writing *is* placing.
307
277308**Every post is local.** The canonical record always exists on the P2P side; that is what authoring
278309one means. "Local" is therefore not a destination you opt into, and there is no way to opt out of
279310it. What the author actually chooses is **which projections to emit**.