OverviewFilesCommitsIssuesDocsReleasesPeers

favela-markdown commits / b9470f0

spec/record-vocabulary: reconcile 4, 6, 8, 9, 10.5, 11, 12 to the room
Each argument that ran through audience is rewritten to its surviving half: reach where the concern is operational, the room where it is addressing. 4.1's typed-field rule is superseded by something stronger - a parse error cannot widen an audience that was never parsed. Banner lifted. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
b9470f0c9ece31e455e71f78162825b1da065ec6
by plankatron <fleeky@gmail.com> · 2026-09-05T22:49:44.000Z

Changed files (1) +37 -17

spec/record-vocabulary.md +37 -17
……@@ -237,9 +237,14 @@ plus resolved target, so facets are *derived from the parse* when projecting (§
237237hand-maintained.
238238
239239**The one exception: addressing.** Getting a spoiler wrong makes a post render plainly. Getting
240"who can see this" wrong sends it to the wrong people. So `audience` is a **typed field, never
241parsed out of loose text** — a spelling quirk must not be able to change who receives a post.
242Everything else may be casual.
240"who can see this" wrong sends it to the wrong people.
241
242**Revised 2026-09-05 — the answer is now stronger than a typed field.** This section argued that
243`audience` must be a typed field, never parsed out of loose text, so that a spelling quirk could not
244change who receives a post. Correct about the danger, and superseded by a better fix: **addressing is
245not a field at all.** Who can see a record is the **room it was written in**, enforced by
246replication. A parse error cannot widen an audience that was never parsed
247(`SOCIAL-MODEL.md` §4, `design.md` §5.1). Everything else may be casual.
243248
244249### 4.2 The strip — must be specified, not merely implemented
245250
……@@ -305,7 +310,8 @@ must not conceal it.
305310**Not writing a bsky projection means bsky does not INDEX the post. It does not mean the fediverse
306311or anyone else cannot read it.** The canonical record still rides the firehose and is fetchable by
307312anyone who fronts the DID. This is the same caveat DESIGN §10.2 already makes about local-only
308("unindexed by bsky, not unreplicated"), and it MUST be surfaced wherever audience is chosen.
313("unindexed by bsky, not unreplicated"), and it MUST be surfaced wherever a **room** is chosen —
314which is now the moment addressing is decided.
309315
310316Composer copy must never imply erasure or privacy. Until encryption lands, the honest phrasing
311317remains "community, not secret".
……@@ -340,8 +346,8 @@ Which yields the same four combinations, described honestly:
340346**Why the framing matters, and why the earlier "four scopes" table was wrong.** Enumerating scopes
341347implies a named scope per combination, so adding a third network would mean **eight** scopes and a
342348UI listing all of them. Toggles do not: a new network adds **one** toggle and nothing else changes —
343which is the same reason `audience` is a list of URIs rather than a field per protocol (§5.1). The
344combinatorial explosion was hiding in the vocabulary, not in the mechanism.
349which is the same reason **`reach` names networks rather than carrying a field per protocol**
350(§5.1). The combinatorial explosion was hiding in the vocabulary, not in the mechanism.
345351
346352It also names the thing correctly for the UI. The question a composer should ask is not "what scope
347353is this post" but "who is this for", and the answer is always "me and my peers, plus whoever else I
……@@ -362,8 +368,9 @@ concrete gap this design closes:
362368 *that*. The only such collection today is `net.favela.local.post`, and teaching ap-bridge to
363369 project it would make local-only posts federate — destroying the single guarantee that scope has.
364370
365Both therefore require the record to carry the intent. That is `audience`, and it is the reason this
366document exists.
371Both therefore require the record to carry the intent — **which half of it is `reach`**, the
372operational field that survives the room model (§5.1). The other half, *who* may see the post, moved
373out of the record entirely and became the room. This document exists for that split.
367374
368375---
369376
……@@ -388,7 +395,7 @@ container key mirroring NSID authority:
388395⛔ **Never `$`-prefixed** — `$type`, `$link`, `$bytes` are atproto's reserved space.
389396
390397**The constraint is narrower than it first appears.** Namespacing only matters when attaching extras
391to *someone else's* lexicon. On `net.favela.post` we own the namespace outright, so bare `audience`
398to *someone else's* lexicon. On `net.favela.post` we own the namespace outright, so a bare `reach`
392399is correct and cannot collide.
393400
394401⛔ **Never hang favela data on `app.bsky.feed.post`.** The projection is bsky's view; anything bsky
……@@ -435,8 +442,12 @@ the atproto spec warns about precisely this: *"when deserializing and re-seriali
435442fields risk being stripped away."*
436443
437444**Legacy rule:** an `app.bsky.feed.post` with **no** canonical counterpart at the same rkey is a
438legacy post. It is read as `audience: [Public]`, `source.mediaType: text/plain`, no extensions. It
439renders, federates and threads exactly as it does today. Nothing rewrites it.
445legacy post. It is read as living in the author's **public room**, `source.mediaType: text/plain`,
446no extensions. It renders, federates and threads exactly as it does today. Nothing rewrites it.
447
448⚠ **Revised 2026-09-05.** This previously said a legacy record "is read as `audience: [Public]`".
449Under the room model there is no such field to synthesise: a legacy record is resolved *forward* into
450the public room, the same attrition rule §8 already applies to everything else.
440451
441452**Two canonical vocabularies must not coexist indefinitely.** New writes go to `net.favela.post`
442453from the cutover. Legacy records age out by attrition and are never converted. The end state is one
……@@ -453,7 +464,9 @@ buried.
453464
454465Affected in favela-web:
455466
456- `lib/scopes.js` — `SCOPE_COLLECTION` stops being a scope→collection map; audience replaces it.
467- `lib/scopes.js` — `SCOPE_COLLECTION` stops being a scope→collection map. **The room replaces it**
468 for addressing, and `reach` for which networks; a scope was doing both jobs at once, which is why
469 it had to be a map in the first place.
457470- `lib/appview.js` — ingest folds `net.favela.post` **and** legacy collections, deduplicating by
458471 rkey with canonical preferred. `INGEST_VERSION` bumps; a full re-index is required.
459472- `lib/render.js` — renders `source.content` as markdown when present, falling back to
……@@ -463,7 +476,8 @@ Affected in favela-web:
463476- `lib/writer.js` — authors canonical + projection.
464477
465478Affected elsewhere: hpp's sign/materialize path (§7.4), ap-bridge's `POST_COLLECTION` and its
466projection rule (audience-driven rather than collection-driven), atproto-store's `POST_COLLECTIONS`.
479projection rule (**room-driven** rather than collection-driven — a public room projects, others do
480not), atproto-store's `POST_COLLECTIONS`.
467481
468482---
469483
……@@ -496,7 +510,7 @@ The tell is an inconsistency, not an error: **`getRecord` and `getPosts` reflect
496510`getPostThread` does not.** It is described there as *"a specification unique to bsky post"* — an
497511application policy, not a protocol limit.
498512
499This is the SAME shape as the audience problem (§5), arriving independently: bsky's application
513This is the SAME shape as the addressing problem (§5), arriving independently: bsky's application
500514constraints became ours because its lexicon is our storage format.
501515
502516| surface | edit visible? | why |
……@@ -581,8 +595,13 @@ Honest gaps. This design is not complete until these are closed.
581595 markdown image syntax) is not worked through.
5825963. ~~**The extension dialect** (§4.1) names candidates but does not pin a set.~~ **CLOSED** —
583597 pinned in `markdown-strip.md` §2.1 (CommonMark 0.31.2 + six named extensions).
5844. **Community/list audience URIs** — the URI form for a favela community room is unspecified.
5855. **`audience` semantics for replies** — does a reply inherit its parent's audience? Almost
5984. **Community/list URIs** — ~~the URI form for a favela community room~~ **ANSWERED 2026-09-05**
599 for rooms: `favela://<roomKey>/…` (`interaction.md` §3.4). What a *list* is under the room model
600 is still unspecified.
6015. ~~**`audience` semantics for replies**~~ **RESOLVED STRUCTURALLY 2026-09-05** — a reply is
602 written in the room its parent lives in, so it inherits the audience by construction rather than
603 by a rule that could be forgotten (`design.md` §5.1). Superseded text follows: does a reply
604 inherit its parent's audience? Almost
586605 certainly yes, but the failure mode of getting it wrong is publishing to the wrong people.
5876066. **Whether the AP projection should carry `source.mediaType: text/markdown`** and let Mastodon
588607 render it, or continue sending pre-rendered HTML. Live output currently sends both.
……@@ -591,7 +610,8 @@ Honest gaps. This design is not complete until these are closed.
591610
592611## 12. What this design does *not* claim
593612
594- It does not make `audience` enforceable on atproto. Nothing can.
613- It does not make addressing enforceable on atproto. Nothing can — atproto has none, which is why
614 only a public room projects at all (§5.2).
595615- It does not remove the double-write. It bounds it.
596616- It does not improve deliverability, availability or performance. It is a vocabulary decision.
597617- Its forcing function is thin today: one fediverse follower, one enforcement point. It is