Docs Menu

Specs

spec/04-access-control.md · shown exactly as it appears in the repository

04: Access Control

Status: Draft, v0.1. License: This text is CC-BY-4.0; the field names, enum values, and other machine-readable schema elements defined here are also MPL-2.0. See LICENSE-DOCS and LICENSE.

Overview

A subscription is the mechanism that implements Layer 1 access control (see 00-overview.md): a connection receives nothing it hasn't been granted, and a subscription record is the only thing that grants it. This layer has no idea what content looks like; it only tracks, per connection, which labels it may receive.

The subscription record

A subscription record holds: the connection (or bulk invitation) it binds to, a subLabel, the domain it applies to, a direction, and a subStatus.

subStatus Meaning
pending Requested but not yet granted.
subscribed Active: content under this label is shared.
declined Explicitly refused.
left Previously subscribed, since withdrawn.

direction is relative to the local party:

  • from: this connection receives content under this label from me. An implementation MUST check this record, and only this record, before sending content under this label; it's the protocol's enforcement point for access control.
  • to: I've asked to receive content under this label from this connection. This drives what I expect and display, but doesn't by itself cause my peer to send anything; only a matching from record on their side does that. A domain MAY additionally verify incoming content against its own to record before accepting it, a second check layered on top of the sender's from gate, though the protocol doesn't require this since the from check is already the enforcement point.

subLabel: what access control actually grants access to

A domain doesn't grant access per piece of content: it grants access per subLabel, a grouping key derived from a content item's type:

  • Subbable content (e.g. a group, a shared album): its own ck is the subLabel. Subscribing grants access to the thing itself.
  • Subbable-related content (e.g. a membership record): the subLabel is its parent's ck, derived by a domain-supplied function, so it travels with the parent's access grant.
  • Unshareable or subscription-unrelated content: never assigned a subLabel, never shared under any subscription.
  • Anything else: its subLabel is its parent's ck with Content appended, separating "may see this container exists" from "may see what's inside it."

Which content types fall into which category is declared by the domain (see 05-content-model.md), not by this layer.

Subscribing

A party that wants labelled content sends a subscription / subscribe message to the connection it expects to receive it from, carrying the subLabel and domain. An implementation MUST record its own to subscription locally before sending: the same before-you-act ordering as registering a key before being addressed (see 03-cross-domain.md).

A domain MAY grant either direction, or both, in response to a subscribe message: the protocol only defines the message, not what granting it means. Granting both at once makes the subscriber able to both receive content under the label and share their own content back under it, which is how a domain might implement visible two-way membership. A domain with a simpler, one-directional model MAY instead grant only the from record the message actually asked for.

Granting access MAY also fulfill the subscription: backfilling content already matching the label that the requester doesn't have yet and still has propagation hops remaining (see 06-propagation.md). Whether to do this immediately and in batches of 10 is a domain policy choice, not a protocol requirement; a domain may instead grant access without backfilling anything, leaving the requester to receive only what's shared from that point forward.

Planned extension (non-normative preview): a future version is expected to support granting access scoped to a date range rather than an all-or-nothing backfill.

Unsubscribing

A party withdraws by sending subscription / unsubscribe. The receiving side updates its own subscription pair to left (or declined, if this was a refusal rather than a withdrawal) and, for domains with a visible membership count, updates it accordingly.

Refreshing

After a connection is established or completed (see 02-connections.md), or a cross-domain extension is accepted (see 03-cross-domain.md), the completing party MUST send subscription / refreshSubscriptions. This re-sends the party's alias (below) and re-fulfills every label it currently grants (from, status subscribed) to that connection. It MUST use recreate rather than create: the peer may already hold some of this content from before the resync, and recreate avoids resending it as if new.

Alias

An alias is a token (cxnAliasCode) a party shares with its connections so that content propagated through multiple hops (see 06-propagation.md) can be recognized as coming from the same original party, even when it arrives by different paths. Without it, the same person's content or membership could appear as unrelated, duplicate entries to someone several hops away.

A domain MAY use the same alias for every connection, or MAY generate a fresh one per connection: it trades off recognizability for unlinkability. Using one alias everywhere lets a domain correctly recognize the same person's re-shared content and membership across multi-hop propagation, at the cost that two of a party's own connections could, if they compared notes directly with each other (outside the protocol: neither the router nor any observer ever sees an alias in the clear), recognize they're both connected to the same person. A domain with a stronger unlinkability requirement MAY instead generate a distinct alias per connection, losing that cross-hop recognition in exchange.