AIDE Platform — Specification (v4)
2026-10-25 · Ben Fielder · v4, built on the v3 codebase in this repo. Earlier versions: v1 docs/v1/SPEC.md, v2 docs/v2/SPEC.md, v3 docs/v3/SPEC.md. Stack: STACK.md. Source: docs/v4/DRAFT_SPEC.md (drafted 2026-10-18 for a v3 that became the Next.js/Railway platform version) plus Ben’s v4 brief (2026-10-25).
How this spec relates to v1–v3. v4 is an increment on the running v3 app. Everything in
docs/v1/SPEC.md,docs/v2/SPEC.mdanddocs/v3/SPEC.mdstays in force unless this document amends or replaces it. That covers FR-1 to FR-112, the domain rules (v1 §4.1–4.9, v2 §4.10–4.15), the data models, APIs, NFRs and deployment sections. Amended items are listed in §3.0 and marked (amends FR-n) where they change. The v2 tech stack presets (v2 FR-74–FR-77, §4.11, §4.12) are replaced by stack composition (§3.1–§3.4). New requirements continue from FR-113, domain rules from §4.16, decisions useD-V4-n, and milestones continue from M23. Earlier tests stay green unless an amendment changes the behaviour they assert. In that case, update the test and name the amendment in it.Paths. Links and paths are relative to the repo root.
../stack-packs/stack-packs/plugins/is Ben’s stack-packs marketplace (read-only reference; all plugins at 1.0.0, read 2026-10-25).
1. Overview
AIDE turns an idea into released software through a governed six-phase lifecycle: Refinement → Specification → Planning → Execution → Review → Release. AI agents produce versioned artifacts, humans approve them at gates, and every state change is deterministic, logged and reversible. v2 added release versions, imports, per-user connections, two Next.js presets and Vercel. v3 moved AIDE’s own frontend to Next.js and deployed AIDE to Railway.
v4 makes AIDE stack-agnostic, and lets agents write long outputs:
- Stack composition replaces presets. A user picks one option per layer, in this order: Frontend → Backend → Data → VCS → Deployment. Options that don’t fit earlier choices are filtered out, with the reason shown. The catalog and its rules are adapted from stack-packs: 5 frontends, 5 backends, 7 data layers, 3 Git hosts and 6 deployment platforms.
- Specialist agents per layer. A fixed team of generic specialists (from stack-packs
stack-core) each load the pack skill chosen for their layer. For example, the backend engineer loadsbe-fastapifor FastAPI andbe-aspnetfor ASP.NET. - More Git hosts: GitLab (gitlab.com and allow-listed self-managed instances) and Bitbucket Cloud join GitHub.
- More deployment platforms: Netlify, Cloudflare, Render, Railway and GitHub Pages join Vercel. Hosts are chosen per component, with preview environments and production deployments.
- Managed databases: Supabase (BaaS and host mode), Render Postgres and Railway Postgres are provisioned or linked, wired into env vars, and migrated safely.
- LLM choice at project creation, and long outputs. The wizard picks the provider, connection and model. The Railway deployment hides Ollama and Fake. Each model’s real output limit is used (up to 128,000 tokens on current Claude models), and cut-off answers get up to 5 continuations.
Success means:
- A user composes React + Vite / ASP.NET Core / EF Core + PostgreSQL / gitlab.com / Netlify (frontend) + Render (backend and Postgres). Incompatible options (e.g. Vercel for the ASP.NET backend) can’t be picked. The specialists build version 0.1.0 with the right pack skills. The review sees a Netlify preview talking to a Render preview service with its own preview database. The release merges the GitLab merge request, creates a GitLab Release, and deploys both components to production.
- A second user imports a Bitbucket repo that AIDE detects as Vue + Supabase (BaaS) and deploys it to Vercel. On release, AIDE applies the version’s Supabase migrations to the linked Supabase project.
- Projects created on the v2
nextjs-supabaseandnextjs-frontendpresets keep working as compositions, with no action from their owners. - On Railway production, a user creates a project, chooses Anthropic with their own key and
claude-opus-5-5in the wizard, and never sees Ollama or Fake. A long spec is written in one 128,000-token call, without hitting the old 16,000 cap.
In scope (v4):
- Stack catalog and composition, compatibility rules, the wizard Stack step, stack settings, import detection (§3.1)
- Specialist agents and pack skills (§3.2), driving spec, plan, execution and review (§3.3)
- Retiring the v2 presets: data migration, then removal of preset code and endpoints (§3.4)
- GitLab (§3.5) and Bitbucket Cloud (§3.6), with provider-neutral Git lifecycle rules (§3.7)
- Platform connections: Netlify, Cloudflare, Render, Railway, Supabase, with Vercel moved onto the same model; GitHub Pages through the GitHub connection (§3.8)
- Deployment targets per component, env inventory, origin model, preview environment, production deployments, rollback (§3.9)
- Managed databases and migrations (§3.10)
- Events, notifications and CLI additions (§3.11)
- LLM providers: enablement per deployment, choice in the wizard, per-model output limits, streaming, continuations (§3.12)
Out of scope (v4):
- A build or generator runner. Agents still can’t run commands, and AIDE doesn’t build, install or execute generated code (D-V2-9, D-V4-5). CI on the Git host and the platforms’ own builds do the verification.
- Deploying by local build, upload or CI-run platform CLI (
wrangler deploy,railway up,netlify deploy, Docker image pushes). Where a platform can’t build from a Git host, that combination is incompatible (D-V4-9), even though stack-packs documents CLI deploys from CI. Candidate for a later version (§11). - The stack-packs
local-docker-desktop-k8spack as a target for generated apps. The Compose dev-loop pack (local-docker-desktop) is used only as a skill for the deploy engineer. - Supabase branching, Netlify Database, Vercel Marketplace databases, Neon as a first-class host (it’s usable as External), Cloudflare D1/Hyperdrive/Containers, and managed SQL Server.
- OAuth for Netlify, Cloudflare, Render, Railway and Supabase. Pasted, scoped tokens are used instead, as for Vercel (D-V4-8).
- GitHub Enterprise Server, Bitbucket Data Center, Azure DevOps.
- New stack packs beyond those in stack-packs today. Adding one is a catalog change (§4.16), not a feature.
- New LLM providers. Live model listing from provider APIs (the model catalog is configuration, §4.24).
- Changes to AIDE’s own stack and hosting (v3 stays as built: Next.js web, ASP.NET API, Railway + Docker Desktop Kubernetes).
2. Users and permissions
The v1–v3 roles are unchanged. v4 adds:
| Role | Can (new in v4) | Cannot |
|---|---|---|
| Admin | Add, edit, disable and remove Git host instances: gitlab.com and self-managed GitLab (allow-listed base URLs) with their OAuth application credentials, and the Bitbucket Cloud OAuth consumer. View the stack catalog and pack versions | Add a GitLab host that resolves to loopback, link-local or metadata addresses, or to private ranges when GitLab:AllowPrivateNetworks=false (§8). See any user’s credentials. Enable an LLM provider the deployment disabled (§4.24) |
| User | Connect GitLab and Bitbucket accounts (OAuth or token fallback) and sign in with them. Connect Netlify, Cloudflare, Render, Railway and Supabase accounts (pasted tokens). Pick or create repos on any connected Git host. Choose the LLM provider, connection and model when creating a project | Use another user’s connection directly |
| Project Owner | Compose or change the stack until the plan is locked. Bind their own Git host connection for GitLab/Bitbucket projects. Configure deployment targets and databases with their own platform connections. Reset the preview database. Promote and roll back production deployments | Bind another user’s connection (CREDENTIAL_NOT_OWNER → 403). Change the stack after the version’s implementation plan is locked (STACK_LOCKED → 409) |
| Project Contributor | Trigger preview deployments (v2). View the composition, targets, databases and migration runs | Change the stack, targets, databases or env vars, or reset the preview database (403) |
| Project Viewer | View the composition, targets, databases, deployments and migration runs | Any mutation (403) |
Auth: the v1 cookie auth, API tokens and antiforgery, and the v2 GitHub App sign-in, are unchanged.
- “Continue with GitLab” and “Continue with Bitbucket” are added to the sign-in page for each enabled Git host instance (FR-131, FR-134).
- Accounts are matched by
(provider, instance, external user id), and a signed-in user can link more Git identities from Account. - Connections stay personal (v2 §2). GitLab and Bitbucket background Git work runs with the binding Owner’s connection, because neither host has GitHub-style installation tokens (D-V4-6).
3. Functional requirements
Each FR names its layer in italics. Acceptance criteria follow the requirement, as “Given … when … then …”.
3.0 Amended earlier requirements (summary)
| Earlier | What changes | Where |
|---|---|---|
| FR-2 (v1), FR-80 (v2) | Sign-in also offers GitLab and Bitbucket | FR-131, FR-134 |
| FR-5 (v1), FR-73 (v2) | The wizard’s Stack step becomes five ordered layer sub-steps. The Repository step lists repos of the chosen VCS. Import may come from GitLab or Bitbucket and pre-fills the stack by detection. The Configuration step gains an AI model section | FR-114, FR-118, FR-141, FR-159 |
| FR-9 (v1) | Project settings gain Stack. Git settings gain the gitlab and bitbucket providers. Deployment settings become per-component targets plus databases |
FR-117, FR-136, FR-144 |
| FR-22/23 (v1), FR-71 (v2) | The LLM call uses each model’s output limit and streams. Continuations go up to 5 | FR-160–FR-162 |
| FR-28 (v1), FR-75 (v2) | Spec sections come from the composition’s packs | FR-123 |
| FR-29/31 (v1), FR-76 (v2) | Plan steps use generic specialist keys, and validation rules come from the composition | FR-124, FR-125 |
| FR-35/36 (v1), FR-82 (v2) | Git providers gain GitLab and Bitbucket. Background work uses the binding Owner’s connection for those hosts | FR-136, FR-137 |
| FR-42/43 (v1), FR-64/65 (v2) | Release creates a GitLab Release, or an annotated tag with the notes on Bitbucket. Merge strategies are mapped per host | FR-140 |
| FR-74–FR-77 (v2), §4.11–§4.12 | Removed, replaced by stack composition. Existing projects are migrated | FR-113–FR-129 |
| FR-78/79 (v2) | LLM providers can be disabled per deployment. The connections page gains Git host, platform and database sections | FR-142, FR-157 |
| FR-84/85 (v2) | Merge readiness and CI status for GitLab and Bitbucket | FR-138, FR-139 |
| FR-87–FR-91 (v2), §4.15 | Vercel moves onto the generic platform connection and deployment target model. Previews become a project preview environment. Production deploys every target in order | FR-142–FR-151 |
| FR-92/93 (v2) | New events, notifications and CLI commands | FR-155, FR-156 |
| D-V2-10 (v2) | “Supabase stays manual” is replaced: Supabase becomes a connection and a managed database | FR-152–FR-154, D-V4-12 |
| v3 §9 Railway variables, FR-108 | Railway also disables the Ollama and Fake LLM providers | FR-157 |
3.1 Stack catalog and composition
- FR-113 (backend + data) The stack catalog is versioned data shipped in the repo (§4.16), adapted from stack-packs. It defines:
- the five layers
- their options: one per stack-packs pack, plus “None” where allowed
- each option’s chips, description and source pack version
- the compatibility rules (§4.17) and the recommended combinations (from
compose-stack)
GET /api/stack/catalogreturns it for the wizard, settings and Admin → Stack catalog. The catalog version and content hash are recorded on every release version and agent run.- Given the shipped catalog, when
GET /api/stack/catalogis called, then it lists, each with itssourcepack and version:- 5 frontend options (
fe-react-vite,fe-nextjs,fe-angular,fe-vue,fe-vanilla) plus None - 5 backends (
be-aspnet,be-spring-boot,be-fastapi,be-express,be-nestjs) plus None - 7 data options plus None
- 3 VCS options
- 6 deployment platforms
- 5 frontend options (
- FR-114 (frontend + backend) The new-project wizard’s Stack step (amends FR-5, replaces the v2 preset cards) has five ordered sub-steps: Frontend → Backend → Data → VCS → Deployment.
- Option cards: each sub-step shows cards with a name, chips, a one-line description and a recommended badge. Options come from
POST /api/stack/optionswith the choices made so far (§4.17). - Option states: each option is one of:
allowedcaveat: allowed, with the ⚠ text shown on the cardforbidden: shown disabled in a collapsed “Not available with your choices (n)” list, each with its reason
- A forbidden option can’t be selected, and the API rejects it anyway (
STACK_INCOMPATIBLE). - Going back: changing an earlier layer re-evaluates the later layers. A later choice that became forbidden is cleared, and a toast names what was cleared and why.
- Summary card: shows the composition, the derived facts (Next.js mode, origin model, which specialists will work) and the caveats.
- “No stack (general)” stays as a choice at the top of the step. It gives v1/v2 behaviour with the
generalistagent (FR-120). - Given Frontend = React + Vite, when the user reaches Backend, then every backend is
allowed. - Given Backend = ASP.NET Core, when the user reaches Data, then only these are selectable:
- EF Core + PostgreSQL
- EF Core + SQL Server, as a caveat (“no managed SQL Server on the catalog’s platforms”)
- None
Prisma shows the reason “Prisma works with Express, NestJS or Next.js full-stack”.
- Given the user changes Backend from FastAPI to ASP.NET after choosing SQLAlchemy, then Data is cleared and the toast says “SQLAlchemy was cleared: it needs FastAPI”.
- Option cards: each sub-step shows cards with a name, chips, a one-line description and a recommended badge. Options come from
- FR-115 (frontend + backend) The Deployment sub-step has up to four choices, each filtered by the earlier layers and by each other (§4.17):
- Frontend host (when there’s a frontend): Vercel, Netlify, Cloudflare, GitHub Pages, Render, Railway, or None.
- Backend host (when there’s a backend): Vercel, Render, Railway, or None. “Same as frontend” is preselected when that platform can run the backend.
- Database host (when Data needs a server): Supabase, Render Postgres, Railway Postgres, External (a connection string the Owner enters, e.g. Neon or Azure SQL), or None (local only). Data = Supabase (BaaS) fixes it to Supabase.
- Local dev loop: Docker Compose (the default when there’s a backend or a database) or None. Compose loads the
local-docker-desktoppack for the deploy engineer (D-V4-2). - Origin model: the derived model (
same-origin,proxyorcross-origin, §4.17 R10) is shown with a one-line explanation. Where more than one model is allowed, the Owner can switch between them. - Given Frontend = React + Vite on Netlify and Backend = ASP.NET on Render:
- then the database host options are Render Postgres, Supabase (host mode) and External
- Railway Postgres is a
caveat: “Database on a different platform from the backend: higher latency, public connection” - the origin model is
proxy
- FR-116 (backend) Compositions are validated on the server with the same rules.
POST /api/stack/validatereturns the normalised composition with its derived fields (§4.18). Otherwise it returnsSTACK_INCOMPATIBLE → 400withviolations[] {layer, option, rule, reason}.- Project create,
PUT /projects/:id/stackandPOST /versionswith a stack run the same validation. - Given the composition
fe-vue+be-aspnet+data-prisma, when it is posted, thenSTACK_INCOMPATIBLEwith ruleR3and reason “Prisma needs Express, NestJS or Next.js full-stack”.
- FR-117 (backend + frontend + data) The composition is pinned on the release version, with the catalog version and hash (replaces FR-74’s preset pinning).
- Editing: an Owner can change it in Settings → Stack until the version’s first implementation plan is locked. After that,
STACK_LOCKED → 409. It can also be changed when starting a new version: v2 FR-60’s dialog gains a “Change stack” link that opens the same five sub-steps, pre-filled. - Stack change: changing a layer of a release version whose base commit already has code (an iteration or import) is a stack change.
- The spec agent is told which layers changed, and the spec must contain a “Stack Migration” section (FR-123).
- The plan must contain at least one step owned by the new layer’s specialist.
- VCS layer: it can only change to the host of the project’s repo (
VCS_MISMATCH → 400), and is set automatically when the repo is chosen. - Event:
PRESET_CHANGEDis replaced bySTACK_CHANGED {from, to, layersChanged[]}. - Given a locked implementation plan, when an Owner changes the backend, then
STACK_LOCKED.
- Editing: an Owner can change it in Settings → Stack until the version’s first implementation plan is locked. After that,
- FR-118 (backend + frontend) Import detection (amends FR-73): when a repo is chosen for import, AIDE runs the pure stack detector (§4.18 “Detection”) over the repo’s file list and manifests at the chosen base, and pre-fills the Stack step.
- Detected options are marked “detected”, with the evidence (e.g. “
next16.1 inpackage.json”). - The VCS layer is fixed to the repo’s host. Undetected layers are left empty for the user.
- Layer paths are detected too (e.g.
apps/web,api/). - Detection never blocks. The user can change any detected choice, and the Repository Analyst (v2 FR-70) still runs.
- Given an imported repo with
frontend/package.json(vue, vite),supabase/config.tomlandsupabase/migrations/, and no backend, then the suggestion isfe-vueatfrontend/, Backend None, anddata-supabase(BaaS).
- Detected options are marked “detected”, with the evidence (e.g. “
- FR-119 (backend) Each composition records the path of each layer in the repo (§4.18).
- Defaults follow the stack-packs STACK template:
frontend/andbackend/. A Next.js full-stack app or a frontend-only repo may use the repo root.. - Constraints: paths are relative and normalised, may not escape the repo (
..), and must differ between frontend and backend unless one of them is None. - Specialists’ owned paths, plan rules and review checks are expanded from these paths.
- Given a composition with
frontend: "."andfe-nextjs, when the scaffolder’s owned paths are listed, then they start at the repo root.
- Defaults follow the stack-packs STACK template:
3.2 Specialist agents and pack skills
-
FR-120 (backend) AIDE has one catalogue of generic specialists, adapted from the stack-packs
stack-coreagents (§4.18). The agents in play for a project are those whose layer exists in its composition:Key From stack-core Present when scaffolderstack-scaffolderalways (with a composition) data-engineerstack-data-engineerData ≠ None backend-engineerstack-backend-engineerBackend ≠ None frontend-engineerstack-frontend-engineerFrontend ≠ None test-engineerstack-qa-engineeralways ci-engineerstack-ci-engineeralways deploy-engineerstack-deploy-engineerany deployment host or the local dev loop is set docs-writerstack-docs-writeralways generalist— only with “No stack (general)” (v1 behaviour) stack-reviewerandstack-auditormap to AIDE’s existing Code Review agent and review cycle. They aren’t plan agents. Per-agent model overrides (v2ModelsByAgent) apply to the plan agents as a group (codeGenerator), unchanged.- Given a composition with no backend and
data-supabase(BaaS), then the plan generator’s catalogue has scaffolder, data-engineer, frontend-engineer, test-engineer, ci-engineer, deploy-engineer and docs-writer, and no backend-engineer.
- Given a composition with no backend and
- FR-121 (backend) Each specialist loads the pack skills for its layer, plus shared core skills: the full adapted skill, or named sections of it, as listed in §4.18 “Skill resolution”.
- Prompt order:
- the base
code_generator.md - the composition summary: the STACK.md equivalent (packs, paths, origin model, env names, gates)
- the agent’s own prompt
- each resolved skill, as
## Skill: <pack> — <section> - the step input
- the base
- Each agent run records
agent_key, the composition id, and thepacks[] {id, version, sections[]}it loaded. - Given a composition with
be-fastapianddata-sqlalchemy, when abackend-engineerstep runs, then:- its prompt contains the adapted
be-fastapiskill,stack-api-contract, and thedata-sqlalchemySetupandModellingsections - the run records
packs: [be-fastapi@1.0.0+aide.1, stack-api-contract@1.0.0+aide.1, data-sqlalchemy@1.0.0+aide.1]
- its prompt contains the adapted
- Given the same project switched to
be-aspnetbefore planning, when the nextbackend-engineerstep runs, then it loadsbe-aspnetinstead.
- Prompt order:
- FR-122 (backend) Pack and agent content is adapted, not copied, from stack-packs (§4.16 “Adaptation rules”), in the same spirit as v2’s claude-kits adaptation.
- Every pack file records its source plugin and version.
- A catalog that references a missing file, an unknown section heading or an unknown pack fails to load at startup. The readiness check then fails (
stack-catalogunhealthy, replacingpresets). - A unit test asserts that the shipped catalog loads.
- Given a pack whose agent mapping names a section “Gates” that the adapted skill doesn’t have (it has “Gate”), then catalog loading fails, naming that pack and section.
3.3 Spec, plan, execution and review
- FR-123 (backend) The Spec Generator (replaces FR-75’s preset input) gets the composition summary, plus the union of the selected packs’ required spec sections with their guidance (§4.18 “Spec sections”).
- Spec validation requires those sections. The v2 rules on requirement IDs, acceptance criteria and iteration tags are unchanged.
- A stack change (FR-117) adds the required section “Stack Migration”: what moves, data migration, compatibility, rollout.
- Given a composition with a backend and a cross-origin deployment, when the spec lacks “API Contract & Error Codes” or “Origin Model (CORS, Cookies, Auth)”, then the run gets the one corrective retry, naming the missing sections.
- FR-124 (backend) The Plan Generator (replaces FR-76’s preset input) gets the composition’s specialists (key, name, description, owned paths) and the composed plan rules (§4.18 “Plan rules”). Validation is v2 §4.11’s, with the rules generated from the composition instead of a preset file:
- unknown or missing
agent→INVALID_PLAN - files outside the agent’s owned paths →
AGENT_SCOPEwarning - order and exclusivity rules → errors (soft rules →
PLAN_ORDERwarning, per the v2 decision) - uncovered new or changed requirements →
UNCOVERED_REQUIREMENTSwarning - a stack change without a step for the new layer’s specialist →
STACK_CHANGE_UNPLANNEDerror - Given a composition with
data-efcore-postgres, when a plan has abackend-engineerstep creating a file under the backend’sMigrations/folder, thenINVALID_PLANnames the exclusivity rule “migrations belong to data-engineer”.
- unknown or missing
- FR-125 (backend) During execution, each step runs as its specialist with its resolved skills (FR-121).
- The Guardian’s protected paths are the union of the v1 rules and the packs’ protected paths (§4.18), with the v2 “owner may declare” exception.
- Migrations that exist on the base commit are immutable for every data pack, not only Supabase.
- Given a composition with
data-jpa-flyway, when a step modifiesbackend/src/main/resources/db/migration/V2__orders.sqland that file exists on the base commit, then the Guardian rejects the step with a protected-path violation.
- FR-126 (backend) The Code Review agent (replaces FR-77) scores against the composed checklist: each selected pack’s
Checklistitems, mapped to a v1 category and severity in the pack file.- The deterministic checks are those registered by the selected packs (§4.18 “Review checks”).
- Checklist findings are agent violations without penalties, as in v2.
- Given
fe-react-viteand a file that readsimport.meta.env.VITE_STRIPE_SECRET_KEY, when review runs, then a critical security finding “secret-like value exposed to the browser” applies −0.20 to security. - Given
data-efcore-postgresandEnsureCreated()in non-test code, then a majorarchitecturefinding applies −0.10.
- FR-127 (backend) Generated files. There’s no build runner (D-V4-5), so agents can’t run generators. The composition’s packs declare how each generated file is handled (§4.18 “Generated files”):
- Hand-written in the generator’s shape by the owning specialist, when small and deterministic. Examples: the OpenAPI document for changed endpoints, the generated client types, Supabase
database.types.ts, EF Core migration and snapshot files. - Produced or checked by CI, composed by the
ci-engineer. Examples: freshness checks such asgit diff --exit-code backend/openapi/api.json,dotnet ef migrations has-pending-model-changes,prisma migrate diff --exit-code. - Absent until the first CI run (lockfiles). When no lockfile exists, CI uses
npm install,uv syncwithout--frozen, and so on (v2 decision). - CI results on the PR head are review evidence on every Git host (FR-139). A failed freshness check becomes a finding that the optimisation plan must fix.
- Given a backend step that adds an endpoint, when the plan is validated, then the step (or a later step) must declare
backend/openapi/api.json. Otherwise anUNDECLARED_GENERATED_FILEwarning names it.
- Hand-written in the generator’s shape by the owning specialist, when small and deterministic. Examples: the OpenAPI document for changed endpoints, the generated client types, Supabase
- FR-128 (backend) The Repository Analyst and refinement (amends FR-70/72) get the composition summary and, for imports, the detector’s evidence.
- The codebase summary’s “Stack & Dependencies” section must name the detected packs and any mismatch with the composition.
- Given an import detected as
fe-vuewhose Owner chosefe-react-vite, then the summary flags the mismatch and the spec must contain “Stack Migration”.
3.4 Retiring the v2 presets
- FR-129 (backend + frontend + data) The v2 presets
nextjs-supabaseandnextjs-frontendare removed. That means:- the
Agents/Presets/folder,Aide.Domain/Presetsloader and rules, andPresetCatalog GET /api/presets[/:id]andPATCH /api/projects/:id/presetfrontend/src/features/presets/, the wizard’s preset cards and the projects-list preset filter- the
presetshealth check
One EF migration converts existing data (§4.23):
- Every release version pinned to a preset gets the equivalent composition:
nextjs-supabase→fe-nextjsat.+ no backend +data-supabaseBaaS + VCS from the Git provider + Vercel frontend host + Supabase database host unlinkednextjs-frontend→ the same, without data
- The preset id, version and hash stay on the release version and agent runs as history, shown as “Preset nextjs-supabase@1.0.1 (retired, migrated)”.
- In-flight plans keep their v2 agent keys, mapped at run time (§4.23 table), so a locked plan still executes.
- The dev seed’s preset demo project becomes a composition demo (§9).
- Given a v2 project in
executiononnextjs-supabasewhose step 4 isdb-engineer, when step 4 runs after the upgrade, then it runs asdata-engineerwith thedata-supabaseskill, and the run recordslegacyAgentKey: db-engineer. - Given any client calling
GET /api/presets, then404(breaking change accepted in D-V4-4).
- the
3.5 GitLab
- FR-130 (backend + frontend + data) An Admin manages GitLab instances in Admin → Integrations → GitLab.
- Instances:
gitlab.comis offered by default. Self-managed instances are added by base URL:https://only, unless “allow insecure HTTP” is ticked for a lab host. - Stored per instance: name, base URL, and the OAuth application credentials (client id, and the client secret, encrypted).
- Registration help: GitLab has no manifest flow, so the page shows the exact callback URL (
<public base URL>/api/auth/gitlab/callback) and the scopes (§4.19) to register. - Checks: adding or changing a base URL runs the SSRF guard (§8) and a reachability check.
GET /api/v4/versionmust answer with GitLab’s JSON shape, and an unauthenticated 401 counts as reachable. - Config: instances can also be configured from
secrets.envor Railway variables (GitLab__Instances__<n>__*), which take precedence. - Disable or remove: a disabled instance stops existing connections working, and projects show “GitLab instance disabled”. An instance can be removed only when no project uses it (
CONFLICT → 409otherwise). - Given an Admin adds
https://169.254.169.254, thenGIT_HOST_NOT_ALLOWED → 400. - Given
https://gitlab.internal.exampleresolving to10.0.4.7, then it is accepted on Docker Desktop Kubernetes (GitLab:AllowPrivateNetworks=true), and refused on Railway (false, §8).
- Instances:
- FR-131 (backend + frontend + data) In Account → Connections → GitLab, a user connects GitLab per instance.
- OAuth: authorization code + PKCE S256, scopes
api write_repository openid profile email. Access tokens last 2 h. AIDE refreshes them before expiry and stores the rotated refresh token atomically, because GitLab invalidates the old pair on refresh. - Token fallback: a personal, project or group access token with
apiandwrite_repository. AIDE validates it withGET /api/v4/userandGET /api/v4/personal_access_tokens/self(scopes, expiry), and warns 14 days before expiry. - Sign-in: “Continue with GitLab” on the sign-in page uses the same OAuth app. Users are matched by
(gitlab, instance, user id). - Invalid connections: a connection that GitLab rejects (401 after one refresh) is marked
invalid, and its owner is notified (v2connection_invalid). - Given a user signs in with GitLab on gitlab.com for the first time, then an AIDE account is created (or linked by verified email, only when the user confirms) and a GitLab connection exists.
- OAuth: authorization code + PKCE S256, scopes
- FR-132 (backend + frontend) With a GitLab connection:
- Repo picker: lists the projects the user can develop in (
GET /projects?membership=true&min_access_level=30&simple=true&search=, keyset pagination). - Create project:
- namespace: the user’s personal namespace, or a group whose
project_creation_levelallows the user (GET /groups?min_access_level=30) - name, path, description, visibility, and
initialize_with_readme=true, somainexists anddefault_branchis set
- namespace: the user’s personal namespace, or a group whose
- Errors: name or path taken →
REPO_NAME_TAKEN → 409. Not allowed in the group →FORBIDDEN → 403. The GitLab.com project-creation limit →UPSTREAM_RATE_LIMITED → 429. - Given a connected user, when they create
acme/todo-app(private) in groupacme, then the project exists withmainand a README, and the wizard selects it.
- Repo picker: lists the projects the user can develop in (
3.6 Bitbucket Cloud
- FR-133 (backend + frontend + data) An Admin configures the Bitbucket OAuth consumer in Admin → Integrations → Bitbucket.
- Stored: the client key and secret (encrypted).
- Registration help: Bitbucket fixes scopes on the consumer, so the page lists the callback URL and the consumer permissions to tick:
account,email,repository:write,repository:admin,pullrequest:write,project,webhook(§4.19). - Config: it can also come from
secrets.envor Railway variables (Bitbucket__OAuth__*). - App passwords are not supported (Atlassian has removed them).
- FR-134 (backend + frontend + data) In Account → Connections → Bitbucket, a user connects Bitbucket.
- OAuth: authorization code. Access tokens last 2 h. AIDE refreshes them and always persists whatever refresh token comes back.
- Token fallback: an Atlassian API token with scopes, plus the Atlassian account email.
- Required scopes, all
:bitbucket:read:user,read:workspace,read:project,read:repository,write:repository,admin:repository,read:pullrequest,write:pullrequest,read:pipeline. - AIDE validates with
GET /2.0/userand checks each scope with a harmless call. Missing scopes are listed (CONNECTION_SCOPE_INSUFFICIENT → 400).
- Required scopes, all
- Sign-in: “Continue with Bitbucket” signs in through the consumer (identity from
GET /2.0/user, email fromGET /2.0/user/emails). - Given a user saves an API token without
write:pullrequest, thenCONNECTION_SCOPE_INSUFFICIENTlists it and nothing is saved. - A scope that can’t be verified (e.g. no repository to probe) is refused too, listed as unverifiable, with the advice to use OAuth or create a repository first (build decision 2026-10-29).
- FR-135 (backend + frontend) With a Bitbucket connection:
- Repo picker: lists repos across the user’s workspaces where they have write access (
GET /user/workspaces, thenGET /repositories/{workspace}?role=contributor&q=name~"…"). - Create repository:
- workspace (from
GET /user/workspaces), project (required by Bitbucket; picker fromGET /workspaces/{ws}/projects), name, description, private - AIDE calls
POST /repositories/{ws}/{slug}withscm: git, then pushes an initial commit with a README onmain, so the default branch exists (Bitbucket has no auto-init)
- workspace (from
- Errors: slug taken →
REPO_NAME_TAKEN → 409. Not permitted →FORBIDDEN → 403. - Given a user creates
acme/todo-appin projectTODO, then the repo exists withmainholding a README, and the wizard selects it.
- Repo picker: lists repos across the user’s workspaces where they have write access (
3.7 Git hosts in the lifecycle
- FR-136 (backend + data) The Git provider set (amends FR-82) is
github_app,github_pat,gitlab,bitbucketandlocal.- A GitLab or Bitbucket project stores the instance, the external repo id and full path, and the binding Owner’s connection (
git_connection_id). - Background Git work (clone, fetch, push, MR/PR, merge, tag, release, polling) uses that connection’s token, refreshed per job and passed as an
http.extraHeaderBasic header:- GitLab:
oauth2:<token> - Bitbucket OAuth:
x-token-auth:<token> - Bitbucket API tokens:
x-bitbucket-api-token-auth:<token>
Tokens are never written to disk, put in a remote URL, logged or given to an LLM (v2 §4.14).
- GitLab:
- Lost binding: if the binding Owner loses the Owner role, leaves, or deletes or invalidates the connection:
- the binding is cleared
- Git jobs fail with
GIT_NOT_CONNECTED - Owners are notified (
git_binding_cleared) - another Owner re-binds their own connection in Settings → Git, after AIDE checks it has developer/write access to the repo
- Branches stay
aide/<slug>/v<version>on every host. - Given a GitLab project whose binding Owner leaves, when a push is due, then the job fails with
GIT_NOT_CONNECTEDand the remaining Owners are notified.
- A GitLab or Bitbucket project stores the instance, the external repo id and full path, and the binding Owner’s connection (
- FR-137 (backend) Merge requests and pull requests on the new hosts follow FR-36/FR-65 semantics (§4.19 mapping):
- GitLab: one MR per release version, opened as draft (
Draft:title prefix) and marked ready by removing the prefix. - Bitbucket: one PR with
draft: true, marked ready withdraft: false. - The body links back to the AIDE project, as on GitHub.
- Given a Bitbucket project whose execution run completes, then a draft PR from
aide/<slug>/v0.1.0tomainexists, and the Release page links it.
- GitLab: one MR per release version, opened as draft (
- FR-138 (backend) Merge readiness (amends FR-84):
- GitLab: reads the MR’s
detailed_merge_status, the approvals endpoint, the protected branch merge levels and the head pipeline. The status is re-polled while it isunchecked,checking,preparingorapprovals_syncing. - Bitbucket: reads
GET …/pullrequests/{id}/mergeability/checks. Ready means no check hasblocking: true. - Reasons map to the v2 reason codes:
review_required,checks_failing,checks_pending,merge_conflict,no_permission,draft,discussions_unresolved,rebase_required(v2 wire names, build decision 2026-10-28). - Given a GitLab MR with
detailed_merge_status = not_approved, when readiness is checked, thencanMerge: falsewith reasonreview_required. - Given a Bitbucket PR whose
standard_merge_checkfor “require passing builds” is blocking and failed, then reasonchecks_failing.
- GitLab: reads the MR’s
- FR-139 (backend) CI status (amends FR-85) is a deterministic review input on every host.
- Sources: GitLab head pipeline and commit statuses (
GET …/repository/commits/:sha/statuses). Bitbucket commit statuses (GET …/commit/{sha}/statuses). - Severity: a failed status that the host requires for merge is a major
testingfinding (GitLab “pipelines must succeed”; Bitbucketrequire_passing_builds_to_merge, as seen in the mergeability checks). Other failures are minor. - Pending statuses are waited for up to 15 minutes (v2 rule).
- Given a GitLab pipeline whose
test:backendjob fails, on a project requiring pipelines to succeed, then a major finding “required pipeline job test:backend failed”, with the job URL, applies −0.10 to testing.
- Sources: GitLab head pipeline and commit statuses (
- FR-140 (backend) Release (amends FR-64/65):
- GitLab merge:
PUT …/mergewith:sha: the reviewed head (409 = the head moved, so re-review is required)squashper the GitLab project’ssquash_option(AIDE’ssquashandfast_forwardstrategies are Bitbucket only)should_remove_source_branch: false
The merged commit is
merge_commit_sha, elsesquash_commit_sha, else the headsha(fast-forward). - GitLab release:
POST /projects/:id/releaseswithtag_name,ref= the merged commit,tag_message(annotated tag), and the AIDE release notes asdescription. - Bitbucket merge:
POST …/pullrequests/{id}/mergewithmerge_strategyfrom the project setting (merge_commit,squashorfast_forward). A202is followed throughmerge/task-status, and a555is retried. - Bitbucket release: an annotated tag,
POST …/refs/tags {name, target.hash, message}, whose message holds the release notes. Bitbucket has no Releases (D-V4-7). The full notes are kept on the AIDE release and shown on the Release page. manualstrategy: themanualmerge strategy and awaiting merge work as in v2 FR-65. AIDE marks the MR/PR ready and polls (§4.19 cadence) until it is merged (the tag and release then complete) or closed (PR_CLOSED).- Errors:
- GitLab 405 or Bitbucket 400 (not mergeable) →
awaiting_merge, with readiness reasons. - The GitLab tag-creation limit → retried at the reset.
- GitLab 405 or Bitbucket 400 (not mergeable) →
- Given a GitLab project with
aide_merges, when the release job succeeds, then the MR is merged, a GitLab Releasev0.1.0exists on the merged commit, and the badge reads “Released 0.1.0”. - Given a Bitbucket project, when the release completes, then the annotated tag
v0.1.0exists onmerge_commit.hash, with the notes as its message.
- GitLab merge:
- FR-141 (backend + frontend) Import (amends FR-73) works from GitLab and Bitbucket.
- Released-version picker: lists GitLab Releases and semver tags (
GET …/releases,GET …/repository/tags), or Bitbucket semver tags (GET …/refs/tags). - Default branch: from
default_branch(GitLab) ormainbranch.name(Bitbucket). - The stack is detected (FR-118).
- Given a GitLab project with tags
v1.0.0andv1.1.0, when it is imported, thenv1.1.0is preselected and the version to develop defaults to1.2.0.
- Released-version picker: lists GitLab Releases and semver tags (
3.8 Platform connections
- FR-142 (backend + frontend + data) Account → Connections (amends FR-87) has four sections:
- LLM providers (v2; only the providers enabled on this deployment, FR-157)
- Source control: GitHub, GitLab per instance, Bitbucket
- Deployment platforms: Vercel, Netlify, Cloudflare, Render, Railway
- Databases: Supabase
How platform connections work:
- A platform connection is a pasted token with a label. §4.20 lists, per provider, what to create, the scopes, and how it’s validated.
- AIDE validates the token on save and stores it encrypted, and never returns it. It shows the account, team, workspace or org the token reaches, its scope, expiry and status.
- Re-test, rename and delete work as for v2 Vercel connections.
- Existing v2 Vercel connections migrate unchanged into the generic model (§5).
- Rejected tokens: a token the platform rejects (401, or the provider’s invalid-token 403) marks the connection
invalidand notifies its owner. - Missing permissions: a 403 for missing permissions marks it
insufficient_scope, with the missing permission names where the API returns them (Supabasemissing_permissions, Cloudflare token policies). - Given a Cloudflare account-owned token (
cfat_prefix), when it is saved, thenCONNECTION_SCOPE_INSUFFICIENTexplains that Workers Builds needs a user token. - Given a Supabase scoped token without “API Key Secrets: Read”, then the connection saves with status
insufficient_scope, and the database setup page names the missing permission.
- FR-143 (backend + frontend) GitHub Pages needs no extra connection: it uses the project’s GitHub App installation (or PAT).
- The AIDE GitHub App manifest adds Pages: read & write (amends v2 §4.14).
- Existing App registrations show “Pages permission missing: update the App’s permissions in GitHub, then accept them on each installation” until the installation token reports the permission.
- Given an App without the Pages permission, when an Owner selects GitHub Pages as the frontend host, then
PLATFORM_PERMISSION_MISSING → 409, with the App settings link. - Pages variable sync (build, 2026-10-31): the manifest also asks for
actions_variables: write(public build-time values become repository Actions variables, §4.20) andactions: write(re-running a Pages build that started before its variables were synced). Existing installations must accept them. A missing permission shows as the variable’ssyncErroror, for a needed re-run, fails the deployment withPLATFORM_ERRORand a message naming it.
3.9 Deployment targets and environments
- FR-144 (backend + frontend + data) Settings → Deployment (replaces v2 FR-88’s single Vercel setting) shows:
- one target card per component in the composition (frontend, backend)
- one database card per environment (production, preview)
- the environment inventory (FR-145) and the origin model
For each target, an Owner:
- binds one of their own connections for the host chosen in the composition
- creates the platform resource linked to the repo, or links an existing one (picker). The build configuration is derived from the composition (§4.20): root directory, build command, output directory, start command, Dockerfile, health check path, pre-deploy migrate command.
- sees a readiness checklist from
POST …/targets/:component/verify:- the connection is valid
- the platform’s Git app or credentials are connected to the repo’s host and repo
- the required env vars are present
- auto-deploys for
aide/**are disabled - the preview environment is ready
- plan limits (§4.20)
Other rules:
- The resource is created by a
deployment_setupjob, since it may take several API calls and polls. The card shows its progress. - Errors:
- The platform’s Git integration lacks access →
PLATFORM_GIT_NOT_CONNECTED → 409, with the platform’s install or connect link. - A plan restriction (e.g. Vercel Hobby with an org repo, or a Netlify private org repo without Pro) →
PLATFORM_PLAN_LIMIT → 409. - The composition doesn’t allow this host →
STACK_INCOMPATIBLE.
- The platform’s Git integration lacks access →
- Local Git projects may use targets only with the Fake provider (v2 decision kept).
- Given an ASP.NET backend on Render, when the Owner creates the target, then AIDE creates a Docker web service with:
dockerfilePath: backend/DockerfilehealthCheckPath: /api/health/ready- auto-deploy from
mainfor production (§4.20) - a
preDeployCommandrunning the EF migration bundle
It also creates a
-previewservice withautoDeployTrigger: "off"(FR-147).
- FR-145 (backend + frontend + data) The environment inventory (amends FR-88 env vars) lists, per variable:
- key
- the components it applies to (frontend, backend) and its environments (preview, production)
- a sensitive flag, and a public flag (
NEXT_PUBLIC_/VITE_) - its source:
user: entered by an Owner (the value is encrypted and never returned)derived: written by AIDE from the databases, Supabase project and origin model (§4.22). Read-only, shown with its origin (“from Render Postgres (production)”).required: declared by the packs and missing (flagged)
Other rules:
- Sync: AIDE syncs the inventory to each target on save and before each deployment. Each provider has its own mechanics (§4.20), e.g. Netlify contexts
production/deploy-preview/branch-deploy, Railway per environment withskipDeploys: true, Render per service with a full mergedPUT. Sync failures keep the local value and showsyncError(v2 rule). - Public secrets refused: a public variable whose key or value looks secret (§4.18 secret patterns) is refused (
VALIDATION, “secret values can’t be public”).- (build, 2026-10-31) “Looks secret” also covers database URLs by name (
VITE_DB_URL,*_DATABASE_URL,*CONNECTION_STRING) and by value (connection URIs of a database scheme, ADO-style strings withPassword=), and JWTs whose decoded payload has the roleservice_role. Anon or publishable JWTs pass. On every platform, a public variable saved earlier that looks secret is no longer sent (one sent earlier is removed) and showssyncError.
- (build, 2026-10-31) “Looks secret” also covers database URLs by name (
- GitHub Pages sync timing (build, 2026-10-31): the merge push starts the build, so pending variables are pushed in the release job, before the merge and as the release enters
awaiting_merge. A failure doesn’t block the release (syncErrorand a warning). The deployment job pushes again first, even when it adopts the run. A run that started before the newest synced variable is cancelled if still going, then re-run once, so the production build holds the values. - Given a Vue + Supabase (BaaS) project, when the Supabase database is linked, then:
VITE_SUPABASE_URLandVITE_SUPABASE_PUBLISHABLE_KEYappear as derived, public, frontend variables for both environments- no secret key is exposed to the frontend
- FR-146 (backend + frontend) The origin model (§4.17 R10) is part of the composition. Each model works as follows:
- same-origin: one service serves both. Examples: Next.js full-stack; an SPA container proxying
/apito the backend over the platform’s private network on Railway; local Compose. - proxy: the frontend platform rewrites
/api/*toAPI_ORIGIN. Mechanisms:- Next.js
rewrites() - a Netlify
_redirectsfile generated at build fromAPI_ORIGIN - a Cloudflare Worker with
run_worker_first: ["/api/*"] - Render static-site
routesset by AIDE per service
- Next.js
- cross-origin: the frontend calls
API_BASE_URL(set at build time), the backend allowsCORS_ALLOWED_ORIGINS, and auth uses bearer tokens orSameSite=None; Securecookies.
The derived variables (
API_ORIGIN,API_BASE_URL/VITE_API_BASE_URL/BACKEND_URL,CORS_ALLOWED_ORIGINS) are filled per environment from the backend target’s production and preview URLs. The stack-packs vocabulary has two values: AIDEsame-originandproxyare both pack “same-origin”, andcross-originmatches.- Given a React SPA on GitHub Pages with a FastAPI backend on Railway, then:
- the origin model is
cross-origin VITE_API_BASE_URLis derived from the Railway production domainCORS_ALLOWED_ORIGINSon the backend includes the Pages URL
- the origin model is
- same-origin: one service serves both. Examples: Next.js full-stack; an SPA container proxying
- FR-147 (backend + frontend) Preview environment (replaces FR-90’s per-run Vercel preview). Each project with deployment targets has one persistent preview environment, matching AIDE’s one-development-version-at-a-time model (D-V4-10).
- Per target:
- Vercel, Netlify and Cloudflare: preview deployments of the version branch
- Render: a
-previewservice per backend or SSR app (and per static site when the frontend is on Render) - Railway: an
aide-previewenvironment copied from production
- Per database: a preview database, so migrations from unmerged code never touch production (§4.21). That’s Render or Railway Postgres in the preview service or environment, or a second Supabase project when the Owner links one.
- Rollout: when an execution run completes (PR opened or updated), a
preview_rolloutjob deploys the PR head in dependency order. Each step waits forreadybefore the next:- preview database migrations (Supabase BaaS, applied by AIDE, §4.21)
- backend, whose pre-deploy migrates its preview database
- frontend, built against the backend’s preview URL, which is stable for the life of the version branch
- The Review page shows each component’s preview URL, state and build-log tail. A Contributor can redeploy.
- Review waits for the whole rollout, up to 30 minutes (the v2 ceiling).
- A failed build or pre-deploy is a critical
architecturefinding, with the log tail as evidence (v2 FR-90). - A failed preview migration is a critical
data_modelfinding.
- A failed build or pre-deploy is a critical
- Hosts without previews (GitHub Pages) are skipped, with a note on the Review page. Review doesn’t wait for them.
- Given a React (Netlify) + ASP.NET (Render) + Render Postgres project, when execution completes, then:
- the preview database is migrated by the backend’s pre-deploy on
<app>-api-preview - the Netlify branch deploy is built with
API_ORIGIN= the preview service URL - the Review page lists both previews as
ready
- the preview database is migrated by the backend’s pre-deploy on
- Given the backend preview fails its pre-deploy migration, then the frontend preview isn’t started, and the report has a critical finding with the pre-deploy log.
- Per target:
- FR-148 (backend + frontend) Preview database reset. An Owner can reset the preview database: drop and re-provision it or, for Supabase, reset the linked preview project’s schema by re-applying the migrations from the base commit.
- Reset is offered automatically after a rollback that removed migrations (v1 FR-44).
- Reset is required before the next preview rollout when AIDE detects applied preview migrations that no longer exist in the branch. That raises a
PREVIEW_DATABASE_DIVERGEDwarning, and the preview rollout is blocked until reset. - Given a rollback to a checkpoint before migration
0003, when the next preview rollout starts, then it is blocked withPREVIEW_DATABASE_DIVERGED, and the Owner sees “Reset preview database”.
- FR-149 (backend + frontend) Production deployments (amends FR-91). After the release merge, a
production_rolloutjob deploys every target in order: database migrations (Supabase BaaS only, §4.21) → backend → frontend.- Adopt or create: it adopts a deployment the platform started from the merge commit, matched by commit SHA within 60 s. That covers Vercel, Netlify and Cloudflare production builds of the default branch, and Render/Railway auto-deploys from
main. Otherwise it creates one explicitly:- Render:
POST /deploys {commitId} - Railway:
serviceInstanceDeployV2(commitSha) - Netlify:
POST /buildswith the production branch - Workers Builds:
POST /builds {branch, commit_hash}
- Render:
- It’s idempotent per
(release_id, component), and each deployment is recorded on the release. - Failure: a failed production deployment doesn’t undo the release (v2). It notifies Owners (
DEPLOYMENT_FAILED) and shows “Redeploy” on the Release page. - GitHub Pages: AIDE waits for the Pages workflow run on the merge commit, then reads
GET …/pages/deployments/{sha}andhtml_url. - Given release 0.1.0 of the React + ASP.NET project is merged, when both deployments are
ready, then the Release page shows “Released 0.1.0 · live on Netlify and Render” with both URLs.
- Adopt or create: it adopts a deployment the platform started from the merge commit, matched by commit SHA within 60 s. That covers Vercel, Netlify and Cloudflare production builds of the default branch, and Render/Railway auto-deploys from
- FR-150 (backend + frontend) Rollback and promote (amends FR-91) follow each provider’s capability (§4.20):
- Vercel: promote
- Netlify:
restore - Cloudflare Workers: deploy an earlier version at 100 %
- Cloudflare Pages:
rollback - Render:
rollback {deployId} - Railway:
deploymentRollback(whencanRollback) - GitHub Pages has no rollback (
DEPLOYMENT_ACTION_UNSUPPORTED → 409)
Rolling back a backend never rolls back its database. The dialog warns when the target deployment is older than the latest applied migration.
- Given a Render backend with two
liveproduction deploys, when an Owner rolls back to the older one, then Render’s rollback deploy is recorded with triggerrollback, and the Deployments page shows it as current.
- FR-151 (frontend + backend) The Deployments page (amends FR-91) lists every deployment with: component, environment (preview/production), provider, version, SHA, state, URL, time and who triggered it.
- It can be filtered by component and environment.
- The project overview shows the production URL of each component.
3.10 Managed databases and migrations
- FR-152 (backend + frontend + data) The database card (FR-144) for each environment lets an Owner:
- Provision a new database on the composition’s database host:
- a Supabase project (
POST /v1/projectswithorganization_slug,region_selectionand an AIDE-generateddb_pass) - Render Postgres (
POST /v1/postgres, version 17) - Railway Postgres (a Postgres service with a volume in the project’s environment)
- a Supabase project (
- Link an existing one (picker from the connection).
- Enter an External connection string: a
postgres://orpostgresql://URI, or an ADO.NET string for SQL Server. It is stored encrypted.
Provisioning:
- A
database_provisioningjob polls until the database is available (§4.21), and stores the connection details encrypted. - The card shows status, region and plan, plus warnings:
- Render free Postgres expiry (30 days, plus a 14-day grace period)
- Supabase free-plan pausing after 7 days of inactivity
- the Supabase 2-free-projects limit (
PLATFORM_PLAN_LIMIT, with “link an existing project instead”)
- The preview database is created automatically with the preview service or environment on Render and Railway. For Supabase it’s optional: the Owner links or provisions a second project.
- The preview and production databases must be separate. The same provider and resource id, the same Supabase project ref, or the same host, port and database is refused (
VALIDATION). It is checked on save, in the provisioning job, on reset and before preview migrations. A preview with no database of its own is never reset or migrated (build, 2026-10-31). - Given an Owner provisions a Supabase project, when its status reaches
ACTIVE_HEALTHYand the health check reports db, pooler, auth and rest as healthy, then the card showsavailableand the derived env vars appear (FR-145).
- Provision a new database on the composition’s database host:
- FR-153 (backend) Supabase BaaS projects:
- API keys: AIDE fetches the project’s keys (
GET …/api-keys?reveal=true), using the publishable (sb_publishable_…) and secret (sb_secret_…) keys, and creates them when missing. Legacyanon/service_rolekeys are used only for migrated v2 projects that already reference them. - Auth URLs: AIDE sets Auth
site_urlto the frontend production URL, anduri_allow_listto the production URL, the frontend host’s preview URL pattern, andhttp://localhost:<dev port>/**(PATCH …/config/auth). It updates them when target URLs change. - Migrations: files in
supabase/migrations/*.sqlare applied by AIDE’s own migration runner (§4.21): to the preview project during preview rollout, and to the production project during release. - Advisors: after applying, AIDE reads
GET …/advisors/securityand adds its findings (e.g. a table without RLS) to the next review as deterministic findings. - Given a BaaS project releasing a version that adds
0003_orders.sql, when the release job runs, then the migration is:- applied to the production project before the PR is merged (§4.21 “When”)
- recorded in
supabase_migrations.schema_migrationswith version0003’s timestamp - listed under the release’s migration run
- API keys: AIDE fetches the project’s keys (
- FR-154 (backend + deploy) Host-mode databases (Supabase, Render, Railway or External as the Postgres for an ORM). AIDE never runs ORM migrations itself. The ORM’s one-off migrate command (from the data pack) runs on the platform:
- Render/Railway backends: the service’s pre-deploy command, on Render paid instances and on Railway. On Render free instances, the deploy engineer runs migrations at container start (single instance), recorded as a caveat.
- Serverless hosts (Vercel/Netlify functions; Next.js full-stack on Vercel, Netlify or Cloudflare): the production build command runs the migrate command only when
AIDE_RUN_MIGRATIONS=true. AIDE sets that only in environments with their own database: production, and preview when a preview database exists (D-V4-13). - Connection variables:
DATABASE_URL. For Supabase: the transaction pooler (:6543, prepared statements off) for serverless, the session pooler (:5432) otherwise.DIRECT_URL: the session pooler (:5432), for migrations.- The Supabase direct host
db.<ref>.supabase.cois never used (it is IPv6-only).
- Given a Next.js + Prisma project on Vercel with Supabase host mode and no preview database, then:
AIDE_RUN_MIGRATIONSistruefor production only- preview builds skip migrations
- the Review page notes “Preview uses the production database without this version’s migrations” (unmerged migrations never reach production)
3.11 Events, notifications and CLI
- FR-155 (backend + frontend) New events (live, activity feed, admin):
STACK_CHANGED,STACK_DETECTED,GIT_HOST_INSTANCE_CHANGED,GIT_BINDING_CLEARED,DEPLOYMENT_TARGET_CONFIGURED,DEPLOYMENT_TARGET_FAILED,DATABASE_PROVISIONED,DATABASE_PROVISIONING_FAILED,DATABASE_EXPIRING,DATABASE_RESET,MIGRATION_RUN_STARTED,MIGRATION_RUN_COMPLETED,MIGRATION_RUN_FAILED,PREVIEW_ROLLOUT_COMPLETED.PRESET_CHANGEDis retired (its history is kept).STACK_DETECTEDis written by the repository analysis job, with the codebase summary.POST /api/stack/detectruns before the project exists and writes no event (build, 2026-10-31).- New notification types, all sent to Owners:
- Git binding cleared
- database provisioning failed
- database expiring: 7 days before a Render free database expires, or when a Supabase project is paused
- migration failed
- deployment target needs attention: platform Git access lost, or the token is invalid. A setup or readiness failure writes its event only, except a setup refused with
PLATFORM_GIT_NOT_CONNECTED, which counts as lost Git access (build, 2026-10-31)
- Database expiry is checked by a
database_expiry_sweepjob queued everyNotifications:DatabaseExpirySweepInterval(default 1 hour). It reads each database’s state without changing the platform, and moves the stored status toexpiringorpaused. - Migration failures: a failed migration sends Owners one in-app
migration_failednotification, notdeployment_failed. Channels subscribed todeployment_failedalso receivemigration_failed, so they can’t opt out of it separately. A failure before the merge sends bothrelease_failedandmigration_failed. - Given a Render free Postgres 7 days from expiry, then Owners get a “database expiring” notification with the date.
- FR-156 (cli + backend) CLI additions (amends FR-93):
- Stack commands:
aide stack catalog(layers and options)aide stack show <id>aide stack set <id> --frontend <opt> --backend <opt> --data <opt>[:baas] --vcs <opt> --frontend-host <p> --backend-host <p> --database-host <p> [--local compose|none]. It is validated, and prints any violations. The named flags merge over the current composition (the API’s PUT replaces the whole document). The origin model resets to its default when the frontend, backend or their hosts change, with a warning on stderr; there is no flag to set it (build, 2026-10-31).aide stack detect <repo-url>, for a connected repo
- Version start:
aide version start … --presetis replaced by the--stack-*flags above.--presetexits 2 with “presets were removed in v4; use –frontend …”. - Project create:
aide project creategains--llm-provider <p> --llm-model <id> [--llm-connection <id>](FR-159). - Listings:
aide deployments <id> [--component frontend|backend] [--env preview|production],aide databases <id>,aide migrations <id>. statusincludes the composition summary, each target’s production URL and the latest migration run. With--jsonit addsstackandlatestMigrationRun, which takes 3 requests.- Exit codes follow v1.
STACK_INCOMPATIBLE,STACK_LOCKEDandLLM_PROVIDER_UNAVAILABLE→ 4.VCS_MISMATCHstays 1. - Given
aide stack set <id> --backend be-aspnet --data data-prisma, then it exits 4 and prints theR3violation.
- Stack commands:
3.12 LLM providers, models and long outputs
- FR-157 (backend + frontend + deploy) Provider enablement per deployment. Each LLM provider has
Llm:Providers:<P>:Enabled(defaulttrue). A disabled provider:- is absent from every provider list:
GET /api/llm/providers, project LLM settings, Connections, Admin → Integrations → LLM, and the wizard - is refused on project create and on
PATCH …/settings/llm(LLM_PROVIDER_UNAVAILABLE → 400) - makes agent runs of projects still bound to it fail with
LLM_PROVIDER_UNAVAILABLE. Their settings page shows “This provider is turned off on this server. Choose another provider.”
Deployment rules:
- Startup validation fails unless at least one provider is enabled, and
Llm:DefaultProvidermust be an enabled provider. - Railway (
previewandproduction) setsLlm__Providers__Ollama__Enabled=falseandLlm__Providers__Fake__Enabled=falsein.railway/railway.tsanddeploy/railway/variables.md. - The dev loop, Docker Desktop Kubernetes (including the E2E overlay) and GitHub Actions CI keep the defaults, so all four providers stay enabled and CI’s
Llm__DefaultProvider: fakekeeps working. - Given Railway production, when a user opens the wizard’s AI model section, Connections or project LLM settings, then only Anthropic and OpenAI are listed.
- Given a Railway project still set to Fake from before v4, when its next agent run starts, then the run fails with
LLM_PROVIDER_UNAVAILABLE, and the settings page asks the Owner to choose another provider. - Given CI with defaults, when
GET /api/llm/providersis called, then all four providers are listed and Fake isusable.
- is absent from every provider list:
- FR-158 (backend)
GET /api/llm/providersreturns, for the signed-in user, each enabled provider with:usableandreason: one ofsystem_key,own_connection,keyless, orneeds_connection- the user’s LLM connections for it (id, label, last4; FR-78)
- its model catalog (§4.24): id, display name, max output tokens, context window, recommended flag
- the provider’s default model
isDefault: true on the one provider the default rule (§4.24) would pick for this user, so the wizard preselects it (build decision 2026-10-25)
It replaces the per-project
providers[]as the wizard’s source.providers[]on project LLM settings stays, filtered to enabled providers.- Given Anthropic with no system key and a user holding one Anthropic connection, then Anthropic is
usablewith reasonown_connectionand lists that connection. Its catalog showsclaude-opus-5-5(128,000 max output tokens, recommended).
- FR-159 (frontend + backend + cli) LLM choice at project creation (amends FR-5). The wizard’s Configuration step gains an AI model section with:
- Provider: only enabled providers. Ones the user can’t use yet show “Add a connection” and link to Connections, keeping the wizard’s state.
- Connection: the user’s own connections for that provider. Shown when the provider needs a key and system keys aren’t allowed or available. The first connection is preselected.
- Default model: a picker over the provider’s catalog, showing each model’s max output tokens, plus “Other model id…” (free text).
- Per-agent models: optional overrides in a collapsed “Advanced” area (the v2
ModelsByAgentagents).
Create request and defaults:
POST /api/projectsgainsllm {provider, credentialId?, defaultModel, modelsByAgent?}. It createsProjectLlmSettingsbound to that credential, as v2 FR-79 binding does.- When the provider needs a key, no system key is usable and no
credentialIdis sent, the caller’s newest valid connection for that provider is bound (build decision 2026-10-25). llmis optional for API and CLI callers. When omitted, today’s default applies (DefaultForNewProject), except that a disabled provider is never chosen. If no enabled provider is usable, create fails withLLM_PROVIDER_UNAVAILABLE.- The Review step shows the provider, model and connection.
- Errors: provider disabled or not usable by this user →
LLM_PROVIDER_UNAVAILABLE → 400. A credential that isn’t the caller’s →CREDENTIAL_NOT_OWNER → 403. An empty model id →VALIDATION → 400. - Given a user on Railway with one Anthropic connection, when they create a project choosing Anthropic, that connection and
claude-opus-5-5, then the project’s LLM settings are bound to the connection with default modelclaude-opus-5-5, and its first refinement run uses them. No visit to Settings → LLM is needed. - Given an API call to
POST /api/projectswith nollm, on Railway with no system keys, by a user with no connections, thenLLM_PROVIDER_UNAVAILABLE(it never silently falls back to Fake there).
- FR-160 (backend) Per-model maximum output tokens (replaces the single
Llm:MaxOutputTokenscap). Each LLM call’smax_tokens(Anthropic),max_completion_tokens(OpenAI) ornum_predict(Ollama) is resolved per provider and model by the pure rule in §4.24.- Sources: the model catalog, provider discovery for unlisted Anthropic models, the operator ceiling, and the fallback.
- The resolved value is recorded on each agent run (
maxOutputTokens). - Given project model
claude-opus-5-5and no ceiling, when a spec run calls Anthropic, then the request hasmax_tokens: 128000. - Given
claude-haiku-4-5, then64000. Given an unlisted Ollama model, thenLlm:MaxOutputTokens(default 16,000).
- FR-161 (backend) Streaming LLM calls. Anthropic, OpenAI and Ollama calls, with or without tools, use the providers’ streaming responses and assemble the final message, stop reason, tool calls and usage from the stream. This lets 128,000-token outputs finish without HTTP timeouts.
- Timeouts:
Llm:RequestTimeoutbecomes the whole-call ceiling (default00:30:00). The newLlm:StreamIdleTimeout(default00:02:00) fails a call that receives no stream data for that long. - Failures: both timeouts map to the existing provider failure handling and retries. A stream that ends without a final stop event is a retryable provider error. Cancellation (run cancelled or lease lost) aborts the stream.
- Token usage and cost are taken from the stream’s usage events, as before.
- Given a scripted stub stream that delivers 100,000 output tokens over 12 minutes with no gap over 30 s, then the call succeeds.
- Given a stub that stalls for 3 minutes mid-stream, then the call fails with the idle timeout and is retried under the v1 retry rule.
- Timeouts:
- FR-162 (backend + frontend) Continuing cut-off answers (amends the v2 continuation fix under FR-23/FR-71). When an answer stops at the output limit (
max_tokens,length, or Ollamalength), AIDE sends the answer so far plus the continue prompt, up to 5 continuations (6 calls) for one agent answer, i.e. one artifact or one run output.- Continuations are stitched as today (§4.24).
- The v1 corrective retry (e.g.
MISSING_SECTIONS) is a new answer with its own allowance of 5. - Stop conditions: AIDE stops early, with the same warning, when the next continuation’s input would exceed the model’s context window, or when the token budget hook refuses (v2:
LlmToolLoopStoppedException). output_truncatedwarning: if the answer is still cut off after the 5th continuation, the run still completes with the warningoutput_truncated.- It becomes an
AgentRunWarningsconstant. - Its frontend copy is “The model hit its output limit and was still writing after 5 continuations, so this output may be incomplete.”
- It shows on run details and the run’s artifact page.
- It becomes an
- The run output records
continuations(0–5). - Given a stub that stops with
max_tokenson calls 1–3 andend_turnon call 4, then the joined answer is returned,continuations: 3, and there’s no warning. - Given a stub that stops with
max_tokenson all 6 calls, then exactly 6 requests are made, the run completes withoutput_truncated, and the UI shows the copy above.
4. Domain rules
v1 §4.1–4.9 and v2 §4.10 and §4.13–§4.15 apply, with these amendments and additions. v2 §4.11 and §4.12 are replaced by §4.16–§4.18 and §4.23. Every worked example is a unit test.
4.16 Stack catalog format (replaces v2 §4.11)
-
Location: the catalog is the folder
backend/src/Aide.Infrastructure/Agents/Stack/(embedded resources), loaded once at startup into pure Domain types:Stack/ catalog.json version, layers[], options[], rules (§4.17), recommendations[] packs/<pack-id>/ pack.json manifest (below) skill.md adapted SKILL.md; its "## " headings are the section interface core/ stack-api-contract.md adapted stack-core skill composition-summary.md template rendering the STACK.md equivalent for prompts agents/<key>.md adapted stack-core agent prompts (FR-120) legacy/ frozen v2 preset globs and agent-key map (§4.23) - Version and hash:
catalog.json.version(semver), plus a SHA-256 over all files using v2’s preset hash algorithm: sorted paths,path \0 content \0,\nline endings, BOM stripped. Both are pinned on the release version and recorded on agent runs asstack@<version>. -
pack.json(unknown properties are load errors, as in v2):id (= folder, = stack-packs plugin name), layer (frontend|backend|data|vcs|deploy|local), name, version ("<plugin version>+aide.<n>"), source {plugin, version, snapshot: "2026-10-25"}, chips[], description, defaultPath ("frontend"|"backend"|"."|null), publicEnvPrefix ("NEXT_PUBLIC_"|"VITE_"|null), # frontend packs runtime ("node"|"python"|"dotnet"|"java"|"static"), # frontend/backend packs, for deploy shapes modes[] {id, name, description} # e.g. data-supabase: baas, host; fe-nextjs: full-stack, ui specSections[] {title, guidance, when?} # when: a condition on the composition (e.g. "mode == baas") ownership {<agentKey>: [globs with {fe}/{be} placeholders]} agentSections {<agentKey>: ["*" | section heading]} # which skill sections each agent loads planRules {order[], exclusive[], maxFilesPerStep[]} # v2 rule shapes, globs with placeholders protectedPaths[] {glob, existingOnBase?, except?, reason} generatedFiles[] {glob, handling: hand_written|ci_checked|ci_generated, ciCheck?} requiredEnv[] {key, appliesTo[], targets[], sensitive, public, description, when?} review {checklist[] {item, category, severity}, deterministic[] (rule ids, §4.18)} gate (fe/be packs: the pack's Gate commands, for the ci-engineer and the composition summary) migrate {command, needsDirectUrl} # data packs: the one-off migrate command deploy {...} # deploy packs: provider id and supported shapes (§4.17 R6–R8) - Section headings are the pack’s
##headings, matched after stripping a trailing parenthetical. For example,## Modelling (BaaS)matchesModelling, and## Config as code (\netlify.toml`)matchesConfig as code. The headings per layer, from stack-packsauthor-pack` and the packs as they stand:- frontend:
Detect · Scaffold · Conventions · API consumption · Errors, permissions, states · Config · Testing · Gate · Containerise · Deploy notes · Checklist - backend:
Detect · Scaffold · Conventions · Errors · Auth · Contract export · Config and database URL · Health · Testing · Gate · Containerise · Deploy notes · Checklist - data:
Detect · Compatible with · Setup · Modelling · Migrations · Local database · Testing · Production notes · Checklist(noGate) - vcs:
Detect · Branches and commits · PRs · CI · Secrets and variables · Dependency updates · Templates · Safety · Checklist. vcs-gitlab hasPRs/MRsinstead ofPRs, and vcs-bitbucket addsAuth - deploy:
Supports · Prerequisites and auth · Config as code · Environment variables · Database · Deploy commands · Previews and promotion · Agent autonomy · Troubleshooting · Checklist, plusBase pathon GitHub Pages - local:
Prerequisites · Files · Commands · Safety · Troubleshooting · Checklist
- frontend:
- Adaptation rules (D-V4-3, extending v2’s claude-kits rules):
- Keep each pack’s section headings. They are the interface.
- Drop:
- CLI scaffolding commands (the scaffolder writes the files they would create)
- “run X” instructions and “only when the user asks” remote actions (AIDE performs Git and platform operations itself, under its own rules)
- MCP setup,
link/loginsteps, CLI deploys (railway up,wrangler deploy,netlify deploy) - anything about pushing, PRs or reviews
- the packs’
Agent autonomysections. AIDE’s own rules replace them, and the section is loaded by no agent.
- Keep, as output requirements: conventions, folder layouts, error and auth patterns, env names, health paths, container recipes, gate commands (for CI files), migration rules and checklists.
- Versions: replace “current stable, check the docs” with “use current stable versions you are confident exist; pin Node/.NET/Java/Python versions in their version files”.
- Provenance: every adapted file starts with a comment naming its source file and plugin version. Content changes bump the pack’s
+aide.<n>revision and the catalog version.
- Load errors: missing files, unknown section headings in
agentSections, unknown pack ids in rules, and invalid globs fail loading (FR-122).
4.17 Compatibility rules
The rules are data in catalog.json, evaluated by one pure Domain function: StackCompatibility.Evaluate(partialComposition) → options per layer with state and reasons.
- It’s used by
/options,/validate, project create,PUT /stack,POST /versionsand the CLI. - Rule ids are stable, because tests and violations cite them.
- The rules come from stack-packs
compose-stack(its matrix lines 51–69), narrowed to Git-integration builds (D-V4-9), plus the provider research in §4.19–§4.21.
| Rule | Statement |
|---|---|
| R1 | Frontend and Backend can’t both be None. |
| R2 | The fe-nextjs mode is derived: full-stack when Backend = None, ui when there’s a backend. |
| R3 | Data ↔ backend: data-efcore-postgres and data-efcore-sqlserver → be-aspnet. data-jpa-flyway → be-spring-boot. data-sqlalchemy → be-fastapi. data-prisma and data-drizzle → be-express, be-nestjs, or Backend None with fe-nextjs. data-supabase (BaaS) → Backend None and any frontend. None → always allowed. |
| R4 | data-efcore-sqlserver is a caveat: “No managed SQL Server on the catalog’s platforms: use an External connection string (e.g. Azure SQL) or local only.” |
| R5 | VCS: any option, but fixed to the repo’s host once a repo is chosen or imported (VCS_MISMATCH). |
| R6 | Frontend host, by frontend. Static SPAs (fe-react-vite, fe-vue, fe-angular, fe-vanilla) → Vercel, Netlify, Cloudflare, GitHub Pages, Render (static site), Railway (static or nginx container). fe-nextjs → Vercel, Netlify, Render (Node service), Railway, and Cloudflare as a caveat (“Cloudflare’s Next.js adapter: some Node APIs and middleware are limited”). GitHub Pages is allowed only in ui mode or with data-supabase, as a caveat (“static export: no server actions, route handlers or SSR”). Otherwise it’s forbidden (“Next.js full-stack needs a server”). |
| R7 | Backend host, by backend. be-aspnet and be-spring-boot → Render (Docker), Railway. be-fastapi, be-express and be-nestjs → Vercel as a caveat (“serverless functions: no background work, pooled DB connections”), Render, Railway. Netlify, Cloudflare and GitHub Pages can’t host backends (“not covered by the deploy pack”). |
| R8 | Database host, by data. ORM packs on PostgreSQL (data-efcore-postgres, data-jpa-flyway, data-sqlalchemy, data-prisma, data-drizzle) → Supabase (host mode), Render Postgres, Railway Postgres, External, None. Render or Railway Postgres is allowed when the backend (or the Next.js full-stack app) runs on the same platform, and a caveat otherwise (“database on another platform: public connection, higher latency”). data-efcore-sqlserver → External or None. data-supabase (BaaS) → Supabase only. Data None → no database host. |
| R9 | Deploy ↔ VCS (each platform’s Git integration, §4.20). GitHub → every platform. gitlab.com → Vercel, Netlify, Cloudflare, Render. Not Railway (“Railway only builds from GitHub”) and not GitHub Pages. Self-managed GitLab → Netlify only (“only Netlify builds from self-managed GitLab”). Bitbucket → Vercel, Netlify, Render. Not Cloudflare, Railway or GitHub Pages. Netlify with GitLab or Bitbucket is a caveat (“linked with a deploy key and webhook that AIDE adds to the repo”). |
| R10 | Origin model. Next.js full-stack, or no backend → same-origin. Backend and SPA both on Railway → same-origin (the SPA container proxies over the private network) or cross-origin. Frontend on Netlify, Cloudflare or Render static, or fe-nextjs ui mode anywhere → proxy (default) or cross-origin. Frontend on Vercel as a static SPA, or on GitHub Pages → cross-origin only (D-V4-20). Frontend host None → same-origin (local). |
| R11 | Every host may be None (local only). A database host other than None needs a deploy target that can reach it: a backend host, or the frontend host in Next.js full-stack or BaaS mode. Otherwise it’s a caveat: “database only used by the local dev loop”. |
| R12 | local-docker-desktop (the Compose dev loop) is offered when there’s a backend or a database, and is the default then. |
Worked examples:
| # | Composition | Result |
|---|---|---|
| 1 | fe-react-vite · be-aspnet · data-efcore-postgres · gitlab.com · frontend Netlify, backend Render, DB Render Postgres |
✓ allowed. Netlify caveat (R9 deploy key). Origin proxy |
| 2 | as 1, with backend host Vercel | ✗ R7 “Vercel can’t run ASP.NET Core” |
| 3 | fe-vue · None · data-supabase · Bitbucket · frontend Cloudflare |
✗ R9 “Cloudflare can’t build Bitbucket repos”. Vercel, Netlify and Render are offered |
| 4 | fe-nextjs · None · data-prisma · GitHub · frontend GitHub Pages |
✗ R6 “Next.js full-stack needs a server”. Vercel is ✓ with DB Supabase (host) |
| 5 | None · be-fastapi · data-sqlalchemy · GitHub · backend Railway, DB Railway Postgres |
✓. Origin n/a (API only) |
| 6 | fe-angular · be-spring-boot · data-jpa-flyway · self-managed GitLab · backend Render |
✗ R9. Backend host offers only None |
| 7 | fe-react-vite · be-aspnet · data-efcore-sqlserver · GitHub · backend Railway, DB External |
✓ with the R4 caveat |
| 8 | fe-vanilla · None · None · GitHub · frontend GitHub Pages |
✓. Origin same-origin |
| 9 | fe-react-vite · be-nestjs · data-drizzle · GitHub · frontend Vercel, backend Render, DB Supabase |
✓. Origin cross-origin only (R10). Supabase host mode |
| 10 | fe-nextjs · be-express · data-prisma · gitlab.com · frontend Vercel, backend Railway |
✗ R9 “Railway only builds from GitHub” |
POST /api/stack/options returns, for each layer after the last chosen one, every option with its state and reasons[] {rule, text}. Earlier layers are re-evaluated, so a now-forbidden choice is reported as invalidated.
4.18 Composition → agents, skills, rules and checks
-
Normalised composition (stored on the release version as
jsonb, validated by the Domain):{ catalogVersion, catalogHash, frontend: {pack, path} | null, backend: {pack, path} | null, data: {pack, mode?} | null, vcs: {pack, instanceId?}, deploy: {frontendHost?, backendHost?, databaseHost?, local: "compose"|"none"}, originModel, derived: {nextjsMode?, agents[], specSections[], envNames[], gates[]} } -
Skill resolution (which sections each specialist loads;
*= the whole adapted skill):Agent Loads scaffolderfrontend packs: Detect,Scaffold,Conventions,Config. Backend packs:Detect,Scaffold,Conventions,Config and database URL. Data pack:Setup. VCS pack:Detect.stack-api-contractwhen there’s a backenddata-engineerdata pack *. With database host Supabase in host mode, alsodata-supabase:Production notesbackend-engineerbackend pack *,stack-api-contract, and the data pack’sSetupandModelling(to use the client, not to own the schema)frontend-engineerfrontend pack *.stack-api-contractwhen there’s a backend.data-supabase*in BaaS mode. The data pack’sSetupin Next.js full-stack modetest-engineerTestingandGateof the frontend/backend packs, andTestingof the data packci-engineerVCS pack *.Gateof the frontend/backend packs. The data pack’sMigrationsdeploy-engineereach selected deploy pack *(minusAgent autonomy). Frontend/backendContaineriseandDeploy notes. The data pack’sMigrationsandProduction notes.local-docker-desktop*when the dev loop is Composedocs-writerthe composition summary only (README outline from stack-docs-writer) -
Ownership (globs expanded with
{fe}and{be}, the layer paths):Agent Owns scaffolder**(any file), but only in step 1 of an empty repo or the first step of a stack change (plan rule)data-engineerdata pack paths. EF: {be}/src/*Infrastructure*/Persistence/**,{be}/src/*Infrastructure*/Migrations/**. Flyway:{be}/src/main/resources/db/migration/**,{be}/src/main/java/**/domain/**/*Entity.java. Alembic:{be}/alembic/**,{be}/app/models/**. Prisma:{be or fe}/prisma/**. Drizzle:{be or fe}/src/db/**,{be or fe}/drizzle/**. Supabase:supabase/**,{fe}/src/lib/database.types.tsbackend-engineer{be}/**, except the data-engineer’s globs and the deploy files. Includes{be}/openapi/api.jsonfrontend-engineer{fe}/**, except the data, deploy and E2E globs. In Next.js full-stack, also{fe}/src/app/api/**and{fe}/src/server/**test-engineer**/e2e/**,**/playwright.config.*, and the backend integration test projects/folders ({be}/tests/**,{be}/test/**,{be}/src/test/**)ci-engineer.github/**(exceptpages.yml),.gitlab-ci.yml,.gitlab/**,bitbucket-pipelines.yml,renovate.json,CODEOWNERS,scripts/ci/**,docs/pr/**deploy-engineer**/Dockerfile,**/.dockerignore,compose.yaml,**/nginx.conf,vercel.json,netlify.toml,**/_redirects,**/wrangler.jsonc,{fe}/worker/**,.github/workflows/pages.yml,render.yamland.railway/**(documentation only, D-V4-11),.env.exampledocs-writerREADME.md,docs/**(exceptdocs/pr/**) - Plan rules (composed, in v2 rule shapes):
- Scaffold first when the repo is empty (the scaffolder is exempt from order rules, per the v2 decision).
- The data pack’s migrations come before the backend (or Next.js server) steps that use them.
- A step changing
{be}/openapi/api.jsoncomes before frontend steps that change the generated client path (soft rule). - Exclusivity: data-engineer steps must not declare frontend UI or endpoint files. Backend and frontend steps must not declare migration globs.
- One migration per step.
- The ci-engineer is the only agent for CI files, and the deploy-engineer is the only agent for platform config.
- Spec sections (their union, deduplicated by title, each with the pack’s guidance):
- any backend → “API Contract & Error Codes”
- any data pack → “Data Model & Migrations”
data-supabaseBaaS → “Data Model & RLS Policies” (replacing the previous one) and “Auth Flows”fe-nextjs→ “Routes & Rendering Strategy”- a frontend without a backend or data → “Data Sources & Consumed APIs”
- any deploy host → “Environments & Configuration”
- origin model
proxyorcross-origin→ “Origin Model (CORS, Cookies, Auth)” - a stack change → “Stack Migration”
-
Review checks (deterministic, language-independent text checks; ids are registered by packs; severities per v1 §4.7):
Rule id Packs Finding rls_on_every_tabledata-supabaseBaaScreate tableinsupabase/migrations/withoutenable row level security→ critical security (v2 rule)grants_for_exposed_tablesdata-supabaseBaaSa new publictable without agrant … to authenticated/anon→ majorapi_contracts(“not exposed to the Data API”, the Supabase 2026 default)no_secret_in_clientfe-nextjs,fe-react-vite,fe-vue,fe-vanilla,fe-angularcritical security (generalises v2), when one of these appears: (1) a public-prefixed name ( NEXT_PUBLIC_/VITE_), or an Angularenvironment*.tskey, matching the secret patterns (SECRET,SERVICE_ROLE,PRIVATE,PASSWORD,sb_secret_,sk_live); (2) a non-public env name read in a"use client"fileno_committed_secretsvcs-*a match of the vcs-github secret-scan pattern ( PASSWORD=,SECRET=,API_KEY=,PRIVATE KEY,sk_live,service_role) outside*.example→ critical securityno_ensure_createddata-efcore-*EnsureCreated(orDatabase.Migrate(outside tests → major architectureno_ddl_auto_updatedata-jpa-flywayddl-autoset toupdate,createorcreate-dropoutside test config → major architectureno_create_alldata-sqlalchemycreate_all(outside tests → major architectureno_schema_pushdata-prisma,data-drizzleprisma db pushordrizzle-kit pushin package scripts → major architecturemigration_backward_compatibleevery data pack drop table,drop column,rename columnor a type change in a new migration → majordata_model(“expand/contract required”, because preview and production deploys aren’t atomic)container_non_rootbackend packs with a Docker deploy a backend Dockerfilewithout a non-rootUSER→ minor securitydeploy_branch_controldeploy-vercel,deploy-netlify,deploy-cloudflaremajor architecture (generalises v2 FR-89) when: vercel.jsonlacksgit.deploymentEnabled {"aide/**": false},netlify.tomllacks theaide/*ignore rule, or the Workers preview trigger lacks theaide/*excludehealth_endpointsbackend packs the pack’s live/ready health paths are absent from the source → minor feature_coverage - Generated files (FR-127), per pack. Examples:
be-*→{be}/openapi/api.json:hand_written, plus CI export-and-difffe-react-vite,fe-vue,fe-vanilla→{fe}/src/api/generated/**:hand_written, plus CI regenerate-and-difffe-angular→ the Orval output, handled the same waydata-efcore-*→ the migration and*ModelSnapshot.cs:hand_written, plus CIdotnet ef migrations has-pending-model-changesdata-supabase→database.types.ts:hand_written(v2 decision)- lockfiles →
ci_generated(absent until the first CI run)
- Detection (FR-118) is a pure function over the file list and manifest contents.
- Frontend:
nextin apackage.json(or anext.config.*) →fe-nextjs@angular/core+angular.json→fe-angularvue+vite→fe-vuereact+vite→fe-react-vite- an
index.htmlwith no framework dependency →fe-vanilla
- Backend:
- a
*.csprojreferencingMicrosoft.AspNetCore→be-aspnet spring-boot-starter-webinpom.xml/build.gradle*→be-spring-bootfastapiinpyproject.toml/requirements*.txt→be-fastapi@nestjs/core→be-nestjsexpress(without Nest) →be-express
- a
- Data:
Npgsql.EntityFrameworkCore.PostgreSQL→data-efcore-postgresMicrosoft.EntityFrameworkCore.SqlServer→data-efcore-sqlserverflyway+ JPA →data-jpa-flywayalembic+sqlalchemy→data-sqlalchemyprisma→data-prismadrizzle-orm→data-drizzlesupabase/config.tomlwith no other data pack →data-supabaseBaaS. With another data pack, host mode is noted.
- Hosts:
vercel.json,netlify.toml,wrangler.jsonc,render.yaml,.railway/,.github/workflows/pages.yml→ that host as a suggestion. A legacyrailway.json/railway.tomlsuggests Railway, with the evidence “legacy config, deprecated by Railway”. - Each match carries its evidence. Conflicting matches leave the layer empty, with both shown.
- Frontend:
4.19 Git hosts (GitLab, Bitbucket)
- Common: the provider interface (v2
IGitProvider) gainsGetMergeReadinessAsync,GetCommitStatusesAsync,FindReleaseRefsAsyncandPublishReleaseAsync, implemented for GitLab and Bitbucket, andListReposAsync,ListOwnersAsyncandCreateRepoAsync, implemented for GitLab. Bitbucket’s repo operations are in its repo service. GitHub keeps its v2 services, and Local has no host, so both answer NotSupported through the interface. Host base URLs are fixed (https://gitlab.com,https://api.bitbucket.org/2.0,https://bitbucket.org), except Admin-registered GitLab instances. -
GitLab (
{base}/api/v4,Authorization: Bearer <token>):Operation Call Identity GET /user; OIDCGET /oauth/userinfoOAuth /oauth/authorize(code + PKCE S256),/oauth/token(rotating refresh),/oauth/revoke; scopesapi write_repository openid profile emailToken info GET /personal_access_tokens/self(scopes,expires_at)Repos / owners GET /projects?membership=true&min_access_level=30&simple=true&search=&pagination=keyset&order_by=id;GET /groups?min_access_level=30+project_creation_levelCreate POST /projects {name, path, namespace_id?, visibility, initialize_with_readme: true, default_branch: "main"}Default branch, merge settings GET /projects/:id→default_branch,merge_method(merge/rebase_merge/ff),squash_option,only_allow_merge_if_pipeline_succeedsTags / releases GET /projects/:id/repository/tags,GET /projects/:id/releases;POST /projects/:id/releases {tag_name, ref, name, description, tag_message}MR POST /projects/:id/merge_requests {source_branch, target_branch, title: "Draft: …", description, remove_source_branch: false};PUT …/merge_requests/:iid {title}(ready = prefix removed);GET …/merge_requests?source_branch=&state=openedReadiness GET …/merge_requests/:iid(detailed_merge_status,has_conflicts,blocking_discussions_resolved,sha,head_pipeline);GET …/approvals(approved;approvals_lefton Premium only);GET /projects/:id/protected_branches/:name(merge_access_levels)Merge PUT …/merge_requests/:iid/merge {sha, squash, should_remove_source_branch: false}; 405 not mergeable, 409 head moved, 422 failed; result SHA =merge_commit_sha??squash_commit_sha??sha(fast-forward)CI GET /projects/:id/repository/commits/:sha/statuses;GET /projects/:id/pipelines?sha=Netlify link support POST /projects/:id/deploy_keys {title, key, can_push: false},POST /projects/:id/hooks {url, push_events: true}More GitLab rules:
- The status values
unchecked,checking,preparingandapprovals_syncingare transient. - Roles: Developer for push, MR and tags. Maintainer for merging into the protected default branch and for releases (a 403 maps to
no_permission). - Minimum version: GitLab 17.11. It’s checked when an instance is added, and an older version shows a warning.
- The status values
-
Bitbucket Cloud (
https://api.bitbucket.org/2.0, OAuth Bearer or API-token Basicemail:token):Operation Call Identity GET /user,GET /user/emailsOAuth https://bitbucket.org/site/oauth2/authorize,/site/oauth2/access_token(client Basic auth); scopes fixed on the consumerWorkspaces / repos GET /user/workspaces;GET /repositories/{ws}?role=contributor&q=name~"…"&sort=-updated_on(follownext)Create GET /workspaces/{ws}/projects;POST /repositories/{ws}/{slug} {scm: "git", is_private, project: {key}, description}; initial commit pushed by AIDEDefault branch GET /repositories/{ws}/{slug}→mainbranch.nameTags GET …/refs/tags;POST …/refs/tags {name, target: {hash}, message}(annotated; full SHA)PR POST …/pullrequests {title, description, source.branch.name, destination.branch.name, close_source_branch: false, draft: true};PUT …/pullrequests/{id} {draft: false};GET …/pullrequests?state=OPEN&q=source.branch.name="…"Readiness GET …/pullrequests/{id}/mergeability/checks(type,statusPASSED/FAILED/PENDING/SKIPPED/UNKNOWN,required,blocking)Merge POST …/pullrequests/{id}/merge {merge_strategy, close_source_branch: false, message}; 202 +Location→ poll…/merge/task-status/{task}; 409 refs changed; 555 timeout (retry); result SHA =merge_commit.hashCI GET …/commit/{sha}/statuses(FAILED,INPROGRESS,STOPPED,SUCCESSFUL)Netlify link support POST …/deploy-keys {key, label},POST …/hooks {description, url, events: ["repo:push"], active: true}(build, 2026-10-31: Bitbucket requiresdescription)Merge strategies map from AIDE’s per-project setting:
merge_commit(default),squash,fast_forward. - Polling budget (no webhooks, v2 D-V2-8):
- GitLab: merge polls every 2 min for an hour, then every 10 min (v2). Status polls every 30 s while waiting, backing off on 429 (
Retry-After). Merge polls also back off whenRateLimit-Remainingis below 10 %. - Bitbucket allows 1,000 requests per hour per user on repository endpoints, with no remaining-count headers. So AIDE:
- keeps a per-connection token bucket (900/h for background work)
- polls a PR no more than once a minute
- uses the single mergeability call instead of separate restriction, approval and status calls
- Rate-limited jobs are re-queued without counting an attempt (v2 rule). No ETag support is assumed for either host.
- GitLab: merge polls every 2 min for an hour, then every 10 min (v2). Status polls every 30 s while waiting, backing off on 429 (
4.20 Deployment platforms
- Provider interface (generalises v2
IDeploymentProvider):- Operations:
ValidateConnectionAsync,ListResourcesAsync(link picker),SetupTargetAsync(create or link, branch control, preview resource),VerifyTargetAsync(readiness checklist),SyncEnvAsync,DeployAsync {environment, ref, sha},GetDeploymentAsync,GetBuildLogTailAsync,FindDeploymentForShaAsync,CancelAsync,RollbackAsync. Accounts are listed by the platform connection validator (ListAccountsAsync), not the deployment provider. - Capabilities record:
ShaPinned,Previews,Rollback,PerEnvironmentVariables,RuntimeSecrets. - Providers: Vercel, Netlify, Cloudflare (Workers Builds; Pages adopt-only), Render, Railway, GitHub Pages, Fake.
- Operations:
-
Deployment states (v2 §4.15)
queued → building → ready | error | canceled, mapped per provider. The table gives the auth, create/link and branch-control calls:Provider Token (pasted) and validation Create / link Branch control for aide/**Vercel (v2) team/account token; GET /v2/userPOST /v11/projectswithgitRepository(GitHub, GitLab, Bitbucket)vercel.jsongit.deploymentEnabled {"aide/**": false}(minimatch; no other matchingtruerule)Netlify personal access token (expiry chosen by the user); GET /user,GET /accountsPOST /{account_slug}/siteswithrepo {provider, id, repo_path, repo_branch, cmd, dir, allowed_branches}. GitHub viainstallation_id(the Netlify GitHub App, installed by the user). GitLab/Bitbucket viaPOST /deploy_keys, a repo deploy key, and a push webhook tohttps://api.netlify.com/hooks/<provider>, added by AIDEallowed_branches: [<production branch>], Deploy Previews off, and anetlify.toml[build] ignorethat exits 0 foraide/*Cloudflare (Workers + Workers Builds) user API token (Workers Scripts Edit, Workers CI Edit, Account Settings Read, User Details Read; plus Pages Edit for Pages mode); GET /user/tokens/verify,GET /accountsThe user installs the Cloudflare GitHub/GitLab app in the dashboard. AIDE calls PUT /builds/repos/connectionsandPOST /builds/tokens, and creates two triggers: production (branch_includes: [main],deploy_command: npx wrangler deploy) and preview (branch_excludes: ["aide/*"],deploy_command: npx wrangler versions upload). First Worker bootstrap per the §11 checkthe preview trigger excludes aide/*Cloudflare Pages (adopt existing only) as above, plus Pages Edit link an existing project; set source.config.preview_deployment_setting: custom,preview_branch_excludes: ["aide/*"]as left Render API key (account-wide; the user then picks a workspace from GET /v1/owners);GET /v1/usersThe user connects the Git host once in Render (GitHub, GitLab, Bitbucket). AIDE calls POST /v1/services:static_site {buildCommand, publishPath, routes}orweb_service {runtime: docker\|node\|python, envSpecificDetails, preDeployCommand, healthCheckPath, plan, region}, withrootDir,branch: main, andautoDeployTrigger: "commit"for productionthe production service tracks mainonly; the preview service<name>-previewhasautoDeployTrigger: "off"Railway workspace token ( Authorization: Bearer; account token accepted);query { workspace(workspaceId) }/meThe user installs the Railway GitHub app. AIDE calls projectCreate {workspaceId, prDeploys: false},serviceCreate {projectId, source: {repo}, branch: "main"},serviceInstanceUpdate {rootDirectory, dockerfilePath \| buildCommand, startCommand, healthcheckPath, watchPatterns}andserviceDomainCreate. Pre-deploy command per the §11 checkservices trigger only on main; PR environments offGitHub Pages the project’s GitHub App/PAT (Pages RW) POST /repos/{o}/{r}/pages {build_type: "workflow"}; the deploy-engineer writes.github/workflows/pages.ymln/a (only the default branch deploys) Deploying, rolling back and reading logs:
Provider Preview Production Rollback Logs Vercel POST /v13/deploymentsgitSource {ref, sha}adopt the auto-deploy of the merge SHA, else create POST /v10/projects/{id}/promote/{deploymentId}v2 events API Netlify POST /sites/{id}/builds?branch=<version branch>&title=aide:<run>. It builds the branch HEAD, so AIDE checksdeploy.commit_ref= the PR head, else re-triggers. Falls back to a build hook, which ignore rules don’t canceladopt GET /sites/{id}/deploys?production=truewithcommit_ref= the merge SHA, elsePOST /buildsPOST /sites/{id}/deploys/{deploy_id}/restore(readslockedafter)best effort: deploy.error_message+admin_url(no REST log endpoint)Cloudflare POST /builds/triggers/{preview}/builds {branch, commit_hash}(SHA-pinned);preview_urlfromGET /builds/builds/{uuid}adopt the production build with build_trigger_metadata.commit_hash= the merge SHA, else start it with the hashPOST /workers/scripts/{name}/deployments {strategy: percentage, versions: [{version_id, percentage: 100}]}GET /builds/builds/{uuid}/logs(cursor)Cloudflare Pages POST /pages/projects/{name}/deploymentsformbranch(HEAD; verifydeployment_trigger.metadata.commit_hash)adopt POST …/deployments/{id}/rollbackGET …/deployments/{id}/history/logsRender PATCHthe preview service’sbranchto the version branch, thenPOST /v1/services/{id}/deploys {commitId}(SHA-pinned)adopt the auto-deploy of the merge SHA, else POST /deploys {commitId}POST /v1/services/{id}/rollback {deployId}GET /v1/logs?ownerId&resource[]&type=build&startTime&endTime&direction=backward&limit=100(30/min)Railway environmentCreate {name: "aide-preview", sourceEnvironmentId: production, skipInitialDeploys: true}once, thenserviceInstanceDeployV2(serviceId, previewEnvId, commitSha)(SHA-pinned)serviceInstanceDeployV2(prodEnvId, mergeSha), or adoptdeploymentRollback(id)whencanRollbackbuildLogs(deploymentId, limit: 100)GitHub Pages none ( Capabilities.Previews = false)the Actions run on the merge SHA, then GET /repos/{o}/{r}/pages/deployments/{sha}andGET …/pages(html_url)none the failed workflow job’s log URL State mapping:
Provider queued building ready error canceled Vercel v2 v2 v2 v2 v2 Netlify new,pending_review,accepted,enqueuedbuilding,uploading,uploaded,preparing,prepared,processing,processed,retryingreadyerror,rejected— Cloudflare queued,initializingrunningstopped+build_outcomesuccessstopped+ fail or terminatedstopped+ cancelled or skippedCloudflare Pages — any other latest_stagelatest_stagedeploy+successfailurecanceled,skippedRender created,queuedbuild_in_progress,update_in_progress,pre_deploy_in_progresslivebuild_failed,update_failed,pre_deploy_failedcanceled,deactivatedRailway QUEUED,WAITING,INITIALIZINGBUILDING,DEPLOYINGSUCCESSFAILED,CRASHEDSKIPPED,REMOVEDGitHub Pages — deployment_in_progress,syncing_files,updating_pagessucceedfailures — - Environment variable sync:
- Vercel: as in v2.
- Netlify:
POST/PATCH /accounts/{account_id}/env?site_id=, with contextsproductionand, for preview, bothdeploy-previewandbranch-deploy(API branch builds run as branch deploys).is_secretfor sensitive values. - Cloudflare: build variables per trigger (
PATCH /builds/triggers/{uuid}/environment_variables,is_secret), and runtime secrets withPUT /workers/scripts/{name}/secrets. Runtime secrets are shared by preview and production, so a preview-only secret is refused withVALIDATION. - Render: per service, with
PUT /v1/services/{id}/env-varscarrying the full merged set (it replaces), never deploying. - Railway:
variableCollectionUpsert {projectId, environmentId, serviceId, variables, replace: false, skipDeploys: true}. - GitHub Pages (build, 2026-10-31): production values of public, non-sensitive variables go to repository Actions variables (
PATCH, elsePOST /repos/{o}/{r}/actions/variables;DELETEwhen removed), read by the workflow asvars.X. Sensitive, non-public and secret-looking variables andGITHUB_*names are refused per variable (syncError), and one variable’s failure doesn’t stop the others. See FR-145 for timing.
- Polling cadence: v2’s 10 s for 2 min, then 30 s, with a 30-minute ceiling. Exceptions:
- Railway: at least 20 s (the Free plan allows 100 requests/hour)
- Render logs: at most 30/min per user
- Netlify: deploy creation at most 3/min and 100/day per account, enforced before calling
All of them honour 429
Retry-After/reset headers without counting an attempt. - Plan and cost notes surfaced in the readiness checklist (not enforced):
- Netlify production deploys cost 15 credits on credit plans, and the Free plan pauses all sites when credits run out.
- Render free web services sleep after 15 minutes, and free Postgres expires after 30 days.
- Railway Free/Trial limits.
- Workers Builds has a 20-minute build timeout.
- Pack deviations (D-V4-11): stack-packs
deploy-renderis Blueprint-first anddeploy-railwayis IaC/CLI-first. AIDE creates resources through the platform APIs instead. The deploy-engineer may still commitrender.yamlor.railway/railway.tsthat mirror the created resources, as documentation for users who later manage the project themselves. AIDE never applies them. - Fake provider (v2, generalised): every provider id has a Fake mode (
Deployments:Provider=Fakeapplies to all). A Fake deployment:- reaches
readyafter 2 polls, with URLhttps://fake-<sha8>-<component>.<provider>.test - fails when the branch contains
.aide-fake/fail-build - fails a backend pre-deploy when the branch contains
.aide-fake/fail-migration
- reaches
4.21 Managed databases and migrations
- Database states:
provisioning → available | error, pluspaused(SupabaseINACTIVE),expiring(Render free, within 7 days ofexpiresAt) andremoved. Polling every 10 s, with a 10-minute ceiling (DATABASE_PROVISIONING_TIMEOUT). -
Supabase (
https://api.supabase.com/v1, scoped PAT, 120 requests/min per user per endpoint; 429 withX-RateLimit-Reset; AIDE doesn’t pre-limit Supabase calls: a 429 waits for that reset, without counting an attempt):Step Call Validate GET /organizations,GET /organizations/{slug}/projects(401 invalid; 403missing_permissions→insufficient_scope)Create POST /projects {name, organization_slug, db_pass, region_selection: {type: "specific", code} \| {type: "smartGroup", code: americas\|emea\|apac}}(plan,regionandorganization_idare deprecated and never sent)Wait GET /projects/{ref}untilACTIVE_HEALTHY(INIT_FAILED→ error), thenGET /projects/{ref}/health?services=db,pooler,auth,rest, allACTIVE_HEALTHYKeys GET /projects/{ref}/api-keys?reveal=true(typelegacy/publishable/secret);POST /projects/{ref}/api-keys {type, name}when missingConnection GET /projects/{ref}/config/database/pooler→ session (:5432) and transaction (:6543) pooler host and user (theaws-<n>-<region>host can’t be derived). Password = the AIDE-generateddb_pass(reset withPATCH /projects/{ref}/database/passwordwhen linking a project whose password AIDE doesn’t hold)Auth URLs PATCH /projects/{ref}/config/auth {site_url, uri_allow_list}(comma-separated)Advisors GET /projects/{ref}/advisors/securityafter each migration runPaused INACTIVE→ the card shows “Paused”. The Owner action callsPOST /projects/{ref}/restoreRequired scoped-token permissions: Projects RW, Database RW, Connection Pooling R, API Keys R, API Key Secrets R, Auth Config RW, Data API Config RW (and Organization Projects R for the picker).
- Render Postgres:
POST /v1/postgres {name, ownerId, plan, version: "17", region (= the backend's), databaseName}. PollGET /v1/postgres/{id}until available.GET /v1/postgres/{id}/connection-info→internalConnectionStringfor services in the same region and workspace (externalConnectionStringonly when the consumer is elsewhere). The preview database is a second instance named<name>-preview. - Railway Postgres: a Postgres service with a volume in each environment. The production environment’s service is copied into
aide-preview(see the §11 check for the creation call shape). The app references it asDATABASE_URL = ${{Postgres.DATABASE_URL}}(private network). The data pack’sProduction notescover converting it for .NET and Java. - Migration runner (Supabase BaaS only, D-V4-12): AIDE’s own Npgsql runner, over the session pooler.
- It applies the pending
supabase/migrations/<version>_<name>.sqlfiles of a commit in version order, each in a transaction. - It records
(version, name, statements)insupabase_migrations.schema_migrations, the table the Supabase CLI uses, so a latersupabase db pushsees a consistent history. - It never edits or re-applies a recorded version. A recorded version whose file changed or disappeared is
MIGRATION_HISTORY_DIVERGED: in production the run fails, and in preview it means the FR-148 reset. - Runs are recorded (
database_migration_run) with files, durations and errors. - The Management API migrations endpoint isn’t used (it assigns its own versions), and neither is the Beta query endpoint.
- It applies the pending
- When migrations run:
- Preview: during preview rollout, before the backend and frontend previews (FR-147).
- Production, Supabase BaaS: in the release job, after merge readiness passes and immediately before AIDE merges (
aide_merges), or when the release entersawaiting_merge(manual). This is because platforms auto-deploy the default branch on merge.- The
migration_backward_compatiblereview check (§4.18) is what makes “schema before code” safe. - If a PR is closed without merging after its migrations were applied, the release records “migrations applied, code not merged”, and the next version must keep those files (they’re on the branch).
- Readiness not passed (build, 2026-10-31): every readiness reason holds the migrations back except
manual_strategy,no_permissionandmerge_method_not_allowed, which don’t say the PR can’t merge (a person still can).aide_merges:merge_conflictfails withMERGE_CONFLICT(FR-43), and any other reason sends the release toawaiting_mergewithout calling the merge.manual: the release entersawaiting_mergeunmigrated. Readiness that can’t be read counts as passed in the release job (the merge is pinned to the migrated head), including GitHub’s conservativereview_requiredwhen GraphQL is unavailable. - No target set up (build, 2026-10-31): a Supabase BaaS project whose targets are all left for later still gets a migrations-only rollout of each environment that has a database (preview on the PR head, production on the merge commit), with the usual
MIGRATION_RUN_*events. A project with no targets and no such database gets no rollout. - GitLab still checking (build, 2026-10-31): a transient
detailed_merge_statuscounts as readiness that can’t be read for the migrations, in the release job and the merge poll. What people see of readiness stays “still checking” (checks_pending). - During
awaiting_merge(build, 2026-10-31): while the PR is open, the merge poll runs the migrations on its current head once readiness passes, and again when the head moves. Readiness that can’t be read waits for the next poll. A failed run sendsmigration_failedand the release keeps waiting; it runs again when the head moves or the database becomes usable again.releases.migration_run_idis the attempt’s newest pre-merge run, until a succeeded production rollout run replaces a failed one after a person merges. - Merged without migrating (build, 2026-10-31): AIDE can’t stop a merge on the host. A merge of a head no pre-merge run succeeded on writes
MERGED_HEAD_MISMATCH(expectedHeadShanull when none succeeded), and the migrations of that head first run in the production rollout, possibly after the platform auto-deployed the code.
- The
- Production, host mode: on the platform, as part of the backend’s (or the serverless build’s) deployment of the merge commit (FR-154).
- Migrations from a branch that hasn’t reached release never run against a production database. Supabase BaaS applies a release’s migrations just before its merge, as above.
- Unavailable BaaS database: a Supabase database that is
paused,errororprovisioning(with a resource or connection), or is being restored, fails the migration step (a failed run andmigration_failed). That blocks the release or fails the rollout; it doesn’t count as having no database (build, 2026-10-31).
- SQL Server / External: AIDE stores and syncs the connection string only. Migrations run on the platform, as in host mode.
4.22 Environment inventory and derived variables
| Variable | Applies to | Value | Environments |
|---|---|---|---|
DATABASE_URL |
backend (or the Next.js full-stack app) | the database’s URI: Render internalConnectionString, Railway ${{Postgres.DATABASE_URL}}, the Supabase pooler (transaction :6543 with ?pgbouncer=true/prepare=false guidance for serverless hosts, session :5432 otherwise), or the External value |
per environment’s database |
DIRECT_URL |
same, when the data pack’s migrate.needsDirectUrl (Prisma; Drizzle on serverless) |
the Supabase session pooler :5432, or the same as DATABASE_URL |
same |
AIDE_RUN_MIGRATIONS |
serverless hosts in host mode | true where that environment has its own database, else false |
per environment |
<prefix>SUPABASE_URL, <prefix>SUPABASE_PUBLISHABLE_KEY |
frontend (public; prefix from the frontend pack: NEXT_PUBLIC_, VITE_; Angular reads SUPABASE_URL/SUPABASE_PUBLISHABLE_KEY at build into assets/config.json) |
https://<ref>.supabase.co, sb_publishable_… |
per environment’s Supabase project |
SUPABASE_URL, SUPABASE_SECRET_KEY |
server code only (Next.js server, backends), sensitive, only when the spec needs admin actions (pack rule) | sb_secret_… |
same |
API_ORIGIN |
frontend, origin model proxy |
the backend target URL | production / preview |
API_BASE_URL (VITE_API_BASE_URL, NEXT_PUBLIC_API_BASE_URL, Angular config) |
frontend, cross-origin |
the backend target URL | production / preview |
BACKEND_URL |
fe-nextjs ui mode (server-side calls) |
the backend target URL | production / preview |
CORS_ALLOWED_ORIGINS |
backend, cross-origin |
the frontend production URL, plus the frontend host’s preview URL pattern | production / preview |
Derived values are recomputed when a target URL or database changes, and are then synced (FR-145). User variables can’t override derived keys (VALIDATION). The Owner can change the public prefix only by changing the frontend pack.
4.23 Preset migration (replaces v2 §4.12)
- Composition per preset:
nextjs-supabase→{frontend: fe-nextjs @ ".", backend: null, data: data-supabase (baas), vcs: vcs-github (from github_app, github_pat or local), deploy: {frontendHost: vercel if the project had Vercel settings, else null; databaseHost: supabase (unlinked); local: none}, originModel: same-origin}nextjs-frontend→ the same, withdata: nulland no database host
- Env vars: existing v2 env vars (
NEXT_PUBLIC_SUPABASE_URL,NEXT_PUBLIC_SUPABASE_ANON_KEY,SUPABASE_SERVICE_ROLE_KEY) stay asuservariables. Linking a Supabase connection later adds the derived publishable/secret variables, and the UI suggests deleting the legacy ones. -
Agent keys: v2 agent keys → v4 specialists, for plans locked before the upgrade and for history display:
v2 key v4 key project-scaffolderscaffolderdb-engineerdata-engineerdomain-logic-engineer,api-engineer,ui-engineerfrontend-engineer(Next.js full-stack owns domain, route handlers and UI)test-engineertest-engineerdevops-engineerdecided per step by its declared files: ci-engineerfor.github/**, elsedeploy-engineer. A step with both →deploy-engineer, with a warning - Owned paths: owned-path checks for migrated in-flight plans use the v2 preset’s globs (kept as a frozen mapping in
Stack/legacy/) until the plan is replaced, so a locked plan doesn’t suddenly produceAGENT_SCOPEwarnings. - One EF migration performs the data conversion, with the backfill SQL reviewed in PROGRESS.md.
4.24 LLM providers, model catalog and output limits
- Enablement:
Llm:Providers:<P>:Enabled(bool, defaulttrue), with<P>one ofAnthropic,OpenAI,Ollama,Fake. The pure ruleLlmProviderRules.Available(enabled, configured, userConnections, allowSystemKeys)givesusableandreasonfor FR-158:- Ollama and Fake are
keyless - Anthropic and OpenAI are
system_keywhen a system key exists andAllowSystemKeysis on,own_connectionwhen the user has a connection, elseneeds_connection
- Ollama and Fake are
-
Default for a new project (amends
ProjectSettingsRules.DefaultForNewProject): the configuredDefaultProviderif it’s enabled and usable by the creating user. Otherwise the first enabled usable provider in the order Anthropic, OpenAI, Ollama, Fake. OtherwiseLLM_PROVIDER_UNAVAILABLE. A disabled provider is never chosen.Enabled User can use DefaultProvider Result all four none needing a key anthropic Ollama Anthropic, OpenAI (Railway) Anthropic connection anthropic Anthropic Anthropic, OpenAI (Railway) none anthropic LLM_PROVIDER_UNAVAILABLEall four (CI) — fake Fake -
Model catalog:
Llm:Providers:<P>:Catalog:<n>:{Id, Name, MaxOutputTokens, ContextTokens, Recommended}, shipped inappsettings.json. Operators extend it per deployment through config. The shipped entries:Provider Id MaxOutputTokens ContextTokens Notes Anthropic claude-opus-5-5128,000 1,000,000 recommended, default Anthropic claude-sonnet-5-5128,000 1,000,000 idea refinement default Anthropic claude-haiku-5-5128,000 1,000,000 Anthropic claude-fable-5-1128,000 1,000,000 Anthropic claude-opus-5128,000 1,000,000 Anthropic claude-sonnet-5128,000 1,000,000 Anthropic claude-haiku-4-564,000 200,000 OpenAI gpt-6-astraverify at build (§11 Q5) verify default OpenAI gpt-6.1-solverify at build (§11 Q5) verify idea refinement default Ollama qwen2.5-coder16,000 32,768 default Fake fake-1128,000 1,000,000 - Effective output limit per call, by the pure rule
LlmOutputLimits.Resolve(provider, model, catalog, discovered, ceiling, fallback):limit= the catalog entry’sMaxOutputTokens. Else, for Anthropic only, themax_tokensreported byGET /v1/models/{id}(cached per model for 24 h; a lookup failure is ignored). Elsefallback=Llm:MaxOutputTokens(kept, default 16,000; now the fallback for unlisted models).- When
Llm:MaxOutputTokensCeilingis set (optional, ≥ 1),limit = min(limit, ceiling).
Model Catalog Discovered Ceiling Result claude-opus-5-5128,000 — — 128,000 claude-opus-5-5128,000 — 32,000 32,000 claude-haiku-4-564,000 — — 64,000 claude-new-x(unlisted)— 96,000 — 96,000 claude-new-x(unlisted, lookup fails)— — — 16,000 llama3.3on Ollama (unlisted)— n/a — 16,000 - Continuations (FR-162; replaces
MaxContinuations = 4inLlmToolLoop):MaxContinuations = 5. A call stopping withmax_tokensorlengthand non-empty text is continued.- The continue prompt is the existing
ContinuePrompt(no assistant prefill). - Joining keeps
JoinContinuation: drop the longest overlap of 20–2,000 characters, else concatenate. - Stopping early: before each continuation, if the estimated input tokens (original input plus the answer so far, at 4 characters per token) would exceed
ContextTokens − limit, stop and warnoutput_truncated.
Stop reasons per call Calls continuationsWarning end_turn1 0 — max_tokens,max_tokens,end_turn3 2 — max_tokens× 66 5 output_truncatedmax_tokens(context nearly full)1 0 output_truncated
5. Data model
v1 §5 and v2 §5 apply. Changes (all snake_case, UUIDv7, UTC):
| Entity | Key fields | Constraints / indexes | Notes |
|---|---|---|---|
| release_version (changed) | + stack_composition jsonb?, catalog_version?, catalog_hash?; preset_id/version/hash kept (legacy, read-only) |
The composition is validated by the Domain on write | |
| project (changed) | + stack_packs text[] (denormalised pack ids of the current version) |
GIN index on stack_packs |
List filter ?pack= replaces the preset filter |
| execution_step (changed) | agent_key values are v4 keys; + legacy_agent_key? |
||
| agent_run (changed) | + packs jsonb ([{id, version, sections[]}]), catalog_version?; preset_* kept |
maxOutputTokens and continuations live in the run output JSON (build decision 2026-10-25: no M23 migration) |
|
| git_host_instance (new) | id, kind (gitlab/bitbucket), name, base_url (null for Bitbucket and gitlab.com), oauth_client_id, oauth_client_secret_encrypted, allow_insecure_http, enabled, version?, created_by, created_at |
unique (kind, base_url) | Env config overrides rows with the same base URL |
| user_external_login (new) | id, user_id, provider (github/gitlab/bitbucket), instance_id?, external_user_id, login, created_at |
unique (provider, instance_id, external_user_id) | users.github_id is backfilled here (kept for v2 code paths until removed) |
| git_connection (new) | id, user_id, provider (gitlab/bitbucket), instance_id, auth_kind (oauth/token), external_user_id, login, access_token_encrypted, access_expires_at?, refresh_token_encrypted?, token_email_encrypted? (Bitbucket API token), scopes text[], expires_at?, status (valid/invalid/insufficient_scope), last_validated_at, updated_at, xmin |
unique (user_id, provider, instance_id, external_user_id) | Refresh rotation updates are concurrency-checked. github_connection (v2) is unchanged |
| project_git_settings (changed) | provider adds gitlab, bitbucket; + instance_id?, git_connection_id? (FK set null), bound_by_user_id?, external_repo_id?; repo_full_name holds group/sub/project or workspace/slug |
check: gitlab/bitbucket need instance_id, external_repo_id, repo_full_name | |
platform_connection (new, replaces vercel_connections) |
id, user_id, provider (vercel/netlify/cloudflare/render/railway/supabase), label, secret_encrypted, last4, account_id?, account_name?, metadata jsonb (team/workspace/org ids, token policies), scopes text[], expires_at?, status (valid/invalid/insufficient_scope/unchecked), last_validated_at?, created_at |
index (user_id, provider) | v2 rows are copied with the same id, so FKs survive. vercel_connections is dropped in the same migration |
deployment_target (new, replaces project_deployment_settings) |
id, project_id, component (frontend/backend), provider, connection_id? (FK set null), bound_by_user_id?, resource_id (Vercel project / Netlify site / Worker / Pages project / Render service / Railway project+service), preview_resource_id? (Render preview service / Railway environment / Workers preview trigger), name, root_directory, build_config jsonb, production_url?, preview_url?, status (not_configured/configuring/ready/attention/error), status_reason?, updated_at, xmin |
unique (project_id, component) | v2 settings rows become frontend targets |
| managed_database (new) | id, project_id, environment (production/preview), provider (supabase/render/railway/external/fake), connection_id?, resource_id?, region?, plan?, status, connection_encrypted (URIs, keys, pooler config as JSON), db_password_encrypted?, expires_at?, created_at, updated_at, xmin |
unique (project_id, environment) | |
| database_migration_run (new) | id, project_id, managed_database_id, release_id?, release_version_id, git_sha, environment, status (running/succeeded/failed), applied jsonb ([{version, name, ms}]), error_code?, error_message?, started_at, finished_at? |
index (project_id, started_at) | Supabase BaaS runner only |
| deployment_env_var (changed) | + applies_to text[] (frontend/backend), public bool, source (user/derived), derived_from? |
unique (project_id, key) unchanged | Derived rows are written by AIDE only |
| deployment (changed) | + target_id, component, environment (replaces the target preview/production naming; v2 values kept), kind (deploy/rollback/promote), rollout_id? |
unique (release_id, component) where environment = production and trigger = auto | rollout_id is the rollout job’s id: there is no rollouts table (build, 2026-10-31) |
| release (changed) | + migration_run_id? (FK set null; the pre-merge run), release_notes text (always stored; the Bitbucket notes surface), merge_submitted_at? (set just before AIDE asks Bitbucket to merge) |
github_release_url is not renamed (build, 2026-10-31): it keeps its column and DTO name and holds the host’s release URL (GitLab Release, Bitbucket tag page). There is no host_release_url |
LLM settings need no schema change. project_llm_settings already holds provider, model, agent models and the bound credential. Provider enablement and the model catalog are configuration.
Migration notes:
- One migration per milestone: catalog/presets, GitLab+Bitbucket, platforms+targets, databases. No committed migration is edited (the latest today is
20261017171130_VercelDeployments). - The presets migration and the
vercel_connections/project_deployment_settingsreplacement aren’t safe for rolling updates. Kubernetes uses the Recreate strategy (v2 precedent). Railway runs one API replica and migrates in pre-deploy (v3 FR-109), so it’s safe there too.
6. API
v1 §6 and v2 §6 apply. All new routes are under /api, with a ProblemDetails errorCode. The contract (backend/openapi/api.json) and the generated client are regenerated.
Breaking-change decision (D-V4-4). These change:
- The preset endpoints and fields are removed:
/api/presets,PATCH /projects/:id/preset,presetIdon create and onPOST /versions, and the?preset=list filter. - The Vercel connection endpoints move to
/api/me/connections/platforms. /settings/deploymentchanges shape to targets + databases.- Vercel-specific error codes are replaced by provider-neutral ones:
VERCEL_GIT_NOT_CONNECTED→PLATFORM_GIT_NOT_CONNECTED,VERCEL_PLAN_LIMIT→PLATFORM_PLAN_LIMIT,VERCEL_ERROR→PLATFORM_ERROR.
The web app and CLI ship together (D-V2-11 precedent). Record the contract diff (oasdiff, or a manual review when it isn’t available) in PROGRESS.md.
| Method | Route | Body / query | Response | Errors (code → status) | Auth |
|---|---|---|---|---|---|
| GET | /api/stack/catalog | — | layers, options (chips, source, modes), recommendations, catalog version | — | user |
| POST | /api/stack/options | partial composition | all nine choices (frontend, backend, data, vcs, frontendHost, backendHost, databaseHost, local, originModel) with selected/default, each option with state (allowed/caveat/forbidden/invalidated) and reasons[] |
VALIDATION → 400 |
user |
| POST | /api/stack/validate | composition | normalised composition + derived (agents, sections, env names, origin model) | STACK_INCOMPATIBLE → 400 |
user |
| POST | /api/stack/detect | git {provider, instanceId?, repo, ref?, token? (github_pat only)} |
suggested composition with evidence | GIT_NOT_CONNECTED → 409, NOT_FOUND → 404 |
user |
| GET / PUT | /api/projects/:id/stack | composition | composition (GET: any member) | STACK_INCOMPATIBLE → 400, STACK_LOCKED → 409, VCS_MISMATCH → 400 |
owner |
| GET/POST/PATCH/DELETE | /api/admin/integrations/gitlab[/:instanceId] | name, baseUrl, clientId, clientSecret, allowInsecureHttp, enabled | instances (secrets as flags) | GIT_HOST_NOT_ALLOWED → 400, UPSTREAM_ERROR → 502, CONFLICT → 409 |
admin (browser session) |
| GET/PUT/DELETE | /api/admin/integrations/bitbucket | clientId, clientSecret | consumer status | CONFLICT → 409 |
admin (browser session) |
| GET | /api/auth/{gitlab,bitbucket}/start | instanceId?, mode (signin/connect), returnUrl |
redirect | NOT_FOUND → 404 |
anonymous / user |
| GET | /api/auth/{gitlab,bitbucket}/callback | code, state | redirect | GIT_AUTH_FAILED redirect |
— |
| POST | /api/admin/integrations/gitlab/:instanceId/check (build, 2026-10-28) | — | instance with version and reachability | GIT_HOST_NOT_ALLOWED → 400, UPSTREAM_ERROR → 502 |
admin |
| GET / POST / DELETE | /api/auth/gitlab/link (build, 2026-10-28) | — | the pending email link (GET), confirm (POST, 204) or cancel (DELETE) after the user signs in to the matching account (D-V4-18) | FORBIDDEN → 403, NOT_FOUND → 404, CONFLICT → 409 |
anonymous / user |
| GET / POST / DELETE | /api/auth/bitbucket/link (build, 2026-10-29) | — | as /api/auth/gitlab/link (D-V4-18) |
FORBIDDEN → 403, NOT_FOUND → 404, CONFLICT → 409 |
anonymous / user |
| GET | /api/auth/providers (changed, build 2026-10-28/29) | — | + gitlab[] (sign-in instances), + bitbucket (consumer configured) |
— | anonymous |
| GET | /api/me/connections/gitlab/instances (build, 2026-10-28) | — | enabled instances for the connect dialog | — | user |
| GET/POST/DELETE | /api/me/connections/{gitlab,bitbucket}[/:id] · /:id/test | POST: token fallback {instanceId?, token, email?} | connections (login, instance, auth kind, scopes, expiry, status) | CREDENTIAL_INVALID → 400, CONNECTION_SCOPE_INSUFFICIENT → 400 |
user (browser session) |
| GET | /api/me/git/:provider/repos · /owners · /repos/:repoId/refs | connectionId, search, cursor | repos / namespaces, groups, workspaces+projects / tags, releases, default branch | GIT_NOT_CONNECTED → 409 |
user |
| POST | /api/me/git/:provider/repos | connectionId, owner, project? (Bitbucket), name, description, private | 201 repo | REPO_NAME_TAKEN → 409, FORBIDDEN → 403, UPSTREAM_RATE_LIMITED → 429 |
user |
| GET/POST/PATCH/DELETE | /api/me/connections/platforms[/:id] · /:id/test | provider, label, token, accountId? | connections (provider, account, scopes, expiry, status) | CREDENTIAL_INVALID → 400, CONNECTION_SCOPE_INSUFFICIENT → 400 |
user (browser session) |
| GET | /api/me/connections/platforms/:id/accounts · /resources | type? (sites, services, projects, workers, orgs, regions) | picker data | UPSTREAM_ERROR → 502 |
user |
| GET | /api/projects/:id/settings/deployment | — | targets, databases, env inventory, origin model, readiness | — | member |
| PUT / DELETE | /api/projects/:id/settings/deployment/targets/:component | provider, connectionId, create {name, region?, plan?} | link {resourceId}; neither = rebind a target already on that provider (connectionId required except for github_pages) |
202 target (setup job) | STACK_INCOMPATIBLE → 400, CREDENTIAL_NOT_OWNER → 403, PLATFORM_PERMISSION_MISSING → 409. PLATFORM_GIT_NOT_CONNECTED, PLATFORM_PLAN_LIMIT, CREDENTIAL_INVALID, NOT_FOUND and VALIDATION come from the setup job onto the target (status: error, statusReason) (build, 2026-10-30) |
owner |
| POST | /api/projects/:id/settings/deployment/targets/:component/verify | — | readiness checklist | — | owner |
| PUT / DELETE | /api/projects/:id/settings/deployment/databases/:environment | provider, connectionId, create {region, plan} | link {resourceId} | external {connectionString} | 202 database (provisioning job). DELETE keeps the row as removed with its resource, connection and secrets cleared; migration runs are kept (build, 2026-10-31) |
STACK_INCOMPATIBLE → 400, VALIDATION → 400 (preview and production not separate), CREDENTIAL_NOT_OWNER → 403, CONFLICT → 409 (project lock), UPSTREAM_ERROR → 502. PLATFORM_PLAN_LIMIT lands on the card from the job |
owner |
| POST | /api/projects/:id/settings/deployment/databases/:environment/restore (build, 2026-10-31) | — | 202 (Supabase: restore a paused project) | INVALID_STATE → 409, CONFLICT → 409 |
owner |
| POST | /api/projects/:id/settings/deployment/databases/preview/reset | — | 202 | INVALID_STATE → 409 (rollout running) |
owner |
| GET | /api/projects/:id/migrations · /migrations/:runId | releaseVersionId? | migration runs | NOT_FOUND → 404 |
member |
| GET/PUT/DELETE | /api/projects/:id/settings/deployment/env[/:key] (changed) | + appliesTo[], public | inventory (hasValue, source, derivedFrom) |
VALIDATION → 400 (derived key, secret-looking public value), UPSTREAM_ERROR → 502 |
owner |
| GET/POST | /api/projects/:id/deployments (changed) · /deployments/preview | component?, environment? / — | deployments / GET preview: the current rollout; POST preview: 202 RolloutDto (breaking from v2’s deployment body; the web app is the only caller) (build, 2026-10-31) |
DEPLOYMENT_NOT_CONFIGURED → 409, PREVIEW_DATABASE_DIVERGED → 409, INVALID_STATE → 409 (a reset is pending) |
member / writer |
| POST | /api/projects/:id/deployments/:deploymentId/rollback (renamed from /promote; /promote kept as an alias for Vercel) · /redeploy | — | 202 deployment | INVALID_DEPLOYMENT → 409, DEPLOYMENT_ACTION_UNSUPPORTED → 409, FORBIDDEN → 403 |
rollback: owner; redeploy: writer for preview, owner for production |
| PATCH | /api/projects/:id/settings/git (changed) | + connectionId (rebind, GitLab/Bitbucket); mergeStrategy adds fast_forward and squash (Bitbucket only; build decision 2026-10-29; VALIDATION on GitHub and GitLab). A project can’t be moved onto gitlab or bitbucket here: VALIDATION on provider. The binding is set on create or import and changed only by rebind |
section | CREDENTIAL_NOT_OWNER → 403, GIT_NOT_CONNECTED → 409, FORBIDDEN → 403 |
owner |
| GET | /api/llm/providers (new) | — | enabled providers: usable, reason, isDefault, connections, catalog (id, name, maxOutputTokens, contextTokens, recommended), defaultModel |
— | user |
| PATCH | /api/projects/:id/settings/llm (changed) | as v2 | section (providers filtered to enabled) | + LLM_PROVIDER_UNAVAILABLE → 400 |
owner |
| POST | /api/projects (changed) | − presetId; + stack (composition); + llm {provider, credentialId?, defaultModel, modelsByAgent?}; git {mode: github_app|github_pat|gitlab|bitbucket|local, instanceId?, connectionId?, repoId?, repoFullName? …} | 201 project | + STACK_INCOMPATIBLE, VCS_MISMATCH, GIT_NOT_CONNECTED, LLM_PROVIDER_UNAVAILABLE, CREDENTIAL_NOT_OWNER |
user |
| POST | /api/projects/:id/versions (changed) | − presetId; + stack? (omitted = keep the base version’s, re-pinned to the current catalog) | 201 | + STACK_INCOMPATIBLE |
owner |
| GET | /api/admin/stack | — | catalog with pack sources, versions, hash, load status | — | admin |
| GET | /api/projects/:id/releases/:releaseId/notes (build, 2026-10-29) | — | the release notes, rebuilt on read (the Bitbucket notes surface; release lists and /releases/latest omit them) |
NOT_FOUND → 404 |
member |
Notes:
- New run and job failure codes:
GIT_NOT_CONNECTED,MIGRATION_FAILED,MIGRATION_HISTORY_DIVERGED,DATABASE_PROVISIONING_FAILED,DATABASE_PROVISIONING_TIMEOUT,PLATFORM_ERROR,PLATFORM_RATE_LIMITED,GITLAB_RATE_LIMITED,BITBUCKET_RATE_LIMITED,LLM_PROVIDER_UNAVAILABLE. - New plan codes:
STACK_CHANGE_UNPLANNED(error),UNDECLARED_GENERATED_FILE(warning). - New run warning:
output_truncated. - Every new error code and warning gets frontend copy in the one error map (v1 rule 4; warnings in
run-format.ts). - Connection and Admin integration mutations require a browser session (v2 rule).
Error codes (new in v4)
| Code | Status | Meaning | User-facing copy |
|---|---|---|---|
STACK_INCOMPATIBLE |
400 | Composition breaks a compatibility rule | “These stack choices don’t work together: {reasons}.” |
STACK_LOCKED |
409 | Stack change after the plan is locked | “The stack can’t change after the implementation plan is locked. Start a new version to change it.” |
VCS_MISMATCH |
400 | VCS layer differs from the repo’s host | “The version control choice must match the project’s repository host.” |
GIT_HOST_NOT_ALLOWED |
400 | GitLab base URL fails the SSRF guard | “That address isn’t allowed for a GitLab instance.” |
GIT_AUTH_FAILED |
redirect | GitLab/Bitbucket OAuth failed or was denied | “Signing in with {host} didn’t complete. Try again.” |
GIT_NOT_CONNECTED |
409 | No usable Git connection for the project or request | “Connect your {host} account (or ask an Owner to re-bind the project) to continue.” |
CONNECTION_SCOPE_INSUFFICIENT |
400 | Token is missing scopes or has the wrong token type | “This token is missing permissions: {missing}.” |
REPO_NAME_TAKEN |
409 | Repo name/path/slug exists | “A repository with that name already exists.” |
UPSTREAM_RATE_LIMITED |
429 | Host or platform rate limit | “{provider} is rate limiting requests. Try again in a few minutes.” |
PLATFORM_GIT_NOT_CONNECTED |
409 | Platform can’t access the repo (replaces VERCEL_GIT_NOT_CONNECTED) |
“{platform} can’t see this repository. Connect it in {platform}, then verify again.” |
PLATFORM_PLAN_LIMIT |
409 | Platform plan restriction (replaces VERCEL_PLAN_LIMIT) |
“Your {platform} plan doesn’t allow this. {detail}” |
PLATFORM_PERMISSION_MISSING |
409 | GitHub App lacks Pages permission | “The AIDE GitHub App needs the Pages permission. Update it in GitHub settings.” |
PLATFORM_ERROR |
502 | Platform API failure (replaces VERCEL_ERROR) |
“{platform} returned an error. Try again later.” |
PLATFORM_RATE_LIMITED |
job code | Platform rate limit hit during a job | “{platform} is rate limiting deployments. AIDE will retry.” |
GITLAB_RATE_LIMITED, BITBUCKET_RATE_LIMITED |
job code | Host rate limit during a job | “{host} is rate limiting requests. AIDE will retry.” |
DEPLOYMENT_ACTION_UNSUPPORTED |
409 | Provider can’t roll back or promote | “{platform} doesn’t support this action.” |
PREVIEW_DATABASE_DIVERGED |
409 | Preview DB has migrations the branch no longer has | “The preview database has migrations this branch no longer has. Reset the preview database to continue.” |
INVALID_STATE |
409 | The action conflicts with work in progress (e.g. a preview reset while a rollout or reset runs, or the database isn’t available yet) (build, 2026-10-31) | “This can’t be done right now: {reason}.” |
MIGRATION_FAILED |
job code | Migration run failed | “A database migration failed: {detail}.” |
MIGRATION_HISTORY_DIVERGED |
job code | Recorded migration changed or disappeared | “The database’s migration history doesn’t match the repository.” |
DATABASE_PROVISIONING_FAILED |
job code | Provisioning failed | “Creating the database failed: {detail}.” |
DATABASE_PROVISIONING_TIMEOUT |
job code | Not available within 10 minutes | “The database took too long to become available.” |
STACK_CHANGE_UNPLANNED |
plan error | Stack change has no step for the new layer’s specialist | “The plan must include work for the {layer} change.” |
UNDECLARED_GENERATED_FILE |
plan warning | Generated file not declared | “{file} is generated from this change but no step declares it.” |
LLM_PROVIDER_UNAVAILABLE |
400 / run code | Provider disabled on this server or not usable by the user | “This AI provider isn’t available here. Choose another provider or add a connection.” |
7. Pages and UI
v2 §7 (design system, shell, routes) and the v3 Next.js mapping apply. Changes:
| Route | Page | Key content / states |
|---|---|---|
| /projects/new | New project wizard (amends) | Source → Idea/Request → Repository (GitHub App/PAT, GitLab instance, Bitbucket; picker or create; Local) → Version → Stack (five sub-steps: Frontend, Backend, Data, VCS, Deployment; the repository fixes the VCS; option cards with chips and a recommended badge; a collapsed “Not available with your choices (n)” list with reasons; caveat banners; a live summary card with the origin model and specialists) → Configuration (approval and limits, plus the AI model section: provider, connection, model with max output tokens, Advanced per-agent models) → Deployment (bind connections per target and database; create or link; readiness preview) → Review & create. Import: the same order, with the Stack pre-filled by detection, VCS locked and “detected” chips. (build, 2026-10-31: the as-built order, M25: the repository comes first and fixes the VCS. The Deployment step is optional: chosen hosts are set up right after create through the Settings → Deployment endpoints, and a refusal leaves the project usable and points there. “No stack (general)” has no hosts, so its step keeps the Vercel choice, sent as deployment on POST /api/projects.) |
| /projects/:id/settings/stack | Stack (new; replaces the preset section) | The current composition per layer, with pack versions. “Change stack” (the same sub-steps) while unlocked. A locked state with the reason. A stack-change warning for iterations. The legacy preset note for migrated projects |
| /projects/:id/settings/git | Git (amends) | Host, instance, repo, mode. For GitLab/Bitbucket, the binding (“connected by Ana”) and Rebind. Merge strategy options per host |
| /projects/:id/settings/llm | LLM (amends) | Providers limited to enabled ones. A “provider turned off on this server” state. The default model picker uses the catalog (with max output tokens) plus “Other model id…” |
| /projects/:id/settings/deployment | Deployment (replaces) | An origin model card. Target cards per component (provider logo, connection, resource link, URLs, status, readiness checklist with fix links). Database cards per environment (status, region, plan, expiry/pause warnings, Reset preview). The environment inventory table, with source badges (user / derived / required-missing) and component and environment columns |
| /projects/:id/review | Review (amends) | A preview rollout panel: one row per component (and the preview migration run), with state, URL, log tail and redeploy |
| /projects/:id/release | Release (amends) | The production rollout timeline (migration run → backend → frontend). The host release link (GitHub/GitLab), or the annotated tag with notes (Bitbucket) |
| /projects/:id/deployments | Deployments (amends) | Component and environment filters. Rollback per provider capability (hidden, with the reason, when unsupported) |
| /projects/:id/migrations | Migrations (new; in the project nav when a Supabase BaaS database exists) | Runs with environment, SHA, files, duration, status and error |
| /projects/:id/runs/:runId | Run details (amends) | The output_truncated warning copy (FR-162), the number of continuations, and the max output tokens used |
| /connections | Connections (amends) | Sections: LLM providers (enabled only) · Source control (GitHub, GitLab per instance, Bitbucket) · Deployment platforms (Vercel, Netlify, Cloudflare, Render, Railway) · Databases (Supabase). Each connection dialog shows exactly what to create on the provider (token type, scopes or permissions, expiry advice), with a link to the provider’s token page |
| /login | Auth (amends) | “Continue with GitHub / GitLab (per enabled instance) / Bitbucket” |
| /admin/integrations | Admin (amends) | GitHub (v2) · GitLab instances (add/edit/disable; the callback URL and scopes to register; version and reachability) · Bitbucket consumer · LLM (enabled providers only) · Stack catalog (layers, packs, sources, versions, hash, load status; replaces the presets list) |
UI rules (in addition to v1–v3):
- Forbidden options are never selectable, and their reason is always visible. There are no hover-only reasons, for touch and screen readers.
- The wizard keeps no compatibility logic of its own. It renders
/api/stack/options(D-V4-19). - Provider logos are inline SVG components (no external image hosts; the v3 CSP stays
img-src 'self' data:). - Every target and database card shows whose connection it uses (“connected by …”).
- The wizard never preselects a provider the user can’t use. “Add a connection” opens Connections in the same tab and returns to the wizard with its state kept (session storage).
- Devices and accessibility are unchanged: desktop-first, usable at 375 px, WCAG 2.2 AA.
8. Non-functional requirements
v1–v3 §8 apply, plus:
- Security:
- New secrets are encrypted with Data Protection (a purpose per secret kind), never returned, never logged, never put in LLM prompts, and masked in build-log tails (the v2 rule, extended to every provider). That covers GitLab/Bitbucket OAuth client secrets and user tokens, platform tokens, the Supabase PAT, database passwords and connection strings, and API keys. On Railway, the
/datavolume backup note (v3 FR-112) now also covers these. - SSRF:
- Only Admin-registered GitLab instances are configurable hosts.
- Base URLs must be
https(unlessallow_insecure_http). - The resolved addresses are checked on every connection (DNS-rebinding guard). Always refused: loopback, link-local (169.254.0.0/16, fe80::/10), metadata (
fd00:ec2::254) and0.0.0.0. - Private ranges (10/8, 172.16/12, 192.168/16, fc00::/7, and
*.railway.internal) are allowed only whenGitLab:AllowPrivateNetworks=true. It’s on for Docker Desktop Kubernetes and the dev loop (self-managed GitLab often lives there), and off on Railway, where private addresses are AIDE’s own services. - Cross-host redirects aren’t followed.
- Git runs with
protocol.allow=neverandprotocol.https.allow=always(plushttponly for insecure instances). - Tokens are bound to the instance that issued them and never sent to another host.
- OAuth flows (GitLab, Bitbucket) use a signed, single-use
state(the v2 manifest rule), and PKCE where supported (GitLab). - Netlify links: deploy keys AIDE adds for Netlify are read-only (
can_push: false), and the webhooks point only athttps://api.netlify.com/hooks/<provider>. - Supabase keys: secret keys, database URLs and passwords are never assigned to public variables (FR-145 rule).
<prefix>SUPABASE_PUBLISHABLE_KEYis the only Supabase key a frontend gets. - LLM providers: a disabled provider can’t be reached through any API path (FR-157). The Fake LLM provider is off on Railway.
- New secrets are encrypted with Data Protection (a purpose per secret kind), never returned, never logged, never put in LLM prompts, and masked in build-log tails (the v2 rule, extended to every provider). That covers GitLab/Bitbucket OAuth client secrets and user tokens, platform tokens, the Supabase PAT, database passwords and connection strings, and API keys. On Railway, the
- Reliability:
- Setup, provisioning, rollout, migration and poll work are jobs in the v1 job table (outbox, leases, retries), and a restart resumes them.
- Per-provider rate budgets (§4.19–§4.21) are enforced by a limiter per provider, keyed by connection or account. Rate-limit waits never count as failed attempts.
- Migration runs are idempotent by
(managed_database_id, version). A crashed run resumes from the last committed file. - Streaming LLM calls hold one connection per running agent for up to
Llm:RequestTimeout. The existing worker concurrency limits apply.
- Performance:
POST /api/stack/optionsp95 < 50 ms (pure, in memory).- The projects list p95 stays < 300 ms locally (< 500 ms on Railway, v3) with the
stack_packsfilter. - The deployment settings page p95 < 300 ms with two targets, two databases and 50 variables.
GET /api/llm/providersp95 < 100 ms (config plus one query).
- Cost: per-call output limits rise up to 8×. The existing run token budgets (
tokenBudget) and cost recording still apply before every call, including continuations. - Accessibility: the Stack step is keyboard navigable. Option cards are radio groups with
aria-describedbyreasons, and disabled options stay focusable so their reasons can be read. The axe checks cover the Stack step, the AI model section, Settings → Stack, Settings → Deployment and Connections. - Testing:
- Every rule in §4.17 has unit tests, including all the worked examples. An exhaustive test checks that every composition
/optionsreports as allowed passes/validate, and vice versa, over the enumerated composition space. - Detection, skill resolution (including the heading-suffix matching), ownership expansion, composed plan rules, spec sections, review checks (each rule id: a hit and a near miss), env derivation, preset migration, the LLM availability rules, the output-limit rules and the continuation table all have unit tests.
- GitLab, Bitbucket, Netlify, Cloudflare, Render, Railway, GitHub Pages and Supabase are tested with stub HTTP handlers per operation, including 401, 403-scope, 429 with reset, transient statuses (GitLab
checking, the Bitbucket 202 merge task, 555) and provider state sequences. - LLM streaming is tested with stub SSE/NDJSON streams per provider: text, tool calls, usage,
max_tokens/lengthstops, a stall (idle timeout), and an early end. - No test calls a real provider API (CLAUDE.md rule 10).
- The migration runner is integration-tested against Testcontainers PostgreSQL 17 with a
supabase_migrationsschema: ordering, history rows, idempotent re-runs, divergence detection. - E2E (Fake LLM + Local Git + Fake deployments and databases):
- create a React + ASP.NET + EF Core composition with Netlify/Render/Render Postgres hosts → preview rollout with both components → release → production rollout
- a Vue + Supabase BaaS composition with a Fake migration run
- the Stack step’s filtering: pick ASP.NET, and see Prisma disabled with its reason
- a migrated v2 preset project releasing a version
- the wizard’s AI model section choosing Fake and
fake-1
- CI runs E2E (gate per CLAUDE.md “Commit gate”).
- A backend integration test with
Llm__Providers__Fake__Enabled=falseasserts that Fake is absent from/api/llm/providersand refused on create.
- Every rule in §4.17 has unit tests, including all the worked examples. An exhaustive test checks that every composition
9. Environments
| Environment | Where | Web URL | v4 notes |
|---|---|---|---|
| Dev loop | your machine | http://localhost:3000 | All LLM providers enabled. GitLab:AllowPrivateNetworks=true. Seed on |
| Local Kubernetes | Docker Desktop (docker-desktop) |
http://localhost:8080 | As the dev loop. deploy-local.sh --e2e sets Fake LLM, deployments and databases |
| CI (GitHub Actions) | runners | — | Defaults: all providers enabled, Llm__DefaultProvider: fake, Deployments__Provider: Fake, Databases__Provider: Fake, Deployments__AllowFake: "true" |
Railway preview / production |
Railway | generated *.up.railway.app |
Llm__Providers__Ollama__Enabled=false, Llm__Providers__Fake__Enabled=false, GitLab__AllowPrivateNetworks=false. Never any Fake deployment or database provider |
- Optional secrets (
secrets.envlocally; Railway variables remotely, listed indeploy/railway/variables.md):GitLab__Instances__0__Name,__BaseUrl(empty = gitlab.com),__ClientId,__ClientSecret;Bitbucket__OAuth__ClientId,Bitbucket__OAuth__ClientSecret. These override DB-registered entries with the same host. - New config:
Llm__Providers__<P>__Enabled,Llm__Providers__<P>__Catalog__<n>__*,Llm__MaxOutputTokensCeiling,Llm__StreamIdleTimeout,Llm__RequestTimeout(new default 30 min),Databases__Provider,GitLab__AllowPrivateNetworks,Notifications__DatabaseExpirySweepInterval(default 1 hour). Add them tosecrets.env.example, the README configuration table,.railway/railway.tsanddeploy/railway/variables.md. - Callbacks use
App:PublicBaseUrl(v2):<base>/api/auth/gitlab/callback,<base>/api/auth/bitbucket/callback. GitLab treatshttp://localhostredirect URIs as “development” but accepts them (§11 check). On Railway they use the environment’s web URL. - Egress: the API reaches
gitlab.com, Admin-registered GitLab hosts,api.bitbucket.org,bitbucket.org,api.netlify.com,api.cloudflare.com,api.render.com,backboard.railway.com,api.supabase.com, and the Supabase pooler hosts (*.pooler.supabase.com:5432) for migrations. Docker Desktop and Railway allow this by default, so nothing changes. - Fake providers:
Deployments:Provider=Fake(v2) applies to every deployment provider.Databases:Provider=Fakefakes provisioning and the migration runner (it records files without a database). Both needDeployments:AllowFake=trueoutside Development/Testing (v2 rule). The E2E overlay sets both. - Seed (dev):
- The released demo project (v2) gets the composition
fe-react-vite+be-aspnet+data-efcore-postgres+vcs-githubwith Fake hosts, so “Start new version” shows the stack. - The v2 preset demo project (“nextjs-frontend in planning”) is migrated like any preset project (§4.23), showing the retired-preset note.
- A third demo project uses
fe-vue+data-supabase(BaaS) with a Fake Supabase database, inspecification.
- The released demo project (v2) gets the composition
10. Decisions and assumptions
v1 D1–D21, v2 D-V2-1 to D-V2-15 and v3 D-V3-1 to D-V3-13 stand, except where amended below.
| # | Decision / assumption | Source | Status |
|---|---|---|---|
| D-V4-1 | Stack composition from stack-packs replaces presets. One option per layer from a catalog adapted from stack-packs (5 frontends, 5 backends, 7 data packs, 3 VCS, 6 deploy targets), with compatibility rules from compose-stack. Why: any supported combination works with the same agents, and adding a stack means adding a pack folder. Presets are a subset of compositions, so no capability is lost |
Ben (draft, 2026-10-18; v4 brief) | agreed |
| D-V4-2 | Deployment is one layer with per-component hosts. The stack-packs model allows split deploys, and a host’s compatibility depends on the component it runs. So the Deployment step picks a frontend, backend and database host, plus the Compose dev loop (local-docker-desktop, as a skill only). The order stays Frontend → Backend → Data → VCS → Deployment |
Ben (draft) | agreed |
| D-V4-3 | Generic specialists plus pack skills, mirroring stack-core. Domain logic moves to the backend engineer (or the frontend engineer for Next.js full-stack and BaaS apps). stack-reviewer/stack-auditor map to AIDE’s review agent. Content is adapted, not copied (§4.16), as in D-V2-12. Section headings are matched after stripping a trailing parenthetical, because the current packs suffix some headings |
Ben (draft); headings: stack-packs 2026-10-25 | agreed |
| D-V4-4 | Presets removed with a data migration and a breaking API change. Existing projects are converted to equivalent compositions, locked plans keep running through an agent-key map, and history keeps the preset labels. The preset endpoints and the Vercel-specific connection and error names go. The web app and CLI ship together | Ben (draft) | agreed |
| D-V4-5 | Still no build or generator runner. Generated files are hand-written by the owning specialist, or produced/checked by CI, and CI results feed review on every Git host (FR-127). A sandboxed runner remains an open question (§11) | Ben (draft) | agreed |
| D-V4-6 | GitLab through an Admin-registered OAuth application per instance, with a PAT fallback. Background work runs as the binding Owner. GitLab has no App manifest or installation tokens. Refresh tokens rotate. The api scope is unavoidable. Self-managed instances are allowed only from an Admin allowlist with SSRF checks |
Ben (draft) | agreed |
| D-V4-7 | Bitbucket Cloud through an OAuth consumer, with a scoped API-token fallback. No app passwords. Annotated tags instead of Releases. The 1,000 requests/hour/user budget drives the per-connection limiter and the single mergeability-checks call | Ben (draft) | agreed |
| D-V4-8 | Pasted, scoped tokens for every deployment platform and Supabase. OAuth would need each self-hoster to register an app with every provider. This follows the v2 Vercel pattern: validated, encrypted, never returned, scope-checked where the API allows | Ben (draft) | agreed |
| D-V4-9 | A platform must build from the repo’s Git host. No local builds, uploads or CI-run CLI deploys. Railway builds only from GitHub. Cloudflare doesn’t build Bitbucket or self-managed GitLab. GitHub Pages needs GitHub. Only Netlify builds from self-managed GitLab. These combinations are incompatible (R9). stack-packs now documents CLI deploys from any VCS’s CI (compose-stack line 67). That path was considered for v4 and deferred (§11) |
Ben, v4 interview 2026-10-25 | agreed |
| D-V4-10 | One persistent preview environment per project, with its own preview database, rolled out in dependency order. Why: AIDE develops one version at a time on one branch. Render and Railway native PR previews copy the production database URL. Migrations from unmerged code must never touch production | Ben (draft) | agreed |
| D-V4-11 | Platform APIs over repo config-as-code for resources. Railway IaC is applied only by its CLI, legacy railway.json stops working on 2026-12-01, and Render Blueprints can’t be created or synced through the API. So AIDE uses the APIs. Repo config files are used only where the platform reads them during its own build. render.yaml and .railway/railway.ts may be committed as documentation (the packs are IaC-first, and this is a deliberate deviation) |
Ben (draft); updated for stack-packs 2026-10-25 | agreed |
| D-V4-12 | Supabase becomes a connection and a managed database. BaaS migrations run through AIDE’s own Npgsql runner over the session pooler, recording CLI-compatible history. The new sb_publishable_/sb_secret_ keys are used, because anon/service_role are being retired. Replaces D-V2-10 |
Ben (draft) | agreed |
| D-V4-13 | ORM migrations always run on the platform, gated per environment: in pre-deploy (Render paid, Railway), at container start (Render free), or in the production build on serverless hosts when AIDE_RUN_MIGRATIONS=true |
Ben (draft) | agreed |
| D-V4-14 | Supabase BaaS production migrations run immediately before the merge, with expand/contract enforced by migration_backward_compatible |
Ben (draft) | agreed |
| D-V4-15 | Cloudflare defaults to Workers with static assets and Workers Builds. Pages is only for existing projects. Workers Builds builds are SHA-pinned and promote the exact previewed version. It needs a user token | Ben (draft); matches deploy-cloudflare |
agreed |
| D-V4-16 | Netlify with GitLab/Bitbucket is linked by AIDE with a read-only deploy key and a push webhook to Netlify | Ben (draft) | agreed |
| D-V4-17 | GitHub Pages is a production-only frontend host using the GitHub App, with the Pages permission added to the manifest | Ben (draft) | agreed |
| D-V4-18 | Sign-in with GitLab and Bitbucket. Accounts link by provider identity (user_external_login), never silently by email |
Ben (draft) | agreed |
| D-V4-19 | Compatibility lives only in the Domain and is served to the UI. One function powers the wizard, settings, API validation and the CLI | Ben (draft) | agreed |
| D-V4-20 | A static SPA on Vercel is cross-origin only. vercel.json rewrites have fixed destinations, so they can’t point preview and production at different backend URLs. Netlify generates _redirects at build time instead. The deploy-vercel pack documents an /api rewrite for a single backend URL, which doesn’t fit AIDE’s preview environment |
build (2026-10-25) | assumed |
| D-V4-21 | LLM providers are enabled per deployment by configuration. Railway disables Ollama (unreachable there) and Fake (testing only). Local runs and CI keep all four. A disabled provider is never a silent default | Ben, v4 brief 2026-10-25 | agreed |
| D-V4-22 | The LLM provider, connection and model are chosen in the wizard’s Configuration step, not a new step. POST /api/projects gains an optional llm object, so API and CLI callers keep working |
Ben, v4 brief; placement: build | assumed (placement) |
| D-V4-23 | The model catalog is configuration. It lists the known models with their max output and context tokens. Unlisted models: Anthropic limits come from the Models API, otherwise the 16,000 fallback. Live model listing is out of scope | build (2026-10-25) | assumed |
| D-V4-24 | LLM calls stream. 128,000-token outputs need minutes, and SDK guidance requires streaming at that size. The whole-call timeout goes to 30 minutes, with a 2-minute idle timeout. Replaces the v2 note “the cap stays at 16,000 because calls don’t stream” | build (2026-10-25) | assumed |
| D-V4-25 | Up to 5 continuations (6 calls) per agent answer, then output_truncated. The corrective retry gets its own allowance. Ben may revisit this in a later version |
Ben, v4 interview 2026-10-25 | agreed |
11. Open questions
| # | Question | Who can answer | Blocks |
|---|---|---|---|
| Q1 | A sandboxed build/generator runner, and CI-run platform CLI deploys (railway up, wrangler deploy from GitLab CI or Bitbucket Pipelines with a platform token stored as a CI secret)? Either would remove the R9 gaps and the hand-written generated files. Proposed: a later version |
Ben | nothing in v4 |
| Q2 | Supabase branching (Pro plan) as the preview database for BaaS projects, instead of a second Supabase project? Proposed: later | Ben | nothing in v4 |
| Q3 | OAuth for Netlify, Cloudflare, Railway and Supabase, as “bring your own client” options? Vercel OAuth REST permissions (carried from v2)? Proposed: later | Ben | nothing in v4 |
| Q4 | Abandoning a version in development, and the v3 Q1 first-Admin guard on public deploys (carried). Proposed: still later | Ben | nothing in v4 |
| Q5 | Build-time check: the OpenAI output and context limits of gpt-6-astra and gpt-6.1-sol from OpenAI’s model docs, and whether OpenAI streaming reports usage in the final chunk (stream_options.include_usage). Until verified, use 16,000 output tokens |
build agents | FR-160 values only |
| Q6 | Refresh the catalog from a newer stack-packs snapshot each AIDE version, and add packs (Svelte, Go, Fly.io) through author-pack? Proposed: refresh per version |
Ben | nothing in v4 |
| Q7 | Build-time checks against live accounts or docs (not questions for Ben; record the results in PROGRESS.md). See the list below | build agents | the named FRs |
Q7 checks:
- Netlify:
- whether
POST /sites/{id}/builds?branch=aide/…runs when the branch isn’t inallowed_branchesor anignorerule matches (else use the build hook) - the API field that turns off Deploy Previews
- the PAT expiry options
- build-log access with a PAT
- whether GitLab/Bitbucket linking by deploy key + webhook builds reliably
- whether
- Cloudflare:
- bootstrapping the Worker before the first build
- whether trigger branch filters apply to API-started builds
- GitLab repo connections, end to end, through the API
- the currently recommended Next.js adapter (
deploy-cloudflareline 10)
- Railway:
preDeployCommandandwatchPatternsinServiceInstanceUpdateInput- whether
commitShamust be on the connected branch - whether a copied environment gets a fresh Postgres and volume
- the Postgres creation call
- the full
DeploymentStatusenum
- Render:
- the Postgres status values after
creating - the service URL field
- whether a service can be created on a private repo before the Git provider is connected
- the Postgres status values after
- Supabase:
- the
supabase_migrations.schema_migrationscolumns - whether the pooler
connection_stringcontains a placeholder - provisioning time
- whether new projects still get legacy keys
- the
- GitLab:
http://localhostOAuth redirect URIs on gitlab.com- whether fast-forward merges leave
merge_commit_shanull
- Bitbucket:
- whether
draft: falsethroughPUTmarks a PR ready - refresh-token rotation
- the consumer permission name for webhooks
- the app-password removal date (the pack says 9 June 2026; the draft said 2026-07-28; neither changes behaviour)
- whether
- GitHub Pages: whether
POST /pageswithbuild_type: workflowneeds Administration as well as Pages. - LLM streaming:
- the Anthropic
message_deltausage fields - the OpenAI streamed tool-call deltas
- Ollama
/api/chatstreaming with tools
- the Anthropic
12. Milestones
M0 Baseline:
- The repo, CI, Railway and the v3 app already exist. Do not re-scaffold.
- The v3
SPEC.mdandPROGRESS.mdare already indocs/v3/. Add the v4 row to CLAUDE.md’s Versions table and work onbuild/aide-v4. - Confirm CI is green on the baseline commit, and record the run URL in a new root
PROGRESS.md. The gate runs on GitHub Actions (CLAUDE.md “Commit gate”).
- M23 LLM providers and long outputs (independent; first, because it’s small and unblocks real use on Railway):
- provider enablement and the Railway config
GET /api/llm/providers, the model catalog and output limits- streaming for all three HTTP providers
- 5 continuations and the
output_truncatedconstant and copy - the wizard AI model section,
llmon create, and the CLI flags - FR-157–FR-162.
- M24 Stack catalog + compatibility:
- the catalog format and loader
- the adapted pack and agent content from stack-packs (the 27 layer packs in scope, i.e. all but
local-docker-desktop-k8s, plus the stack-core skills and agents) StackCompatibilitywith the §4.17 rules and tests/stack/catalog,/stack/options,/stack/validate, Admin → Stack catalog, the readiness check- FR-113, FR-116, FR-122.
- M25 Composition in projects + preset retirement:
- the composition on release versions, Settings → Stack, the wizard Stack step,
STACK_LOCKED, stack changes, layer paths - the detector and import pre-fill
- preset migration and removal, and the seeds
- FR-114, FR-115, FR-117–FR-119, FR-129. Depends on M24.
- the composition on release versions, Settings → Stack, the wizard Stack step,
- M26 Specialists across the lifecycle:
- skill resolution, ownership, composed spec sections, plan rules, Guardian protected paths
- review checklists and deterministic checks, the generated-file policy
- Fake scripted outputs per specialist
- FR-120, FR-121, FR-123–FR-128. Depends on M25.
- M27 GitLab:
- instances (Admin, SSRF guard), OAuth + PAT connections, sign-in, picker and create
- provider operations (push, MR, readiness, pipelines, merge, Releases, tags), import
- FR-130–FR-132, and the GitLab parts of FR-136–FR-141. Depends on M25.
- M28 Bitbucket:
- the consumer, OAuth + API-token connections, sign-in, picker and create with an initial commit
- provider operations (PR, mergeability checks, statuses, merge tasks, annotated tags), the rate limiter, import
- FR-133–FR-135, and the Bitbucket parts of FR-136–FR-141. Depends on M27 (shared provider-neutral work).
- M29 Platforms I: connections, targets, frontends:
- generic platform connections (Vercel migrated), the deployment target model and migration
- env inventory and derivation, the origin model, the readiness checklist
- Netlify, Cloudflare (Workers Builds + Pages adopt), GitHub Pages, Vercel on the new model
- FR-142–FR-146, and the frontend parts of FR-150 and FR-151. Depends on M25.
- M30 Platforms II: backends, databases, environments:
- Render, Railway, managed databases (Supabase, Render Postgres, Railway Postgres, External)
- the Supabase BaaS migration runner and advisors, host-mode migration wiring
- preview environment rollout, preview reset, production rollout
- FR-144, FR-147–FR-154. Depends on M29.
- M31 Integrate & harden:
- events and notifications (FR-155), the CLI (FR-156), the E2E suites (§8), axe, perf
- the README,
deploy/railway/variables.md, STACK.md and SPEC sync - the spec-verifier over v1, v2, v3 and v4 requirements
- Docker Desktop redeploy and smoke: manual checks for Ben
Revision notes
- 2026-10-25: v4 spec created from
docs/v4/DRAFT_SPEC.md(2026-10-18) and Ben’s v4 brief.- Renumbering: draft FR-94–FR-137 → FR-113–FR-156, D-V3-1–19 → D-V4-1–19, milestones M17–M24 → M24–M31.
- Updated against stack-packs as of 2026-10-25:
- section headings per layer, and heading-suffix matching
- the scaffolder, test-engineer and ci-engineer skill resolution (data packs have no
Gate) .railway/**andCODEOWNERSownership- detection of
next.config.*, the Pages workflow and legacy Railway config - the Cloudflare Next.js caveat text
- the
no_committed_secretscheck - the origin-model vocabulary mapping
- the Render/Railway IaC deviation (D-V4-11)
- CLI-deploy deferral (D-V4-9)
- Hosting: the draft assumed AIDE ran only on Docker Desktop. v4 adds the Railway settings (
GitLab:AllowPrivateNetworks, provider enablement). - New: §3.12 and §4.24 (LLM enablement, wizard choice, per-model output limits, streaming, 5 continuations).
- Confirmed with Ben: keep CLI deploys out (D-V4-9), keep the whole draft scope, and 5 continuations (D-V4-25).
- 2026-10-31 (M31): spec synced with the build decisions of M24-M31.
github_release_urlis kept, not renamed (section 5), anddeployments.rollout_idis the rollout job id (no rollouts table).- Section 6 gains the database
restoreand releasenotesroutes, the preview rolloutRolloutDtoresponse, theINVALID_STATEuses,tokenon/stack/detect, and the rule that platform setup refusals come from the job. - FR-155 and 4.21 gain the
STACK_DETECTEDemission point, thedatabase_expiry_sweepjob,migration_faileddelivery and the unavailable-database rule. - FR-152 gains the separate preview and production databases rule.
- FR-156 gains the
stack setmerge and origin-model reset. - Section 9 gains
Notifications__DatabaseExpirySweepInterval.