Appearance
Host link
The Host Link is the durable record that says "this principal owns this Device on this Space, with this set of scopes, and this is its current heartbeat-token hash." It is the cloud-side authority for everything Devices related. The Device mirrors a subset of it locally so it can post heartbeats without round-tripping to the cloud.
Internal name vs product term: internally the storage and API call this a "Host Link" with
hostLinkId. The user-facing product term is Device Link, presented as just "Device" in HiveCast UI copy. Don't introduce "Host Link" into normal product UI.
Cloud-side shape
ts
// projects/matrix-3/packages/system-auth/src/host-auth.ts:94-113
export interface IHostLinkRecord {
readonly id: string; // hostlink_<24 hex>
readonly hostId: string; // host_<20 hex>
readonly hostName?: string;
readonly deviceSlug: string;
readonly natsUserPublicKeys?: readonly string[];
heartbeatTokenHash?: string; // sha256 of the plaintext token
heartbeatTokenIssuedAt?: string;
readonly principalId: string;
readonly spaceId: string;
readonly routeKey?: string;
readonly publicNamespace?: string;
readonly authorityRoot: string;
readonly scopes: readonly string[];
status: 'active' | 'revoked';
readonly createdAt: string;
updatedAt: string;
lastRefreshedAt?: string;
revokedAt?: string;
}Fields and what they're for:
| Field | Purpose |
|---|---|
id | Primary key. hostlink_<24 hex>. Mint per pairing. |
hostId | Stable Device identity. host_<20 hex>. Reused across re-pairings of the same install. |
hostName | Mutable human label. displayName is forbidden here. |
deviceSlug | Stable address-safe management projection key under system.devices.<deviceSlug>. Collision-resolved via allocateDeviceSlug(). |
heartbeatTokenHash | SHA-256 of the bearer token issued to the Device. The token itself is never persisted. |
heartbeatTokenIssuedAt | Last rotation timestamp. |
principalId | Owner of this link. |
spaceId | Which Space this link runs in. A principal may own multiple Spaces. |
routeKey / publicNamespace | Optional Space-path / typed namespace claim (v1 alias and canonical form). |
authorityRoot | Bus / security / runtime authority root. Heartbeats and bus addressing key off this. |
scopes | Capability list attached at pairing. |
status | active or revoked. |
natsUserPublicKeys | Recorded public keys for the device-scoped NATS user JWTs (used to map auth_user events back to a Device). |
Operations on a Host Link
The methods of HostLinkStore (in host-auth.ts:799-988) describe the full operation set:
| Method | Op | Effect |
|---|---|---|
create({ hostId?, principalId, spaceId, authorityRoot, … }) | auth.hostLink.create | Mints a new link; revokes any prior active link with the same hostId. |
get(id) | auth.hostLink.get | Look up by link id. |
getByHostId(hostId) | auth.hostLink.get (when called with hostId) | Look up the active link for a hostId. |
listForPrincipal(principalId) | auth.hostLink.list | All links for a principal, including revoked. |
refresh({ id?, hostId?, principalId? }) | auth.hostLink.refresh | Bumps lastRefreshedAt / updatedAt. |
rotateHeartbeatToken(...) | called by auth.hostLink.credentials.refresh | Mints a new token, hashes it, stores hash, returns plaintext exactly once. |
recordNatsUserPublicKey({ userPublicKey, … }) | called during credential issue | Records the device's NATS user key so audit can correlate. |
verifyHeartbeatToken({ token, hostId?, … }) | auth.hostLink.verifyHeartbeat | Compares SHA-256 with constant-time equality; refreshes lastRefreshedAt on success. |
revoke({ id?, hostId?, principalId? }) | auth.hostLink.revoke | Marks revoked, sets revokedAt. |
Device-side mirror
ts
// projects/matrix-3/packages/mx-cli/src/utils/hivecast-link-store.ts:19-38
export interface IHiveCastHostLinkRecord {
readonly version: 1;
readonly cloudUrl: string;
readonly deviceRegistryRoot?: string;
readonly hostLinkId?: string;
readonly hostId?: string;
readonly principalId?: string;
readonly hostName?: string;
readonly deviceSlug?: string;
readonly spaceId?: string;
readonly routeKey?: string;
readonly publicNamespace?: string;
readonly authorityRoot: string;
readonly linkedAt: string;
readonly credentials: {
readonly hostLinkTokenRef?: string;
readonly registryTokenRef?: string;
readonly natsCredentialRef: string;
};
}The Device mirror is intentionally a subset: no scopes, no natsUserPublicKeys, no heartbeatTokenHash (the Device holds the plaintext token in a separate file), no status (the Device assumes active until a heartbeat returns 401/403). The mirror also adds local-only fields:
cloudUrl— where to post heartbeats.deviceRegistryRoot— used for cloud-side device registry routing if set.linkedAt— when this mirror was written.credentials.*— references to local credential files (file paths relative to<host-home>).
Where the mirror lives on disk
Saved by saveHiveCastHostLink():
<host-home>/credentials/hivecast-link.json <- mirror, 0o600
<host-home>/credentials/hivecast-host-link-token <- plaintext bearer, 0o600
<host-home>/credentials/hivecast-nats.json <- device NATS creds, 0o600
<host-home>/credentials.json <- mirror NATS creds for local NATS clientsaveHiveCastHostLink() also rewrites <host-home>/host.json so its transport block points at the cloud NATS URL with { mode: 'external', credentialsRef: '<absolute path>' }. That is what flips the local Host into "linked" mode on next start.
Where the cloud-side record lives
system.auth persists state through HostAuthStateStore (in host-auth.ts, referenced from index.ts). The state file location depends on how the auth actor is configured: by default it is under the auth actor's home directory. The structure is a single JSON document with all the auth stores keyed by collection (hostLinks[], principals[], etc.).
Active uniqueness
A hostId may have at most one active Host Link at any moment. HostLinkStore.create() enforces this:
ts
// projects/matrix-3/packages/system-auth/src/host-auth.ts:834-840
for (const entry of this._state.state.hostLinks) {
if (entry.hostId === hostId && entry.status === 'active') {
entry.status = 'revoked';
entry.updatedAt = now;
entry.revokedAt = now;
}
}So re-pairing the same install always revokes the prior link before creating the new one. getByHostId() returns only the active link.
listForPrincipal() returns all links (active and revoked) so the Devices page can show history if the UI wants to.
See also
- Pairing grant — the wire response that delivers a redacted/expanded view of this record to the Device.
- Reference / Host link schema — the reference table of every field.
- Inventory / Device records — how the link record is projected into a Device row.
Source:
projects/matrix-3/packages/system-auth/src/host-auth.ts(IHostLinkRecord,HostLinkStore); cloud<>device mirror atprojects/matrix-3/packages/mx-cli/src/utils/hivecast-link-store.ts(IHiveCastHostLinkRecord,saveHiveCastHostLink).