Appearance
Device records
A Device record is the in-memory entry kept by SystemDevicesActor for each linked compute participant. The actor stores them in this._devices: Map<string, IDeviceRecord> keyed by deviceId. This page is the field-by-field reference for that record and for the IDeviceView shape that devices.list / devices.get actually return.
In-memory record (authoritative)
ts
// projects/matrix-3/packages/system/src/SystemDevicesActor.ts:27-46
interface IDeviceRecord {
deviceId: string; // == hostId
hostName?: string;
deviceSlug?: string;
hostLinkId?: string;
principalId?: string;
spaceId?: string;
linkStatus?: 'active' | 'revoked';
authorityRoot?: string;
publicNamespace?: string;
routeKey?: string;
status: 'online' | 'offline'; // presence-only
source: 'presence'; // always 'presence' in storage
registeredAt: number;
updatedAt: number;
lastHeartbeatAt: number;
heartbeatTtlMs: number; // default 45_000
metadata: Record<string, unknown>;
runtimes: Map<string, IDeviceRuntimeRecord>;
}Notes on the storage shape:
runtimesis aMap, not an array. The view flattens it into a sorted array (sortedRuntimes()).statusin storage is onlyonlineoroffline. Thelinked/revokedview-statuses come from the cloud-side Host Link record, not from in-memory presence.sourcein storage is always'presence'because the actor only stores records that have actually heartbeat. Host-link-only records show up in the view via the merge step but are NOT persisted in the map.
Wire view (what devices.list / devices.get returns)
ts
// projects/matrix-3/packages/system/src/SystemDevicesActor.ts:48-53
interface IDeviceView extends Omit<IDeviceRecord, 'runtimes' | 'status' | 'source'> {
source: 'presence' | 'host-link' | 'merged';
status: 'online' | 'offline' | 'linked' | 'revoked';
runtimeCount: number;
runtimes: IDeviceRuntimeRecord[]; // flattened, sorted
}Differences from the in-memory record:
sourcewidens from'presence'only to all three values.statuswidens from two values to four.runtimesis an array, not a Map.runtimeCountis added so UIs can render the count without iterating.
The three sources
The source field on a view tells you where the data came from:
source | Provenance | When it appears |
|---|---|---|
presence | _devices map only — record is live but the cloud Host Link is unknown to this actor | Local-owner Hosts that are not linked to a HiveCast cloud account; or when devices.list is called without a principalId |
host-link | auth.hostLink.list only — durable record exists but no live presence | Linked Devices that are currently offline; long-tail "I have these Devices" inventory |
merged | Both presence AND host-link agree (same hostLinkId, principalId, spaceId, authorityRoot) | Linked Devices that are currently online |
The merge logic is in _listHostLinksForPrincipal() and hostLinkDeviceView(). The actor will not merge a presence record with a host-link record that disagrees on any of those four fields — see deviceCanMergeWithHostLink(). This prevents a malicious or buggy heartbeat from "claiming" a host-link it does not own.
Source compaction
devices.list runs the resulting views through compactDeviceViews() to prefer one canonical record per device. The rules:
1. If a record has a stable installId (matches /^install_[a-f0-9]{20}$/),
it wins for its installId.
2. Records grouped by (authorityRoot, publicNamespace, routeKey) prefer
one entry per group.
3. The 'preferred' record within a group is the online one; ties break
on most recent updatedAt.Why: re-pairing the same install with a new hostId would otherwise show two entries (one revoked Host Link plus the new live one). The compaction keeps the live one and elides the dead namespace-keyed record.
Status semantics in the wire view
status | Meaning |
|---|---|
online | Live presence within heartbeatTtlMs |
offline | Presence record exists but heartbeat exceeded TTL |
linked | Durable Host Link with status: 'active', no live presence |
revoked | Host Link status: 'revoked' |
devices.list({ includeOffline: false }) excludes offline and revoked views. The default behaviour is includeOffline: false.
Re-keying on devices.register
devices.register is idempotent across re-pairings, with a critical safety check: a record already in memory cannot be carried forward unless the new payload's identity scope agrees with the previous one (deviceRecordCanCarryForward). Specifically:
- if the previous record has a
principalId, the new payload MUST have the sameprincipalId - same for
spaceId,authorityRoot,hostLinkId,publicNamespace,routeKey
If they don't agree, devices.register returns { ok: false } and the record is left untouched. This is the "ownership cannot be silently mutated" rule in code form.
See also
- Heartbeats — how records get registered.
- Online / offline status — TTLs and the prune timer.
- Reference / Device schema — full table of every field.
Source:
projects/matrix-3/packages/system/src/SystemDevicesActor.ts(IDeviceRecordat lines 27-46,IDeviceViewat lines 48-53,compactDeviceViews()at lines 593-621,deviceRecordCanCarryForward()at lines 510-536).