2026-05-19 00:20:15 +02:00
|
|
|
|
# CHOICES.md
|
|
|
|
|
|
|
|
|
|
|
|
Decisions made during the autonomous Sprints 1–3 build where the spec was ambiguous or
|
|
|
|
|
|
where production-quality required a concrete pick.
|
|
|
|
|
|
|
|
|
|
|
|
## Stream transport: WebSocket over SSE
|
|
|
|
|
|
Spec lists both in different sections. The user's goal explicitly says
|
|
|
|
|
|
"WebSocket-streamed build flow", so the dashboard subscribes to build events via
|
|
|
|
|
|
`/v1/builds/:id/stream` WebSocket on the control-plane API. Generator-worker publishes
|
|
|
|
|
|
events to Redis pub/sub; API fans out per-WebSocket subscriber.
|
|
|
|
|
|
|
|
|
|
|
|
## Local container deploy (no Coolify in dev)
|
|
|
|
|
|
Spec targets Coolify on Hetzner for production. In dev the generator spawns containers
|
|
|
|
|
|
directly via the local Docker daemon (Docker Engine API on `/var/run/docker.sock` or
|
|
|
|
|
|
`npipe:////./pipe/docker_engine` on Windows). Each generated server gets a host port from
|
|
|
|
|
|
the `RUNNER_PORT_RANGE_*` window and is exposed at `http://localhost:<port>`. The
|
|
|
|
|
|
`coolify` adapter is interface-compatible so swapping in for production is a single import.
|
|
|
|
|
|
|
|
|
|
|
|
## Local addressing: ports not wildcard subdomains
|
|
|
|
|
|
`*.mcp.buildmymcpserver.com` requires DNS + Traefik + wildcard SSL — pointless on
|
|
|
|
|
|
localhost. Dev uses `http://localhost:<port>` for each generated container. Production
|
|
|
|
|
|
Traefik routing is wired but unused by `pnpm dev`.
|
|
|
|
|
|
|
|
|
|
|
|
## OAuth 2.1 Authorization Server lives in the control plane
|
|
|
|
|
|
The runner-template is a Resource Server only. The control-plane API is the AS:
|
|
|
|
|
|
issues codes, exchanges tokens, signs RS256 JWTs, exposes JWKS. Each generated server
|
|
|
|
|
|
verifies tokens against `${CONTROL_PLANE_URL}/oauth/jwks`. This matches the spec's
|
|
|
|
|
|
"token exchange not pass-through" mandate.
|
|
|
|
|
|
|
2026-05-19 00:24:47 +02:00
|
|
|
|
## Auth: tight in-house magic-link instead of Better-Auth dependency
|
|
|
|
|
|
Spec names Better-Auth. In practice Better-Auth adds a large surface (plugins, adapter
|
|
|
|
|
|
config, cookie middleware) that we'd have to vendor-wrap to share between Fastify
|
|
|
|
|
|
control-plane and Next.js Server Actions. For a 3-sprint MVP we wrote a ~150-line
|
|
|
|
|
|
magic-link + session module in `packages/auth` directly on top of the Drizzle schema:
|
|
|
|
|
|
hash-only token storage, 32-byte CSPRNG tokens, 15-min link TTL, 30-day session TTL,
|
|
|
|
|
|
auto-org bootstrap on first sign-in. The seams are clean — swapping in Better-Auth
|
|
|
|
|
|
later means changing `packages/auth/src/index.ts` only. In dev, magic-link URLs are
|
|
|
|
|
|
printed to the API stdout for the developer to click.
|
2026-05-19 00:20:15 +02:00
|
|
|
|
|
|
|
|
|
|
## Generator: Claude API call gated by env, mock fallback for offline dev
|
|
|
|
|
|
If `ANTHROPIC_API_KEY` is set, the worker calls the real Claude API
|
|
|
|
|
|
(`claude-opus-4-7`). If not, a deterministic mock returns a small "echo" tool so the
|
|
|
|
|
|
end-to-end build flow stays demoable without burning credits. The choice is logged in
|
|
|
|
|
|
build logs so users always know which path ran.
|
|
|
|
|
|
|
|
|
|
|
|
## Static checks: tsc + targeted regex scan
|
|
|
|
|
|
- `tsc --noEmit` on the rendered server code in a tmp dir.
|
|
|
|
|
|
- Regex scan for banned tokens: `eval(`, `Function(`, `child_process`, `fs.unlink`,
|
|
|
|
|
|
prompt-injection markers (`ignore previous instructions`, `disregard the above`).
|
|
|
|
|
|
- esbuild bundle produced for the runner so the image is one file + deps.
|
|
|
|
|
|
|
|
|
|
|
|
## Sandboxing: dev relaxed, prod flags documented
|
|
|
|
|
|
Dev containers run without `--read-only` / `--cap-drop=ALL` because Windows Docker
|
|
|
|
|
|
Desktop refuses some combinations. The production launch flags are baked into
|
|
|
|
|
|
`apps/generator/src/lib/deploy.ts` constants but commented `// prod-only` for clarity.
|
|
|
|
|
|
|
|
|
|
|
|
## ChatGPT install snippet
|
|
|
|
|
|
ChatGPT supports MCP via "Custom GPTs → Actions → Add MCP server" or the
|
|
|
|
|
|
research "Custom Connectors" surface. We render a copy block with `URL`,
|
|
|
|
|
|
`Auth: OAuth 2.1`, and a link to the install-flow page on chat.openai.com. If the
|
|
|
|
|
|
official deep link path changes, only `apps/web/lib/install-snippets.ts` needs editing.
|
|
|
|
|
|
|
|
|
|
|
|
## Tailwind v4 + shadcn registry
|
|
|
|
|
|
Tailwind v4 uses CSS-first config. shadcn components are vendored into
|
|
|
|
|
|
`apps/web/components/ui/*` — only the primitives the spec lists. No drop-in defaults
|
|
|
|
|
|
left untouched: every component has been re-styled against the design tokens.
|
|
|
|
|
|
|
|
|
|
|
|
## Stripe / metering / quotas: scaffolded only in Sprint 1–3
|
|
|
|
|
|
The schema + types are complete. Wired Stripe checkout / webhooks are Sprint 4
|
|
|
|
|
|
per spec, so we leave a clean seam in `apps/api/src/routes/billing.ts` but do not
|
|
|
|
|
|
implement the flow yet. Quotas are read-only in the dashboard.
|
|
|
|
|
|
|
|
|
|
|
|
## Drizzle: `migrate` strategy
|
|
|
|
|
|
Schema lives in `packages/db/src/schema.ts`. `pnpm db:push` is dev-only and pushes
|
|
|
|
|
|
schema directly. Migrations are generated via `pnpm db:generate` and applied via
|
|
|
|
|
|
`pnpm db:migrate` on first API boot. Local Postgres comes from docker-compose.
|
|
|
|
|
|
|
|
|
|
|
|
## Node 20 LTS, ESM everywhere
|
|
|
|
|
|
All packages are `"type": "module"`. `tsx` runs ts directly in dev. esbuild bundles
|
|
|
|
|
|
for prod. No CJS.
|
|
|
|
|
|
|
|
|
|
|
|
## Conventional commits per task, single branch
|
|
|
|
|
|
Single `main` branch, conventional-commits messages. No PR flow because this is one
|
|
|
|
|
|
autonomous run.
|