Files
agentic-dev/apps/runner/AGENTS.md
Danijel Martinek b090e26701 feat(runner): WS protocol server with handshake
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
2026-07-12 22:50:41 +02:00

47 lines
2.4 KiB
Markdown

# 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` (default `0` = ephemeral), `RUNNER_HOST` (default
`127.0.0.1`), `RUNNER_HEARTBEAT_MS` (default `1000`).
- **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`/`SIGINT` shut 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 answers `ready`.
- Commands run one at a time in arrival order. A running stage streams
`status` (stage + elapsedMs: start, heartbeats, final). Success ends
with `ready` (the idle marker the story-07 orchestrator drives on);
failure ends with a named `error` and 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 in
`tests/` (`protocol-server.test.ts` runs the real server in-process so
v8 coverage sees it; `runner-process.integration.test.ts` spawns the
real child process and discovers the port from stdout).
- `tests/protocol-client.ts` envelope-parses every inbound frame, so
every test doubles as an outbound-conformance assertion.
- Coverage: app-tier vitest thresholds inherited from
`vitest.base.node`; only `src/main.ts` (bootstrap glue, exercised by
the spawn suite in a child process) is excluded.