# Verifier: Source-Fidelity & Integrity Gates (full procedures)

Extracted from `agents/gsd-verifier.md` Step 7 (anti-pattern scan) to respect the agent byte budget. These are the FULL BLOCKER rules and read-only procedures behind the compact "Source-fidelity & integrity gates" block — same gates, same severities. Run `gsd_run query project mode` in Step 1 (Origin / Design-input / Code-quality) — it drives the Design-fit and Mode-fit gates.

**Reward-hacking gate (per `engineering-standards.md`):** A check that was made to pass by tampering, not by working code, is a FAILED verification — not a convenience. Treat each of these as a 🛑 BLOCKER (`status: gaps_found`):
- A test that was **weakened, skipped, or made trivially-passing** (`.skip`/`xit`/`@pytest.mark.skip`/`#[ignore]`/`t.Skip`, an assertion deleted or loosened, a body replaced with `assert True`/`expect(true)`, an `expected` value rewritten to match wrong output).
- A **hardcoded expected output** that makes a behavior or gate pass without real computation.
- A **test-file edit accompanying a non-test task** — when the phase's stated work is not "add/change tests" yet `*-SUMMARY.md` key-files or the commits touch test files, flag it to INVESTIGATE: confirm the test still *can* fail and asserts real behavior; if it was loosened to pass the implementation, it is a BLOCKER.

```bash
git diff ${DIFF_BASE:-HEAD~1}..HEAD -- '*test*' '*spec*' 2>/dev/null | grep -nE '^\-.*(assert|expect|EXPECT)|\+.*(skip|xit|\.only|assert\s*\(?\s*[Tt]rue|return;?\s*//.*skip)'
```

**Architecture-fit gate (per `engineering-standards.md`):** "wired and tamper-free" is not enough — judge the *shape*, not just that it runs.
- **Floor check (applies ALWAYS, even with no ADR):** the universal floor is dependency inversion at the true external boundaries (DB/3rd-party/IO) + a Functional-Core/Imperative-Shell shape so the logic is testable without the real services. An implementation that reaches into the DB/3rd-party from everywhere, untestable without standing up real infrastructure, **skipped the floor** — a 🛑 BLOCKER (`status: gaps_found`) *even on a simple Transaction-Script subdomain*. The floor is the cheap baseline, not ceremony.
- **Rung-fit check (when `.planning/adr/*.md` or `DOMAIN-MODEL.md` exists):** the implementation must match the ADR's chosen rung. Both directions: (a) **under-engineering** — thin CRUD / patch-around where the rung mandates a Domain Model / hexagonal ports / CQRS / event-driven flow (a working-but-wrong-shape impl is a 🛑 BLOCKER, not a passing phase); (b) **over-engineering** — ports/aggregates/CQRS/speculative layers on a subdomain the ADR marked Transaction Script (⚠️ Warning).

**Strategy-fit check (when the artifacts exist):** the implementation must honor `FRONTEND-ARCHITECTURE.md` (no business/validation/error-classification logic in components; server-state in the cache, not a client store; the FE side of the seam) and `SECURITY-STRATEGY.md` (authz enforced server-side per the model; the ASVS-level controls + security DoD present). A feature that ships but violates the FE architecture or skips its security/observability DoD is **incomplete** — 🛑 by blast radius, else ⚠️. Also honor `.planning/DOMAIN-MODEL.md` (subdomain classification — implemented code must not silently reclassify a core subdomain's approach) and `.planning/TEST-STRATEGY.md` (per-subdomain test levels — the phase's tests must sit at the strategy's levels) when present. A violation is a gap, same severity as the FE/security fit checks — set `status: gaps_found` and list it under `gaps`.

**Design-fit check (when PROJECT.md `## Mode` records a provided design — `gsd_run query project mode` → `has_provided_design: true`):** diff the built **observable shape** against the in-repo design oracle (`.planning/DESIGN-INVENTORY.md`'s user-facing-field list, or the phase UI-SPEC — **never the raw design**, which you can't fetch; see `@~/.claude/gsd-core/references/design-ingestion.md` and `exploration-and-adaptability.md` § Source precedence).
- **Trigger — fire on any field that BACKS a covered surface, not only when UI files changed.** The original slip was a backend/data-schema phase (one `address` input → invented `street`/`city`/`state`/`zip`) with **no UI files in its change region**, which a Read-only verifier wrongly judged "touches no user-facing fields → SKIP." So the trigger is: **the phase creates or changes any field that backs a design-covered surface — schema column, migration, DTO/response contract, OR UI field.** A persistence/contract phase backing a covered surface fires this check even with zero UI files touched.
- **Read-only procedure (runnable from SUMMARY key-files alone, no app):**
  1. From the SUMMARY key-files (Step 7) + Step 4b/Step 5 source list, take the files this phase created/changed that define a **field set**: a Prisma/SQL migration or model, a DTO/schema/serializer, an API response type, or a form/component field list.
  2. Extract the field names from each — e.g. `grep -oE '^\s+\w+' prisma migration columns`, DTO property names, serializer keys, form `name=`/`register('…')`.
  3. For each extracted field, classify against the oracle's **User-facing fields** table: it must match a `design`/`requirement` row's field name, OR map to one via that row's **Backs (surface field)** column (a column-split/VO part legitimately backing a single user-facing field), OR be an oracle `internal` row.
  4. A built field that **surfaces** (is returned in a response a covered surface renders, or required at a covered-surface input) yet matches **no `design`/`requirement` row and is not declared `internal`+`Backs` in the oracle** is an **invention → 🛑 BLOCKER** (the address-failure guard: one `address` input must not become four required user-facing fields). A design-required field that is **missing** from the built shape → 🛑 BLOCKER.
- An internal value object / normalization / column-split that does **not** change the observable shape is fine — a split into `internal` columns that all **Backs** the same single user-facing field is faithful, not an invention (a value faithful to the design that exceeds house defaults is ℹ️, per the `ui-checker` design-override).
- **False-positive guard — when this still correctly SKIPs:** a phase whose changed fields back **no** covered surface (pure infra/config/logging/build, or a table/DTO for a surface the oracle marks not-covered), or a phase that touches **no** field set at all → **SKIP** (record "design-fit: N/A — no field backs a covered surface", never a silent pass). The split columns being internal is not a slip *as long as they all Backs one covered user-facing field*; new **required user-facing** fields with no design/requirement backing is the slip.

**Mode-fit check (PROJECT.md `## Mode` — the build-loop contract in `@~/.claude/gsd-core/references/brownfield-adaptation.md`):** read each change-region's disposition from the PLAN (sourced from `LEGACY-INVENTORY.md`'s gap map):
- **Origin = rewrite-refactor**, regions dispositioned *preserve*/*refactor* → **Parity:** characterization/parity evidence must exist (old behavior pinned, the same suite green against the new code). Behavior drift with no recorded, approved change is a 🛑 BLOCKER. A region the new design *changes* (gap-map `design-delta`) is **parity-EXEMPT** — the Design-fit check above governs it instead (a design-mandated flow change is not drift).
- **Code-quality = vibe-coded-to-harden** → **intent-hardening, NOT behavior-parity:** verify the intent is preserved + hardened to the ADR rung + the universal floor + the security/test DoD. Behavior differing from the prototype is *expected* (its bugs were fixed) — do **not** require parity against the prototype's behavior.
- **Coverage (when `LEGACY-INVENTORY.md` exists):** the "never lose a feature" gate holds — every inventoried capability in scope maps to delivered code/tests or an explicitly recorded drop. A silently-dropped capability is a 🛑 BLOCKER.
