Specs
spec/06-propagation.md · shown exactly as it appears in the repository
06 — Propagation
Status: Draft, v0.1.
License: CC-BY-4.0 — see LICENSE-DOCS.
Overview
This chapter covers what happens to content after it has already been
shared once: how a receiving party can recognize who originally shared
something even after it has passed through other parties, how far content
is allowed to travel beyond the connections it was directly shared with,
and how a party decides whether an incoming create, update, or delete is
stale and should be discarded. It does not cover who is allowed to receive
content in the first place — that is 04-access-control.md's subscription
mechanism, which this chapter builds on top of.
Recognizing the originator across hops
Once content has traveled even one hop, a receiver has no way to know who
originally shared it: 05-content-model.md requires receivedFromCxnId
to be stripped before content is transmitted, and that field only ever
identified which of the receiver's own connections a piece of content
arrived on in the first place — never the content's original owner.
The alias mechanism (04-access-control.md) already gives two directly
connected parties a way to exchange a cxnAliasCode with each other. A
domain MAY build on this by stamping content it defines — the pieces
of content whose originator a recipient should be able to recognize — with
the owner's own cxnAliasCode, under whatever field name the domain
chooses. Because this code is not on the baseline list of fields stripped
before sharing (05-content-model.md), it survives every further hop
unchanged, riding along with the content itself.
Recognition is then purely local and connection-scoped: a party can
resolve a code it sees on incoming content back to one of its own
connections only if it also holds a direct connection with that code's
original owner, and has separately received that same cxnAliasCode from
them over that connection. This is independent of how many hops the
content traveled to arrive — recognition depends on the receiver's own
direct connection to the originator, not on the path the content took. A
party with no such connection simply sees an opaque code it cannot
resolve, and the content's true originator stays unrecognized to it.
Worked example (Pools domain, non-normative): Pools stamps each
member record with the owning party's cxnAliasCode as aliasCode.
When displaying a pool's members, a party looks up each member's
aliasCode against its own connections; where it resolves, that member is
labeled with the name of the party's own direct connection to them —
recognizing someone as the same person across a membership list that may
have reached it through several relayed hops. Where it doesn't resolve,
the member is shown with no connection attached.
Hop-limited resharing
originDOS and remainingDOS (05-content-model.md) bound how far content
may propagate beyond the connections it was directly shared with.
originDOS is set once, by the owner, at the moment content is created,
and MUST NOT change afterward — it is a fixed record of the hop budget
the owner originally allowed, not a live counter. If the owner does not
set it explicitly, it defaults to 1: content that is shared with direct
subscribers but never eligible for further resharing beyond them.
remainingDOS is the live counter. An implementation MUST decrement it
by one whenever previously-shared content is received and persisted —
on create, update, or delete — before that content becomes eligible for
any further relay. This is what actually enforces the limit: a party
MUST NOT share or forward content once its remainingDOS is 0. With
the default originDOS of 1, this means a direct subscriber's copy
reaches 0 as soon as it's received, so it is never relayed onward at
all — reaching only the connections the owner shared with directly.
A party that originates a share (sharing its own content with its own
subscribers) and a party that relays content it already holds (passing on
something it previously received, to its own other subscribers) are
treated differently with respect to shareSequence: origination MUST
stamp a fresh shareSequence, while relaying previously-received content
MUST NOT re-stamp it — the relayed copy MUST carry forward the
shareSequence already recorded on it. This keeps every copy of the same
create, update, or delete traceable to a single shareSequence, regardless
of how many independent paths relay it onward, which the next section
depends on.
Ordering and idempotency
Content can reach a party more than once, out of order, or after the original has already been superseded or deleted — the same content may travel multiple relay paths at different speeds, and a hop-limited resend can arrive well after a later delete. Two separate mechanisms resolve this, because they guard against two different kinds of staleness.
Update-vs-update ordering. version (05-content-model.md) is
incremented by exactly one on every owner-driven update. When applying an
incoming update against content it still holds, an implementation MUST
discard the incoming update unless its version is strictly greater than
the version already stored — this resolves two updates to the same
content arriving out of order, for example over relay paths with
different latencies.
Ordering against a delete. version cannot resolve staleness against
a delete, because once content is deleted there is no longer a stored
document to compare against. Instead, deleting content MUST record a
tombstone — the content's ck and the delete's own shareSequence — in a
record that outlives the deleted document itself. An implementation
MUST discard any later-arriving create or update for that ck unless
its shareSequence is strictly greater than the one recorded in the
tombstone — i.e., unless it happened after the delete it might otherwise
be racing against. Symmetrically, a delete can itself arrive after
something newer has already landed locally: an implementation MUST
discard an incoming delete against any matched document whose own
currently stored shareSequence is not strictly less than the delete's.
Because the previous section established that relaying previously-received
content preserves its shareSequence rather than re-stamping it, these
comparisons remain meaningful no matter how many independent paths a given
create, update, or delete travels through to reach a party — every copy of
the same original share carries the same shareSequence, so "strictly
greater than" means "a genuinely later share," not "arrived more
recently."