CXO Feeds¶
| Status | Implemented |
|---|---|
| Revision | 2026-10-27, skywire f37a464a4 |
| Code | pkg/cxo/treestore, pkg/cxo/cxosub, pkg/cxo/cxoaggregate, pkg/cxo/cxoutils, pkg/cxo/sysfeed, pkg/skyenv |
Abstract¶
A CXO feed is a signed, content addressed tree of named leaves that a publisher serves on a dmsg port. A subscriber receives each new root and fetches only the objects it does not hold. Deployment services and visors use feeds to distribute state, so that a reader can take a snapshot, and follow changes, without polling an HTTP API.
Overview¶
The feed tree (pkg/cxo/treestore) is built on the CXO object store
(pkg/cxo/skyobject). The publisher keeps the tree in memory, and after a
batch window it encodes the changed branches and saves a new signed root. An
unchanged branch keeps its hash, so a subscriber does not fetch it again. A
subscriber walks the filled tree into a path to bytes map.
Two connection directions occur.
- Subscribe. The subscriber dials the publisher at
<publisher-pk>:<port>and subscribes to the feed key. Readers of the deployment feeds (cxosub.Manager) do this. - Announce. The publisher dials an aggregator (
Publisher.AnnounceTo). The aggregator holds the inbound connection and subscribes to the feed whose key is the connection's remote key. Visors use this toward TPD, dmsg discovery, the address resolver and service discovery.
Addressing¶
The feed key is the publisher's public key (Publisher.Feed). A publisher MAY
serve several feeds under one key on different dmsg ports
(treestore.PubConfig.DmsgPort). The default port is
cxotransport.DefaultCXOPort, which is skyenv.DmsgCXOPort (50). A subscriber
creates a treestore.Subscriber with SubConfig.DmsgPort set to the same port
and calls Connect with the publisher key. The dmsg ports are in the table below
and in 01. A visor lists its feeds at GET /feeds on its dmsg
HTTP service as an array of {"name", "dmsg_port", "description", "system"}.
Data Formats¶
Paths. A path is slash separated segments. A segment is not empty and does
not contain /. The empty path is the root. A prefix matches whole segments.
Leaves. A leaf is a byte string stored in the tree entry
(treestore.TreeEntry.Leaf). A single object is limited to
skyobject.Config.MaxObjectSize (16 MiB); a publish that contains a larger
object is refused. Most leaves are JSON, gzip compressed. Two encodings occur.
- A bare gzip body. A reader decompresses with
cxoutils.Gunzip, which detects the gzip header and passes other bodies through. - A frame: a version byte followed by gzip (
cxoutils.FrameGzip,UnframeGzip). A reader skips a leaf whose version it does not know.
Layout conventions.
entryandtombstone. Older feeds publish<id>/entryfor a record and<id>/tombstonefor its removal. Current feeds do not use tombstones. A record is removed by omitting it from its batch leaf or by deleting its leaf.- A snapshot leaf replaces per record leaves where a reader needs the complete set. The reader treats absence as removal.
- A shard is a leaf for a subset of keys, with a fixed number of shards, so that a reader can fetch one subset.
- A day leaf,
<prefix>/day/<YYYY-MM-DD>, holds one settled calendar day that no longer changes.
Interfaces¶
CXO Feeds¶
Every feed below is served by treestore.Publisher on the given dmsg port.
"Open" means no subscriber restriction (SetAllowlist(nil)).
| Port | Identifier | Publisher | Root layout | Cadence | Consumer |
|---|---|---|---|---|---|
| 50 | DmsgCXOPort |
visor | transports/telemetry/<sh> and transports/checkpoints/<sh> with <sh> two hex digits from 00 to 0f; transports/<uuid>/<date>/timeline; tiers/<tier>/<date>; services/<svc>/<date>; tp-list as a fallback |
batch window 10 s; samples every minute | TPD aggregator |
| 69 | DmsgVisorTPListCXOPort |
visor | tp-list: gzip JSON {"version", "c": [CompactEntry]} |
batch window 2 s; republished every 90 s and on a transport change | TPD aggregator |
| 67 | DmsgDMSGDRegistrationCXOPort |
visor | entry: JSON disc.Entry |
batch window 5 s; on each registration | dmsg discovery |
| 71 | DmsgVisorARBindCXOPort |
visor | one leaf per transport type, named by the type: JSON of the bound LocalAddresses |
batch window 5 s; on each bind | address resolver |
| 72 | DmsgVisorSDRegCXOPort |
visor | services: framed gzip (version 1) JSON array of servicedisc.Service |
batch window 5 s; on each change | service discovery |
| 51 | DmsgTPDMetricsCXOPort |
TPD | metrics/day/<YYYY-MM-DD>: gzip JSON, with metrics/day/<date>/part/<NNNN> if a day is too large |
every 5 minutes for the current day | reward system, hypervisor |
| 52 | DmsgTPDUptimeCXOPort |
TPD | uptimes/days/<n> for n in 1, 7, 30 |
every 60 s | hypervisor |
| 73 | DmsgTPDStatsCXOPort |
TPD | stats/network, stats/versions, stats/daily |
every 12 s; stats/daily every 5 minutes |
network view |
| 75 | DmsgTPDRoutingCXOPort |
TPD | routing/<pk> snapshot shards and routing/delta/<seq> (12 digits) |
every 10 minutes; a snapshot every 6 publishes | visors |
| 77 | DmsgTPDVisorBWCXOPort |
TPD | visorbw/day/<YYYY-MM-DD>, the last 7 settled days |
checks every 10 minutes | reward system |
| 79 | DmsgTPDPerKeyCXOPort |
TPD | perkey/stats |
every minute | network view, CLI |
| 53 | DmsgSDServicesCXOPort |
service discovery | services/<type>/all: framed gzip JSON array of servicedisc.Service, sorted by key |
batch window 1 s, on registration change | hypervisor network visualizer |
| 54 | DmsgDMSGDClientsByServerCXOPort |
dmsg discovery | clients-by-server/<server-pk> presence leaf (version 2, no entries); clients-by-server/servers/<pk> signed server entry (framed gzip, version 1) |
batch window 30 s | visors, dmsg servers, TPD |
| 74 | DmsgARBindingsCXOPort |
address resolver | <h2>/<h3> for the third and fourth hex digits of the key: framed gzip JSON map from key to bindings, 256 buckets always present |
batch window 30 s; full resync every 5 minutes | visors |
| 76 | DmsgARReachCXOPort |
address resolver | reach/<h> for the first hex digit after the 02 or 03 prefix, 16 leaves |
batch window 30 s | visors (autoconnect) |
| 78 | DmsgConfCXOPort |
configuration bootstrapper | services/current: gzip JSON deployment.Services (deployment.ServicesCXOPath) |
checks every minute; publishes on change | visors |
| 55 | DmsgTPDAllTransportsCXOPort |
none | transports/all/. No publisher runs. |
A user feed has a name and a port chosen by the visor owner. The visor refuses a
port that belongs to a system service or to the chat pair range, and the name
stats.
Access¶
A visor feed (ports 50, 69, 67) restricts subscribers to the visor's peer
whitelist plus the consuming service (composeFeedAllowlist). The restriction is
open only when both are empty. The deployment feeds in the table are open.
Behaviour¶
Publish. Put, PutBatch and Delete change the in memory tree. After the
batch window the publisher encodes the changed branches and saves a root. When
nothing changes for heartbeatInterval (45 seconds) it republishes the current
root so that the connection stays alive. The publisher keeps the newest roots
(PubConfig.KeepRoots) so that a subscriber that is still filling an older root
can finish.
Subscribe. A subscriber fills the whole root before it can read a leaf. A
feed with few top level children fills faster. A subscriber that sees no root for
60 seconds reconnects (reconnectQuietThreshold). cxosub.Manager runs one
cycle per feed while a consumer holds it: subscribe, wait for the first root,
copy the tree into a snapshot, unsubscribe, and sleep for the interval.
Aggregation. An aggregator (cxoaggregate.Core) runs a CXO node on the
feed's port. It subscribes to each connection whose remote key is a feed key,
decodes the leaves of each filled root and passes them to the service. Leaves
that name a record a reporter does not own are rejected by the service. When
dmsg discovery, the address resolver or service discovery run in the TPD process,
visors may carry their registrations on the telemetry feed as leaves named
reg.dmsgd., reg.ar. and reg.sd. followed by the leaf name used on the
dedicated feed (pkg/cxo/sysfeed).
Security Considerations¶
The root signature proves which key published a feed. A leaf is not signed on its
own, so a service MUST check that the reporter is entitled to the record a leaf
names. TPD requires the reporter to be an edge of every transport it lists, and
dmsg discovery requires the feed key to equal the entry's static. An open feed
exposes its contents to any dmsg peer. A restricted feed exposes them to the
listed keys.