feat(relay): rendezvous-relay service — 7 packages + plans (contracts/transport/agent/control-plane/e2e/auth/web)
Multi-tenant reverse-tunnel service ("ngrok for Claude Code" with E2E): a
host-agent dials OUT to an operator-run relay; external devices reach the host
THROUGH the relay, routed by per-tenant subdomain, forwarding ciphertext only
(the relay never sees plaintext). Lets a customer reach their own self-hosted
web-terminal from anywhere with zero networking setup.
Packages — all tsc-strict + vitest green (656 tests), cross-package integration verified:
- relay-contracts: frozen shared contracts (mux frame codec, data model,
capability token, E2E envelope, pairing) — the src/types.ts analog
- term-relay: native WS mux + stateless data plane (subdomain routing, ciphertext forward)
- agent: host-agent (pairing, per-host Ed25519 + mTLS dial-out, forwards to 127.0.0.1:3000)
- control-plane: accounts/hosts registry, pairing-code flow, routing table, provisioning
- relay-e2e: browser<->agent E2E (X25519 ECDH through relay, AEAD, anti-replay, recoverable replay key)
- relay-auth: Passkey/WebAuthn, capability tokens, per-host certs, deny-by-default tenant isolation
- relay-web: browser login + Web Crypto E2E + client-side preview rendering
Security invariants INV1-15 enforced; cross-tenant isolation CI tripwire live
(.github/workflows/relay-tripwire.yml). Design + implementation-level plans in
docs/PLAN_RELAY_*.md and docs/EXPLORE_RELAY_SERVICE.md.
NOTE: generated autonomously per the reviewed plans. The security-critical
packages (relay-e2e, relay-auth) REQUIRE expert security audit before any real
deployment — passing tests prove self-consistency, not resistance to attackers.
Base app (src/, public/) unchanged; concurrent desktop work left uncommitted.
This commit is contained in:
130
relay-contracts/test/model.test.ts
Normal file
130
relay-contracts/test/model.test.ts
Normal file
@@ -0,0 +1,130 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import {
|
||||
AccountRecordSchema,
|
||||
HostRecordSchema,
|
||||
KillSignalSchema,
|
||||
RELAY_REVOCATIONS_CHANNEL,
|
||||
REVOCATION_PUSH_BUDGET_MS,
|
||||
RevocationScopeSchema,
|
||||
RouteEntrySchema,
|
||||
SessionRecordSchema,
|
||||
INV8_VERSION_TABLE_DDL,
|
||||
HOST_VERSIONS_DDL,
|
||||
HOSTS_CURRENT_DDL,
|
||||
} from '../src/index.js'
|
||||
|
||||
const UUID = '11111111-1111-4111-8111-111111111111'
|
||||
const TS = '2026-07-01T00:00:00.000Z'
|
||||
|
||||
describe('§4.2 account/host/session Zod schemas', () => {
|
||||
it('accepts a valid HostRecord', () => {
|
||||
const rec = {
|
||||
hostId: UUID,
|
||||
accountId: UUID,
|
||||
subdomain: 'alice',
|
||||
agentPubkey: new Uint8Array([1, 2, 3]),
|
||||
enrollFpr: 'fpr:abc',
|
||||
status: 'online',
|
||||
lastSeen: TS,
|
||||
createdAt: TS,
|
||||
revokedAt: null,
|
||||
}
|
||||
expect(HostRecordSchema.parse(rec)).toEqual(rec)
|
||||
})
|
||||
|
||||
it('rejects a HostRecord with an invalid status', () => {
|
||||
expect(() =>
|
||||
HostRecordSchema.parse({
|
||||
hostId: UUID,
|
||||
accountId: UUID,
|
||||
subdomain: 'a',
|
||||
agentPubkey: new Uint8Array(),
|
||||
enrollFpr: 'f',
|
||||
status: 'zombie',
|
||||
lastSeen: TS,
|
||||
createdAt: TS,
|
||||
revokedAt: null,
|
||||
}),
|
||||
).toThrow()
|
||||
})
|
||||
|
||||
it('rejects a HostRecord with a non-UUID hostId', () => {
|
||||
const bad = {
|
||||
hostId: 'not-a-uuid',
|
||||
accountId: UUID,
|
||||
subdomain: 'a',
|
||||
agentPubkey: new Uint8Array(),
|
||||
enrollFpr: 'f',
|
||||
status: 'online',
|
||||
lastSeen: TS,
|
||||
createdAt: TS,
|
||||
revokedAt: null,
|
||||
}
|
||||
expect(HostRecordSchema.safeParse(bad).success).toBe(false)
|
||||
})
|
||||
|
||||
it('rejects unknown extra keys (strict)', () => {
|
||||
expect(
|
||||
AccountRecordSchema.safeParse({
|
||||
accountId: UUID,
|
||||
plan: 'pro',
|
||||
createdAt: TS,
|
||||
status: 'active',
|
||||
extra: 1,
|
||||
}).success,
|
||||
).toBe(false)
|
||||
})
|
||||
|
||||
it('accepts a valid AccountRecord across all plan tiers', () => {
|
||||
for (const plan of ['free', 'personal', 'pro', 'team'] as const) {
|
||||
expect(AccountRecordSchema.parse({ accountId: UUID, plan, createdAt: TS, status: 'active' }).plan).toBe(
|
||||
plan,
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
it('validates SessionRecord and RouteEntry', () => {
|
||||
expect(
|
||||
SessionRecordSchema.parse({
|
||||
sessionId: UUID,
|
||||
hostId: UUID,
|
||||
accountId: UUID,
|
||||
createdAt: TS,
|
||||
lastAttachAt: TS,
|
||||
}).sessionId,
|
||||
).toBe(UUID)
|
||||
expect(RouteEntrySchema.parse({ relayNodeId: 'node-1', updatedAt: TS }).relayNodeId).toBe('node-1')
|
||||
})
|
||||
})
|
||||
|
||||
describe('§4.2 FIX 4 revocation teardown shapes', () => {
|
||||
it('exposes the frozen channel + budget constants', () => {
|
||||
expect(RELAY_REVOCATIONS_CHANNEL).toBe('relay:revocations')
|
||||
expect(REVOCATION_PUSH_BUDGET_MS).toBe(2000)
|
||||
})
|
||||
|
||||
it('accepts each RevocationScope variant', () => {
|
||||
expect(RevocationScopeSchema.parse({ kind: 'host', hostId: UUID }).kind).toBe('host')
|
||||
expect(RevocationScopeSchema.parse({ kind: 'account', accountId: UUID }).kind).toBe('account')
|
||||
expect(RevocationScopeSchema.parse({ kind: 'global' }).kind).toBe('global')
|
||||
})
|
||||
|
||||
it('rejects a host scope missing hostId', () => {
|
||||
expect(RevocationScopeSchema.safeParse({ kind: 'host' }).success).toBe(false)
|
||||
})
|
||||
|
||||
it('validates a full KillSignal', () => {
|
||||
const signal = { scope: { kind: 'global' }, at: 1719800000, reason: 'operator drain' }
|
||||
expect(KillSignalSchema.parse(signal)).toEqual(signal)
|
||||
})
|
||||
})
|
||||
|
||||
describe('§4.2 INV8 version-table DDL contract', () => {
|
||||
it('exposes host_versions + hosts_current DDL in order', () => {
|
||||
expect(INV8_VERSION_TABLE_DDL).toEqual([HOST_VERSIONS_DDL, HOSTS_CURRENT_DDL])
|
||||
expect(HOST_VERSIONS_DDL).toContain('host_versions')
|
||||
expect(HOST_VERSIONS_DDL).toContain('supersedes')
|
||||
expect(HOSTS_CURRENT_DDL).toContain('hosts_current')
|
||||
expect(HOSTS_CURRENT_DDL).toContain('version_id')
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user