OverviewFilesCommitsIssuesDocsReleasesPeers

favela-markdown commits / 6780297

spec/interaction: verbs extend where derivation is per-reader; an edge's reach is bounded by its subject
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
6780297f9dbcc648b2a261e5d1bbfdda9d1ce685
by plankatron <fleeky@gmail.com> · 2026-09-05T19:17:35.000Z

Changed files (1) +115 -14

spec/interaction.md +115 -14
……@@ -254,10 +254,99 @@ projections is local-only, which unifies the two ways that is expressible today
254254
255255---
256256
257## 5. The verb set — pinned
257### 4.5 An edge's reach is bounded by its SUBJECT
258258
259Adding a verb is a **spec change**, per the same rule as directives (`library.md` §5). It is not a
260plugin point and there is no registry.
259`maxReach` is a lattice (`library.md` §6.1). An edge sits at the **meet** of two constraints, not
260one:
261
262```
263reach(edge) = maxReach(edge.visibility, edge.verb) ∩ maxReach(subject.visibility)
264```
265
266The second term is the one that is easy to forget and expensive to get wrong. **An edge discloses
267its subject.** Projecting a `like` on a followers-only post to bsky publishes two facts — that the
268post exists, and who engaged with it — even when the like itself was marked public. The subject's
269audience is therefore a ceiling on the edge, exactly as `visibility` is a ceiling on `reach` for a
270post (`design.md` §5.1).
271
272**When the subject's visibility cannot be determined, the meet is `{favela}`.** Fail-closed, the same
273direction as an unparseable frontmatter (`library.md` §6.2) and an unknown verb (§5.2).
274
275Two cases behave differently and both are correct:
276
277- **A foreign subject** (a bsky post, a Mastodon status) is already public on the network it came
278 from, so the meet is unrestrictive. This is the common case and it costs nothing.
279- **A favela-native subject with restricted visibility** is where the rule bites, and it is the case
280 that does not exist yet — which is precisely why it must be written before it does.
281
282`reply` inherits rather than intersects: a reply to a restricted post takes its parent's
283`visibility`, not merely a bound on its reach. `record-vocabulary.md` §11 #5 calls this *"almost
284certainly inherit"* — it is, and the reason to state it explicitly rather than let it emerge is that
285the failure mode is publishing to the wrong people.
286
287⚠ **Limit, not solved here.** A subject can become *more* restricted after an edge on it was already
288projected. The edge cannot be recalled by the lattice; recalling it is the retraction problem, and
289that is per-bridge delivery bookkeeping (`identity.md` §6.3), not a property of the record.
290
291---
292
293## 5. Verbs — extensible where derivation is per-reader, frozen where it is shared
294
295An earlier draft pinned the verb set outright, by analogy to directives (`library.md` §5). **The
296analogy does not hold**, and getting this wrong would contradict the design's own second governing
297property: *"a new extension never migrates old records, because projections are derived on read"*
298(`design.md` §1.1).
299
300### 5.1 The actual rule
301
302Directives are frozen for a mechanical reason: the strip runs **inside the signing path**, so two
303peers with different directive sets sign different bytes (`library.md` §10.3). The question for a
304verb is therefore not "is it core?" but **who derives its projection**:
305
306| where the projection is derived | consequence of an unknown verb | verdict |
307|---|---|---|
308| **per reader** — favela-native rendering | that reader ignores it; no one else is affected | **free** |
309| **by a bridge**, delivered outward | nothing is delivered; reported in `loss[]` | **free**, degradation is visible |
310| **by the elected signer**, into the signed MST | signer A materialises a record signer B does not ⇒ **different MST ⇒ a fork** | **frozen + versioned** |
311
312So: **an extension is free wherever derivation is per-reader, and consensus-critical wherever
313derivation is shared.** That is the same asymmetry that makes ap-bridge light and the atproto path
314heavy, arriving for the third time — ActivityPub derives per-consumer and delivers; atproto
315materialises into one signed structure that every peer must agree on byte for byte.
316
317### 5.2 No registry — `maxReach()` becomes total over verbs
318
319The mechanism already exists. `maxReach(visibility) -> Set<network>` is specified as total and
320deny-by-default (`library.md` §6.1). Extend it to take the verb as well:
321
322**An unknown verb's maximum reach is `{favela}`.**
323
324That is the whole extension mechanism. A verb becomes *bridgeable* when a projection is specified for
325it, and *signable* when it enters the versioned set of §5.4. Until then it is native-only and
326therefore cannot fork anything, because it never reaches a shared derivation.
327
328⛔ **Do not build a verb registry, a governance process or per-verb versioning.** One function
329becoming total over an open input is not a registry. (The same correction was already needed once for
330the extension concept generally — `design.md` §1.1.)
331
332### 5.3 Naming, and what a reader does with an unknown verb
333
334Verbs are namespaced exactly as collections are: bare names (`like`, `repost`) are core; anything
335else is reverse-DNS (`net.example.rsvp`). A third party's verb therefore cannot collide with a core
336one and is visibly foreign on sight — the `__`/NSID discipline again, no registry needed.
337
338**An unknown verb is RETAINED and IGNORED.** Retained because it is a record and records are durable;
339ignored for rendering, for counting, and — importantly — for **notification**. An edge whose meaning
340this node cannot state must not produce a notification it cannot explain. Fail-closed, in the same
341direction as §4.4.
342
343⚠ Markdown degrades to literal text *by nature* (`design.md` §5.2); an edge has no such natural
344degradation, because a tuple with an unknown verb has no reading. That is why the rule has to be
345written down rather than inherited.
346
347### 5.4 The signed set — frozen and versioned
348
349These are the verbs that materialise into the atproto projection, and they are consensus-critical:
261350
262351| verb | subject | bsky | fediverse |
263352|---|---|---|---|
……@@ -266,15 +355,28 @@ plugin point and there is no registry.
266355| `follow` | an actor | `app.bsky.graph.follow` | `Follow` -> `Accept` |
267356| `block` | an actor | `app.bsky.graph.block` | `Block` (not federated by Mastodon) |
268357| `reply` | a record | `app.bsky.feed.post` + `reply:{root,parent}` | `inReplyTo` |
269| `alias` | a record or actor | **none** — internal (§3.3) | **none** — internal |
270358
271`reply` is the edge that is also a document (§2.1). `alias` never projects; it is how this node
272knows two names denote one thing, and asserting it is a claim by its author like any other.
359`reply` is the edge that is also a document (§2.1).
360
361Changing this set is a **`STRIP_VERSION` bump**, and it is covered by the guard that already has to
362exist (`library.md` §10.3): an under-versioned peer **refuses the signer role** rather than signing a
363view it cannot fully materialise. Silent fork becomes a loud halt. No new mechanism is introduced
364here — the signed verb set simply travels in the same version as the strip.
365
366### 5.5 Native verbs — where extension actually lives
367
368`alias` (§3.3) is the first one: it never projects, it is how a node knows two names denote one
369thing, and asserting it is a claim by its author like any other. `bookmark`, `pin`, `endorse` and
370anything else favela wants live here too.
371
372**This costs nothing, because atproto cannot express them anyway.** Extensibility lands exactly where
373the protocol ceiling already was — which is the point of `design.md` §1: the P2P side is the rich
374side, and the projections are where things are lost.
273375
274**Every verb states its loss.** `project()` returns `{output, loss[]}` (`library.md` §4), and these
275add entries: `block-not-federated`, `alias-internal`, and `subject-unresolvable` — the last being
276what `projectLike` currently expresses by returning `null` (`as2-project.js:165-168`), which is a
277silence, and §5.3's honesty requirement forbids silences.
376**Every verb states its loss.** `project()` returns `{output, loss[]}` (`library.md` §4). These add
377`block-not-federated`, `alias-internal`, `verb-native-only` and `subject-unresolvable` — the last
378being what `projectLike` currently expresses by returning `null` (`as2-project.js:165-168`), which is
379a silence, and §5.3's honesty requirement forbids silences.
278380
279381---
280382
……@@ -336,10 +438,9 @@ content one shape, and an inbound edge is the same idea applied to relationships
3364382. ~~**The native URI scheme**~~ **CLOSED 2026-09-05** — `favela://<identity-root>/…`, §3.4. The
337439 remaining work is the fail-closed minting rule (§3.4.1), not a decision.
3384403. **Inbox admission** (§6) — rate limiting, and how long an unverified claim is retained.
3394. **Where an edge's `reach` defaults from.** A like on a public post is presumably public; a like on
340 a followers-only post must not project. Almost certainly inherited from the subject, but the
341 failure mode is publishing to the wrong people, so it must be explicit — the same reasoning as
342 `record-vocabulary.md` §11 #5.
4414. ~~**Where an edge's `reach` defaults from.**~~ **CLOSED 2026-09-05** — the meet of the edge's own
442 maxReach and the subject's, fail-closed to `{favela}` when the subject is unknown; `reply`
443 inherits. §4.5. What remains open is *recall* after a subject is restricted later (§4.5 limit).
3434445. **Multi-DID id purity.** `records.js:72` warns the AP id functions stop being pure *"the moment we
344445 front more than one DID"*, and favela DIDs are co-owned communities. Edges make this sharper,
345446 because an edge names a subject that may belong to another fronted DID.