fix(FN-2127): harden Quality Postgres store (project scope, races) (#2230)

## Summary

Follow-up after **#2164** and main’s **FN-8103 / FN-8104**
(Postgres-only data access / SQLite retirement).

Main already routes Quality through `AsyncQualityStore` +
`getAsyncLayer()`. This PR keeps the **review hardening** that was still
missing:

- **Project binding** — reject request `projectId` mismatches vs bound
AsyncDataLayer; all SQL uses bound project
- **`createRunIfNoActive`** — advisory lock so concurrent starts cannot
double-queue
- **Cancel-safe runner** — cancel slot registered before the running
write; pre-spawn cancel skips process
- **`finalizeRun`** — never overwrites a `cancelled` terminal status
- **Detached execute** — catch only execution failures; prune fail-soft
in `finally`
- Guardrail tests + Quality v2 plan doc

## Test plan

- [ ] Task QA loads under PostgreSQL (no SQLite/backend-mode error)
- [ ] Concurrent start for same task → 409 second start
- [ ] Cancel during start does not leave a live orphan process
- [ ] Cancelled run stays cancelled after process exit
- [ ] `pnpm --filter @fusion-plugin-examples/quality test` (37 tests)
This commit is contained in:
gsxdsm
2026-07-16 11:38:34 -07:00
committed by GitHub
parent 4a797d3804
commit 446c969968
15 changed files with 1093 additions and 216 deletions

View File

@@ -0,0 +1,169 @@
---
title: "feat: Quality plugin v2 — post-merge agent QA, evidence packs, and the QA results board"
type: feat
status: active
date: 2026-07-16
origin: docs/plans/2026-07-14-001-feat-quality-plugin-plan.md
updated: 2026-07-16
---
# feat: Quality plugin v2 — post-merge agent QA, evidence packs, and the QA results board
## Summary
Evolve `fusion-plugin-quality` from a manual per-task QA surface into a **post-merge, agent-driven QA system**: done tasks are QA'd by an agent that resurrects the task's code in a QA worktree, browser-exercises the change, walks the suggested cases, runs targeted tests, probes adjacent surfaces for regressions, and produces an **evidence pack** (screenshots + per-case verdicts). Failures file follow-up tasks with evidence attached. The Quality hub becomes a **QA results board**: queue + verdicts, evidence gallery, CI + gate health.
## Operator interview findings (2026-07-16)
How QA actually happens today and what the operator wants next:
- **Current flow:** trusts executor/reviewer verification for most tasks; manually previews in a browser for the ones that matter.
- **Top pains:** (1) knowing *what* to check for a given task, (2) no visual evidence on landed tasks, (3) too many surfaces (terminal, Dev Server, Artifacts, PR checks).
- **Desired direction:** agent-driven QA + zero-click evidence + hub/CI depth. Explicitly *not* asking for more manual-tab polish as the priority.
- **QA timing:** **after merge (done tasks)** and in **batch sessions** — not a pre-merge gate. The done-task QA worktree path (FN-2127) is the intended substrate, not an edge case.
- **Agent session scope:** browser-exercise the change + capture evidence pack + run targeted tests + **Surface-Enumeration-bounded regression hunt** (adjacent surfaces derived from the diff: both breakpoints, shared components, sibling states — not open-ended roaming).
- **Trigger:** auto-queue on done, **setting-gated per project** (off by default), with concurrency cap and per-session budget.
- **On failure:** auto-file a follow-up bug task with evidence attached, linked to the original.
- **Hub first paint:** QA queue + verdicts, evidence gallery, CI + gate health. Test plans (U4 runner) were *not* selected — park them.
- **First slice:** prove the **agent QA engine** end-to-end on one done task (manual trigger) before building queue/auto plumbing.
## Current state (v1 audit, 2026-07-16)
From the v1 plan (2026-07-14) units:
| Unit | Status | Notes |
|------|--------|-------|
| U1 scaffold/registration/slot context | ✅ done | Full registration matrix; `TaskDetailModal` injects `taskId`/`worktree`/`projectId`/`modifiedFiles` |
| U2 schema/store | ✅ done | Dual SQLite/PG, projectId-scoped, retention pruning |
| U3 presets/runner/hub runs | ✅ done | 5 allowlisted presets, `superviseSpawn` from `@fusion/core` (shim not needed), 409 concurrency |
| U6 Task QA tab shell | ✅ done | Action-first sections; Run tests + Reports functional |
| U11 task preview server | ✅ done | Incl. **done-task QA worktrees** (`preview/task-code-worktree.ts`: `.fusion/quality-qa/<taskId>`, branch → `fusion/<id>` → `mergeDetails.commitSha`) — also used for task-scoped test runs |
| U13 suggested cases | ◐ partial | Heuristics only; checklist is display-only (no tick/copy/run-related); no AI enrichment |
| U4 test plans | ◐ partial | Store + CRUD routes; no runner, no UI — **parked by this plan** |
| U5 CI read-only | ✗ placeholder | Static text in tab; no host route |
| U12 screenshots gallery | ✗ placeholder | Static text in tab |
| U7 browser-verification toggle | ✗ not started | In-review-oriented — **deprioritized** (operator QAs post-merge) |
| U9 workflow palette | ✗ not started | **Deprioritized** for same reason |
| U10 agent QA sessions | ✗ not started | **Becomes the centerpiece of this plan** |
Known rough edges to absorb: preview sessions are an in-memory singleton (lost on restart, unlike runs); suggested-case `done` is stored but untoggleable; dead ternary in `create-routes.ts` `confirm_required` branch; `ensureQualitySchema` runs per request; all routes gated on `experimentalFeatures.qualityPlugin`.
## Product shape
```text
Task merges → done
└─ (if project autoQa enabled) enqueue QA job
└─ Agent QA session (cap N concurrent, budgeted)
1. Resolve QA worktree (done-task resurrection path — already shipped)
2. Install/build if needed; start preview session (free port, never 4040)
3. Load QA script: AI-enriched suggested cases + Surface Enumeration targets from diff
4. Browser-exercise change (agent-browser skill); capture screenshots per case
5. Run targeted tests (file-scoped preset) into the same session record
6. Regression hunt: adjacent surfaces only (both breakpoints, shared components, sibling states)
7. Emit structured verdict: per-case pass/fail/blocked + evidence artifact refs + summary
└─ Verdict lands in hub QA board + task QA tab
├─ pass → evidence pack visible on task (zero-click evidence)
└─ fail → auto-file follow-up bug task (evidence attached, linked to original)
Hub = QA results board: queue/in-flight/verdicts | evidence gallery | CI + gate health
```
Invariants carried forward from v1 (unchanged): advisory only — never merge-blocking; allowlisted commands only; never port 4040; never `execSync`; compose agent-browser + artifact registry + Dev Server patterns rather than forking them; projectId isolation on every row.
New invariants:
- **Agent QA is read-only on the repo**: sessions never commit, push, merge, or mutate task lifecycle except (a) writing QA records/artifacts and (b) filing the follow-up task through the normal task-creation path.
- **Budgets are hard**: per-session wall-clock + token budget; inactivity watchdog; queue concurrency cap (default 1). Exhaustion → `blocked` verdict, never silent partial pass.
- **Auto-QA is opt-in per project** (`autoQa.enabled`, default false). Manual trigger works regardless of the auto setting.
- **Evidence lives in the artifact registry** (task-scoped image artifacts), not a parallel media store; QA records hold refs only.
## Implementation units
### V1. Agent QA session engine (manual, single task) — first slice
**Goal:** From a done task's QA tab, one click starts an agent QA session that ends with a structured verdict and at least one captured screenshot. Prove the whole loop before any queue exists.
- New `src/agent-qa/`: session orchestrator using `ctx.createInteractiveAiSession` (probe; empty-state CTA when engine factory missing), CE detach + `onProgress` + inactivity watchdog pattern.
- Session cwd = QA worktree from `resolveTaskCodeCwd` (reuse as-is); orchestrator owns preview-session start/stop around the agent run.
- System prompt: QA charter — walk the provided case list, capture screenshot evidence per case via agent-browser, run the file-scoped test preset, then Surface-Enumeration hunt (explicit target list provided, see V3), return verdict JSON `{cases: [{id, verdict: pass|fail|blocked, evidence: [artifactRef], note}], regressions: [...], summary}`.
- New store table `qa_sessions` (projectId, taskId, status queued|running|passed|failed|blocked|error|cancelled, budgets, verdict JSON, startedAt/finishedAt); routes start/get/cancel; honor mock/testMode; never log tokens.
- QA tab: "Agent QA" section becomes real — start button (done tasks included), live progress, verdict rendering.
- Tests: mocked interactive session; detach semantics; watchdog; verdict persistence; factory-missing empty state; testMode.
### V2. Evidence pack + screenshots gallery (completes U12)
**Goal:** Zero-click evidence — opening a done task's QA tab shows what the change looks like.
- Evidence capture path: agent sessions register screenshots as task-scoped image artifacts (artifact registry); QA session verdict stores artifact refs.
- Screenshots section: task-filtered artifact gallery (image MIME) + `design-preview` doc link + QA-session evidence grouped by case; thumbnails, open-in-Artifacts; actionable empty state ("Run agent QA").
- Tests: populated/empty/cross-task isolation; evidence-ref integrity when artifacts pruned.
### V3. QA script: AI-enriched suggested cases + Surface Enumeration targets (completes U13)
**Goal:** Suggested cases become the executable QA script consumed by both the operator and the agent.
- AI enrichment via short `createAiSession` pass over PROMPT + file scope + diff summary; fail-soft to existing heuristics; `method` field distinguishes heuristic/ai.
- New deterministic **surface-target derivation** from the diff (the regression-hunt bound): changed components → both breakpoints, shared hooks/components that reuse them, sibling data states — reusing the Surface Enumeration checklist taxonomy from `docs/testing.md`.
- Checklist UI becomes interactive: tick (persisted `done`), copy-all, "run related tests" wiring to the file-scoped preset. Agent verdicts auto-tick cases they cover.
- Tests: AI-failure fallback; tick persistence; target derivation from representative diffs.
### V4. Verdict → follow-up task filing
**Goal:** A failed QA verdict becomes an actionable, evidence-backed bug task without operator typing.
- On `failed` verdict (auto mode) or via "File follow-up" button (manual): create a bug task through the normal task-creation path — title from failing case, body with **Original symptom / Exact reproduction / evidence artifact links**, link to the original task (dependency/reference), evidence attached.
- Setting `autoQa.fileFollowUps`: `auto` | `draft-confirm` | `off` (default `auto` per interview, but respect global testMode).
- Never reopen/move the original task; never touch merge state.
- Tests: filing payload shape; dedup (same case doesn't file twice across re-runs); off/draft modes.
### V5. QA queue: auto-trigger on done, batch sweep, budgets
**Goal:** Setting-gated projects get every done task QA'd automatically; any project can run a batch sweep.
- Queue table + worker loop inside the plugin (supervised, resumable on restart — persist queue rows, reconcile in-flight on boot); enqueue hook on task→done transition (plugin lifecycle hook or poll fallback); concurrency cap (default 1, max 2); per-session budget settings.
- Batch sweep: hub action "QA last N done tasks" → enqueues; idempotent (skip tasks with a fresh verdict unless forced).
- Settings: `autoQa.enabled` (per project, default false), `autoQa.concurrency`, `autoQa.sessionBudget{Ms,Tokens}`, `autoQa.fileFollowUps`.
- QA-worktree hygiene: cap live QA worktrees, prune oldest on enqueue (bounded, prefix-scoped — never temp-root walks).
- Tests: gating off by default; cap enforcement; restart reconciliation; idempotent sweep.
### V6. Hub → QA results board (+ CI/gate health, completes U5)
**Goal:** First paint answers "what shipped, was it QA'd, did anything break?"
- Board layout: **QA queue + verdicts** (awaiting / in-flight / recent verdicts with evidence links, filter by pass/fail) | **evidence gallery** (recent screenshots across tasks, click → task QA tab) | **CI + gate health** (host-owned read route reusing dashboard GitHub auth for open-PR check rollups + default-branch gate status; auth/empty/429 states; read-only).
- Keep existing run history reachable; park Plans UI behind an overflow section (store/CRUD kept, runner still unbuilt — explicitly deferred).
- Tests: board states (empty project, queue drained, failures present); CI route auth empty states; no plugin import of dashboard-private GitHub client.
### V7. Hardening + polish (absorbs v1 rough edges)
- Persist preview sessions (or reconcile-on-boot kill+mark), replacing the in-memory-only singleton.
- Fix dead `confirm_required` ternary; memoize schema init per store instance.
- Graduation criteria for the `experimentalFeatures.qualityPlugin` gate (documented; flip is a separate operator decision).
- Settings/docs/CONCEPTS updates (Agent QA Session, QA Verdict, Evidence Pack, QA Queue); FNXC comments on budgets, read-only session posture, follow-up filing policy; changeset (`minor`) when the published surface ships.
## Phasing
| Phase | Units | Ship gate |
|-------|-------|-----------|
| A — Prove the loop | V1 | Manual agent QA on a done task yields verdict + screenshot |
| B — Evidence + script | V2, V3 | Zero-click evidence on QA'd tasks; cases drive both human and agent QA |
| C — Close the loop | V4 | Failed verdicts file linked, evidence-backed follow-ups |
| D — Scale | V5, V6 | Opt-in projects auto-QA every done task; hub is the results board |
| E — Polish | V7 | Rough edges gone; docs/changeset shipped |
## Explicitly parked (from v1 plan)
- U4 plan runner + plans UI (operator did not select; CRUD substrate kept)
- U7 browser-verification toggle and U9 workflow palette (pre-merge-oriented; this plan is post-merge)
- Pre-merge QA gating of any kind (advisory promise unchanged)
- Pixel-diff/visual-regression product; multi-provider CI; Actions log streaming
## Risks
| Risk | Mitigation |
|------|------------|
| Agent QA cost blowup | Off by default; per-project opt-in; concurrency cap; hard session budgets; `blocked` on exhaustion |
| False-positive follow-up spam | Dedup per case+task; `draft-confirm` mode; evidence required in every filed task |
| QA worktree disk growth | Live-worktree cap + prune on enqueue (prefix-scoped) |
| Agent mutates repo/tasks | Read-only session charter + tool posture; only artifact/QA-record writes + task-creation seam |
| Preview/build fails on resurrected code | `blocked` verdict with build log evidence, not `failed`; surfaces as actionable in board |
| Regression hunt scope creep | Deterministic target list from diff (V3); agent may not add targets beyond budget |