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's already been shared once: how a receiver can recognize who originally shared something even after it's passed through other parties, how far content may 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 doesn't cover who's allowed to receive content in the first place: that's 04-access-control.md's subscription mechanism, which this chapter builds on.

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 content arrived on, never its original owner.

The alias mechanism (04-access-control.md) already gives two directly connected parties a way to exchange a cxnAliasCode. A domain MAY build on this by stamping content whose originator should be recognizable with the owner's own cxnAliasCode, under whatever field name it chooses. Since this code isn't on the baseline list of fields stripped before sharing (05-content-model.md), it survives every further hop unchanged, riding along with the content.

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: recognition depends on the receiver's own direct connection to the originator, not the path the content took. A party with no such connection simply sees an opaque code it can't resolve, and the content's true originator stays unrecognized to it.

Worked example (non-normative): a domain that models group membership might stamp each membership record with the owning party's cxnAliasCode as aliasCode. When displaying a group'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, when content is created, and MUST NOT change afterward: it's a fixed record of the hop budget originally allowed, not a live counter. If the owner doesn't set it explicitly, it defaults to 1: the content 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 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, a direct subscriber's copy reaches 0 as soon as it's received, so it's never relayed onward, 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, carrying forward the shareSequence already recorded on it instead. 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, guarding against two different kinds of staleness.

Update-vs-update ordering. version (05-content-model.md) increments by exactly one on every owner-driven update. When applying an incoming update against content it still holds, an implementation MUST discard the update unless its version is strictly greater than the version already stored: this resolves two updates to the same content arriving out of order, e.g. over relay paths with different latencies.

Ordering against a delete. version can't resolve staleness against a delete: once content is deleted there's no 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. An implementation MUST discard any later-arriving create or update for that ck unless its shareSequence is strictly greater than the one 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 currently stored shareSequence isn't strictly less than the delete's.

Because relaying previously-received content preserves its shareSequence rather than re-stamping it (above), these comparisons stay meaningful no matter how many independent paths a create, update, or delete travels through: every copy of the same original share carries the same shareSequence, so "strictly greater than" means "a genuinely later share," not "arrived more recently."