OPER-66
Real-DB integration test for OPER-60 admission gate + composed transaction
Suggested
Justin Cooke
Body
feature_id: FEAT-studio-dispatcher ## Context The OPER-60 admission gate (PR #177, merged [REDACTED-DOB]) ships with 22 tests in `apps/web/src/lib/__tests__/plan-validation-runner.test.ts` and 14 tests in `apps/web/src/lib/__tests__/plan-size-gate-wiring.test.ts`. All 36 tests use a fake `scopedDb` (`h.getScopedDb.mockImplementation(...)`) — no Postgres, no Prisma engine. The fake covers the shape (`task.findFirst`, `task.update`, `planningValidation.create`, `planningRejection.create`, `agentWorker.findMany`, `$transaction`, `auditLog.create`), but it does NOT cover: 1. The `PlanningRejection` FK/unique constraints (`taskId + planningValidationId` cardinality; whether the composite index the migration created is actually used by the runner's writes). 2. The `NEEDS_RESCOPE` enum on `TaskStatus` in the live schema — the fake accepts any string, so a typo in `status: "NEEDS_RESCOPE"` would still pass fake tests but blow up in production against the check constraint. 3. The composed transaction with the size gate — both gates write inside the same `$transaction`, and only a real DB verifies that a partial commit is impossible (i.e. that admission's `PlanningRejection` row and size gate's `Task.status = AWAITING_HUMAN` cannot both land). Nika (Chaos Engineer) flagged this during the OPER-60 memo review: "the fake tells us the runtime calls the right method, but not that the DB accepts the payload." This ticket closes that gap. ## Scope 1. Add `apps/web/src/lib/__tests__/plan-validation-runner.integration.test.ts` that runs against a real Postgres database (use the existing test-DB helper — see `apps/web/src/test/db.ts` for the pattern already in use by other `.integration.test.ts` files). 2. Cover exactly these paths against the real DB: - Happy path: an admissible ticket with a passing plan promotes to `PLANNING_VALIDATED` and writes a `PlanningValidation` row. - Admission-fail path: an unsized ticket writes a `PlanningRejection` row with the correct `reasons[]` and transitions the task to `NEEDS_RESCOPE`. - Size-gate-fail path: an admissible size-L ticket writes a `PlanningValidation(verdict='fail')` row and transitions to `AWAITING_HUMAN` with the two labels applied. - Composed-transaction path: verify that when admission fails, NO size-gate side effects (no AWAITING_HUMAN transition, no label writes, no PLAN_SIZE_REFUSED_EVENT) land. 3. Do NOT duplicate the 36 unit tests. Real-DB tests are slower (~1s each vs 2ms); pick the 4 above as the coverage floor. 4. Use `beforeEach` teardown that truncates the tables the test touches (`Task`, `PlanningValidation`, `PlanningRejection`, `AuditLog`). Do not `prisma migrate reset` between tests — it is 10x slower and the CI job has no budget for it. ## Acceptance Criteria - 4 new `.integration.test.ts` cases pass locally against a real Postgres instance. - Each case runs in <2s including setup/teardown. - CI runs the integration test file (add to whatever pipeline currently runs the other `.integration.test.ts` files; if none, that is a separate ticket — do NOT add a new CI job in this ticket). - The test file imports the same `runPlanValidation` symbol the unit tests use; no fork of the runtime. - Test-DB helper is reused, not copied. ## Non-goals - End-to-end HTTP test through the `/api/studio/[team]/tasks/[id]/validate-plan` route. That is the existing route test; this ticket is about the runner. - Load / concurrency testing. If concurrent validations on the same task can corrupt the row, that is a separate ticket surfaced by observability, not by this test. - Coverage of the PHI-reviewer gate (`phi_no_reviewer` reason). That path fires against a live worker pool and belongs in its own integration test. ## Verification `pnpm --filter web vitest run src/lib/__tests__/plan-validation-runner.integration.test.ts`
Attachments
Loading attachments…
Comments
Loading comments…