docs(work): add Veect ADRs 027-029, glossary terms, first two PRDs

Records the 2026-07-12 grill-session decisions repurposing this repo
for Veect v1 as a hosted SaaS: ADR-027 (hosted distribution +
control-plane/runner split), ADR-028 (iframe canvas), ADR-029
(DesignDoc v1 canonical + editor rebuild). Seeds the work pipeline
with the platform-retrofit and walking-skeleton PRDs (approved) and
adds the Veect product-domain section to the glossary. These
consciously supersede the .proto bundle's Electron/local-first
distribution decisions.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016j8z4VHjedXDTjEDNg7qHK
This commit is contained in:
2026-07-12 12:17:35 +02:00
parent ee0fec0691
commit 442b70476c
6 changed files with 415 additions and 0 deletions

View File

@@ -0,0 +1,61 @@
# ADR-027 — Hosted SaaS distribution and the control-plane/runner split
**Status:** Accepted
**Date:** 2026-07-12
## Context
This repo is being repurposed from the `template-vertical` starter into **Veect** — a design-system-native canvas that connects to a code repository, discovers its real components and tokens, lets a designer compose screens constrained to that system, and publishes real TSX as an ordinary pull request. The product specification bundle lives in `.proto/veect-product-docs/` (tech spec rev 1.2.2, design spec, UI gap spec, PRD, strategy docs).
That bundle **locked** a distribution decision on 2026-07-10: standalone desktop app (Electron, bundled Node runtime) as the default, plus a self-host Docker image, with "do not relitigate" markers. Large parts of the tech spec derive from it: the `node` PATH shim, Electron ABI policy, `safeStorage` credential storage, GitHub OAuth *device flow* (a desktop app cannot hold a client secret), per-workspace SQLite, and the "designer needs no dev environment" assumption (A5) satisfied by bundling the runtime.
On 2026-07-12 the founder superseded that decision during the architecture grill session: **Veect v1 is web-based — a hosted cloud SaaS — with a Tauri desktop shell later.** The spec itself declared the editor UI shell-agnostic and Tauri a documented alternative, so the editor carries over; but "who runs the server" changes everything downstream: auth posture, secret storage, isolation, persistence, and milestone acceptance criteria.
The central new problem: Veect executes **untrusted customer repo code by design**`pnpm install` lifecycle scripts, `next dev`/Vite servers, arbitrary config plugins. On a desktop that risk belonged to the user's own machine (mitigated by a workspace-trust prompt). Hosted, it lands on Veect's infrastructure and neighbors' tenants.
## Decision
**1. Veect v1 is a hosted cloud SaaS.** Users sign in from a browser; no local install is required for the primary path. Assumption A5 is fully restored — stronger than the Electron plan ever made it. A Tauri desktop shell is a later distribution, not v1.
**2. The architecture splits into a control plane and workspace runners, joined by a single runner protocol.**
| Part | What it is | Where it runs |
| --- | --- | --- |
| **Control plane** | Multi-tenant service: auth, tenancy, workspace/project metadata, design-doc persistence, AI proxying, runner orchestration. Template-shaped: Payload + Postgres, tRPC (ADR-013 stands), features in `packages/*`, UI in `apps/web-next` | Veect-operated infrastructure |
| **Cloud runner** | Executes a workspace's repo code: clone, worktrees, dependency installs, discovery scans, preview adapters, git checkpoint/publish. **One isolated container per workspace**: CPU/mem limits, egress allowlist (npm registry, git host), no cross-tenant network | Veect-operated infrastructure |
| **Local runner** (CLI agent) | The same runner contract implemented as a CLI the developer runs against a local checkout; the browser editor reaches it directly at `127.0.0.1` after pairing with the hosted session | User's machine |
The **runner protocol** (plain WS/JSON — deliberately *not* tRPC, because a slim CLI must implement it and the browser must speak it to a local runner) is the generalization of tech spec §9's "common runtime protocol". One protocol, N runner implementations — cloud, local, and later Tauri-embedded.
**3. v1 connection modes: remote repos (cloud runner) + local checkouts (local runner).** The self-host Docker image is **cut from v1** (fast-follow for enterprise); the core stays 12-factor/deployable-anywhere so it is not foreclosed.
**4. Identity is provider-agnostic.** Email/password accounts via the existing `auth` feature; repo access is a per-workspace credential (PAT or OAuth). The device-flow screens are retired. A GitHub App is a compatible later upgrade.
**5. Control-plane persistence is Payload + Postgres.** Users, workspaces, projects, designs, revisions, and AI runs are Payload collections behind the template's repository pattern. The spec's per-workspace SQLite (§11) is **deleted from v1**: runners hold only ephemeral working state and are rebuildable from the control plane + git. Canonical design state = Postgres (working) + `design.veect.json` on the branch (shared, reviewed) with file-wins reconciliation.
**6. The telemetry and compliance stances flip with the distribution.** "No telemetry v1" was a desktop-privacy posture; operating a multi-tenant service reverses it — the OTel/Sentry stack (ADR-014/017), `core-audit`, `core-consent`, `core-dsr`, and `core-analytics` are **kept and wired** (an EU-entity SaaS with user accounts has GDPR obligations on day one, and the activation/NSM metrics require product analytics). Demo content features (`blog`, `media`, `navigation`, `marketing-pages`) and `apps/web-tanstack` are deleted; `apps/cms` stays as the Payload admin; the landing page becomes a static route in `web-next`.
**7. AI compose is Veect-metered with a BYO escape hatch.** Server-side Anthropic key with per-tenant quotas (Haiku 4.5 default, Sonnet opt-up) *and* an optional per-workspace BYO key override, both from day one. The §14 "what was sent" disclosure is a hard requirement since prompts transit Veect infrastructure.
## Alternatives considered
- **CLI + Docker local-first (`npx veect` + self-host image).** The closest web-based reading of the original spec: repos, secrets, and execution stay on the user's machine. Rejected: reintroduces the install/dev-environment friction for designers that the founder explicitly wants gone; the developer-champion has to run/maintain a server for the team.
- **Docker-only self-host.** Simplest release engineering, strongest data-residency story. Rejected: heaviest first-run, clumsy local-folder support, no zero-install designer path.
- **Electron per spec.** Rejected by the founder's web-first direction; Tauri (not Electron) is the eventual desktop shell, and the runner protocol keeps that path open.
- **Shared-host execution with OS-level separation (cloud side).** Cheapest isolation. Rejected: indefensible to the engineering/security veto persona for untrusted-code execution; container-per-workspace is the industry floor (Codespaces/StackBlitz class), hardening to gVisor/microVMs later without protocol changes.
## Consequences
- **Positive:** zero-install activation for designers (the <20 min time-to-value target gets its biggest lever); the template's machinery survives largely intact (Payload repositories, tRPC, generators, conformance, dev-seed, work pipeline); `InMemoryEventBus`/`PayloadJobsEventBus` and `IJobQueue` are all genuinely useful again; one runner codebase serves cloud, CLI, and future Tauri.
- **Negative / accepted:** repo code and env values now transit and execute on Veect infrastructure the enterprise trust question sharpens ("your code leaves your machines") and needs a security FAQ before design-partner outreach; per-workspace compute (installs, dev servers) is a real COGS line requiring hibernation/pooling; the local runner is a second runner implementation to maintain from v1; publishes author as the workspace's stored credential (multi-seat honesty must be surfaced in the publish UI).
- **Retired spec surface (do not implement):** Electron shell, bundled-runtime PATH shim, ABI policy, `safeStorage`, auto-update/adapter-drain, deep links, GitHub device flow, per-workspace SQLite, self-host Docker (v1), SSH-via-system-agent for cloud runners (returns with the local runner, where an agent exists).
- **Milestones M0M7 must be re-derived**: the M0 acceptance ("boots with no system Node") is meaningless hosted; its replacement is the walking-skeleton PRD (sign-in connect fixture repo cloud runner one real component in the editor).
- The `.proto` bundle remains the product-behavior authority **except** where this ADR and the grill decisions supersede it; the authority table in `.proto/veect-product-docs/README.md` §2 is amended by this ADR.
## Related
- ADR-013 (tRPC conventions stands), ADR-014/017 (instrumentation exporter layer now stays), ADR-015 (events/jobs both impls in use), ADR-016 (realtime control-plane pushes), ADR-025 (EU compliance baseline applies for real now)
- ADR-028 (iframe canvas), ADR-029 (DesignDoc v1 + editor rebuild)
- `.proto/veect-product-docs/veect-technical-spec.md` rev 1.2.2 (superseded on distribution, §3611 partially retired)
- Glossary: "Veect product domain" section in `docs/glossary.md`

View File

@@ -0,0 +1,41 @@
# ADR-028 — Iframe canvas: the one-runtime invariant on the board
**Status:** Accepted
**Date:** 2026-07-12
## Context
Veect's first product invariant is **one runtime**: canvas, preview, isolation, and exported code render the same node tree through the same renderer — "no approximations, no 'roughly how it'll look'". The tech spec strengthens it for repo-native mode: every component instance on the canvas is *the repo's own module compiled by the repo's own framework toolchain* (embedded Vite / managed `next dev`), rendered into the editor through a cross-origin iframe with a pinned-`targetOrigin` postMessage protocol (`runtime.ready`, `fonts.loaded`-gated measurement).
The inherited editor prototype renders frames differently: **NodeRenderer**, a hand-built React component that approximates a 13-type demo kit with inline styles. It is excellent for the Playground fiction it was built for — and it is precisely the "approximation" the invariant forbids for real repos. The product bundle simultaneously says "inherit the editor, do not rebuild" and specifies an architecture in which the canvas interior cannot be NodeRenderer. This collision was unowned by any milestone; it is the hardest UI-architecture problem in the product (selection, hover, drag, hit-testing, and measurement across an origin boundary; React Flow frames containing live iframes; pan/zoom performance).
## Decision
**1. The canvas renders real repo components through iframes served by the workspace runner's preview adapter.** Each frame on the React Flow board hosts a cross-origin iframe whose content is the frame's subtree rendered by the repo's own toolchain inside the runner (ADR-027). What you see on the board *is* the compiled output — the invariant holds by construction, and the checkpoint parity check compares like with like.
**2. An injected agent script provides editor affordances over postMessage.** The adapter serves the frame wrapped with a Veect agent script that implements: node hit-testing and selection reporting, hover highlights, geometry measurement (post-`fonts.loaded`), scroll/viewport sync, and drop-target resolution for drag-from-library. The editor side speaks this **canvas protocol** (part of the runner protocol family, tech spec §9) with pinned `targetOrigin` both ways. Direct manipulation renders selection/hover rings as editor-side overlays positioned from reported geometry — chrome never lives inside the customer's document.
**3. NodeRenderer retires to the Playground** (the no-repo sandbox and landing-page demo funnel), where the base kit is the registry and an in-editor renderer is honest. It may also serve as a boot skeleton while an adapter cold-starts, clearly presented as "starting preview", never as the real render.
**4. Latency budgets are honored by splitting interaction from render.** Board pan/zoom and frame dragging are React Flow transforms on the iframe containers (no round-trip). Tree mutations flow through the doc engine to the adapter via HMR-grade updates; the <16 ms/frame budget applies to direct manipulation, the <500 ms budget (spec §7) to content updates.
## Alternatives considered
- **NodeRenderer-first MVP, iframes later.** Fastest to demo and maximizes prototype inheritance. Rejected: it ships the exact lie the wedge attacks the trust bar ("an engineer would merge this because preview equals output") cannot be honestly measured against approximated rendering, and the migration rewrites the canvas anyway.
- **Hybrid per node type** (base-kit nodes via NodeRenderer, discovered components via per-component iframe islands, Storybook-composition style). Rejected: two runtimes on one canvas cross-island layout, double theming, and parity semantics become incoherent; the invariant is violated at every boundary between the two.
- **Server-side screenshot streaming** (render in the runner, stream images). Rejected: kills interaction latency and real hover/focus states; contradicts the "real components with real states for free" promise.
## Consequences
- The canvas protocol (agent script + postMessage surface) becomes a named deliverable of the walking-skeleton and editor-foundation PRDs it is the technical risk to front-load, exactly as the original spec front-loaded adapter risk in M0.
- The editor rebuild (ADR-029) targets this architecture from the start; no NodeRenderer-era selection/hit-testing code is ported to the board path.
- Per-frame iframes bound the practical frame count per board; the adapter serves multiple frames from one origin with one iframe per frame (or view), and the LRU adapter-pool rules from the spec apply per workspace, not per frame.
- Multi-view frames render as N iframes of the same subtree at different widths the view-cluster math from the prototype's `engine/views.ts` survives as editor-side layout logic.
- Security: the iframe boundary is also the trust boundary customer code never executes in the editor's origin; the agent script is Veect-authored and injected by the adapter, never sourced from the repo.
## Related
- ADR-027 (control plane / runner split the adapter lives in the runner), ADR-029 (editor rebuild)
- Tech spec §1 (invariant), §9 (adapters + protocol), §16 (budgets); UI gap spec §3 (adapter status states, server-component affordances)
- Glossary: **Iframe canvas**, **Preview adapter**, **Canvas protocol** (under runner protocol), **Playground**

View File

@@ -0,0 +1,45 @@
# ADR-029 — DesignDoc v1 as the committed schema; editor rebuilt to conventions
**Status:** Accepted
**Date:** 2026-07-12
## Context
Two related inheritance questions from the `.proto` bundle needed resolution before PRDs could be written.
**The schema schism.** The bundle contains two different design-document models: the tech spec's **DesignDoc v1** (§8 — Frame/Element/Text nodes; `lit | token | expr` prop values where `expr` renders as a chip and emits verbatim with a review marker; Element nodes referencing *arbitrary discovered components*; unknown-component error states) and the prototype's **VeectNode** (13 hardcoded demo-kit types with one flat props bag). This choice is contract-level: `design.veect.json` is committed on the project's branch and reviewed by engineers in every Veect PR — the single hardest artifact to migrate later.
**The inheritance question.** The bundle loudly locks "inherit the editor, do not rebuild — the codebase seeds `packages/editor`". But analysis showed the bundle holds *three generations* of the editor (React codebase < written design spec < `Veect.dc.html` interactive prototype), with the codebase missing table-stakes surfaces (history panel, preview overlay, tool pill, K, toasts, AI ghost rendering, node deletion). The lock's premises also predate the 2026-07-12 decisions: the iframe canvas (ADR-028) retires NodeRenderer the codebase's biggest organism from the board path; DesignDoc v1 replaces the store's node model; and the hosted topology (ADR-027) moves the editor into `apps/web-next` on React 19, ending its Vite standalone-ness. What the lock protected has mostly been invalidated by newer decisions.
## Decision
**1. DesignDoc v1 (tech spec §8) is the canonical schema for `design.veect.json`.** Zod schemas live in the `design-doc` feature (entities layer). Element nodes reference discovered components by registry id; prop values are `lit | token | expr`; `expr` is never evaluated it renders as a chip and emits verbatim with a `// veect:expr` review marker. The Playground's base kit is just another registry, so Playground documents use the same schema.
**2. The editor's in-memory model is a view-model with an explicit mapping layer.** Whatever shape the editor store needs for interaction performance, it maps to/from DesignDoc v1 at the persistence and codegen boundaries. The prototype's `VeectNode` shape is at most an internal detail of the Playground never the committed contract.
**3. Code-panel edit mode ships honestly.** The bidirectional grammar is proven for the generated shape of registry components; it is *not* proven for arbitrary hand-written TSX. Edit mode is therefore enabled exactly where the grammar provably round-trips (golden tests emit parse emit byte-identical, per spec §12), and the panel downgrades to read-only with an explanatory banner elsewhere (server-component subtrees, `expr`-bearing nodes, code-owned files). Honesty over false parity the same voice as the parity-failure state.
**4. The editor UI is rebuilt under template conventions, not vendored.** This deliberately reverses the bundle's "do not rebuild" lock, for the reasons in Context. The rebuild is conformance-native from day one: components in `packages/editor` (+ the ink design language in `core-ui`) with stories and tests as the ESLint rules expect, React 19, store designed around DesignDoc v1 and the iframe canvas.
**5. What "inherit the editor" still means — the engine logic and the design.** The prototype's pure logic ports as tested libraries: the codegen/parser grammar (generalized per §12), the view-cluster math (`views.ts`), the Polish rule engine, the AI whitelist sanitizer, color/token math. The interaction design, copy, and visual language port from the design artifacts with this **ground-truth order: `Veect.dc.html` (newest — interaction + copy) > `veect-design-spec-current.md` > prototype codebase (reference only, never vendored).** This amends the bundle's authority table, which had no row for the HTML.
## Alternatives considered
- **VeectNode as the committed schema, extended with a `custom` type.** Fastest vendoring, no mapping layer. Rejected: bakes demo-kit assumptions into the reviewed contract; migrating design partners' committed JSON later is the migration you never want.
- **Vendor the codebase + "editor completion" epic to HTML parity.** The pre-grill recommendation. Rejected by the founder in light of ADR-027/028: the completion epic would rebuild the canvas, store, and React version anyway while dragging along un-storied, un-tested vendored files behind a lint-exemption zone.
- **Adopt DesignDoc v1 but cut edit mode from v1.** Safest. Rejected: the bidirectional grammar is one of the prototype's two headline innovations and the tech spec explicitly adopted it; the honest-downgrade rule contains the risk without cutting the feature.
## Consequences
- An **editor-foundation PRD** (board shell, panels, store on DesignDoc v1, keyboard model, canvas protocol client) precedes the subsystem PRDs that add panels; there is no "editor completion" epic.
- `component-must-have-story` / `component-must-have-test` apply to the editor from the first commit no exemption zone, no tightening debt.
- The grammar's generalization to arbitrary registry components (imports via registry metadata, prop serialization rules, escaping the prototype never escaped text content) is in scope for the codegen PRD, with the round-trip golden suite as its gate.
- The prototype codebase and `.dc.html` files are committed as reference material (location decided in the platform-retrofit PRD) so dispatch agents can read them; `packages/editor` starts empty.
- Node identity across code edits (the prototype minted fresh ids on parse-back, breaking anchor claims) must be solved in the design-doc PRD stable-id preservation is a schema-level concern.
## Related
- ADR-027 (hosted topology React 19 / web-next placement), ADR-028 (iframe canvas what the rebuilt board targets)
- Tech spec §8 (DesignDoc v1), §12 (codegen + golden tests); design spec §5 (prototype node model), §9 (grammar)
- Glossary: **Design doc**, **Registry**, **Iframe canvas**, **Playground**