apps/runner scaffold (app-tier, walking-skeleton story 04): WS server speaking @repo/core-runner-protocol. Every inbound/outbound frame is envelope-wrapped and zod-parsed; hello/ready handshake gates on the workspace-scoped token (constant-time compare, redacted token on rejection replies); named error events for version/schema/auth rejections. Config via env only (token never argv); port announced on stdout for the story-06 provisioner. Runtime deps: ws (the standard Node WS server; ADR-022 traces do not apply to app-tier) and zod. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016j8z4VHjedXDTjEDNg7qHK
2.4 KiB
2.4 KiB
apps/runner — workspace runner (cloud-runner process)
The runner process of ADR-027: executes a workspace's repo code (clone,
install, scan, preview adapter) on behalf of the control plane, speaking
@repo/core-runner-protocol over plain WS/JSON. App-tier (tags: ["app"]): imperative code is expected here; there is no feature
manifest, no use-case layer, no DI container.
Process contract (what the provisioner — story 06 — relies on)
- Config is env-only (see
src/config.ts):RUNNER_TOKEN(required, secret — never argv),RUNNER_WORKSPACE_DIR(required),RUNNER_PORT(default0= ephemeral),RUNNER_HOST(default127.0.0.1),RUNNER_HEARTBEAT_MS(default1000). - Port discovery: on boot the runner writes a JSON line to stdout:
{"level":"info","event":"listening","host":...,"port":...}. - Logs are JSON lines on stdout. Log event names + safe fields only — never protocol payloads, never credentials. Integration tests grep the entire child output for the PAT and the workspace token.
SIGTERM/SIGINTshut down gracefully.
Protocol session semantics (documented in src/server.ts)
- Every inbound/outbound frame is envelope-wrapped and zod-parsed
(
envelopeSchema); rejections emit named errors (unsupported-protocol-version/invalid-message/unauthorized). Pre-handshake rejections and token mismatches close the socket (1008). - First message must be
hello; the runner answersready. - Commands run one at a time in arrival order. A running stage streams
status(stage + elapsedMs: start, heartbeats, final). Success ends withready(the idle marker the story-07 orchestrator drives on); failure ends with a namederrorand the session stays open. - Zod issues are summarized as path + code only — payload values (which may include a PAT) are never echoed into errors or logs.
Testing
- Unit suites live next to sources in
src/; protocol/WS suites live intests/(protocol-server.test.tsruns the real server in-process so v8 coverage sees it;runner-process.integration.test.tsspawns the real child process and discovers the port from stdout). tests/protocol-client.tsenvelope-parses every inbound frame, so every test doubles as an outbound-conformance assertion.- Coverage: app-tier vitest thresholds inherited from
vitest.base.node; onlysrc/main.ts(bootstrap glue, exercised by the spawn suite in a child process) is excluded.