Appearance
Local testing
You can exercise the entire pairing and session flow without ever touching production HiveCast. The dev pattern is "sibling Hosts": one local Host plays the platform/cloud role, another plays the linked Device role. Both use the same code paths, the same actor names, and the same wire shapes.
Two-runtime topology
CLAUDE.md § "Two-Runtime Dev Topology" describes the canonical setup. A worker container or workstation runs:
matrix-webruntime as the platform (account-facing shell, OAuth surface, Devices dashboard), exposing port 5001.matrix-edgeruntime as the Device (local Host UI), exposing port 5002.
Both runtimes share the same Host Service, the same local NATS, the same authority root, and the same system.devices.
bash
# Start a Host without auto-launched default runtimes:
hivecast install --home /tmp/matrix-home --no-start
hivecast start --home /tmp/matrix-home --no-default-runtimes
# Bring up the platform shell on a fixed port:
matrix up @open-matrix/matrix-web --serve --port 5001 \
--runtime-id WEB --env hivecast --startup auto --restart always
# And the Device shell on another fixed port:
matrix up @open-matrix/matrix-edge --serve --port 5002 \
--runtime-id EDGE --env hivecast --startup auto --restart alwaysBrowse:
http://127.0.0.1:5001/apps/web/— platform-role shellhttp://127.0.0.1:5002/apps/edge/— Device-role shell
Both are real packages running their real code paths.
Pairing against a sibling Host
To test the pairing flow without https://hivecast.ai:
bash
# Treat the platform-role Host as "the cloud":
hivecast login --device --cloud http://127.0.0.1:5001 \
--route-key dev-pairing-$(date -u +%s) \
--home /tmp/another-matrix-homeThe --cloud URL points at the sibling Host. The pairing protocol is identical. The Devices page on the sibling Host shows the linked Device.
projects/matrix-3/scripts/prove-fresh-device-link.ts automates this case for proof runs.
OAuth in local testing
Local-loopback provider OAuth (Anthropic, Codex) works exactly as in production — these flows do not depend on a cloud OAuth client.
HiveCast account OAuth (Google) requires a registered redirect URI. For local dev, you have three options:
Option A — Use a staging Google client
Set up a Google OAuth client whose redirect URI is http://127.0.0.1:5001/api/auth/callback/google. Note that Google permits http://127.0.0.1 (and http://localhost) as authorized redirect URIs for development.
Configure the platform-role Host with that client's clientId / clientSecret. Sign-in works.
Option B — Mock the principal directly
For dev runs that don't need real Google, the local Host's auth state store can have a principal pre-seeded:
bash
matrix invoke system.auth auth.principal.ensure \
'{"issuer":"dev-mock","subject":"alice","email":"alice@example.com","displayName":"Alice"}'This creates a principal record without going through OIDC. Then mint a session directly:
bash
matrix invoke system.auth auth.session.create \
'{"principalId":"p_<sha20>","email":"alice@example.com"}'Set the resulting JWT as the mx_session cookie. Sign-in is bypassed.
Trade-off (Option B): the entire OAuth path is untested. Anything that depends on
verifyGoogleIdToken()is skipped. Use this only for tests that don't exercise sign-in itself.
Option C — auth.mode: 'public-session'
Some dev configurations set auth.mode: 'public-session' to allow unauthenticated session creation. This is appropriate for unit tests and CI smoke tests but should never be used in user-facing dev environments.
Trade-off (Option C): session creation is a no-op gate. Anyone can mint a session for any
principalId. Only safe inside CI.
Verifying auth properties locally
Verify session JWT signing
bash
# Mint a session, then validate it:
TOKEN=$(matrix invoke system.auth auth.session.create \
'{"principalId":"p_test"}' | jq -r .token)
matrix invoke system.auth auth.session.validate '{"token":"'$TOKEN'"}'
# expect: { ok: true, claims: { sub: 'p_test', iss: 'localhost-dev', ... } }Verify revocation
bash
matrix invoke system.auth auth.session.revoke \
'{"headers":{"cookie":"mx_session='$TOKEN'"}}'
matrix invoke system.auth auth.session.validate '{"token":"'$TOKEN'"}'
# expect: { ok: false } (jti is now in revokedSessions[])Verify identity from transport metadata
bash
matrix invoke system.auth auth.identity.resolve \
'{"headers":{"cookie":"mx_session='$TOKEN'"},"loopback":true}'
# expect: { authenticated: true, principalId: 'p_test', localClient: true }Verify pairing flow end-to-end
bash
# Start a device-code:
RESP=$(matrix invoke system.auth auth.device.start '{}')
USER_CODE=$(echo $RESP | jq -r .userCode)
DEVICE_CODE=$(echo $RESP | jq -r .deviceCode)
# Approve from a "user" session:
matrix invoke system.auth auth.device.approve \
'{"userCode":"'$USER_CODE'","principalId":"p_test"}'
# Exchange to get the credentials:
matrix invoke system.auth auth.device.exchange \
'{"deviceCode":"'$DEVICE_CODE'"}'The exchange response contains the DeviceSetupExchangeResponse-shaped payload. Inspect credentials to see the device-scoped NATS user JWT and heartbeat token.
What you cannot test locally
- Production registry behavior. The cloud's Devices page UI may rely on cloud-only state synced from federation peers; sibling Hosts don't replicate that.
- Real DNS-verified domain claims.
auth.namespace.claimfordomain.*namespaces requires DNS TXT verification; that path is cloud-only. - Real federation between Hosts. The two-runtime dev topology runs in one Host. The full federated case (one platform Host + one separate Device Host federated as a NATS leaf) is owned by
WORKSTREAMS/runtime-environment-multi-instance/.
See also
- Staging OAuth — staging OAuth client setup.
- Device flow — what the pairing actually does.
- HiveCast account login — what the session JWT actually is.
- CLAUDE.md § "Two-Runtime Dev Topology".
Source: CLAUDE.md two-runtime dev topology;
projects/matrix-3/packages/system-auth/src/host-auth.ts(HostSessionService,HostPrincipalStore);projects/matrix-3/scripts/prove-fresh-device-link.ts.