| … | … | @@ -254,10 +254,99 @@ projections is local-only, which unifies the two ways that is expressible today |
| 254 | 254 | |
| 255 | 255 | --- |
| 256 | 256 | |
| 257 | | ## 5. The verb set — pinned |
| 257 | ### 4.5 An edge's reach is bounded by its SUBJECT |
| 258 | 258 | |
| 259 | | Adding a verb is a **spec change**, per the same rule as directives (`library.md` §5). It is not a |
| 260 | | plugin 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 |
| 260 | one: |
| 261 | |
| 262 | ``` |
| 263 | reach(edge) = maxReach(edge.visibility, edge.verb) ∩ maxReach(subject.visibility) |
| 264 | ``` |
| 265 | |
| 266 | The second term is the one that is easy to forget and expensive to get wrong. **An edge discloses |
| 267 | its subject.** Projecting a `like` on a followers-only post to bsky publishes two facts — that the |
| 268 | post exists, and who engaged with it — even when the like itself was marked public. The subject's |
| 269 | audience is therefore a ceiling on the edge, exactly as `visibility` is a ceiling on `reach` for a |
| 270 | post (`design.md` §5.1). |
| 271 | |
| 272 | **When the subject's visibility cannot be determined, the meet is `{favela}`.** Fail-closed, the same |
| 273 | direction as an unparseable frontmatter (`library.md` §6.2) and an unknown verb (§5.2). |
| 274 | |
| 275 | Two 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 |
| 284 | certainly inherit"* — it is, and the reason to state it explicitly rather than let it emerge is that |
| 285 | the 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 |
| 288 | projected. The edge cannot be recalled by the lattice; recalling it is the retraction problem, and |
| 289 | that 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 | |
| 295 | An earlier draft pinned the verb set outright, by analogy to directives (`library.md` §5). **The |
| 296 | analogy does not hold**, and getting this wrong would contradict the design's own second governing |
| 297 | property: *"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 | |
| 302 | Directives are frozen for a mechanical reason: the strip runs **inside the signing path**, so two |
| 303 | peers with different directive sets sign different bytes (`library.md` §10.3). The question for a |
| 304 | verb 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 | |
| 312 | So: **an extension is free wherever derivation is per-reader, and consensus-critical wherever |
| 313 | derivation is shared.** That is the same asymmetry that makes ap-bridge light and the atproto path |
| 314 | heavy, arriving for the third time — ActivityPub derives per-consumer and delivers; atproto |
| 315 | materialises 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 | |
| 319 | The mechanism already exists. `maxReach(visibility) -> Set<network>` is specified as total and |
| 320 | deny-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 | |
| 324 | That is the whole extension mechanism. A verb becomes *bridgeable* when a projection is specified for |
| 325 | it, and *signable* when it enters the versioned set of §5.4. Until then it is native-only and |
| 326 | therefore 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 |
| 329 | becoming total over an open input is not a registry. (The same correction was already needed once for |
| 330 | the extension concept generally — `design.md` §1.1.) |
| 331 | |
| 332 | ### 5.3 Naming, and what a reader does with an unknown verb |
| 333 | |
| 334 | Verbs are namespaced exactly as collections are: bare names (`like`, `repost`) are core; anything |
| 335 | else is reverse-DNS (`net.example.rsvp`). A third party's verb therefore cannot collide with a core |
| 336 | one 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; |
| 339 | ignored for rendering, for counting, and — importantly — for **notification**. An edge whose meaning |
| 340 | this node cannot state must not produce a notification it cannot explain. Fail-closed, in the same |
| 341 | direction as §4.4. |
| 342 | |
| 343 | ⚠ Markdown degrades to literal text *by nature* (`design.md` §5.2); an edge has no such natural |
| 344 | degradation, because a tuple with an unknown verb has no reading. That is why the rule has to be |
| 345 | written down rather than inherited. |
| 346 | |
| 347 | ### 5.4 The signed set — frozen and versioned |
| 348 | |
| 349 | These are the verbs that materialise into the atproto projection, and they are consensus-critical: |
| 261 | 350 | |
| 262 | 351 | | verb | subject | bsky | fediverse | |
| 263 | 352 | |---|---|---|---| |
| … | … | @@ -266,15 +355,28 @@ plugin point and there is no registry. |
| 266 | 355 | | `follow` | an actor | `app.bsky.graph.follow` | `Follow` -> `Accept` | |
| 267 | 356 | | `block` | an actor | `app.bsky.graph.block` | `Block` (not federated by Mastodon) | |
| 268 | 357 | | `reply` | a record | `app.bsky.feed.post` + `reply:{root,parent}` | `inReplyTo` | |
| 269 | | | `alias` | a record or actor | **none** — internal (§3.3) | **none** — internal | |
| 270 | 358 | |
| 271 | | `reply` is the edge that is also a document (§2.1). `alias` never projects; it is how this node |
| 272 | | knows 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 | |
| 361 | Changing this set is a **`STRIP_VERSION` bump**, and it is covered by the guard that already has to |
| 362 | exist (`library.md` §10.3): an under-versioned peer **refuses the signer role** rather than signing a |
| 363 | view it cannot fully materialise. Silent fork becomes a loud halt. No new mechanism is introduced |
| 364 | here — 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 |
| 369 | thing, and asserting it is a claim by its author like any other. `bookmark`, `pin`, `endorse` and |
| 370 | anything else favela wants live here too. |
| 371 | |
| 372 | **This costs nothing, because atproto cannot express them anyway.** Extensibility lands exactly where |
| 373 | the protocol ceiling already was — which is the point of `design.md` §1: the P2P side is the rich |
| 374 | side, and the projections are where things are lost. |
| 273 | 375 | |
| 274 | | **Every verb states its loss.** `project()` returns `{output, loss[]}` (`library.md` §4), and these |
| 275 | | add entries: `block-not-federated`, `alias-internal`, and `subject-unresolvable` — the last being |
| 276 | | what `projectLike` currently expresses by returning `null` (`as2-project.js:165-168`), which is a |
| 277 | | silence, 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 |
| 378 | being what `projectLike` currently expresses by returning `null` (`as2-project.js:165-168`), which is |
| 379 | a silence, and §5.3's honesty requirement forbids silences. |
| 278 | 380 | |
| 279 | 381 | --- |
| 280 | 382 | |
| … | … | @@ -336,10 +438,9 @@ content one shape, and an inbound edge is the same idea applied to relationships |
| 336 | 438 | 2. ~~**The native URI scheme**~~ **CLOSED 2026-09-05** — `favela://<identity-root>/…`, §3.4. The |
| 337 | 439 | remaining work is the fail-closed minting rule (§3.4.1), not a decision. |
| 338 | 440 | 3. **Inbox admission** (§6) — rate limiting, and how long an unverified claim is retained. |
| 339 | | 4. **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. |
| 441 | 4. ~~**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). |
| 343 | 444 | 5. **Multi-DID id purity.** `records.js:72` warns the AP id functions stop being pure *"the moment we |
| 344 | 445 | front more than one DID"*, and favela DIDs are co-owned communities. Edges make this sharper, |
| 345 | 446 | because an edge names a subject that may belong to another fronted DID. |