ADR-006: Tech Stack
Status: Accepted | Date: 2026-02-10
Context
Fascia has a split architecture: a Control Plane (Fascia-hosted) for design-time operations and a Data Plane (customer's GCP project) for runtime execution. The tech stack must serve two distinct environments with different requirements:
- Control Plane -- JSON Schema-heavy spec management, LLM API integration for the Safety Agent, deployment orchestration, and a visual flow builder UI.
- Data Plane (Executor) -- Runs on Cloud Run in the customer's GCP project. Cold start time is critical because it directly impacts every API call made by end users of customer products.
- Frontend -- Flow Studio requires a mature visual graph/canvas library for building flow graphs.
Decision
| Layer | Choice | Version Target |
|---|---|---|
| Frontend | React + TypeScript + ReactFlow (xyflow) | React 19+, ReactFlow 12+ |
| Platform Backend | Node.js + TypeScript + Fastify | Node 22 LTS, Fastify 5+ |
| Executor (Customer Cloud Run) | Go | Go 1.23+ |
| Platform Database | PostgreSQL | Postgres 16+ |
| LLM Strategy | Multi-model cross-check (Claude + GPT-4 + Gemini fallback) | Provider abstraction layer |
Why Fastify
Fastify was chosen over NestJS and Hono primarily for its native JSON Schema validation via Ajv. Fascia's entire domain model is defined as JSON Schema -- Entity specs, Tool input/output schemas, and Policy conditions are all JSON Schema documents. Fastify's route-level schema declaration allows specs to flow naturally from domain definitions to API validation to auto-generated Swagger documentation.
- NestJS -- Too much boilerplate (dependency injection, decorators, modules) for a spec management platform. Its class-validator approach is less natural than JSON Schema for Fascia's domain.
- Hono -- Faster benchmarks and a cleaner API, but uses Zod validators rather than JSON Schema natively. Its multi-runtime advantage (Bun, Deno, Cloudflare Workers) is irrelevant since the Control Plane runs on GCP Cloud Run with Node.js.
Why Go for the Executor
The Executor is a well-bounded, rarely-changing component. Once the flow graph engine and node handlers are implemented, changes are infrequent. The cold start advantage of Go (~50ms vs ~300-500ms for Node.js) directly impacts every API call made by end users.
Go also produces small, statically-linked binaries that are ideal for container deployment on Cloud Run, and its built-in concurrency primitives (goroutines, channels) are well-suited for parallel node execution within flow graphs.
Why React + ReactFlow
ReactFlow (xyflow) is the de facto standard for building visual graph editors in the browser. Flow Studio -- where users design Tool execution logic by connecting nodes -- is a critical product surface. ReactFlow provides production-ready pan, zoom, node dragging, edge routing, and minimap capabilities that would take months to build from scratch.
Alternatives Considered
Frontend
| Option | Pros | Cons |
|---|---|---|
| React + TypeScript | Largest ecosystem, ReactFlow is the standard for flow builders | Bundle size, ecosystem complexity |
| SvelteKit | Smaller bundle, simpler state management | Fewer graph/flow libraries; Flow Studio is on the critical path |
| Vue + TypeScript | Good balance of simplicity and power | Fewer flow editor options than React ecosystem |
Platform Backend
| Option | Pros | Cons |
|---|---|---|
| Fastify (Node.js + TS) | Native JSON Schema via Ajv, fast, type sharing with frontend | Single-threaded (mitigatable with clustering) |
| Hono (Node.js + TS) | Ultra-lightweight, modern API | Zod-based validation, smaller plugin ecosystem |
| NestJS (Node.js + TS) | Strong structure (DI, modules) | Heavy boilerplate, class-validator less aligned with JSON Schema |
| Go | High performance, good concurrency | No type sharing with frontend, less ergonomic for JSON-heavy spec work |
| Python (FastAPI) | Good LLM library ecosystem | Different language from frontend, GIL limitations, LLM calls are just HTTP |
Executor
| Option | Pros | Cons |
|---|---|---|
| Go | ~50ms cold start, small binary, excellent concurrency, low memory | Dual-language codebase |
| Node.js + TypeScript | Same language as platform, shared types | ~300-500ms cold start, higher memory footprint |
| Rust | Fastest cold start, smallest binary | Steep learning curve, slower development velocity |
Database
| Option | Pros | Cons |
|---|---|---|
| PostgreSQL | JSONB for spec storage, rich query features, relational integrity for metadata | Requires management (mitigated by Cloud SQL) |
| MongoDB | Flexible schema for specs | Weaker transaction support, specs have known structure that benefits from relational model |
Consequences
Positive
- JSON Schema alignment -- Schemas flow seamlessly from spec definitions to Fastify route validation to auto-generated Swagger documentation. No translation layer needed.
- Shared TypeScript types -- Frontend and platform backend share TypeScript type definitions for spec interfaces, creating a single source of truth for the Control Plane.
- Sub-100ms cold start -- The Go Executor delivers fast cold starts on Cloud Run, providing excellent end-user experience for customer products.
- Production-ready flow canvas -- ReactFlow provides a mature visual editor for Flow Studio, saving months of custom development.
- Efficient spec storage -- PostgreSQL JSONB stores spec documents efficiently while maintaining relational integrity for workspace and version metadata.
Negative
- Dual-language codebase -- TypeScript for the Control Plane and Go for the Executor requires proficiency in both languages. Developer onboarding takes longer.
- Type synchronization -- The Go Executor cannot import TypeScript type definitions. Spec types must be kept in sync through JSON Schema as the shared contract, with code generation to reduce manual duplication.
- Smaller community -- Fastify has a smaller community than Express or NestJS. Some patterns require custom implementation rather than off-the-shelf plugins.
Risks
- Type drift between TypeScript and Go -- Mitigated by using JSON Schema as the shared contract and generating Go structs from the same schemas.
- Go Executor maintenance burden -- Mitigated by keeping the Executor's scope minimal. It only executes compiled spec bundles; business logic lives in specs, not in Executor code.
- Fastify plugin gaps -- Mitigated by leveraging the broader Node.js ecosystem. Most Express middleware is compatible via
@fastify/middie.