ci(deploy): establish staged Dokploy promotion

This commit is contained in:
jjbrunton committed 2026-08-28 14:54:12 +01:00
1 parent 2dad7aa352
commit a455325147
14 files changed
+199 -310

No files matched your search

+1
View File
@@ -9,6 +9,7 @@ historical notes. Agents should load only the section relevant to their task.
- [Local full-stack development](development/local-stack.md)
- [Testing strategy](testing/strategy.md)
- [Database migration safety](operations/database-migrations.md)
- [Backend deployment and branching](operations/backend-deployments.md)
- [Agent routing and effort](agents/routing.md)
- [Learning promotion](agents/learnings.md)
- [Authentication](authentication.md)
+44
View File
@@ -0,0 +1,44 @@
# Backend deployment and branching
Dokploy deploys the standalone backend directly from GitHub. GitHub Actions is
the verification gate; it does not hold a broad Dokploy API credential and does
not deploy the retired Supabase data plane.
## Branch contract
| Branch | Purpose | Dokploy target | Public API |
|---|---|---|---|
| feature branches | isolated reviewed work | none | none |
| `staging` | integrated release candidate | `backend-staging` | `https://api.preprod.sauci.app` |
| `main` | production promotion | `backend-production` | `https://api.sauci.app` |
| `master` | frozen legacy default | none | none |
Normal feature pull requests target `staging`. After CI and authenticated staging
acceptance pass, open a promotion pull request from `staging` to `main`. Do not
add production-only commits to `main`: production should run the exact commit
already exercised in staging. Direct pushes to either deployment branch are for
explicit incident recovery only.
Each Dokploy Compose application is connected to `jjbrunton/Sauci`, watches its
single branch with push-triggered automatic deployment, and uses
`infra/backend/compose.production.yaml`. The Compose file deliberately has no
top-level project name so Dokploy assigns different project names, networks, and
named volumes to staging and production. Never point both applications at the
same PostgreSQL or media volume.
## Promotion checks
1. GitHub `Verify / static` and `Verify / backend-integration` succeed for the
candidate commit.
2. The Dokploy deployment for `staging` reports success; `/health/live` and
`/health/ready` return 200 over valid TLS.
3. An unauthenticated protected request returns 401, proving the public route is
not bypassing authentication.
4. The mobile app signs in through non-production Supabase Auth and reads real
migrated content from `https://api.preprod.sauci.app`.
5. Promote the exact `staging` commit to `main` and verify the production health,
TLS, and authentication boundary independently.
Health proves infrastructure, not customer-data cutover. The App Store build
must not be released against production until the controlled final sync and the
checks in [backend-cutover.md](backend-cutover.md) are complete.
+27 -18
View File
@@ -1,26 +1,35 @@
# Database migration safety
All schema changes are versioned local files under `apps/supabase/migrations/`.
The standalone PostgreSQL schema is owned by ordered SQL files under
`apps/api/drizzle/`. Hosted Supabase owns Auth only; its legacy product-data
migrations and Edge Functions are retained as cutover history and are not a
deployment target.
## Required workflow
1. `npm run db:migration:new -- <descriptive_name>`
2. Edit the generated local migration.
3. Prefer idempotent SQL where semantics permit it.
4. `npm run db:reset` against local Supabase.
5. Run `npm run test:supabase` and affected application checks.
6. Commit the migration with dependent code and types.
1. Add the next ordered SQL migration under `apps/api/drizzle/`.
2. Keep application, repository, shared-contract, and client changes in the same
reviewed change where the schema contract requires them.
3. Run `npm run verify:full` against the dedicated loopback PostgreSQL service.
4. Run `infra/backend/verify-production.sh` for container and migration startup.
5. Merge to `staging`; the Dokploy migration service applies the committed files
under an advisory lock before the API receives traffic.
6. Prove staging health and affected authenticated behavior before promoting the
exact commit from `staging` to `main`.
## Forbidden paths
## Safety invariants
- Never call a remote `apply_migration` MCP tool.
- Never execute DDL through MCP SQL execution.
- MCP SQL execution is read-only `SELECT` use only.
- Never insert migration history manually except an explicitly approved repair.
- Never hardcode a production URL in a migration or cron definition.
- Never point local integration tests, reset commands, or disposable verification
at a remote database.
- Never deploy `apps/supabase/migrations` or `apps/supabase/functions` as part of
the standalone backend release.
- Never execute DDL through MCP or provider SQL tools.
- Never edit migration history already applied to a persistent environment; add
a forward migration.
- Back up PostgreSQL and private media together before a destructive production
migration or data import, and prove restore separately.
- A failed migration must block API startup. Do not bypass the one-shot `migrate`
service to force new code against an old schema.
CI deploys committed migrations and Edge Functions to non-production after relevant
pushes. Production promotion is a manual workflow dispatch. Terraform is applied
only when files under `terraform/` change, or when `deploy_infrastructure` is
explicitly selected during a manual deployment. Local verification must not link a
remote project.
Production data cutover has additional stop-source, final-sync, parity, legacy
media, and authenticated-mobile checks in [backend-cutover.md](backend-cutover.md).
+11 -9
View File
@@ -6,23 +6,25 @@ is proved through the real stack.
## Gates
- `verify:fast`: invariant lint, workspace lint/typecheck, and unit tests.
- `verify:full`: fast gate plus builds and local Supabase function tests when the
required local tools are present.
- `verify:full`: fast gate plus builds and standalone API/PostgreSQL integration
tests against a loopback database.
- `verify:e2e`: Playwright browser flows and native Maestro flows against an
already-running local stack.
- `verify`: full plus E2E; this is the pre-PR target.
CI runs deterministic checks on every pull request. Running-stack tests use a
separate job because they require Docker/Supabase and produce traces,
screenshots, and videos as artifacts.
CI runs deterministic checks on every pull request and on pushes to `staging`
and `main`. The backend integration job provisions disposable PostgreSQL and
refuses non-loopback database URLs. User-facing browser/native acceptance remains
a pre-promotion check because it requires running services and test identities.
## Test ownership
- Unit/component tests live with their workspace.
- Supabase function integration tests live in `apps/supabase/tests`.
- Standalone backend integration tests live in `apps/api/test`.
- `apps/supabase/tests` cover the retired data plane and are not a deployment gate.
- Cross-application browser tests live in `e2e`.
- Native black-box flows live in `e2e/maestro`.
E2E must use local or sandbox endpoints. Tests refuse non-local Supabase URLs.
Authentication is proved once through the public flow; reusable fixture sessions
avoid repeating auth in unrelated scenarios.
E2E product-data endpoints must be local or staging, never production. Local
database tests refuse non-loopback URLs. Authenticated staging acceptance uses
the designated non-production hosted Auth project and disposable fixture users.