Files
agentic-dev/docs/decisions/adr-027-hosted-saas-and-runner-split.md
Danijel Martinek 442b70476c 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
2026-07-12 12:17:35 +02:00

8.4 KiB
Raw Blame History

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 designpnpm 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.
  • 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, §3/§6/§11 partially retired)
  • Glossary: "Veect product domain" section in docs/glossary.md