Production QA schema, models, read routes
## Epic context (verbatim work item)
"QA stops at the merge lane's post-merge tree check (fleet-task-lifecycle.md §Post-merge gate verification); forge CI is a live read-only view (forge_ci.py), Jaah Deploy's result never reaches the CP, CloudWatch alarms only page Slack (alarm_relay.py), and the only condition-to-task path is in-process cron alarms (cron_alarms.py). Close the loop to production."
Requirement 7 (verbatim): "Measurement is always collected (deployments, incidents, linkage, regression deltas, the ledger edges). Every behaviour change — filing a task, filing a revert, running probes — is behind the plan feature `production_qa` with org override and per-project opt-in. Dogfood on the Jaah project first."
Design: architecture artifact of Task 12249 (read with `task_read 12249` + `artifact_read`), §2 components, §3 data model, §4 pinned interfaces, §11 routes. PRD: Task 12248's `prd` artifact.
## Scope (architecture C1, schema half)
- Create package `apps/api/app/production_qa/` with the package docstring as the single home of this epic's rules, and add a CLAUDE.md router row pointing at it.
- **The epic's ONE migration** (one additive revision; no other child adds DDL): every table and column of §3: `branch_pushes`, `deployments`, `deployment_environments`, `deployment_commits`, `deployment_prs`, `deployment_metrics`, `incidents` (incl. `last_raised_at`), `incident_events`, `incident_suspects`, `incident_tasks` (with `released_at` and the unique partial index `(incident_id) WHERE role <> 'revert' AND released_at IS NULL`), `production_qa_edges`, `merge_lane_stage_timings`; `projects` columns (`production_qa_enabled` default false, `production_qa_enabled_by`, `production_environment`, `deploy_workflows`, `revert_policy` CHECK off/draft/auto_file default off, regression thresholds, `health_probes`, `smoke_commands`); `billing_plans.production_qa` (default false) and `orgs.production_qa_override` (the `merge_lane_enabled` pattern); `trigger_deliveries.not_before` + `held` in any delivery-state CHECK; `task_triggers` CHECK widened to `sentry`, `datadog`, `pagerduty`, `cloudwatch`, `incident`, `deploy_failed`, `health_breach`. Rebase the revision onto the current alembic head immediately before merge (epic 12243 builds migrations in parallel).
- SQLAlchemy models (OrgScoped, BigInteger identity ids, timestamptz) for every table.
- Every §4 function as a no-op stub in its owning module (`gate.py`, `recorder.py`, `deployments.py`, `incidents.py`, `events.py` incl. `RaiseIntent`, `adapters/*.py`, `linkage.py`, `regression.py`, `probes.py`, `edges.py`, `cron.py`), exact signatures as pinned, so siblings fill them without touching each other's signatures.
- Read routes in `apps/api/app/routes/production_qa.py`: `GET /api/deployments?project_id&environment&status&cursor`, `GET /api/deployments/{id}`, `GET /api/incidents?project_id&severity&state&type&cursor`, `GET /api/incidents/{id}` (detail includes suspects, events, tasks, edges, metrics, environments); org-scoped, limited to projects the caller has `projects:view` on.
Assumed: the C1 split (schema+routes here, incident state machine + recorder frame in its sibling) because one PR carrying both is too large to review.
Credentials & reach:
- AC-29 gates — worktree only — **verified** (nothing external)
Out of scope: Do NOT create or own the `task_outcomes` table or its schema (epic 12243). Do NOT set `production_qa` true for any plan, org or project in code, seeds or migrations, Jaah included (D9). Do NOT add an org-level trigger quota or plan term (D6). Do NOT implement stub bodies owned by sibling children (incident state machine, recorder, ingestion, adapters, linkage, regression, probes). Epic out of scope (verbatim): "building an APM or log store; replacing the customer's monitor; auto-merging a revert without the project's policy; any scoring or ranking logic (the outcome ledger epic reads the states this epic writes)."
Done means:
- Migration upgrades and downgrades cleanly on a fresh `./dc` stack and is additive (no drop/rename of an existing column) — run alembic upgrade/downgrade/upgrade in the changed-area test.
- AC-25 (data half): `production_qa` defaults are OFF on every plan/org/project — a test asserts the column defaults and that no seed sets them true.
- AC-26 (record side): read routes return deployments/incidents written directly into the tables for an org with the feature OFF — route tests for list filters, cursor paging, detail, and a 404/empty result for a project the caller cannot view (cross-org isolation test).
- Every §4 stub importable with its pinned signature — an import/signature test.
- AC-29: `./dc check quick` green; changed-area tests green; merge lane full gate green.
Outcome
Production QA schema half: one additive revision (12 tables, switch columns all OFF, held state, widened trigger CHECK), models and tenancy census, pinned no-op stubs, and scoped read routes. Quick gate and changed-area tests are green (2362 passed). PR #7601 is queued in the merge lane. Deferred: `facts` allowlisting goes to the adapter writers. Memory: none applied.
Artifacts
Activity
- Draft
- Ready
- Runner started
- Runner ended (done)