Specs
spec/05-content-model.md · shown exactly as it appears in the repository
05 — Content Model
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
Content is any object shared between connections — a pool, a post, a
member record — typed by its isA descriptor. What content types exist,
and how each one is categorized for access control, is not fixed by the
protocol: each domain declares its own content model, including which of
its types are subbable, subbable-related, unshareable, or
subscription-unrelated (see 04-access-control.md's subLabel section).
Two domains may categorize structurally similar content completely
differently. This chapter defines the fields every content item carries
regardless of domain, and the mechanism a domain uses to declare its own
content model — not any specific domain's actual content types.
The content envelope
Every content item, regardless of domain, carries a baseline shape:
| Field | Purpose |
|---|---|
ck |
The content's own unique key. |
isA |
Its content type. |
version |
Incremented on every update; used to discard stale/duplicate updates (see 06-propagation.md). |
receivedFromCxnId |
Which connection this content came from, or 'me' if it's the party's own. Determines ownership (below). |
originDOS |
How many hops this content was originally allowed to propagate. Useful to a receiver that wants to know how far content has traveled (originDOS minus remainingDOS is hops traveled so far). |
remainingDOS |
Hops still remaining; decremented on every further share. Content with none remaining is never shared again (see 06-propagation.md). |
created |
When the content item was first created. |
updated |
When the content item was most recently updated. |
Relationship descriptors
A content item declares its relationships to other content and to connections explicitly, rather than requiring those relationships to be derived by traversing the data graph at the moment they're needed:
- Parent pointer —
{parentIsA}CK(e.g.poolCKon amember): a direct, single-slot "this belongs to that" reference. - Content-reliance —
reliesOn{IsA}CK(e.g.reliesOnMemberCK): a denormalized reference to another content item this one depends on, used so a cascading delete can match directly instead of walking a multi-hop chain to re-derive the relationship. A content type MAY declare as many of these as its own dependency graph actually needs — there's no protocol-imposed limit. - Connection-reliance —
reliesOnCxnId(and as many further slots as needed, e.g.reliesOnSecondaryCxnId): the same denormalization applied to a connection instead of another content item, so that deleting a connection can efficiently find everything that depends on it.
Shareable vs. local descriptors
Not every field on a content item travels with it when it's shared. An
implementation MUST strip, before transmission: receivedFromCxnId,
any database-internal identifier (e.g. _id), and every
connection-reliance descriptor (reliesOnCxnId and its further slots) —
a cxnId MUST NOT leave the party that holds it, the same
non-linkability guarantee from 00-overview.md, now extended to any field
that references one. A domain MAY additionally declare its own
content-type-specific fields as local-only (e.g. isSubscribed,
fileSize) that get stripped the same way. Content-reliance descriptors
(reliesOn{IsA}CK) are not stripped — they reference another content
item's ck, which the receiver needs to reconstruct the same dependency
graph on their own side.
Registering a domain's content model
A domain's content model is its own to define, and different domains may define it completely differently.
Schema registration and validation
A domain registers a schema for each of its content types, declaring: required fields, mutually-exclusive field groups, at-least-one-of field groups, and allowed values for specific fields. An implementation MUST validate content against its registered schema before saving it — invalid content MUST NOT be persisted.
Labeling for access control
Separately, a domain registers how its content types map onto the
subLabel categories defined in 04-access-control.md — which types are
subbable, subbable-related, unshareable, or subscription-unrelated, and
how to derive a piece of content's parent for the subbable-related case.
This is a distinct concern from schema validation above: it exists
specifically to drive access-control labeling, not to validate content
shape.
Domain identifier and namespace
A domain's wire identifier (the domain field carried in every envelope,
e.g. bonafide.pools) and the internal namespace its schema and content
validator are registered under are related but not identical: an
implementation MUST derive the namespace by taking the final
dot-separated segment of the domain identifier (bonafide.pools →
pools). Registration — both the schema from Schema registration and
validation above and the content validator from Labeling for access
control above — is keyed by this derived namespace, not by the full
dotted identifier.
Ownership and mutation
A content item has exactly one owner — the party whose receivedFromCxnId
is 'me'. An implementation MUST reject any attempt to locally
create, update, or delete content that isn't owned by the local party —
only the owner may originate a mutation. Content received from a peer is
written through a separate path (governed by the shareSequence/version
ordering rules in 06-propagation.md, not by this ownership check), and a
receiving party MUST NOT treat its own local edits to received content
as an authoritative mutation the owner or anyone else needs to honor —
only the domain's own locally-declared fields (the local-only descriptors
above) may be freely changed locally.