Build progress (v4)
Spec: SPEC.md (v4; v1–v3 in docs/) · Stack: STACK.md (unchanged from v3) · Branch: build/aide-v4 · Started: 2026-10-25
Gate: GitHub Actions CI on the pushed head commit, after a static stack-reviewer pass (CLAUDE.md “Commit gate”).
Agents never run the full gate, Docker, Testcontainers or E2E locally.
Environment
- Packs (STACK.md):
fe-nextjs,be-aspnet,data-efcore-postgres,vcs-github,deploy-railway,local-docker-desktop-k8s. All installed. - Local: Docker Desktop Kubernetes is manual checks only (Ben).
- VCS CLI auth: gh yes (BenFielder1). Railway deploys run only through
.github/workflows/deploy.yml.
Requirements
| ID | Summary | Spec § | Layer(s) | Phase | Status | Evidence |
| — | — | — | — | — | — | — |
| FR-113 | Stack catalog (versioned data, GET /api/stack/catalog) | 3.1 | backend + data | M24 | done | CI 40579534195 |
| FR-114 | Wizard Stack step: five ordered sub-steps | 3.1 | frontend + backend | M25 | done | CI 40679856465 |
| FR-115 | Deployment sub-step: per-component hosts, origin model | 3.1 | frontend + backend | M25 | done | CI 40679856465 |
| FR-116 | Server-side composition validation | 3.1 | backend | M24 | done | CI 40579534195 |
| FR-117 | Composition pinned on release version; stack change; STACK_LOCKED | 3.1 | backend + frontend + data | M25 | done | CI 40679856465 |
| FR-118 | Import stack detection | 3.1 | backend + frontend | M25 | done | CI 40679856465 |
| FR-119 | Layer paths | 3.1 | backend | M25 | done | CI 40679856465 |
| FR-120 | Generic specialists catalogue | 3.2 | backend | M26 | done | CI 40743533120 |
| FR-121 | Specialists load pack skills | 3.2 | backend | M26 | done | CI 40743533120 |
| FR-122 | Adapted pack content; strict catalog loading | 3.2 | backend | M24 | done | CI 40579534195 |
| FR-123 | Spec Generator: composed spec sections | 3.3 | backend | M26 | done | CI 40743533120 |
| FR-124 | Plan Generator: composed plan rules | 3.3 | backend | M26 | done | CI 40743533120 |
| FR-125 | Execution: Guardian protected paths from packs | 3.3 | backend | M26 | done | CI 40743533120 |
| FR-126 | Code Review: composed checklist + deterministic checks | 3.3 | backend | M26 | done | CI 40743533120 |
| FR-127 | Generated-files policy | 3.3 | backend | M26 | done | CI 40743533120 |
| FR-128 | Repository Analyst + refinement get the composition | 3.3 | backend | M26 | done | CI 40743533120 |
| FR-129 | Retire v2 presets (migration + removal) | 3.4 | backend + frontend + data | M26 | done | CI 40743533120 |
| FR-130 | GitLab instances (Admin) | 3.5 | backend + frontend + data | M27 | done | CI 40883397083 |
| FR-131 | GitLab connections + sign-in | 3.5 | backend + frontend + data | M27 | done | CI 40883397083 |
| FR-132 | GitLab repo picker + create | 3.5 | backend + frontend | M27 | done | CI 40883397083 |
| FR-133 | Bitbucket OAuth consumer (Admin) | 3.6 | backend + frontend + data | M28 | done | CI 40914462854 |
| FR-134 | Bitbucket connections + sign-in | 3.6 | backend + frontend + data | M28 | done | CI 40914462854 |
| FR-135 | Bitbucket repo picker + create | 3.6 | backend + frontend | M28 | done | CI 40932865878 |
| FR-136 | Git provider set; binding Owner’s connection | 3.7 | backend + data | M27/M28 | done | CI 40932865878 (GitLab: CI 40897233970) |
| FR-137 | MRs/PRs on GitLab and Bitbucket | 3.7 | backend | M27/M28 | done | CI 40932865878 (GitLab: CI 40897233970) |
| FR-138 | Merge readiness on GitLab and Bitbucket | 3.7 | backend | M27/M28 | done | CI 40932865878 (GitLab: CI 40897233970) |
| FR-139 | CI status on every host | 3.7 | backend | M27/M28 | done | CI 40932865878 (GitLab: CI 40897233970) |
| FR-140 | Release on GitLab and Bitbucket | 3.7 | backend | M27/M28 | done | CI 40932865878 (GitLab: CI 40897233970) |
| FR-141 | Import from GitLab and Bitbucket | 3.7 | backend + frontend | M27/M28 | done | CI 40932865878 (GitLab: CI 40897233970) |
| FR-142 | Platform connections (generic; Vercel migrated) | 3.8 | backend + frontend + data | M29 | done | CI 41029934501 |
| FR-143 | GitHub Pages via the GitHub App | 3.8 | backend + frontend | M29 | done | CI 41080587023 |
| FR-144 | Settings → Deployment: targets, readiness | 3.9 | backend + frontend + data | M29/M30/M31 | done (Vercel, GitHub Pages, Netlify, Cloudflare, Render, Railway, Fake) | CI 41230794746 |
| FR-145 | Environment inventory + sync | 3.9 | backend + frontend + data | M29 | done | CI 41194034458 |
| FR-146 | Origin model | 3.9 | backend + frontend | M29 | done (proxy mechanisms per host land with each provider) | CI 41080587023 |
| FR-147 | Preview environment + rollout | 3.9 | backend + frontend | M30 | done | CI 41209781320 |
| FR-148 | Preview database reset | 3.9 | backend + frontend | M30 | done | CI 41209781320 |
| FR-149 | Production rollout | 3.9 | backend + frontend | M30 | done | CI 41209781320 |
| FR-150 | Rollback and promote per provider | 3.9 | backend + frontend | M29/M30 | done | CI 41209781320 |
| FR-151 | Deployments page | 3.9 | frontend + backend | M29 | done (backend components appear with M30 providers) | CI 41111957062 |
| FR-152 | Database card: provision, link, external | 3.10 | backend + frontend + data | M30 | done | CI 41194034458 |
| FR-153 | Supabase BaaS (keys, auth URLs, migrations, advisors) | 3.10 | backend | M30 | done | CI 41209781320 |
| FR-154 | Host-mode database migrations on the platform | 3.10 | backend + deploy | M30 | done | CI 41209781320 |
| FR-155 | Events and notifications | 3.11 | backend + frontend | M31 | done | CI 41215952412 |
| FR-156 | CLI additions | 3.11 | cli + backend | M31 (LLM flags: M23) | done | CI 41215952412 |
| FR-157 | LLM provider enablement per deployment | 3.12 | backend + frontend + deploy | M23 | done | CI 40579534195 |
| FR-158 | GET /api/llm/providers | 3.12 | backend | M23 | done | CI 40579534195 |
| FR-159 | LLM choice at project creation | 3.12 | frontend + backend + cli | M23 | done | CI 40579534195 |
| FR-160 | Per-model max output tokens | 3.12 | backend | M23 | done | CI 40579534195 |
| FR-161 | Streaming LLM calls | 3.12 | backend | M23 | done | CI 40579534195 |
| FR-162 | Up to 5 continuations; output_truncated | 3.12 | backend + frontend | M23 | done | CI 40579534195 |
v1 (FR-1–FR-52), v2 (FR-53–FR-93) and v3 (FR-94–FR-112) requirements stay in force. Their tests must stay green on CI.
Phases
- M0 Baseline (base: 83d8fa5): CI green on 83d8fa5 (
main): https://github.com/BenFielder1/AI-Driven-Engineering-Platform/actions/runs/40129842995. The v4 prep commit f8a6dfc only touches docs (CI skips it). - M23 LLM providers and long outputs — FR-157–FR-162 (+ FR-156 LLM flags). Commits df8f119 + fixes; CI green on 7fcb9b9: https://github.com/BenFielder1/AI-Driven-Engineering-Platform/actions/runs/40579534195
- M24 Stack catalog + compatibility — FR-113, FR-116, FR-122. Commit 24eaf5b + fixes; CI green on 7fcb9b9 (same run)
- M25 Composition in projects — FR-114, FR-115, FR-117–FR-119, Admin → Stack catalog page. Commit b49f789; CI green: https://github.com/BenFielder1/AI-Driven-Engineering-Platform/actions/runs/40679856465
- M26 Specialists across the lifecycle + preset retirement — FR-120, FR-121, FR-123–FR-128, FR-129. Commits 245b3d6, 0b88937, c5629e2 + fixes; CI green on b36e643: https://github.com/BenFielder1/AI-Driven-Engineering-Platform/actions/runs/40743533120
- M27 GitLab — FR-130–FR-132, FR-136–FR-141 (GitLab). Commits e6400fb (instances, connections, sign-in, picker/create), 5ed0698 (binding, transport, draft MRs, import, rebind), be1791d (readiness, CI status, merge, Releases) + fixes 70c1a08, cb2dcfd; CI green on cb2dcfd: https://github.com/BenFielder1/AI-Driven-Engineering-Platform/actions/runs/40897233970
- M28 Bitbucket — FR-133–FR-135, FR-136–FR-141 (Bitbucket). Commits 1c52401 (consumer, connections, sign-in, picker/create), e56d00d (binding, PRs, readiness, statuses, merge, annotated tags, import) + fixes 384b8b9, 053cd67; CI green on 053cd67: https://github.com/BenFielder1/AI-Driven-Engineering-Platform/actions/runs/40932865878
- M29 Platforms I — FR-142–FR-146, FR-150/151 (frontend parts). Slice 1 (connections, migration
20261029201130_PlatformsAndTargets, Pages permission): commits fc01bb6 + fix 666d5e7; CI green: https://github.com/BenFielder1/AI-Driven-Engineering-Platform/actions/runs/41029934501. Slice 2 (targets, provider interface with Vercel + GitHub Pages + Fake, readiness, env inventory, origin model, Settings → Deployment): commit f887a84 + fix 27c9d26; CI green: https://github.com/BenFielder1/AI-Driven-Engineering-Platform/actions/runs/41080587023. Slice 3 (Netlify, Cloudflare Workers Builds + Pages adopt, rollback per capability, Deployments filters, overview production URLs): commit b344b15; CI green: https://github.com/BenFielder1/AI-Driven-Engineering-Platform/actions/runs/41111957062 - M30 Platforms II — FR-144, FR-147–FR-154. Slice 1 (migration
20261030200753_ManagedDatabases, Render + Railway providers): commit fe1572e; CI green: https://github.com/BenFielder1/AI-Driven-Engineering-Platform/actions/runs/41187473653. Slice 2 (managed databases, Supabase, derived DB vars, migration runner, Migrations page; migration20261031093505_MigrationRunAdvisorFindings): commit ad1303e; CI green: https://github.com/BenFielder1/AI-Driven-Engineering-Platform/actions/runs/41194034458. Slice 3 (preview rollout, preview DB reset, production rollout, Supabase BaaS migrations pinned to the merged head, preview/production database separation): commit c232cd1 + fix 552c37b; CI green: https://github.com/BenFielder1/AI-Driven-Engineering-Platform/actions/runs/41209781320 - M31 Integrate & harden — FR-155, FR-156, E2E, docs, spec-verifier over v1–v4. Slice 1 (events, notifications, CLI, host-neutral copy): commit 287681c; CI green: https://github.com/BenFielder1/AI-Driven-Engineering-Platform/actions/runs/41215952412. Slice 2 (E2E, axe, perf, Fake database history,
Databases:Provider): commit 00abd72 + fix c7d408b, docs 49013c2; CI green: https://github.com/BenFielder1/AI-Driven-Engineering-Platform/actions/runs/41219960087. Slice 3 (audit gaps: wizard hosts, Netlify on GitLab/Bitbucket, Pages variables, BaaS migrations after readiness, migrations-only rollouts, public-secret refusal): commit 7aa1dd0; CI green: https://github.com/BenFielder1/AI-Driven-Engineering-Platform/actions/runs/41230794746. Acceptance audit over v1–v4: v1 52 done, v2 35 done (+6 reserved or replaced), v3 19 done, v4 50 done; v1–v3 and part of v4 were verified by test name and sampling, not a line-by-line read
Decisions
- 2026-10-25 — The draft moved to
docs/v4/DRAFT_SPEC.md(Ben’s prep commit); SPEC.md’s source line points there — SPEC updated: yes - 2026-10-25 — M23:
maxOutputTokensandcontinuationsare stored in the run output JSON, not newagent_runscolumns (no migration needed) — SPEC updated: yes (§5) - 2026-10-25 — M23:
GET /api/llm/providersreturnsisDefaultso the wizard preselects the server’s default provider (CI defaultfakekeeps E2E flows working) — SPEC updated: yes (FR-158, §6) - 2026-10-25 — M23: create binds the caller’s newest valid connection when the provider needs a key and none is sent; invalid connections don’t make a provider usable — SPEC updated: yes (FR-159)
- 2026-10-25 — M23: CLI LLM flags live on
aide project init, withcreateadded as an alias — SPEC updated: no (alias matches the spec’sproject create) - 2026-10-25 — M23: a provider that answers
application/jsonto a streaming request is read as a whole body (keeps compatible servers and the JSON-stubbed tests working) — SPEC updated: no - 2026-10-25 — M23: the integration test factory pins
Llm:DefaultProvider=fake, matching CI — SPEC updated: no - 2026-10-25 — M24: a catalog that fails to load makes readiness (
stack-catalog) unhealthy and logs Critical instead of stopping startup; the stack endpoints then answer 500. The readiness probe keeps the instance out of service, so no coded error is added — SPEC updated: no - 2026-10-25 — M24:
/api/stack/optionsreturns all 9 choices (frontend, backend, data, vcs, frontendHost, backendHost, databaseHost, local, originModel) withselected/default; violationlayeris the choice id (e.g.backendHost) — SPEC updated: no - 2026-10-25 — M24: Supabase in the Data layer is always BaaS; host mode is the database host
supabasewith another ORM (data.mode = "host"→ VALIDATION) — SPEC updated: no - 2026-10-25 — M24: R10 gaps filled: a static SPA on Railway with the backend elsewhere → proxy or cross-origin; Next.js ui mode on GitHub Pages → cross-origin only. R12: Compose forbidden with no backend and no data — SPEC updated: no
- 2026-10-25 — M24: the Admin → Stack catalog page moves to M25 (with the other Stack UI); M24 ships
GET /api/admin/stackonly — SPEC updated: no - 2026-10-28 — M25:
POST /api/stack/detecttakes an optionaltokenforgithub_pat;STACK_DETECTEDisn’t emitted (detect runs before the project exists) — SPEC updated: no - 2026-10-28 — M25: layer paths:
.only when the other layer is None; one layer path inside the other is refused — SPEC updated: no - 2026-10-28 — M25: until M26, a version has a preset OR a composition: PUT stack clears a legacy preset, PATCH preset clears the stack; PUT can’t go back to “No stack” — SPEC updated: no
- 2026-10-28 — M25: stack-change baseline is the base version’s composition; for a first version on a repo with code, the
fromof its first STACK_CHANGED — SPEC updated: no - 2026-10-28 — M25: the wizard keeps the v2 preset cards under “Legacy presets (retiring in v4)” until M26 removes presets (keeps the v2 preset E2E flow working) — SPEC updated: no
- 2026-10-28 — M25: the wizard keeps its v3 step order (Repository and Version before Stack) instead of SPEC §7’s Source → Idea → Stack → Repository; the repo then fixes VCS before the Stack step. Revisit in M27/M28 when the Repository step lists repos of the chosen VCS — SPEC updated: no
- 2026-10-28 — M25: the wizard maps GitHub repo modes to
vcs-githubclient-side (R5) because no project exists yet; the server re-applies R5 on create (VCS_MISMATCH). Move to a server hint with GitLab/Bitbucket (M27/M28) — SPEC updated: no - 2026-10-28 — M26: the RetirePresets migration writes 4 composition documents (preset × Vercel or not) with catalog 1.1.0 and a null catalog hash, re-pinned on the version’s next stack write;
StackComposition.CatalogHashis nullable — SPEC updated: no - 2026-10-28 — M26: a run’s
legacyAgentKeyis derived fromexecution_steps.legacy_agent_key(no new agent_runs column); a v2 key whose mapped specialist isn’t in the stack falls back to the other mapping, thengeneralist, with run warninglegacy_agent_mapped— SPEC updated: no - 2026-10-28 — M26: a stack catalog that failed to load makes agent jobs fail transiently (retry, then dead-letter) instead of VALIDATION — SPEC updated: no
- 2026-10-28 — M26: FR-89’s MISSING_VERCEL_CONFIG plan warning is kept, driven by the
deploy-vercelhost; preset types renamed (VersionStackLoader,VersionStackContext); prompts bumped (spec/plan v5, review v7) — SPEC updated: no - 2026-10-28 — M26: seeds: released demo fe-react-vite + be-aspnet + data-efcore-postgres; preset demo kept as fe-nextjs at “.” with its legacy preset id; new “Demo: Vue Notes” (fe-vue + data-supabase BaaS) in specification, no fake database — SPEC updated: no
- 2026-10-28 — M27: gitlab.com is a real instance row created on first use (can be disabled, not deleted); configured instances (GitLab__Instances__*) get rows whose secrets stay in config; deleting an instance removes its connections and external logins first — SPEC updated: no
- 2026-10-28 — M27: sign-in email linking goes through
/login?link=gitlaband/api/auth/gitlab/link(held 10 minutes); OAuth state and pending links are in memory (one API replica, like the v2 manifest state) — SPEC updated: yes (§6 rows) - 2026-10-28 — M27: unique indexes on git_host_instances and user_external_logins use NULLS NOT DISTINCT (PostgreSQL 15+) — SPEC updated: no
- 2026-10-28 — M27: merge readiness reason codes keep the v2 wire names (
merge_conflict,no_permission) — SPEC updated: yes (FR-138) - 2026-10-29 — M27: a GitLab connection that becomes
insufficient_scopeis treated as invalidated for FR-136: the binding is cleared and Owners getgit_binding_cleared— SPEC updated: no - 2026-10-29 — M27: the §4.19 common
IGitProvidermethods (ListReposAsync…PublishReleaseAsync) are implemented for GitLab (and Bitbucket in M28); GitHub and Local keep their v2 services and inherit NotSupported defaults, since nothing calls them through the interface — SPEC updated: no - 2026-10-29 — M27: PATCH settings/git can’t move a project onto gitlab/bitbucket (400 VALIDATION on
provider); a GitLab binding is set on create/import and changed only by rebind (connectionId) — SPEC updated: no - 2026-10-29 — M27: git CLI calls to a GitLab instance pin the SSRF-vetted addresses (
http.curloptResolve) and don’t follow redirects (http.followRedirects=false), matching the API path (§8) — SPEC updated: no - 2026-10-29 — M27: AIDE’s own draft MR doesn’t count against readiness (it is marked ready before the merge), mirroring v2’s handling of GitHub drafts; a draft only blocks under the
manualstrategy after AIDE marked it ready and a human re-drafted it — SPEC updated: no - 2026-10-29 — M27: GitLab squash follows the GitLab project’s
squash_optionand the merge method itsmerge_method; AIDE’smergeStrategystaysaide_merges/manual.fast_forward(§6) is Bitbucket’smerge_strategyand is added with M28 — SPEC updated: no - 2026-10-29 — M27: no stored reviewed-head SHA: a head that moved before the merge call is re-fetched and retried (v2), and a GitLab 409 fails the release for a new review — SPEC updated: no
- 2026-10-29 — M27: the GitLab Release URL is stored in the existing
githubReleaseUrlfield (no schema change); CI status polls back off on 429 only, the <10 %RateLimit-Remainingback-off applies to merge polls — SPEC updated: no - 2026-10-29 — M28: Bitbucket Cloud is a
git_host_instancesrow (kindbitbucket, base URL NULL) created on first use, like gitlab.com; no migration — SPEC updated: no - 2026-10-29 — M28: one in-memory 900/h token bucket per Bitbucket connection covers request paths and background jobs; a 429 empties it until Retry-After (else 5 min); pre-connection calls (code exchange, token validation) aren’t counted — SPEC updated: no
- 2026-10-29 — M28: API-token scopes are read from Bitbucket’s granted-scopes signals when present, else probed with harmless calls (GETs; write scopes via empty POSTs that 400 without creating anything); a token is saved only when all 9 scopes are verified (unverifiable ones are refused and listed separately). Needs a real-Bitbucket check — SPEC updated: yes (FR-134, §6)
- 2026-10-29 — M28: email-link flow mirrors M27 (
/login?link=bitbucket,/api/auth/bitbucket/link); Admin DELETE of the consumer invalidates OAuth connections at their next refresh (API-token connections keep working);Bitbucket__OAuth__ClientId/ClientSecretmust be set together or not at all (start-up check) — SPEC updated: no - 2026-10-29 — M28:
mergeStrategygainsfast_forwardandsquashfor Bitbucket only (AIDE merges with thatmerge_strategy;aide_mergessendsmerge_commit); VALIDATION on GitHub/GitLab — SPEC updated: yes (§6) - 2026-10-29 — M28: Bitbucket readiness uses only the mergeability-checks call; a status is “required” when the PR’s mergeability checks require passing builds (
GetCommitStatusesAsynctakes an optional PR number). AIDE’s own draft is ignored until AIDE marks the PR ready (as M27) — SPEC updated: no - 2026-10-29 — M28: merge 555 → re-read the PR and retry up to 3 times in the attempt; 202 → poll
merge/task-statusup to 10 × 2 s, then retry the job; shortmerge_commit.hashis resolved to the full SHA before tagging — SPEC updated: no - 2026-10-29 — M28: Bitbucket release URL field holds the tag page; release notes are served by the new
GET /api/projects/{id}/releases/{releaseId}/notes(rebuilt on read; lists and/releases/lateststay cheap); a create retry that hits REPO_NAME_TAKEN on a repo withoutmainpushes the README instead (slice 1 follow-up) — SPEC updated: no - 2026-10-29 — M28:
releases.merge_submitted_at(migration20261029174717_ReleaseMergeSubmittedAt, nullable,ALTER TABLE releases ADD merge_submitted_at timestamp with time zone;) is set just before AIDE asks Bitbucket to merge; rollback treats it as a recorded merge, and a retry that finds the PR merged with it set is AIDE’s own merge — SPEC updated: no - 2026-10-29 — M29: migration
20261029201130_PlatformsAndTargetscopiesvercel_connectionsintoplatform_connections(same id, same ciphertext;SecretPurposes.PlatformTokenmust equalVercelToken), turnsproject_deployment_settingsintofrontendtargets (readywith binding + project,attention/connection_missingwith a project only, elsenot_configured), backfills env vars ({frontend}, public =NEXT_PUBLIC_*,user) and deployments (targetrenamedenvironment,componentfrontend,kindpromote/deploy,target_id), and drops both old tables. Down refuses when non-Vercel connections, backend targets, rollback deployments, derived or non-frontend env vars exist. Not rolling-update safe (§5) — SPEC updated: no - 2026-10-29 — M29: the production deployment unique index is
(release_id, component) WHERE environment = 'production' AND trigger = 'auto'(redeploy/promote rows share the release id); targetresource_idandnameare nullable until linked — SPEC updated: no - 2026-10-29 — M29: a 403 for missing permissions saves any platform connection as
insufficient_scope(Vercel’s plain 403 wasCREDENTIAL_INVALIDin v2); a 403 without names records a placeholder (“API Key Secrets: Read”, “Organization Projects: Read” or “unknown”). Cloudflare: only 401/code 1000 is invalid, 9109/10000 are missing permissions; a refused token-policy read saves the token as valid with no policies. A picker call refused for an optional permission fails that request withCONNECTION_SCOPE_INSUFFICIENTand leaves the status alone; only a rejected token marksinvalidand notifies (CONNECTION_INVALID, once per transition) — SPEC updated: no - 2026-10-29 — M29: a Railway workspace token needs
accountId(the workspace id); Supabase’s “API Key Secrets: Read” is probed withGET /projects/{ref}/api-keys?reveal=trueon the first project, so an org with no projects can’t be checked. Fake mode: tokensfake_invalid*are rejected,fake_scope*giveinsufficient_scope— SPEC updated: no - 2026-10-29 — M29: the schema
DeploymentTarget(preview/production) is renamedDeploymentEnvironmentto free the name;pagesPermission(granted/missing/unknown) is added to the Admin GitHub App and installation DTOs. Contract break per D-V4-4 (Vercel connection endpoints removed); oasdiff isn’t installed, so the diff was checked by hand — SPEC updated: no - 2026-10-30 — M29: target create/link refusals (
PLATFORM_GIT_NOT_CONNECTED,PLATFORM_PLAN_LIMIT,CREDENTIAL_INVALID,NOT_FOUND,VALIDATION) come from thedeployment_setupjob onto the target (status: error,statusReason,setup.errorCode/fixUrl), not as a synchronous 409 from PUT; onlyPLATFORM_PERMISSION_MISSING(GitHub Pages without the Pages permission) is synchronous — SPEC updated: no - 2026-10-30 — M29: PUT target with neither
createnorlinkrebinds a target that already has a resource on the same provider (setup job,mode: rebind);connectionIdis required except forgithub_pages. Only the target’s newest setup job may apply its result (no job-id column; compared with the newest job for the target). PUT re-checks the Owner role under the project lock (FR-79) — SPEC updated: no - 2026-10-30 — M29: a user env value whose key becomes derived is replaced by the derived value with a
SETTINGS_UPDATEDevent (replacedUserValue) and a warning log; derived rows that disappear are deleted on bound targets at the next sync (best effort). Derived rows without a value count as missing in therequired_envreadiness item. Env rows exposeenvironmentswhile the PUT body keepstargets— SPEC updated: no - 2026-10-30 — M29: the provider interface has no
ListAccountsAsync(accounts come from the platform connection endpoints); providers not implemented yet return 502PLATFORM_ERROR“not supported yet” outside Fake mode; the Fake URL is nowhttps://fake-<sha8>-<component>.<provider>.test(§4.20); a manual preview on a host without previews (GitHub Pages) is 409DEPLOYMENT_ACTION_UNSUPPORTED. The wizard’sdeploymentonPOST /api/projectsstays the v2 Vercel flow (with the renamed codes) until the wizard Deployment step is reworked — SPEC updated: no - 2026-10-30 — M29:
VERCEL_*codes left the error map (the frontend test requires it to equalErrorCodes.cs); a history-only copy table keeps storedVERCEL_ERRORdeployment rows readable.PLATFORM_RATE_LIMITEDcopy waits until the backend adds the code — SPEC updated: no - 2026-10-30 — M29: Netlify: the GitHub
installation_idis reused from another site in the account linked to the same GitHub owner (Netlify has no lookup); without one, setup fails withPLATFORM_GIT_NOT_CONNECTEDand the app link. Create on GitLab/Bitbucket returnsPLATFORM_ERROR(deploy key + webhook not automated; linking works). Branch control isallowed_branches+skip_prs, plus thenetlify.toml[build] ignorecheck. Preview ids aredeploy|sha|attempt|branch; a build of the wrong commit is re-triggered once. A preview skipped or cancelled by ignore rules is rebuilt once through a build hook (aide previews <branch>,trigger_title=aide:hook:<sha>), and skipped again → error. The deploy limiter (3/min, 100/day per account) is in memory per process; a 429 sets a separate blocked-until. Rollback (restore) readslockedand unlocks the deploy straight away (this also removes a lock set by hand on that deploy) — SPEC updated: no - 2026-10-30 — M29: Cloudflare: a resource id
pages:<project>is Pages mode (adopt only; create →VALIDATION), anything else a Worker.preview_resource_idholds<preview trigger>|<production trigger>. First-Worker bootstrap (§11): AIDE uploads a placeholder module answering 503, then uses the script tag asexternal_script_id; the build token reuses the user’s API token. Workers rollback uses the build’s version id, else the version whose annotations hold the commit’s first 7 characters. Sensitive production values are also written as runtime secrets; a preview-only sensitive variable on a Worker →VALIDATION. Redeploys and manual rows adopt only builds created after the row (no clock-skew allowance); a Pages production redeploy retries the earlier deployment. Production URLs: Workers keep the workers.dev URL, Pages usehttps://<project>.pages.dev, and a known production URL is never overwritten with null — SPEC updated: no - 2026-10-30 — M29:
POST /deployments/{id}/rollbackis the action for every provider;/promoteis an alias (Vercel and Fake rows keep kind/triggerpromote, othersrollback). Rows serving production (same provider deployment id as the current row) are blocked withrollbackBlockedReason: current. Rollback checks validity (INVALID_DEPLOYMENT) before configuration; a platform 429 → 429UPSTREAM_RATE_LIMITEDwith Retry-After.olderThanLatestMigrationis null until M30.PLATFORM_RATE_LIMITEDis a job-code prefix on deferred deployment/setup jobs (no attempt counted). Deployments filters live in component state, not the URL — SPEC updated: no - 2026-10-31 — M30: migration
20261030200753_ManagedDatabasesis additive (rolling-update safe):managed_databases(unique (project_id, environment), xmin, FK project cascade / connection set null),database_migration_runs(applied jsonb DEFAULT '[]', index (project_id, started_at)),releases.migration_run_id(FK set null) andreleases.release_notes.github_release_urlis NOT renamed tohost_release_url(§5 says renamed; the M27 decision keeps the field and the DTO name) — SPEC updated: no - 2026-10-31 — M30: no rollouts table;
deployments.rollout_idis the rollout job id — SPEC updated: no - 2026-10-31 — M30: Render: deployment ids
service|deploy, env idsproduction:KEY/preview:KEY; env sync pages through the service’s vars (cursor, 100 per page) and PUTs the full merged set; the static-site/api/*rewrite toAPI_ORIGINis merged into the existing routes ahead of the SPA fallback. Docker paths:rootDir: backend,dockerfilePath: ./Dockerfile,dockerContext: .. Services track the repo’s default branch. Logs useresource=<id>, 30/min per API key hash (in memory); a rate-limited log read defers the poll so the tail isn’t lost. A retry adopts only API-triggered deploys created after the AIDE row — SPEC updated: no - 2026-10-31 — M30: Railway:
resource_idisproject:service:prodEnvId; the project is named after the repo so frontend and backend share it and its private network; created and linked backends getPORT=8080(linked only when unset), in production andaide-preview; a same-origin SPA getsAPI_ORIGIN=http://${{<svc>.RAILWAY_PRIVATE_DOMAIN}}:${{<svc>.PORT}}. GitHub repos only (GitLab/Bitbucket →PLATFORM_ERROR).ServiceInstanceUpdateInputtakespreDeployCommand: [String!](§11, from the schema, unverified). Polling and the production adoption recheck wait at least 20 s; rollback withoutcanRollback→INVALID_DEPLOYMENT— SPEC updated: no - 2026-10-31 — M30: managed databases go through a
database_provisioningjob (refusals such asPLATFORM_PLAN_LIMITland on the card, as with targets); create adopts a same-named Supabase project/Render Postgres so a retried job doesn’t create two (an adopted Supabase project gets its password reset); the free-plan limit is detected from the 4xx message. DELETE keeps the row asremovedwith resource, connection and secrets cleared (migration runs survive).POST …/databases/{environment}/restore(Supabase pause) is added (not in §6). Cardprovideris the database provider (render,railway); the host option id moved tohostOption. DELETE and restore take the project lock (concurrency → 409CONFLICT) — SPEC updated: no - 2026-10-31 — M30: Railway Postgres (§11):
serviceCreatewith imageghcr.io/railwayapp-templates/postgres-ssl:17, template variables and an AIDE password,volumeCreateat/var/lib/postgresql/data, thenserviceInstanceDeployV2; it needs the Railway target first (unverified). Render gets the internal connection string only when a Render service hosts the app — SPEC updated: no - 2026-10-31 — M30: derived DB variables: preview falls back to the production database for connection variables;
AIDE_RUN_MIGRATIONSis always derived on a serverless host in host mode,trueonly where the environment has its own database; server Supabase keys (SUPABASE_URL,SUPABASE_SECRET_KEY) go to the backend, or to the frontend only in Next.js full-stack (fixes the M26 Known-issue). Supabase Auth URLs use the frontend pack’s dev port (3000/4200/5173) for localhost; a preview project’ssite_urlis the frontend preview URL; re-applied when the frontend setup job finishes — SPEC updated: no - 2026-10-31 — M30: the Supabase runner records each whole file as one statement and compares SQL with whitespace and
;removed (CLI-split files still match); recorded rows without statements are only checked for presence; an advisory lock per run; the CLI’s columns are created if missing. A pending file the working copy can’t read fails the run (MIGRATION_FAILED) before anything is applied. Stored errors are<file> failed (SQLSTATE x): <first line>(no detail or row data). Advisor findings get their own column (database_migration_runs.advisor_findings) — SPEC updated: no - 2026-10-31 — M30: the Migrations nav item shows when the project’s packs include
data-supabaseand a Supabase database card is configured; Render/Railway database link takes a typed id (no picker type in the contract) — SPEC updated: no - 2026-10-31 — M30: rollouts:
preview_rollout/production_rolloutjobs; rollout state lives on the job, its deployment rows (rollout_id) and the completion event (PREVIEW_ROLLOUT_COMPLETED), no table. Planned per component; GitHub Pages is skipped in preview with a note; only previews of the same component stop each other.GET /deployments/previewreturns the current rollout;POST /deployments/previewnow answers 202 with aRolloutDto(breaking; the web app is the only caller). Pre-deploy failure is recognised from the error message (pre_deploy_failed, “pre-deploy”, the Fake marker) and recorded asMIGRATION_FAILED. A redeployed step doesn’t restart the rest of a failed rollout. Adopt window 60 s; Review waits up to 30 min — SPEC updated: no - 2026-10-31 — M30: findings: a failed build or pre-deploy is critical
architecturetitled “{Host} preview build failed” (v2 “Vercel preview build failed” kept), a failed preview migration criticaldata_model; Supabase advisor ERROR → majorsecurity, WARN → minor, INFO → none — SPEC updated: no - 2026-10-31 — M30: preview divergence compares migration file paths under any
migrations/(oralembic/versions) folder at the last-migrated commit (last successful preview run, or last ready backend preview when the preview has its own database) against the branch head. Reset per provider: Supabase/External/Fake reset the schema (Supabase then re-applies the base commit’s migrations), Render resets over its external URL, Railway ismanual(recorded only). Reset vs rollout: both enqueue under the project lock, a rollout defers while a reset is pending (INVALID_STATE409 on the endpoints), runner and reset share one advisory lock. New codeINVALID_STATE(409; SPEC §6 table row added) — SPEC updated: yes (error table) - 2026-10-31 — M30: the preview and production databases must be separate (
DatabaseSeparationRules: same provider + id, Supabase ref (also extracted fromdb.<ref>.supabase.cohosts andpostgres.<ref>pooler users), or host + port + database; pooler endpoints match on host + user). Checked on PUT (VALIDATION onlink.resourceId/external.connectionString), in the provisioning job, on reset (endpoint and job) and before preview migrations. A preview with no database of its own is never reset or migrated (the migration step is skipped) — SPEC updated: no - 2026-10-31 — M30: Supabase BaaS production migrations run in the release job before the merge for every host and strategy, on exactly the head the provider merges (remote PR head), and the merge is pinned to it (
ReleaseRequest.ExpectedHeadSha: GitHub/GitLabsha, Bitbucket and Local pre-checks; a null Bitbucket source commit counts as moved). A succeeded run only counts for the current head. A conflict fails at once withMERGE_CONFLICT(FR-43) without migrating or merging; a closed PR keeps the v2 path; unknown readiness migrates and pins.releases.migration_run_idkeeps the pre-merge run; the production rollout always re-runs the runner on the merge commit (idempotent). A Bitbucket merge whose source differs from the migrated head writesMERGED_HEAD_MISMATCH. A failed production migration writesDEPLOYMENT_FAILED(component: migrations) so Owners are notified. BaaS is detected by a Supabase database when a project has no composition — SPEC updated: no - 2026-10-31 — M30:
olderThanLatestMigration: BaaS uses the latest production run that applied files; host mode warns on any backend deploy older than the latest ready production backend deploy (conservative) — SPEC updated: no - 2026-10-31 — M31:
STACK_DETECTEDis written by the repository analysis job with the codebase summary (replaces the M25 decision that it isn’t emitted);POST /stack/detectstill writes none — SPEC updated: no - 2026-10-31 — M31: database expiry is a
database_expiry_sweepjob queued hourly (Notifications:DatabaseExpirySweepInterval), using a read-onlyIDatabaseProvider.ReadStateAsync. The stored status now becomesexpiring/paused. No migration; a duplicate queued sweep is harmless because the status update is conditional — SPEC updated: no - 2026-10-31 — M31: a BaaS database that is
paused,errororprovisioningwith a resource or connection fails the migration step (failed run,migration_failed) and so blocks the release or fails the rollout, rather than counting as no database. A restore in progress also blocks — SPEC updated: no - 2026-10-31 — M31: a failed migration gives Owners one in-app
migration_failedrather thandeployment_failed; channels subscribed todeployment_failedalso receivemigration_failed(no backfill), so existing channels keep hearing them and can’t opt out separately. A pre-merge failure still sends bothrelease_failedandmigration_failed— SPEC updated: no - 2026-10-31 — M31:
deployment_target_attentionis sent for lost Git access and invalid tokens;setup_failedandreadiness_failedwrite the event only, except a setup refused withPLATFORM_GIT_NOT_CONNECTED, which counts as lost Git access — SPEC updated: no - 2026-10-31 — M31: CLI:
stack setandversion startmerge flags over the current composition (PUT replaces the whole document). The origin model resets to the default when the frontend, backend or their hosts change, with a stderr warning; there is no flag to set it.version startaccepts--frontendand--stack-frontendforms.status --jsonaddsstackandlatestMigrationRunand now makes 3 requests.VCS_MISMATCHstays exit 1 — SPEC updated: no - 2026-10-31 — M31:
--data <opt>:hostis dropped from FR-156: host mode is the database host choice (M24 decision), so only:baasis valid — SPEC updated: yes (FR-156) - 2026-10-31 — M31: decisions earlier marked
SPEC updated: nowere synced into SPEC.md by the docs pass where they change stated behaviour (M24 options, M25 detect, M27 settings/git, M28 notes route andmerge_submitted_at, M29 target refusals, M30 schema, databases, rollouts, reset, separation, and the M31 entries above) — SPEC updated: yes - 2026-10-31 — M31: BaaS production migrations run only after merge readiness passes, except
no_permission,merge_method_not_allowedand unreadable readiness. Not ready underaide_mergesgoes toawaiting_mergewithout migrating; the merge poll migrates once ready and re-migrates a moved head; a human merge of an unmigrated head writesMERGED_HEAD_MISMATCH. The merge poll never merges (v2 FR-65) — SPEC updated: yes (§4.21) - 2026-10-31 — M31: a rollout is queued when a target is configured or the environment has a migration step, so a BaaS project with no targets gets a migrations-only rollout.
ProjectDto.previewMigrationsEnabled(additive) drives the preview panel and the POST gate — SPEC updated: yes (§4.21) - 2026-10-31 — M31: Netlify create on GitLab/Bitbucket adds the webhook, then a read-only key, then the site; link trusts the existing site. Bitbucket API tokens have no webhook scope, because adding it to the required set would refuse tokens at save; such tokens fail setup with
PLATFORM_PERMISSION_MISSING— SPEC updated: yes (§4.19, §4.20) - 2026-10-31 — M31: GitHub Pages syncs public variables as repo Actions variables (
actions_variables: write), before the merge and before adopting the run; a run that predates the newest sync is cancelled and re-run once (actions: write). Saved public values that look secret (database URIs,Password=,service_roleJWTs) are never sent on any platform — SPEC updated: yes (FR-143, FR-145) - 2026-10-31 — M31: the wizard sets hosts up after create through the existing PUT endpoints (“Set up later” is the default; partial failures keep the project);
deploymentonPOST /api/projectsstays for the No-stack Vercel choice. The wizard keeps Repository and Version before Stack (replaces the M25 “revisit”) — SPEC updated: yes (§7) - 2026-11-01 — M31: contract diff against
main(oasdiffisn’t installed, so a script compared operations and schemas): 63 operations added; 9 removed: the 5 Vercel connection endpoints,GET /presets,GET /presets/{id},PATCH /projects/{id}/presetandPATCH /projects/{id}/settings/deployment; 20 Vercel, preset and old deployment schemas removed. These are the v4 retirements (D-V4-4, FR-129, M29 targets); web app and CLI ship together. Field-level changes weren’t compared by the script — SPEC updated: yes (§6) - 2026-10-28 — FR-129 (preset retirement) moves from M25 to the end of M26: migrated in-flight plans must run as the new specialists, which M26 builds — SPEC updated: no
- 2026-10-25 — M24:
legacy/is created in M26 together with a catalog version bump, so the pinned hash only changes with a version — SPEC updated: no - 2026-10-25 — M24: pack skills are 70–115% of the source length (not 40–60%): hand-writing generated files and CI checks needs the room — SPEC updated: no
- 2026-10-25 — M24: review category for migrations is
data_model(v1 enum), notdata— SPEC updated: yes
M26 inputs (from M24 catalog content)
The catalog schema can’t express these, so the M26 resolver must implement them:
- Expand
{fe},{be},{app}(the backend, or the Next.js full-stack app) in ownership globs,gate,ciCheck, plan-rule globs and agent prompts. Define what a missing layer expands to (e.g. no{fe}→ drop the rule). requiredEnvpublic keys (SUPABASE_URL,SUPABASE_PUBLISHABLE_KEY, API base URL) get the frontend pack’spublicEnvPrefix(Angular: unprefixed, written intoassets/config.json).appliesTo: backendmeans{app}in Next.js full-stack.- Mode-dependent sections: Prisma/Drizzle
Setupfor frontend-engineer only in Next.js full-stack;data-supabasein host mode → data-engineer and deploy-engineer load onlyProduction notes; Supabase-only checks (rls_on_every_table,grants_for_exposed_tables) only in BaaS mode;fe-nextjsfull-stack-only ownership and generated client only in ui mode. container_non_root/health_endpointsinputs (health paths per backend pack, Docker deploy) aren’t manifest fields: derive from the backend pack id or add fields.AIDE_RUN_MIGRATIONS(“serverless host in host mode”) is computed by the resolver, not declared by packs (no OR inwhen).- Composition summary template (
core/composition-summary.md):{{UPPER_SNAKE}}placeholders, rendered separately from the base prompt template; strip leading provenance comments from agent prompts and the summary; mirrorSpecialistPrompt.ComposeSystem. - Plan rules: scaffolder exemption from the deploy/ci exclusivity entries (v2 decision);
planRules.exclusivehas nosoftflag. - SPEC follow-ups: §4.22 should name
API_ORIGINfor the Railway same-origin SPA proxy; §4.20 should say who setsVITE_API_BASE_URLfor GitHub Pages (repo variable via the GitHub App).
M26 preset migration SQL
Migration 20261028141435_RetirePresets (FR-129, §4.23): data only, no DDL; row updates in one transaction (no table rewrites, no long locks). Reviewed SQL from dotnet ef migrations script:
- Release versions pinned to
nextjs-supabase/nextjs-frontendwithout a composition get one of four documents (preset x Vercel deployment settings or not). VCS isvcs-githubfor every Git provider (github_app, github_pat, local or none), so it isn’t branched on. Each document is exactly whatStackCompositionJson.Serializewrites for the equivalent choices validated against the shipped catalog (PresetMigrationCompositionTests), pinned to catalog1.1.0withcatalogHashNULL (column and document). The app re-pins the hash on the version’s next stack write.preset_id,preset_versionandpreset_hashare kept as history. projects.stack_packsis set (StackPinning.PackIds) for projects whose current version (current_release_version_id, else the latest) was converted.- Steps of open (in development, awaiting merge) converted versions: the v2 key moves to
legacy_agent_keyandagent_keygets the §4.23 specialist.test-engineeris the same key in v4 and is left alone.devops-engineerbecomesci-engineerwhen every declared file (normalised likePathRules.Normalize) is under.github/, elsedeploy-engineer. The mixed-files warning is a run-time concern. Released versions’ steps keep their v2 keys for history. - Converted rows are recognised by preset id + catalog
1.1.0+ NULL hash, because the app never pins a NULL hash. Down undoes only those rows: it restores the v2 agent keys, empties the packs and clears the compositions.
Up:
START TRANSACTION;
UPDATE release_versions AS v
SET stack_composition = (CASE
WHEN v.preset_id = 'nextjs-supabase' AND x.vercel THEN '{"catalogVersion":"1.1.0","catalogHash":null,"frontend":{"pack":"fe-nextjs","path":"."},"backend":null,"data":{"pack":"data-supabase","mode":"baas"},"vcs":{"pack":"vcs-github","instanceId":null,"selfManaged":false},"deploy":{"frontendHost":"deploy-vercel","backendHost":null,"databaseHost":"supabase","local":"none"},"originModel":"same-origin","derived":{"nextjsMode":"full-stack","agents":["scaffolder","data-engineer","frontend-engineer","test-engineer","ci-engineer","deploy-engineer","docs-writer"],"originModelOptions":["same-origin"],"specSections":["Routes \u0026 Rendering Strategy","Data Model \u0026 RLS Policies","Auth Flows","Environments \u0026 Configuration"],"envNames":["NEXT_PUBLIC_SUPABASE_URL","NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY","SUPABASE_URL","SUPABASE_PUBLISHABLE_KEY","SUPABASE_SECRET_KEY"],"gates":["cd {fe} \u0026\u0026 npm ci \u0026\u0026 npm run typecheck \u0026\u0026 npm run lint \u0026\u0026 npm test \u0026\u0026 npm run build"]},"caveats":[]}'
WHEN v.preset_id = 'nextjs-supabase' THEN '{"catalogVersion":"1.1.0","catalogHash":null,"frontend":{"pack":"fe-nextjs","path":"."},"backend":null,"data":{"pack":"data-supabase","mode":"baas"},"vcs":{"pack":"vcs-github","instanceId":null,"selfManaged":false},"deploy":{"frontendHost":null,"backendHost":null,"databaseHost":"supabase","local":"none"},"originModel":"same-origin","derived":{"nextjsMode":"full-stack","agents":["scaffolder","data-engineer","frontend-engineer","test-engineer","ci-engineer","deploy-engineer","docs-writer"],"originModelOptions":["same-origin"],"specSections":["Routes \u0026 Rendering Strategy","Data Model \u0026 RLS Policies","Auth Flows"],"envNames":["NEXT_PUBLIC_SUPABASE_URL","NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY","SUPABASE_URL","SUPABASE_PUBLISHABLE_KEY","SUPABASE_SECRET_KEY"],"gates":["cd {fe} \u0026\u0026 npm ci \u0026\u0026 npm run typecheck \u0026\u0026 npm run lint \u0026\u0026 npm test \u0026\u0026 npm run build"]},"caveats":[{"layer":"databaseHost","option":"supabase","rule":"R11","text":"Database only used by the local dev loop"}]}'
WHEN x.vercel THEN '{"catalogVersion":"1.1.0","catalogHash":null,"frontend":{"pack":"fe-nextjs","path":"."},"backend":null,"data":null,"vcs":{"pack":"vcs-github","instanceId":null,"selfManaged":false},"deploy":{"frontendHost":"deploy-vercel","backendHost":null,"databaseHost":null,"local":"none"},"originModel":"same-origin","derived":{"nextjsMode":"full-stack","agents":["scaffolder","frontend-engineer","test-engineer","ci-engineer","deploy-engineer","docs-writer"],"originModelOptions":["same-origin"],"specSections":["Routes \u0026 Rendering Strategy","Data Sources \u0026 Consumed APIs","Environments \u0026 Configuration"],"envNames":[],"gates":["cd {fe} \u0026\u0026 npm ci \u0026\u0026 npm run typecheck \u0026\u0026 npm run lint \u0026\u0026 npm test \u0026\u0026 npm run build"]},"caveats":[]}'
ELSE '{"catalogVersion":"1.1.0","catalogHash":null,"frontend":{"pack":"fe-nextjs","path":"."},"backend":null,"data":null,"vcs":{"pack":"vcs-github","instanceId":null,"selfManaged":false},"deploy":{"frontendHost":null,"backendHost":null,"databaseHost":null,"local":"none"},"originModel":"same-origin","derived":{"nextjsMode":"full-stack","agents":["scaffolder","frontend-engineer","test-engineer","ci-engineer","docs-writer"],"originModelOptions":["same-origin"],"specSections":["Routes \u0026 Rendering Strategy","Data Sources \u0026 Consumed APIs"],"envNames":[],"gates":["cd {fe} \u0026\u0026 npm ci \u0026\u0026 npm run typecheck \u0026\u0026 npm run lint \u0026\u0026 npm test \u0026\u0026 npm run build"]},"caveats":[]}'
END)::jsonb,
catalog_version = '1.1.0',
catalog_hash = NULL
FROM (
SELECT p.id AS project_id,
EXISTS (SELECT 1 FROM project_deployment_settings AS d
WHERE d.project_id = p.id AND d.provider = 'vercel') AS vercel
FROM projects AS p) AS x
WHERE x.project_id = v.project_id
AND v.preset_id IN ('nextjs-supabase', 'nextjs-frontend')
AND v.stack_composition IS NULL;
UPDATE projects AS p
SET stack_packs = (CASE
WHEN v.preset_id = 'nextjs-supabase' AND v.stack_composition -> 'deploy' ->> 'frontendHost' = 'deploy-vercel' THEN '{fe-nextjs,data-supabase,vcs-github,deploy-vercel}'
WHEN v.preset_id = 'nextjs-supabase' THEN '{fe-nextjs,data-supabase,vcs-github}'
WHEN v.stack_composition -> 'deploy' ->> 'frontendHost' = 'deploy-vercel' THEN '{fe-nextjs,vcs-github,deploy-vercel}'
ELSE '{fe-nextjs,vcs-github}'
END)::text[]
FROM release_versions AS v
WHERE v.id = COALESCE(p.current_release_version_id, (SELECT l.id FROM release_versions AS l WHERE l.project_id = p.id ORDER BY l.created_at DESC, l.id DESC LIMIT 1))
AND v.preset_id IN ('nextjs-supabase', 'nextjs-frontend') AND v.stack_composition IS NOT NULL AND v.catalog_version = '1.1.0' AND v.catalog_hash IS NULL;
UPDATE execution_steps AS s
SET legacy_agent_key = s.agent_key,
agent_key = CASE s.agent_key
WHEN 'project-scaffolder' THEN 'scaffolder'
WHEN 'db-engineer' THEN 'data-engineer'
WHEN 'domain-logic-engineer' THEN 'frontend-engineer'
WHEN 'api-engineer' THEN 'frontend-engineer'
WHEN 'ui-engineer' THEN 'frontend-engineer'
WHEN 'devops-engineer' THEN CASE
WHEN jsonb_typeof(s.declared_files) = 'array' AND jsonb_array_length(s.declared_files) > 0
AND NOT EXISTS (
SELECT 1 FROM jsonb_array_elements(s.declared_files) AS f(file)
WHERE NOT starts_with(
rtrim(regexp_replace(regexp_replace(replace(btrim(COALESCE(f.file ->> 'path', '')), '\', '/'),
'/{2,}', '/', 'g'), '^(\./)+', ''), '/'),
'.github/'))
THEN 'ci-engineer'
ELSE 'deploy-engineer'
END
END
FROM release_versions AS v
WHERE v.id = s.release_version_id
AND v.preset_id IN ('nextjs-supabase', 'nextjs-frontend') AND v.stack_composition IS NOT NULL AND v.catalog_version = '1.1.0' AND v.catalog_hash IS NULL
AND v.status IN ('in_development', 'awaiting_merge')
AND s.legacy_agent_key IS NULL
AND s.agent_key IN ('project-scaffolder', 'db-engineer', 'domain-logic-engineer', 'api-engineer', 'ui-engineer', 'devops-engineer');
INSERT INTO __ef_migrations_history (migration_id, product_version)
VALUES ('20261028141435_RetirePresets', '10.0.13');
COMMIT;
Down:
START TRANSACTION;
UPDATE execution_steps AS s
SET agent_key = s.legacy_agent_key,
legacy_agent_key = NULL
FROM release_versions AS v
WHERE v.id = s.release_version_id
AND v.preset_id IN ('nextjs-supabase', 'nextjs-frontend') AND v.stack_composition IS NOT NULL AND v.catalog_version = '1.1.0' AND v.catalog_hash IS NULL
AND s.legacy_agent_key IN ('project-scaffolder', 'db-engineer', 'domain-logic-engineer', 'api-engineer', 'ui-engineer', 'devops-engineer');
UPDATE projects AS p
SET stack_packs = '{}'::text[]
FROM release_versions AS v
WHERE v.id = COALESCE(p.current_release_version_id, (SELECT l.id FROM release_versions AS l WHERE l.project_id = p.id ORDER BY l.created_at DESC, l.id DESC LIMIT 1))
AND v.preset_id IN ('nextjs-supabase', 'nextjs-frontend') AND v.stack_composition IS NOT NULL AND v.catalog_version = '1.1.0' AND v.catalog_hash IS NULL;
UPDATE release_versions AS v
SET stack_composition = NULL,
catalog_version = NULL
WHERE v.preset_id IN ('nextjs-supabase', 'nextjs-frontend') AND v.stack_composition IS NOT NULL AND v.catalog_version = '1.1.0' AND v.catalog_hash IS NULL;
DELETE FROM __ef_migrations_history
WHERE migration_id = '20261028141435_RetirePresets';
COMMIT;
Known issues
- M30 (2026-10-31, minor): no integration test for the Bitbucket head-mismatch event (needs a stubbed Bitbucket release flow).
- M31 (2026-10-31, minor):
PREVIEW_ROLLOUT_COMPLETED.messagecarries the sanitised run error, which may include Postgres message text, and the feed and admin log show it. - M29 (2026-10-30, minor): env vars on a deleted target’s own platform resource stay on the platform.
- M31 (2026-11-01, minor):
GitHostPollBudget.NextGitLabStatusPollAthas no production caller (dead code). If GitLab answers a merge with 405 while still checking, the release waits inawaiting_mergeuntil a person merges.aide stackcan’t set the origin model. Verify doesn’t check that Netlify’s key and hook are still on the repo; a failed delete of a removed variable isn’t retried. Railway’s preview database isn’t offered in the wizard (Settings handles it). - (fixed in M30 slice 2) §4.22 gap (2026-10-28): a Supabase BaaS stack in Next.js full-stack declares
SUPABASE_SECRET_KEYbut not the server-side unprefixedSUPABASE_URL(a pack can’t list a key twice; the publicSUPABASE_URLbecomesNEXT_PUBLIC_SUPABASE_URL). Fix in M30 with the derived Supabase variables (FR-145, FR-153). - Integration test memory (found 2026-10-27): every derived test host (
WithWebHostBuilder, e.g.LlmStubs.App, harnesses) stays alive on the sharedAideApiFactoryuntil the run ends (~37 MB each). With v4’s extra tests one process outgrew a 16 GB runner (heap dump: 50 live hosts after 150 s; RSS ~14.7 GB). Fixed for CI by running integration tests in 3 shards (backend-integrationjob, ci.yml); the stack catalog is also loaded once per process now. Follow-up (test-engineer): dispose derived factories per test so a single process stays small. - Railway
previewdeploy of 83d8fa5 (run 40130552891): the deploy succeeded, but the smoke test’s sign-in returned 401. TheSMOKE_EMAILaccount is missing onpreviewor its password differs. Operator fix (Ben): create the smoke user on preview, or correct theSMOKE_PASSWORDsecret.
Manual checks (Ben)
- GitHub App permissions (M31): update the App to add
actions_variables: writeandactions: write(and Pages, from M29), then accept on each installation. Until then Pages variable sync records a 403syncErrorand a needed re-run fails that deployment. - M31 against the real APIs: Netlify
deploy_keysand siterepo.deploy_key_id/repo_urlwith a key plus webhook, including self-managed GitLab; GitLabcan_pushand duplicate-key 400; Bitbucketdeploy-keysandhooksscopes,PUT hooks/{uuid},description; GitHubactions/variablesroutes and the 409; Railway database list query; Render’s handling of a free plan withoutpreDeployCommand. - Railway: run
railway config planthenapply:.railway/railway.tsgained ignore-list names in M31. - Docker Desktop redeploy and smoke test (
./deploy/scripts/deploy-local.sh), and three-browser E2E. - M30 databases against the real APIs: Supabase create (region_selection), health, pooler config and
api-keysPOST replies, auth config PATCH, advisors, pause/restore; Render Postgres create and thepostgreswrapper in list replies,connection-info; Railway Postgres creation (image, volume) and the volumes query. Run a Supabase BaaS migration against a real project and checksupabase db pushafterwards sees a consistent history. - M30 Render/Railway against the real APIs: Render
resource=vsresource[]on/v1/logs, the Docker path relative torootDir,preDeployCommandon the plan used (free plans need migrate-at-start), and the routes merge. Railway:preDeployCommandfield shape, whetherserviceInstanceDeployV2needs the SHA on the tracked branch, whether a service created afteraide-previewgets an instance there, and the rate-limit error shape. - M29 platforms against the real APIs (CI uses stubs): connect a token for each of Vercel, Netlify, Cloudflare (user token; a
cfat_token must be refused), Render, Railway (workspace id) and Supabase (scoped token without API Key Secrets Read →insufficient_scope). Then set up a Netlify site and a Cloudflare Worker for a GitHub project, run a preview and a release, and roll each back. Unverified API shapes to check: Netlifyskip_prs/allowed_branchesfield names and whether API branch builds respectallowed_branches, thelocked/unlockroutes and build hooks; Workers Builds routes and fields (external_script_id,trigger.trigger_uuid,preview_url, a version id on builds, loglines/cursor/truncated,tagon the scripts list); Pagesenv=query and PATCH shape. GitHub App: update its permissions to add Pages (read & write) and accept them on each installation. - M28 Bitbucket against real Bitbucket Cloud (CI uses a stub): register a consumer, connect by OAuth and by an API token (one missing
write:pullrequestmust be refused), create acme/todo-app in a project, run to release (PR merged, annotated tagv0.1.0with notes). Check the refs lookup by repo UUID (/repositories/{}/{uuid}) and the scope probes behave as on the stub. Check how Bitbucket answers a secondPOST …/mergeon a PR whose merge task is still running (AIDE re-posts on a retry that finds the PR still OPEN). - M27 GitLab against a real instance (no real GitLab in CI): register an OAuth app on gitlab.com with the callback URL from Admin → Integrations → GitLab, connect, sign in, create a project from the wizard, run it to release (MR merged, GitLab Release
v0.1.0). Railway:GitLab__AllowPrivateNetworks=falseis in.railway/railway.ts, so runrailway config plan/applybefore the next deploy. - Railway preview smoke account (see Known issues).
- Before the next Railway deploy after merging M23: run
railway config planthenrailway config applyforpreviewandproduction. M23 addsLlm__Providers__Ollama__Enabled=false,Llm__Providers__Fake__Enabled=falseandGitLab__AllowPrivateNetworks=false, and removesLlm__Providers__Ollama__BaseUrlfrom the ignore list (the plan deletes it where set). Until applied, deploy.yml’s drift check fails. - After that apply, Railway projects still set to Fake (or Ollama) fail their next agent run with
LLM_PROVIDER_UNAVAILABLE; their Owners choose another provider in Settings → LLM. - OpenAI catalog limits for
gpt-6-astra/gpt-6.1-solare placeholders (16,000 output, 400,000 context) until checked against OpenAI’s docs (SPEC Q5).