---
id: walking-skeleton
title: Walking skeleton — one real component, end to end through the hosted spine
type: prd
status: approved
author: Danijel Martinek
elicitation-session: session_016j8z4VHjedXDTjEDNg7qHK
created: 2026-07-12T09:10:00Z
---

## Problem

Every Veect risk that matters lives in the seams, not the parts: control plane ↔ runner provisioning, runner ↔ repo toolchain (clone/install/dev-server of code we don't control), and editor ↔ iframe canvas across an origin boundary (ADR-028). The original tech spec front-loaded exactly this with its M0 "walking skeleton" — but its acceptance criteria ("boots with no system Node") died with the Electron distribution (ADR-027). Nothing yet proves the hosted spine works at all, and every subsequent PRD (workspaces, discovery, design-doc, codegen, projects, ai) builds on these seams. If the spine is wrong, we must find out before nine PRDs are stacked on it.

## Goal

A signed-in user connects a fixture repo; a cloud runner clones and installs it; discovery finds one real component; the editor renders that component on the board **through the repo's own Vite** in an iframe; clicking it inside the iframe selects it in the editor. One thin, honest, end-to-end vertical.

## In scope

- **`workspaces` feature (new, minimal):** `connectWorkspace` (name + git URL + PAT), `getWorkspaceStatus`, `listWorkspaces`; Workspaces Payload collection with encrypted credential storage; runner provisioning + lifecycle status events.
- **`discovery` feature (new, minimal):** registry snapshot storage; `getRegistry`; scan-result ingestion from runner events. Skeleton bar: one component with name + props from the fixture (full `ComponentMeta` fidelity comes in `discovery-and-library`).
- **`core-runner-protocol` (new core package):** versioned, zod-validated message schemas for the runner protocol (hello/ready, clone, install, scan, adapter-start, render-frame, status/error events) shared by features, editor, and runner.
- **`apps/runner` (new app):** the runner process — WS server speaking the protocol; git clone (PAT via ephemeral credential helper); package-manager install; minimal react-docgen-typescript scan; embedded-Vite preview adapter serving a frame host page with the injected **canvas agent script**; status/progress events.
- **`packages/editor` (new, minimal):** React Flow board shell, one iframe frame node, canvas-protocol client (selection round-trip), minimal zustand store. First code of the ADR-029 rebuild — stories + tests from day one.
- **`apps/web-next`:** workspace list + connect form + staged progress (cloning → installing → scanning → starting preview, the ui-gap §5 pattern); editor route hosting the board; tRPC routers for the two features; realtime push of runner status.
- **`fixtures/vite-kitchen` (new):** minimal Vite + React + TS + Tailwind repo with a `Button` component; served to tests via local bare-repo/`git daemon` helpers in `core-testing`.
- **Runner provisioning port:** `IRunnerProvisioner` with a local-child-process implementation (dev/CI) and a single-host Docker implementation (the container-per-workspace baseline of ADR-027 §2).
- Wire `core-events` (runner lifecycle events) and `core-realtime` (browser push) end to end.

## Out of scope

- Checkpoint, publish, worktrees, `projects` feature; design-doc persistence (the skeleton's frame is an ephemeral in-memory doc in the editor).
- AI compose, Polish, history, multi-view frames, isolation, code panel.
- Next.js adapter (`preview-and-nextjs-depth`); watch/HMR and perf-500 budgets (`discovery-and-library`); full connect UX states — org walls, trust prompt, env manager (`workspaces-and-connections`).
- Local runner / CLI agent (`local-runner-cli`); quotas, metering, billing.
- Real GitHub/GitLab connections beyond git-URL+PAT against fixtures; runner fleet orchestration, hibernation, egress policy hardening (single-host Docker is the v1 baseline; hardening tracked in `workspaces-and-connections`).

## Constraints

- ADR-027: container-per-workspace is the cloud baseline; runner protocol is plain WS/JSON (not tRPC); credentials never in URLs/argv/logs (spec §6 credential-helper rules apply verbatim).
- ADR-028: the component renders through the repo's own toolchain in a cross-origin iframe with pinned `targetOrigin`; **no NodeRenderer on the board path**; chrome (selection rings) renders as editor-side overlays from agent-reported geometry.
- ADR-029: editor code follows template conventions from the first commit (`component-must-have-story`/`-test`); React 19; no prototype vendoring.
- Conformance: manifest-first for every use case in both new features; generator-first (`pnpm turbo gen feature`) for `workspaces` and `discovery`; ADR-022 traces for any new feature/core-tier runtime dep (editor deps pre-approved by `platform-retrofit`).
- Budgets (adapted from spec §16): connect-to-rendered-component < 3 min on the warm fixture; board pan/zoom stays editor-side (< 16 ms/frame — no protocol round-trip on drag).
- Security floor even at fixture scale: runner containers get no cross-tenant network; the agent script is Veect-authored, adapter-injected, never sourced from the repo (ADR-028 §consequences).

## Success criteria

- Full gate set green: `pnpm typecheck && pnpm lint && pnpm test && pnpm conformance && pnpm fallow:audit && pnpm coverage:diff`.
- e2e (Playwright, local-process provisioner): sign in → connect `vite-kitchen` via git URL + PAT → staged progress completes → editor shows `Button` rendered inside the adapter's iframe (assert on iframe content, not a screenshot double) → click the button in the iframe → editor selection overlay appears and the selection state names the component. This is the M0-recast acceptance.
- The same flow works with the Docker provisioner on a dev machine (documented manual check; CI uses the process provisioner).
- Protocol integration suite passes against a real spawned runner + local git fixture: clone → install → scan returns ≥1 component → adapter serves the frame host with the agent script.
- Credential never appears in any log, process argv, or `.git/config` in the runner workspace (asserted by the integration suite, per spec §15).
- Both feature manifests declare their use cases, events, and realtime channels; boot assertion green in dev-seed and production modes.

## User stories

1. As a signed-in user, I want to connect a repository by git URL + token, so that Veect can turn it into a design surface.
2. As a user, I want staged, honest progress while my repo clones and installs, so that a minutes-long operation never looks hung.
3. As a user, I want the editor to show my repo's real component rendered by my repo's own toolchain, so that what I see is exactly what ships (invariant #1).
4. As a user, I want to click a component on the canvas and see it selected, so that direct manipulation is proven across the iframe boundary.
5. As a user, I want a clear, named error state when clone or install fails (bad URL, bad token, install failure), so that I know the cause and next action — never a blank board.
6. As the control plane operator, I want each workspace's repo code executing in its own isolated runner, so that a malicious repo cannot touch other tenants (ADR-027).
7. As an AI implementer, I want the runner protocol schemas in one shared package, so that features, editor, and runner cannot drift apart silently.
8. As a developer, I want the whole skeleton to run with a local-process runner and dev-seed mode, so that `pnpm dev` + fixtures need no cloud resources.

## Implementation decisions

- **Protocol:** WS + JSON messages, every message zod-parsed against `core-runner-protocol` schemas, envelope carries `protocolVersion: "0"` and workspace-scoped auth token. Progress is event-streamed (stage + elapsed), mirroring the ui-gap §5 staged-progress pattern.
- **Provisioning port:** `IRunnerProvisioner` (service port in `workspaces` infrastructure): `provision(workspaceId) → RunnerHandle`, `terminate`, `status`. Implementations: local child process (dev/test) and Docker single-host (dockerode or docker CLI — app/infra-tier choice at implementation time). Fleet orchestration is explicitly a later concern behind the same port.
- **Credential handling:** PAT stored in the Workspaces collection encrypted AES-256-GCM with a key derived (scrypt) from `VEECT_SECRET` (the spec §13 Docker mechanism, now the primary); decrypted only server-side, passed to the runner over the authenticated protocol channel, delivered to git via ephemeral credential helper (spec §6 mechanics verbatim). The Payload API never returns the credential (write-only field).
- **Runner internals (app-tier, imperative code is expected here):** clone → detect package manager → install (progress events) → scan (react-docgen-typescript over the fixture's component glob) → start embedded Vite rooted at the clone with a Veect frame-host entry that imports the requested component and mounts the agent script. Dedicated port = dedicated origin per adapter (spec §9).
- **Canvas agent v0:** reports `runtime.ready`, component bounding boxes (post `fonts.loaded`), and click-target resolution; editor overlays selection ring from reported geometry. Pinned `targetOrigin` both directions.
- **Editor v0:** React Flow board with one frame node hosting the adapter iframe; zustand store holds registry + selection only (no document tree yet — DesignDoc v1 arrives with `design-doc-and-editor-foundation`); the frame renders a single Element (the discovered component with default props) requested via `render-frame`.
- **Events/realtime:** runner lifecycle (`workspaces` publishes `runner-status-changed`; realtime channel pushes stage progress + adapter-ready to the browser). Registry snapshots land via `discovery` ingesting the scan event — cross-feature reaction through `core-events`, per rule E0.
- **Manifests:** `workspaces` declares `connectWorkspace` (mutates, audits: workspace-connected), `getWorkspaceStatus`, `listWorkspaces`, publishes runner events, realtime channel; `discovery` declares `getRegistry`, consumes the scan event. Coverage bands: template defaults for both features (use cases are thin orchestration; the heavy imperative code lives in `apps/runner`, covered by the integration suite — the honest-bands rationale from the grill session).
- **Fixtures:** `fixtures/` at repo root, outside pnpm workspace and turbo graph; `core-testing` gains helpers to serve a fixture as a bare repo over the git protocol for tests.

## Testing decisions

- **Use-case unit tests** for every declared use case (conformance-mandated), mocks injected directly.
- **Repository contract suite** for the Workspaces repository (mock + Payload impls) including the write-only credential field behavior.
- **Protocol integration suite** (the load-bearing one): spawns a real runner (process provisioner) against a `git daemon`-served `vite-kitchen`, drives clone→install→scan→adapter over real WS, asserts registry payload, adapter origin liveness, staged events, and credential-leak absence (grep argv/logs/`.git/config`). Runs in CI on ubuntu.
- **Editor:** stories + component tests for board/frame/overlay per conformance rules; canvas-protocol client unit-tested against a scripted agent double.
- **e2e:** the Playwright spec described in Success criteria, dev-seed + process provisioner, tagged as the repo's flagship spec.
- **Good test** here means: assert through the protocol and the UI, never against runner internals — the runner must stay refactorable while the protocol is the contract.

## Open questions

- Q1: dockerode vs docker CLI for the Docker provisioner? — **docker CLI via execa** (fewer moving parts, app-tier so no trace required); revisit with fleet work.
- Q2: Does the skeleton editor live at a real route (`/w/[workspace]`) or a dev-only page? — **Real route**, minimal; it becomes the editor's permanent home and later PRDs extend it.
- Q3: Runner base image? — **node:22-bookworm-slim + git**, pinned digest; doctor-style version checks come with `workspaces-and-connections`.

## Out of scope (deferred)

- Everything listed per-PRD in Out of scope above; plus runner resource limits tuning, egress allowlisting beyond Docker network defaults, and workspace hibernation — tracked in `workspaces-and-connections`.

## Further notes

- Builds on: `platform-retrofit` (clean floor, traces, scaffolded cores). Blocks: every subsequent Veect PRD.
- Authority: ADR-027/028/029; tech spec §6 (credential mechanics), §9 (adapter/protocol), §15 (fixture + leak-assertion testing), §16 (budgets); ui-gap §5 (staged progress pattern).
- The spec's M0 acceptance ("no system Node", "`git status` untouched") is superseded by this PRD's Success criteria — hosted equivalents: isolated runner, credential-leak assertions, honest progress.
