# Read-only production UAT and responsive evidence

Use this pattern when a release must be exercised against a private production runtime without confirming imports or changing business data.

## 1. Freeze the candidate and read-only boundary

- Verify the deployed Git commit, service units/processes, runtime schema, and intended private route before browser work.
- State explicitly which actions are allowed: HTTP GETs, health checks, page loads, and preview-only parsing when the preview contract is proven non-persistent.
- Prohibit confirm/commit/import buttons, automatic ingestion, external provider refreshes, and corrective writes.
- Keep production source files outside the repository and never print raw records, account names, identifiers, or totals in evidence.

## 2. Establish sentinels before UAT

Capture two complementary forms of evidence:

1. **Material business digest** over canonical financial tables, excluding only known volatile presentation metadata.
2. **Strict side-effect sentinels** for fields and tables that a read-only request might accidentally touch, including:
   - account `updated_at` values,
   - ingestion batch/item/audit counts,
   - position, cash, valuation, and performance snapshot counts,
   - any source-specific status rows.

Discover table and key names from the live schema (`sqlite_master`, `PRAGMA table_info`) rather than guessing them from service-layer names. Store only hashes/counts, not row contents.

A material digest alone is insufficient when it intentionally excludes volatile timestamps: pair it with a strict hash over those timestamps so a GET-side-effect regression cannot hide.

Treat alert/quality metadata, occurrence counters, caches, and `last_seen` fields as side effects too. Snapshot immediately before the **final** direct-API plus browser pass, then call every GET used by page startup—not only the newly added endpoint. A new endpoint can be perfectly read-only while a legacy selector invoked by the same render still writes. Compare canonical ordered-row hashes as well as counts, because unchanged counts do not detect updates to existing rows.

If an early UAT already mutated metadata, do not move the baseline forward and call the whole release unchanged. Preserve and disclose the earlier mutation, localize the writer, and prove that the fixed candidate causes no further change. Do not blindly reset alert/audit metadata merely to make a digest pass.

## 3. Network and private-route checks

Check independently:

- loopback backend health,
- private frontend root and target route,
- private reverse-proxy backend health,
- MagicDNS resolution and Tailscale backend state,
- HTTP status, final URL, and content type.

For same-origin frontend/API deployments, absence of `Access-Control-Allow-Origin` is not itself a CORS defect. Confirm that browser requests complete and that the console/network log has no CORS errors instead.

## 4. Preview-only real-source probe

When the preview endpoint is contractually non-persistent:

1. Submit the real external file through the deployed private route.
2. Return only aggregate metadata: recognized document class, record counts, cash-component count, status, and reason-code classes.
3. Never emit payload bytes or detailed records.
4. Do not call confirm, even to test idempotency; confirm belongs on an isolated DB copy.
5. Immediately compare strict sentinels and material digest.

A `partial` preview may be correct for baseline-only statements, ambiguous identifiers, unsupported rows, or missing transaction history. Do not relabel it as a failed parser if the reason codes match the source contract.

## 5. Responsive browser matrix

Exercise at least:

- desktop (for example 1440×1000),
- tablet/iPad (for example 1024×768),
- mobile (for example 390×844).

For each viewport verify:

- app readiness is visibly green,
- no page-level horizontal overflow,
- navigation remains reachable,
- cards and labels remain readable,
- the actual changed/deep section is visited—not only the page header,
- file controls and preview buttons are present and within the viewport,
- no overlap, nested unusable scroll area, clipped controls, or per-digit numeric wrapping,
- zero application `console.error`, uncaught exceptions, and failed API requests.

### Async screenshot pitfall

A screenshot taken immediately after navigation can capture the loading state and falsely show the backend or runtime DB as unavailable. Wait on an explicit readiness marker or bounded application-specific condition. `--virtual-time-budget` can help for deterministic headless captures, but the screenshot must still be checked for the final ready state.

### Layout truthfulness

`scrollWidth <= clientWidth` proves only that the page does not overflow horizontally. It does **not** prove usability. A tablet grid can stay within the viewport while values wrap one or two digits per line. Conversely, `white-space: nowrap` can turn that wrap into text crossing card boundaries. Verify the requested card arrangement at the exact acceptance width, full value visibility, and pixel-level containment; fix with enough card width, a later high-column breakpoint, or responsive typography rather than hiding/truncating values. Record any remaining readability issue as a visual finding even if the technical overflow check passes.

A production UAT finding remains a release gate after the feature PR has merged. Reproduce it, add a focused regression, verify against a production DB copy when data semantics are involved, publish the smallest hotfix PR, deploy its exact merged SHA, and rerun direct GETs, responsive screenshots, and the DB digest. If correctness forces additional CI runs beyond an earlier process target such as “one full CI,” disclose the deviation precisely rather than claiming the original count was met.

### Browser-context pitfall

Do not assume a navigation tool call and a later console-evaluation call still target the same tab/context. Before trusting DOM evidence, evaluate `location.href`, `document.title`, and a readiness marker. If the context is `about:blank` or stale, navigate again rather than treating missing DOM nodes as a product defect.

## 6. Final no-write proof

After all HTTP calls, preview probes, and browser reloads:

1. Recompute the material digest.
2. Compare strict timestamp hashes.
3. Compare ingestion/snapshot/audit counts.
4. Perform one additional full browser reload bracketed by strict pre/post hashes to prove that the read-only page itself does not update account metadata.
5. Confirm services remain active and recent logs contain no application traceback/critical/HTTP-500 events.

The final report should distinguish:

- **passed**,
- **passed with non-blocking visual finding**, and
- **blocked/failed**.

Do not call a responsive matrix fully green if a visible readability defect remains.

## 7. Privacy cleanup

Production screenshots can contain sensitive balances and account names even when the final report does not. Keep them in a private temporary directory, analyze only layout, and delete them after extracting aggregate evidence unless the user explicitly requested delivery or durable retention.

Retain only non-sensitive hashes, counts, status codes, viewport results, and the private route URL appropriate for the owner.
