File-level plan for a single-host `npm start` relay: new relay-run/ package wiring the real P4/P5 + in-memory control-plane + self-signed TLS + agent + relay-web. Seam files to read, 9-step build order, verify steps. Executable spec for a fresh build session; reuses the e2e/harness wiring.
5.6 KiB
Phase 0 — single-host runnable relay (build spec)
Goal: a single
npm startthat boots an end-to-end relay you can click through locally — real P4(crypto)+P5(auth) wiring, in-memory stores, self-signed TLS, agent dialinglocalhost,relay-webserved. No Postgres/Redis/KMS. This is the executable spec for the Phase 0 milestone in DEPLOY_RELAY.md §4. Reuses the wiring proven ine2e/harness/.Run this build in a FRESH session (recommend
/compactfirst). Read the seam files named below before writing — the exact interface shapes must be confirmed against the code, not assumed.
Deliverable
A new package relay-run/ (top-level, symlink node_modules like e2e/) with one entry
relay-run/src/main.ts that npm start runs, plus small wiring modules. Result: browser at
https://localhost:8443/ → authenticates → live terminal to a shell on the same machine, spliced
through the real relay-node, E2E-encrypted (relay sees only ciphertext).
Seam files to read FIRST (confirm exact shapes)
term-relay/data-plane/ws-like.ts— theWebSocketLikeinterface the relay-node consumes.term-relay/data-plane/agent-listener.ts—AgentListener/AgentTunnelshapes (how an agent mux attaches).term-relay/data-plane/relay-node.ts+upgrade.ts+authz-port.ts—createRelayNode,authorizeUpgrade, theAuthorizerport (onUpgrade/onReattach).term-relay/mux/mux-session.ts—createMuxSession,MuxStreamHandle(frame the agent side).control-plane/src/main.ts—buildControlPlane/overrides (stores,verifier,bus,kmsResolver),createMemoryStores,inProcessCaSigner.agent/src/cli.ts+agent/src/e2e/hostEndpoint.ts+transport/dial.ts— how the agent dials + runs the E2E host endpoint + loopback splice.e2e/harness/world.ts— the REFERENCE wiring: it already composes real P5onUpgrade+ P4 handshake + fakes. Lift theEnforceDeps/registry/bucket/audit construction from here (ande2e/harness/fakes.ts).
Build steps (order)
- Scaffold
relay-run/:package.json(type module,"start":"tsx src/main.ts", dep ontsx,ws),tsconfig.json, andnode_modulessymlinks to all relay packages +ws/tsx(mirrore2e/’s manual-symlink approach — avoidsnpm install). Addrelay-run/node_modulesto.gitignorecoverage (root already ignoresnode_modules/). - Self-signed TLS: generate a localhost cert at boot (node
selfsignedor a checked-in dev cert) for the browser WSS listener; a throwaway CA for agent mTLS. - Control-plane (in-memory): boot
buildControlPlanewithoverrides = { stores: createMemoryStores(), verifier: <real P5 verifyCapabilityToken bound to the dev P5 pubkey>, bus: in-memory, kmsResolver: inProcessCaSigner }. Listen on:8080. Seed one account + issue a pairing code programmatically at boot. - P5 EnforceDeps: build the real
EnforceDeps(hosts/sessions/revocation/buckets/audit +stepUpPolicyFor: () => NO_STEPUP_POLICY) backed by the in-memory control-plane registries — copy the construction frome2e/harness. Configure the P5 verify key (configureVerifyKey). WebSocketLikeadapter: wrapwsWebSocket→ theWebSocketLikeinterface (send/close/onmessage/onclose per ws-like.ts). Browser WSS server on:8443; on upgrade, build theUpgradeRequest(extract token from subprotocol/cookie, Origin, DPoP header) and callauthorizeUpgrade(→ realonUpgrade). Close F2 here: pass a resolved principal when the host policy requires step-up (Phase 0 default policy = not-required, sonullis fine).AgentListeneradapter: mTLStls.Serveron:8444; verify the agent client cert against the dev CA (verifyAgentCert), attach each as anAgentTunnel+createMuxSession. WirecreateRelayNode({ config, agentListener, authorizer })and the opaque splice.- Agent: build the CLI (
dist/cli.js) or runagentin-process; enroll against control-planePOST /enrollwith the seeded pairing code (→ SPIFFE cert +hostContentSecret+ subdomain); dialwss://localhost:8444(mTLS) and run the E2E host endpoint splicing to a local shell (LOCAL_TARGET_URL/ node-pty). - relay-web:
npm --prefix relay-web run build; serve the bundle from the browser listener (same origin as WSS so Origin/CSWSH passes). Wire the login → P5 capability-token mint (Phase 0: a dev endpoint that mints a token after a stub human-auth, or reuseissueCapabilityTokendirectly). main.ts: bring up 3→5→6→7→8 in order; print the URL + a ready-to-use token/subdomain.
Verify
npm startinrelay-run/boots without error; console prints the local URL + subdomain.- Browser: open
https://localhost:8443/?..., authenticate, see a live shell; typewhoami→ output. - Kill/restart the agent → session survives per design (reconnect);
revoke()→ stream tears down. - Add a smoke test (Vitest) that boots the world in-process and round-trips one command E2E through
the real relay-node (extend the
e2e/harnesspattern with the realWebSocketLikeadapter).
Explicitly NOT in Phase 0
Postgres/Redis (in-memory only), real KMS (dev in-process signer), wildcard TLS/multi-tenant subdomain routing, horizontal scaling, F6 replay-epoch transport (preview stays inert), metering. Those are Phase 1/2 in DEPLOY_RELAY.md.
Security notes (keep even in dev)
- Real Origin/CSWSH check on the WSS upgrade (don’t stub it off for localhost — set allowedOrigins to the dev origin). Real capability-token verify + DPoP (no bypass). Self-signed certs are dev-only; never reuse the dev CA anywhere real. hostContentSecret 0600, never logged.