Execute the self-contained final QA gate as a Spec's authored terminal `qa` Task — derive a resumable QA matrix from the PRD and task evidence, execute real user flows through every declared surface, probe high-risk user behavior, capture auditable evidence, classify findings by user impact, and write the spec-local dated QA report. Do not use as a substitute for implementation tests or a broad standalone QA knowledge base.
Installs into .claude/skills of the current project.
Are you the author of Qa Gate?
Add the live security badge to your README. It updates with every re-scan.
[](https://www.skillsdirectory.com/skills/marcioaltoe-qa-gate)
---
name: qa-gate
description: Execute the self-contained final QA gate as a Spec's authored terminal `qa` Task — derive a resumable QA matrix from the PRD and task evidence, execute real user flows through every declared surface, probe high-risk user behavior, capture auditable evidence, classify findings by user impact, and write the spec-local dated QA report. Do not use as a substitute for implementation tests or a broad standalone QA knowledge base.
metadata:
category: qa
tags: [qa, testing, browser, workflow]
version: 0.0.9
author: Marcio Altoé
source: https://github.com/marcioaltoe/skills
version: 0.0.9
---
# QA Gate
Validate the assembled feature against the promises in its spec by exercising the public interfaces a real user reaches. The Daemon runs this skill from the unique authored terminal `qa` Task named by `_tasks.md` frontmatter; it is part of the Task Graph, not a per-run request. That node depends on every non-QA leaf and therefore becomes runnable only after the graph it closes settles. This skill owns the complete gate: plan, execution, evidence, findings, report, and final verdict. Use the agent's tools and connected servers directly; do not load another QA skill or create a separate living QA tree.
## Non-negotiables
1. **Real user seat.** Enter through the same frontend, API, CLI, data workflow, or documented operational path as the intended actor. Internal helpers and code inspection can diagnose a failure, but cannot prove a user story passes.
2. **Proof beyond optimistic state.** A pass requires the expected observable, an independent confirmation through a fresh load or another public read path, persistence across refresh/restart when relevant, and captured evidence.
3. **Resumable evidence.** Create the dated report with every row `pending` before the first check. Update it after each row so an interrupted run resumes from disk instead of repeating completed work.
4. **One honest verdict.** The seeded report starts pending. Keep its seeded front matter as the report's only front matter while filling in that block and the Results rows. Every planned row ends as `pass`, `fail`, `blocked`, or `skipped`; the report closes with zero `pending` rows. A report that records no QA row never settles the gate, and a report whose front matter is empty or duplicated never settles the gate.
5. **Typed blocked causes.** Record a row that is unreachable for a proved
environmental cause as `blocked (environment: <cause>)` and count it in
`rows_blocked_environment`; record a row stopped by a finding as `blocked
(finding: <id> — waits on <named failure>)` and count it in
`rows_blocked_finding`; record a row that is unreachable for the reason in
a matching, pre-run Spec declaration as `blocked (declared: <criterion>)`
and count it in `rows_blocked_declared`. Keep the three causes separate:
never fold one into another to make the report or verdict look cleaner.
6. **Binary identities.** The auditor fields are Daemon-owned: keep the seeded
`auditing_binary` and `auditor_staleness` lines unchanged. Record the
`--version` line of the binary that ran every public-CLI row as
`user_flow_binary`. In a Roundfix self-audit, build that binary from the
audited Run Worktree with `make build`, run public-CLI rows only with
`./bin/roundfix`, and never use a `roundfix` found on PATH.
### Runnable evidence
Store runnable evidence under a non-compiled extension such as `.go.txt`,
never as a source file the repository gate compiles or formats. A later
pass's import skips compiled source and records each skipped path.
### QA settlement
The same outcome settles the authored `qa` Task and determines what archive
may move:
| Outcome | Settles | Archives |
| --- | --- | --- |
| `pass` | Settles the QA Task as `completed` and makes the Spec archive-eligible when the report has no disallowed blocked rows. | The Spec and its QA report and evidence. |
| qualifying declared `partial` | Settles the QA Task as `completed` when no row failed, was skipped or is finding-blocked, every declared-blocked row is covered by a matching `## Unreachable Acceptance` declaration, and every environment-blocked row is the pre-PR Pull Request row, recorded as `blocked (environment: no open Pull Request)` with the Pull Request row named in its provenance, or an outside-evidence row the Run sandbox could not reach, recorded as `blocked (environment: network denied: <host>)` with the outside-evidence row named in its provenance. Neither row needs an Unreachable Acceptance declaration, and a partial whose only unmet rows are such rows qualifies. | The Spec, its QA report and evidence, and the declarations' `satisfied-by` record. |
| `environment-blocked` | Leaves the row blocked; the report can still settle as `pass` when equivalent evidence satisfies the environment policy. | Nothing by itself; a qualifying report can archive the Spec. |
| `failed` | Leaves the QA Task unresolved and refuses archive unless an authorized override applies. | Nothing. |
| `missing` | Leaves the QA Task unresolved and refuses archive unless an authorized override applies. | Nothing. |
| `override` | Does not change the QA Task status or report verdict; settles archive as explicitly authorized despite failed or missing QA. | The Spec with `qa_override`, `qa_override_approval`, `qa_override_reason`, `qa_override_qa_outcome`, `qa_override_qa_task_status` when the QA Task is incomplete, and `qa_override_revision`; QA files move byte-identically. |
## 1. Resolve scope and preconditions
Resolve `docs/specs/<slug>/`, then read `_tasks.md`, `_prd.md`, every
`task_NN.md`, and prior files under `qa/`. Confirm that the manifest's `qa:`
field names the current `type: qa` Task and that this node is terminal and
depends on every non-QA leaf. A missing or mismatched authored node is a graph
defect; do not run the gate outside that node.
## Surface Transcripts at the gate
For every Surface Transcript named by the TechSpec, add one QA matrix row. The
row reproduces the transcript's command through the built product and compares
standard output, standard error, and exit code using the TechSpec matching
rules: a line `...` matches zero or more consecutive lines, text in angle
brackets matches one or more characters within the same line, and everything
else matches exactly. Compare standard output and standard error separately;
the exit code always matches exactly. Name the transcript in the row's
provenance and preserve the observed output as evidence.
- A clean authoring check is a precondition of the gate, not a substitute for
it. For every active, non-legacy Spec, run:
```bash
roundfix spec check <slug> --strict
```
The gate keeps this non-probing form because the probe asks whether a
command already passes before its work exists, while the gate runs after
every Task is complete. Probing completed Tasks would therefore report their
already-passing commands as vacuous and refuse the gate. The three authoring
skills use `--run-verification` while the work is still being authored, when
that question is answerable.
Stop before building the matrix when the command fails, and write the refusal
before you stop. A gate that stops here measured no requirement, so its whole
report is the refusal:
```markdown
---
verdict: fail
rows_blocked_precondition: 1
auditing_binary: "<version-and-build-identity>"
auditor_staleness: "<state>: <reason>"
rows_blocked_environment: 0
rows_blocked_finding: 0
rows_blocked_declared: 0
precondition_check: "roundfix spec check <slug> --strict"
precondition_reason: "<every refusing code and the sentence beside it>"
---
# QA Report
## Results
| # | Status | Provenance |
| - | --- | --- |
| 0 | blocked | precondition |
## Precondition refusal
- check: roundfix spec check <slug> --strict
- reason: <every refusing code and the sentence beside it>
```
Row `0` is the entire matrix: `blocked` because nothing ran, provenance
`precondition` because the stop is a refusal and not a measurement. Name every
refusing `SC-*` code in `precondition_reason`, code first and its sentence
after, joined with `; ` and collapsed onto one line, so a maintainer reading
the report knows whether to fix the Spec or the tree. Justify the row in prose
as a list, never as a second table under `## Results`. An empty Results table
is not an option: it refuses every later run on this report instead of on the
Spec, and the prescribed repair — materialize every planned row — is one a run
that never built a matrix cannot perform.
A gate that stopped here writes nothing else: no matrix, and no rows for the
checks that never executed.
When the command passes, record the clean result in the report's scope and
environment, but do not create QA matrix rows that repeat the authoring rules
in the table below. A completed or archived legacy Spec is exempt from forced
constraint backfill; keep every legacy artifact byte-identical and retain any
governance row that has no clean authoring result to replace it. Absence of a
Project Constraints section by itself is not proof of legacy status.
| Authoring rule removed from the QA matrix | `spec check` equivalent |
| --- | --- |
| The authored Task Graph parses, maps PRD promises to Tasks, and resolves declared repository paths. | The Task Graph load required by `SC-COVERAGE-UNTASKED`, plus `SC-COVERAGE-UNTASKED` and `SC-REF-UNRESOLVED` |
| PRD promises map into the TechSpec. | `SC-COVERAGE-UNMAPPED` |
| Every required Project Constraint row exists. | `SC-CONSTRAINT-MISSING` |
| A declared Project Constraint applicability has a reason. | `SC-CONSTRAINT-UNREASONED` |
| A Project Constraint row cites an operative source. | `SC-CONSTRAINT-SOURCE` |
| A cited tooling authorization names the Spec. | `SC-TOOLING-UNAUTHORIZED` |
| Applicable tooling authority declares bounded files. | `SC-TOOLING-UNBOUNDED` |
| A tooling authorization record states its grant in readable fields. | `SC-TOOLING-UNTYPED` |
| Every pending non-QA Task declaration of a Governed Path appears in the authorization record and each present Tooling authority row. | `SC-TOOLING-UNDECLARED` |
| Every pending non-QA Task that names a CLI surface also names a guide in that Task or its transitive dependencies. | `SC-CLI-UNDOCUMENTED` |
| Active ADR obligations are listed, related decisions are accounted for, and attributed claims match the cited record. | `SC-ADR-UNLISTED`, `SC-ADR-RELATED`, and `SC-CITATION-UNSUPPORTED` |
| Task requirements do not contradict each other, rehearsals are declared, and Verification can distinguish Task work from no work. | `SC-REQUIREMENT-CONTRADICTORY`, `SC-REHEARSAL-UNDECLARED`, and `SC-VERIFY-WORK-INDEPENDENT` |
| Emitted vocabulary is documented through the TechSpec's Vocabulary Contract. | `SC-VOCABULARY-UNDOCUMENTED` |
| Repository loop declarations and Finding, Rollup, archive, and promoted Backlog lifecycles agree. | `SC-LOOP-ORDER-DIVERGENT`, `SC-FINDING-LIFECYCLE`, `SC-ROLLUP-MEMBER`, `SC-ARCHIVE-LICENSE`, and `SC-BACKLOG-UNMOVED` |
A named detector in the checker's skipped list did not run. It is not an
equivalent, so retain the corresponding QA row. Do not extend this mapping by
analogy: applicability that the checker did not parse, missing tooling
authorization, outside evidence, Non-Goals, the report contract, current Task
status, and live control or chronology behavior stay in the gate unless a
named checker rule decides them.
- Keep the commit-dependent tooling audit as matrix rows. Identify every Task
that actually creates, edits, renames, moves, or deletes repository-tooling
configuration, scripts, ignore files, plugin declarations, or version pins.
The mechanical stage audits every Task commit between the delivery base and
the audited head, and its authorization audit table names each audited
commit. Audit by command only a Task commit in that range that the table does
not list and that changes a repository-tooling path. When the report's
mechanical skips name `Task commits of earlier Runs`, audit every Task commit
by command. Execute each command row against the actual paths from the
Daemon-owned Task commit and any current worktree delta, then resolve the
authorization, prerequisite-fix, and consequent-fix commits in chronological
ancestry. Use
`git diff-tree --no-commit-id --name-only -r <commit>` for every committed
change rather than trusting a reported file list.
- Report every post-commit authorization-shape problem together in the same
failed command row: missing, late, or untraceable authorization;
authorization or a prerequisite fix folded into the Task commit; a
consequent fix folded into or ordered before the change that caused it; a
claimed derived pin without reproducible evidence from the sanctioned
regeneration command; and every path outside the exact bounded list plus the
assigned Task file. Do not stop at the first problem and defer the rest to
another QA rerun. Once the matrix exists, a finding blocks only the rows that depend on it.
- QA writes only its report and evidence. It never changes Task status or Task
Graph dependencies.
- Require every dependency of the authored `qa` Task to be `completed`. If any
dependency remains incomplete, stop and list it. The Daemon owns the current
gate Task's status and settles it from the final verdict. Run a partial gate
only when the authored QA Task explicitly limits its scope; mark its scope
and final verdict `partial`. The mechanical stage's withholding of the Agent
Session before a matrix exists is unchanged: a blocking machine fact still
stops the gate before it builds a matrix. A declared matrix is a full gate and
can reach `pass`; declaring a matrix does not make the run partial. `partial`
stays reserved for a `qa` Task that explicitly limits its scope or for a
planned row the gate did not run.
- Identify the PRD's declared surfaces, user stories, core features, acceptance criteria, user-experience states, and Non-Goals.
- Read every entry under the PRD's `## Unreachable Acceptance` section before
the run. Treat each declaration as an author's claim to test, not as proof
and not as permission to omit the row. The gate may match a blocked row only
to one of these pre-run declarations; it must never declare unreachability
itself or infer a declaration from a failed attempt.
- Read each task's `## Result`. Credit a task-level criterion only when it
points to named, reproducible evidence and the static checks it depends on
pass. An unrelated static failure does not invalidate that evidence. Spend
live QA effort on assembled user journeys, cross-task seams, persistence,
failure behavior, and scope creep.
- On a rerun, keep every row the Daemon seeded as `carried (established by: …; head: …)`: it counts as passed and is not executed again. Execute every other row, starting with the rows the seeded `## Row carry-forward` section lists as `re-run`.
The scope is complete when coverage is complete and closed.
When a Requirement audits a Task's changed files against its declarations, a
path the Task file lists under `## Recorded paths` counts as declared and is
named in that row. A Governed Path still needs its authorization.
## 2. Build the QA matrix and open the report
Create a collision-safe report path before execution:
`docs/specs/<slug>/qa/qa-report-YYYY-MM-DD.md` for the day's first report, then
`qa-report-YYYY-MM-DD-NN.md` with a suffix one above the highest existing suffix
of that date for same-day reruns; an earlier gap is never filled. Numeric
same-day suffixes are the only allowed suffixes; never use a scope or build
slug. Resume an existing `status: in-progress` report only when it is for the
same build; otherwise create the next numeric sibling and preserve older
reports as history.
Read the Pull Request fact in the Roundfix QA prompt before planning Pull
Request journeys:
The Pull Request row uses the equivalent-evidence path by default. Under
ADR-0088, the authored gate runs before an Open Pull Request exists, so Specs
must not pay to rediscover that arrangement. Keep the row in the matrix and
record equivalent observed or supervised evidence for every control the Pull
Request would have exercised, alongside the row's environment cause:
- **approval** — who accepted the change, or that no acceptance was obtained;
- **checks and status** — the repository Verification and any required check,
run locally, with its result;
- **unresolved review threads** — that none stands, or which do;
- **Merge-Ready acceptance** — the Review Source evidence for the expected
head, or that none exists yet;
- **review-artifact ancestry** — the head the evidence belongs to, and that it
is the head the row is claiming for.
Never silently pass or omit the row. A control with no equivalent evidence keeps
the row blocked; ADR-0080 refuses to credit a journey without evidence, and an
enumerated control left unanswered is exactly that.
- When the fact names an Open Pull Request, those journeys are runnable.
Observe that Pull Request read-only through the existing `gh` and Review
Source boundaries. Check approval, checks and status evidence, unresolved
review threads, Merge-Ready acceptance, and the Daemon's separate
review-artifact commit ancestry where the Spec requires them. Never mutate
the Pull Request, resolve threads, commit, or push.
- When the fact says no Pull Request is open, record every Pull Request journey
as `blocked (environment: no open Pull Request)` and count it in
`rows_blocked_environment`. The pre-PR Pull Request row never decides a
qualifying partial and needs no Unreachable Acceptance declaration; name the
Pull Request row in its provenance. The Pull Request row's provenance item
may carry a note after `Pull Request row`. When the fact says the Pull Request could
not be resolved, the absence is unproven: record the cause as
`blocked (environment: Pull Request unresolved)` and never write it up as a
confirmed absence. Do not try to resolve a Pull Request from the Run
Worktree branch: that per-Run branch is never pushed and has no Pull Request
of its own.
### Coverage sources
Each numbered `qa` Task Requirement that starts with `MUST verify` or `MUST run` is a declared verification Requirement. Every other Requirement constrains how the gate runs.
When a `qa` Task has at least one declared verification Requirement, those
Requirements declare the matrix's coverage sources. A declared Requirement that verifies every Acceptance Criterion of named Tasks makes each of those criteria its own source.
Every matrix, declared or default, covers these non-waivable sources:
- the outside-evidence row;
- the Pull Request row, on its equivalent-evidence path;
- the repository Verification;
- each declaration under the PRD's `## Unreachable Acceptance` section;
- the frontend sweep, when `frontend` is declared.
A `qa` Task without a declared verification Requirement gets the bounded default.
Its coverage sources are:
- the PRD user stories and Goals;
- each non-QA Task's Acceptance Criteria;
- each declared intentional break;
- the non-waivable sources listed above.
A Non-Goal becomes a source only when it names an observable the Spec could
ship. Its row reads only the paths the Spec changed and the surfaces the
Non-Goal names.
### Coverage and provenance
Coverage is complete, closed and named:
- Every row names, in its provenance, each coverage source it covers.
- Every coverage source appears in at least one row's provenance.
- no row's provenance names a source outside the coverage sources.
- Rows may group several sources that one observation settles.
- No row's result is computed from other rows.
- No row re-checks a Mechanical Refusal Code.
For each row record the sources it covers in its provenance, the actor, entry
point, surface, steps, expected observable, independent confirmation,
persistence check, evidence path, and status `pending`. Order rows by user
impact and blast radius. Behavior probes for high-risk journeys stay in the
bounded default only. For each high-risk journey in that default, select 2-5
relevant probes: double submit, refresh or back navigation mid-action,
deep-link/reopen, invalid or out-of-order input, session expiry,
offline/reconnect, concurrent tabs, or locale/accessibility changes. Choose
probes that fit the feature; unrelated probes create noise.
Use the `qa` Task's own identifiers in provenance, such as "Requirement 3",
"Task 01 criterion 2" and "repository Verification".
### Row input declaration
Every row the Agent executes declares a non-empty, typed `inputs:` list in its
detailed evidence block. The row's inputs are fixed when it is planned as
`pending`, bounded to what the row reads. Place the block under a `### <row-id>`
heading whose row identifier matches the row's `#` cell in the Results table,
and use one fenced `yaml` block per row; a block under any other heading is
ignored and the row is never carried. Each entry has a `kind` and a `ref`:
```yaml
inputs:
- kind: repository_path
ref: <repository-relative path or glob>
```
Use one entry for every input the row's truth depends on:
| Kind | Use when |
| --- | --- |
| `repository_path` | The evidence depends on content at a literal path or glob in the current repository. |
| `external_repository` | The evidence depends on content or state in another repository. The row is never carriable. |
| `live_service` | The evidence depends on state observed from a live service. The row is never carriable. |
| `elapsed_time` | The evidence depends on elapsed time, age, duration, or a time window. The row is never carriable. |
| `commit_range` | The row reads Task commits, their authorization, or their changed-file scope. The row is never carriable. |
Declare conservatively: include every source the row reads, including every
source a built binary compiles from and the module manifest. Declare every
applicable kind. A mixed list containing any non-repository input is never
carriable. A row with no `inputs:` declaration or an empty list is invalid for
execution and must be re-observed, so carry-forward remains opt-in and
fail-closed.
A report without `inputs:` behaves exactly as it does today; existing rows,
counts, statuses, verdict rules, and report naming do not change. When a future
round closes, a `pass` row whose declaration carries only repository inputs,
whose ancestry is proven, and whose evidence is byte-identical at the later
head is materialized as `carried (established by: <report>; head: <sha>)` and
is not re-observed.
A row whose inputs grow after it ran is not carriable. Do not add inputs after
execution to make carry-forward eligible.
A gate reopened over a report written before rows declared inputs re-executes
every row that report recorded without `inputs:`; such a row is never
carriable. A row executed again in a new pass declares its inputs for that pass
when the pass plans it as `pending`. That is a new observation with its own
inputs, not inputs added after execution; never add `inputs:` to the earlier
report's rows.
The Agent never writes `evidence_snapshots`; the Daemon records them after the
turn. Rows naming the repository Verification or the Pull Request row are
always observed, and a row declaring `commit_range` is always observed.
One row is the Spec's outside-evidence row: the acceptance row that rests on
evidence originating outside the Spec's own artifacts — a repository the Spec
did not build, a measurement it did not design, or published literature. Record
in that row where its evidence came from, named precisely enough for a later
reader to reach the same source, so the result cannot be read as a rehearsal of
the Spec's own premise. When the Run sandbox denies network access to the
lookup, record the row as `blocked (environment: network denied: <host>)`, naming
the host the lookup tried, and keep an `outside-evidence row` item in its
provenance. This records that the source was not reached and never decides a
qualifying partial. Any other blocked outside-evidence row still blocks Pull
Request preparation. Count every blocked outside-evidence row in
`rows_blocked_environment`; never drop the row, and never satisfy it with
evidence the Spec authored. Task authoring never stalls on it — decomposition
records the blocked row and proceeds — so the obligation lands
here, at the gate, where the Spec is asked to account for it. See ADR-0104.
The plan is complete when every coverage source appears in at least one row's
provenance, no row's provenance names a source outside the coverage sources,
every chosen default probe has a reason, and the report contains the full
pending matrix.
## 3. Run static gates first
In a Daemon-assigned gate, the Daemon already ran the repository Verification and gives its result in the prompt; record that result and do not run the repository Verification again. In a standalone gate, run the repository's selected Verification. If the selected Verification cannot run at all, record the verification gate as blocked. A formatting, test, or build check that runs and fails is a `fail`, not a block — classify it with the code-caused and environment-caused distinction below before recording anything.
A timeout or intermittent failure of the repository Verification is recorded as
a failure, never an environment block. A claimed contention, a
non-reproducible run, or the same failure on the unchanged delivery target is not a proved environmental cause. Only a cause that stops the command from
running at all is environmental.
When a `qa` Task names an analyzer beyond the repository Verification, run it
over the changed packages. A diagnostic identical on the delivery target is
recorded as observed, naming the Spec that owns it, and does not fail the Spec.
A repository Verification failure stays blocking whether or not it is
pre-existing.
When a command fails, diagnose its source before continuing:
- **Code-caused:** Once the matrix exists, a finding blocks only the rows that depend on it. Record a finding and trace the failed check to the matrix
rows whose entry point, observable, or evidence depends on what failed.
Block only those implicated rows as `blocked (finding: <id> — waits on
<named failing check>)`; continue every unimplicated row whose result
remains valid. A failure may block the whole matrix only when every row
genuinely depends on it, such as a build failure that leaves no runnable
artifact; record that dependency on each row instead of asserting it once
for the matrix.
- **Environment-caused:** for checks other than the repository Verification,
prove the constraint with the error or an unchanged-base reproduction, record
affected rows as `blocked (environment: <cause>)`, continue checks that remain
valid, and apply the typed blocked-cause verdict rule in section 6.
The static gate is complete when it passes or every failure is classified,
every implicated row names the check it waits on, every environment-caused
block has concrete proof and an explicit unblocking requirement, and the
remaining rows are identified as safe to run.
## 4. Exercise real flows
Start the application through its production-like entry point. Record the build or commit, commands, URL or binary, data setup, credentials profile, and every parity deviation.
For each matrix row, repeat this loop:
1. Enter as the named actor through a real entry point.
2. Act through the public surface and observe the product's user-facing response.
3. Confirm the result through a fresh load, restart, deep link, second public endpoint/command, or another user-visible read path.
4. Capture evidence at the goal state and every divergence. Store screenshots and focused artifacts under `qa/evidence/<YYYY-MM-DD>-<run-slug>/`; keep exact commands and concise outputs in the report.
5. Update the row immediately. A stall, dead control, infinite spinner, or vanished state is a finding, not a reason to work around the product.
When a row cannot reach its expected observable, compare the row and the
proved cause with the pre-run declarations. Use `blocked (declared:
<criterion>)` only when the row matches a declared criterion and the observed
constraint matches that declaration's reason. A different circumstance stays
environment-blocked or finding-blocked under its actual cause. A blocked row
with no matching declaration keeps its existing cause and remains blocking
under the existing verdict rules.
If the gate can reach a declared row, run it normally and report the
declaration as a wrongly-declared-row finding. Do not accept, ignore, or remove
the declaration merely because the journey passes: the reachable journey is
evidence that the Spec's claim was wrong, and that finding prevents `pass`.
### Surface protocol
| Surface | Exercise | Minimum evidence |
| ---------- | ------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------- |
| `frontend` | Drive a real browser through each actor's journey; re-read interactive state after navigation or DOM changes. | Key-state and failure screenshots, independent confirmation after refresh/deep-link, console errors/warnings, failed network requests. |
| `backend` | Send real requests to the running documented API, including named failure modes. | Exact request, status, payload, and a public read that confirms side effects. |
| `cli` | Run the installed or built command exactly as an operator would. | Command, exit code, output, and a second command/read confirming state. |
| `data` | Apply the real migration or sync to a scratch database and rerun it. | Shape/row checks, preserved distinctions, side effects, and idempotency evidence. |
| `infra` | Apply or inspect through a safe scratch/dry-run path that matches deployment. | Command output, resulting configuration/state, and parity gaps. |
| `docs` | Follow the documentation as its intended reader without undocumented knowledge. | Commands attempted, result, and any missing or misleading step. |
### Frontend sweep
When `frontend` is declared, cover all of these in the matrix:
- **Viewport and input:** use named breakpoints or mobile 375×812, tablet 768×1024, and desktop 1440×900. Complete the primary flow at each size; check clipping, overlap, horizontal scrolling, keyboard-only operation, visible focus, and 200% zoom on key screens.
- **States and recovery:** exercise the PRD's empty, loading, success, validation, and error states. Async actions show prompt feedback, failures preserve input, and recovery offers a concrete next step.
- **Accessibility and language:** inputs and controls have accessible names, heading/focus order is coherent, color is not the only signal, images have appropriate alternatives, and product copy uses the spec's vocabulary.
- **Runtime health:** exercised flows leave no unexplained console errors/warnings or failed requests. Run a focused accessibility or Lighthouse audit when available; disclose when unavailable.
For a TUI, map the same sweep to small/large/degenerate terminal sizes, keyboard flows, focus/follow behavior, color-independent readability, aligned output, and clean interrupt handling.
## 5. Classify results and findings
Assign each row exactly one terminal status:
- `pass`: the expected observable, independent confirmation, persistence check, and evidence all exist;
- `fail`: observed product behavior contradicts the spec or a Non-Goal shipped;
- `blocked (environment: <cause>)`: the gate environment makes the journey
unreachable; include the proof, exact unblocking action, and equivalent
observed or supervised evidence when available;
- `blocked (finding: <id> — waits on <named failure>)`: a finding prevents the
journey from reaching the expected observable; name the failed check or
behavior the row waits on, then link the finding and affected evidence;
- `blocked (declared: <criterion>)`: the row is unreachable for the reason in
the matching pre-run Spec declaration; link the declaration, the proof that
the gate could not reach it, and the named human action that would satisfy
it;
- `skipped`: allowed only for an explicitly partial gate or a risk-based cut; state why and what remains unverified.
Classify each finding by user impact:
- `Blocks-Completion`: the actor cannot finish a value-delivering journey;
- `Data-Loss`: user data is destroyed, corrupted, or silently discarded;
- `Trust-Damage`: contradictory, inaccessible, or unrecoverable behavior undermines confidence;
- `Friction`: the journey completes with avoidable confusion, delay, or repetition;
- `Cosmetic`: presentation differs without affecting completion or trust.
For every failure, record the actor, journey step, expected and actual behavior, reproduction from the public entry point, evidence, impact tier, and affected rows. Search previous reports before naming a finding as new; mark a repeated or regressed symptom instead of splitting its history.
Finish the planned sweep before fixing code. A changed build invalidates remaining comparisons. After repairs, start a new run or clearly version the build, rerun the static gate, the failed flow, and one adjacent canary.
## 6. Close the report
Use this structure:
```markdown
---
spec: <slug>
date: YYYY-MM-DD
build: <audited-commit-or-artifact>
auditing_binary: "<version-and-build-identity>"
auditor_staleness: "<state>: <reason>" # state is current|stale|unknown; the reason names the signal that answered, such as commit ancestry or the declared tree version
user_flow_binary: "<the --version line of the binary that ran public-CLI rows>"
status: in-progress # in-progress | closed
verdict: pending # pending | pass | fail | partial
rows_blocked_environment: 0
rows_blocked_finding: 0
rows_blocked_declared: 0
surfaces: [frontend, backend]
---
# QA report — <feature>
## Scope and environment
<!-- Full or partial scope, app start commands, build, tools, data, actors, parity gaps. -->
## Static gate
<!-- Exact commands and results. -->
## Results
| # | Status | Provenance |
| - | --- | --- |
For each row, put the story or criterion, actor, surface, steps, and evidence
in a block below the table.
## Findings
<!-- One block per finding: impact, expected/actual, reproduction, evidence, affected rows. -->
## Blocked and skipped
<!-- Proof, unblocking action, and uncovered scope. Omit only when empty. -->
## Coverage
<!-- Stories and criteria passed/total; rows_blocked_environment, rows_blocked_finding, and rows_blocked_declared; probes and frontend sweeps attempted; Non-Goals checked. -->
## Final verdict
<!-- Written last: one actionable sentence, plus counts by status and impact tier. -->
```
Close with `status: closed` and apply the verdict mechanically:
- `fail` when any row failed or the gate found a wrongly declared row;
- `partial` when none failed and no declaration was wrong, but any row is
declared-blocked, finding-blocked, or skipped, the authored QA Task defines
a partial run, or an environment-blocked row lacks equivalent observed or
supervised evidence;
- `pass` when every runnable row passed, every environment-blocked row records
its cause and equivalent observed or supervised evidence, no row is
declared-blocked, finding-blocked, or skipped, no declaration was wrong, and
all evidence paths resolve. A nonzero
`rows_blocked_environment` count does not by itself prevent `pass`; a nonzero
`rows_blocked_finding` or `rows_blocked_declared` count does.
Set `rows_blocked_environment`, `rows_blocked_finding`, and
`rows_blocked_declared` to the exact number of rows with each blocked cause.
Keep all three keys in every closed report, including when any count is zero.
A report that carries the precondition refusal row from section 1 carries
`rows_blocked_precondition` as well, set to the exact number of `blocked` rows
whose provenance is `precondition`, plus `precondition_check` and
`precondition_reason`. A gate that reached its matrix writes none of these three
keys: the refusal is an added shape, not a fourth count every report owes.
The gate permits Pull Request preparation on `pass`, or on a qualifying declared `partial` as the QA settlement table defines. On other outcomes, state what must change or be verified before rerunning. In a daemon-assigned Roundfix QA step, write the report but never commit or push; the daemon owns the QA report commit. Daemon-assigned steps may also run sandboxed: when an operation outside the workspace fails with a permission error (writes to `$HOME`, nested tool state), classify it immediately as environment-caused, mark the affected row `blocked (environment: <error>)`, and move on — never retry-loop a sandbox denial — noting in the environment record which checks need a full-access session. For an outside-evidence lookup denied by the Run sandbox's network policy, write `blocked (environment: network denied: <host>)` with the attempted host and the `outside-evidence row` provenance item; record that the source was not reached, and apply the one-rule verdict described above: a Pull Request row with equivalent evidence for every control still allows `pass`, and without it the report closes `partial`, which qualifies when no other unmet row remains.
## Decision examples
- A UI says "Saved", but the record disappears after refresh: `fail`, `Data-Loss`; the optimistic message is not proof.
- The full suite passes, but real OAuth needs a human-controlled account and no equivalent supervised evidence exists: affected rows `blocked (environment: human-controlled account)`, overall `partial`, with exact human verification steps.
- The Spec declares that publishing a real release is unreachable, and the
gate proves that its hermetic environment cannot perform that irreversible
action: the matching row is `blocked (declared: publish the real release)`,
`rows_blocked_declared` is incremented, and the verdict is `partial`.
- The Spec declares a journey unreachable, but the gate can exercise it: run
the journey, record a wrongly-declared-row finding, and return `fail` even if
the journey's product behavior passes.
- A governance check fails but does not affect the runnable application's
behavior: block only the governance rows that wait on that named check and
continue the functional journeys.
- The outside-evidence row names repositories this environment does not hold:
record `blocked (environment: network denied: <host>)` with the attempted
host and an `outside-evidence row` provenance item when the Run sandbox denied
network access; this records that the source was not reached and never decides
a qualifying partial. Any other blocked outside-evidence row still blocks Pull
Request preparation. Count it in `rows_blocked_environment` — never substitute
a rehearsal the Spec authored.
- The prompt names an Open Pull Request and read-only observation proves approval, Merge-Ready acceptance, and review-artifact ancestry: pass those Pull Request journeys without commit, push, or Pull Request mutation authority.
- A task Result names a passing unit test, while the assembled browser journey also persists after refresh with screenshots: credit the task criterion and pass the user-story row from live evidence.
## Anti-patterns
- Declaring pass from tests, source inspection, route rendering, or optimistic UI alone.
- Writing the report only after the run, losing resumability and uncovered rows.
- Skipping failure states, behavior probes, or responsive checks because the happy path passed.
- Capturing hundreds of screenshots instead of goal states and divergences.
- Treating an environment denial as a code failure, or crediting it without a recorded cause and equivalent evidence.
- Softening a failure into a note, leaving rows pending, or claiming coverage without a matrix row.
- Fixing during the sweep and continuing as though every row ran against one build.