pub static WELL_KNOWN: &[(ComponentId, &'static dyn ErasedComponent)]Expand description
Sorted-ascending table of (ComponentId, &dyn ErasedComponent)
entries for every well-known XMTP component.
Order is enforced by [assert_table_is_sorted_and_unique] at
compile time. Tests further pin specific lookup expectations.
§Change control
Component-id ranges in play (mirror of lookup_component below):
| Range | Purpose |
|---|---|
0x8000-0xBFFF | XMTP-allocated well-known ids (this table) |
0xC000-0xFEFF | Application-range RuntimeComponent ids |
0xFF00-0xFFFF | Reserved (hard-rejected, no graceful-degrade) |
Adding a new well-known entry here changes the protocol’s receiver-side acceptance set. Old clients (released before the new entry) handle the new id via the type-aware unknown-component tolerance path in:
apply_app_data_update_payloadexpand_app_data_update_to_changesvalidate_one_app_data_update_with_old_value
That path looks the unknown id up in the on-dict
ComponentRegistry,
pulls its registered ComponentType, and dispatches through the
type-level decoder. The closed type universe covers every shape:
Bytes / String pass-through, TlsSet<InboxId> / TlsSet<bytes> /
TlsMap<InboxId, bytes> / TlsMap<bytes, bytes> apply their deltas
element-wise — old and new clients converge on the same dict bytes.
The tolerance path covers the XMTP range (0x8000-0xBFFF) and the
application range (0xC000-0xFEFF); the reserved range
(0xFF00-0xFFFF) is still hard-rejected — those slots are
protocol-level and have no graceful-degrade story. Do not allocate
new ids there.
Requirements when adding a new well-known component:
- The component MUST be reachable through one of the six
ComponentTypevariants. The wire codec for each is fixed; an old client decodes it the same way a typed client would. - The component MUST NOT carry receive-side invariants beyond
registry policy. Old (type-dispatched) clients lack the per-id
Component::validate_invarianthook — diverging invariant behavior would fork the dict. - Read-side accessors surface the default value for the
component’s type on old clients (empty
Bytes/String/TlsSet/TlsMap). The “absent” state is indistinguishable from “explicitly cleared” on old clients — design semantics accordingly and document that degradation at the accessor boundary. - The component MUST land in the registry before or with the first commit that writes to it. Old clients consult the pre-commit registry snapshot, so a same-commit registration followed by a same-commit write fails to dispatch.
Floor-bump convention (pause, don’t fork). Any release that
introduces something old receivers cannot interpret — a new
ComponentType, a new set/map delta mutation tag, a new registry
entry format, a reserved-range (0xFF00+) allocation, or a change
to the bootstrap synthesis encoding — MUST raise
PROPOSALS_MIN_PROTOCOL_VERSION in the same release AND land each
group’s MIN_SUPPORTED_PROTOCOL_VERSION floor bump in a commit
strictly earlier than the first commit using the new construct
(the floor-bump commit itself must contain nothing format-novel).
Receivers below a committed floor pause the group
(defer-and-reprocess after upgrade) via the pause-before-parse
guards in xmtp_mls::groups::app_data and
ValidatedCommit::from_staged_commit; a same-commit floor bump is
NOT protected — its proposal hasn’t passed the super-admin policy
check when the guards run, and pausing on unvalidated input would
let any member freeze a group.
Two ergonomic patterns for shipping a new component without
editing WELL_KNOWN:
- Application-range
RuntimeComponent. Components in0xC000-0xFCFFregistered at runtime via theRuntimeComponentfacility (seeapp_data::custom) ship without touchingWELL_KNOWN— only the host that registered the component decodes its payload, while old clients type-dispatch via the registry the same way. - Coordinated protocol-version bump. Required only when the new component must reject specific bytes that the type-level codec would otherwise accept (e.g. a per-id invariant beyond type shape).