Docs Menu

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."