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
8.4 KiB
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/PayloadJobsEventBusandIJobQueueare 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 M0–M7 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
.protobundle 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.mdrev 1.2.2 (superseded on distribution, §3/§6/§11 partially retired)- Glossary: "Veect product domain" section in
docs/glossary.md