---
phase: 3
slug: printer-configuration
status: draft
nyquist_compliant: true
wave_0_complete: false
created: 2026-04-10
nyquist_audited: 2026-04-13
nyquist_auditor: Claude (gsd-executor, plan 08-03)
---

# Phase 3 — Validation Strategy

> Per-phase validation contract for feedback sampling during execution.

---

## Test Infrastructure

| Property | Value |
|----------|-------|
| **Framework** | pytest 8.x |
| **Config file** | none — discovered via standard pytest directory scan |
| **Quick run command** | `pytest tests/test_printer_crud.py -x -q` |
| **Full suite command** | `pytest tests/ -v` |
| **Estimated runtime** | ~5 seconds |

---

## Sampling Rate

- **After every task commit:** Run `pytest tests/test_printer_crud.py -x -q`
- **After every plan wave:** Run `pytest tests/ -v`
- **Before `/gsd:verify-work`:** Full suite must be green
- **Max feedback latency:** 5 seconds

---

## Per-Task Verification Map

| Task ID | Plan | Wave | Requirement | Test Type | Automated Command | File Exists | Status |
|---------|------|------|-------------|-----------|-------------------|-------------|--------|
| 03-01-01 | 01 | 1 | PRNT-01, PRNT-02 | integration | `pytest tests/test_printer_crud.py::test_create_printer_persisted -x` | ❌ W0 | ⬜ pending |
| 03-01-02 | 01 | 1 | PRNT-04 | integration | `pytest tests/test_printer_crud.py::test_create_printer_duplex -x` | ❌ W0 | ⬜ pending |
| 03-01-03 | 01 | 1 | PRNT-05 | integration | `pytest tests/test_printer_crud.py::test_create_printer_color_mode -x` | ❌ W0 | ⬜ pending |
| 03-01-04 | 01 | 1 | PRNT-06 | integration | `pytest tests/test_printer_crud.py::test_create_printer_paper_size -x` | ❌ W0 | ⬜ pending |
| 03-01-05 | 01 | 1 | PRNT-07 | integration | `pytest tests/test_printer_crud.py::test_create_printer_collate -x` | ❌ W0 | ⬜ pending |
| 03-01-06 | 01 | 1 | PRNT-09 | integration | `pytest tests/test_printer_crud.py::test_printer_survives_page_refresh -x` | ❌ W0 | ⬜ pending |
| 03-02-01 | 02 | 1 | PRNT-08 | integration | `pytest tests/test_printer_crud.py::test_printer_grouped_by_client -x` | ❌ W0 | ⬜ pending |
| 03-03-01 | 03 | 2 | PRNT-10 | integration | `pytest tests/test_printer_crud.py::test_printer_detail_shows_driver -x` | ❌ W0 | ⬜ pending |
| 03-01-XX | 01 | 1 | PRNT-03 | manual | Manual browser test | n/a | ⬜ pending |

*Status: ⬜ pending · ✅ green · ❌ red · ⚠️ flaky*

---

## Wave 0 Requirements

- [ ] `tests/test_printer_crud.py` — stubs for PRNT-01 through PRNT-10 (integration tests)
- [ ] `tests/test_client_crud.py` — stubs for client creation and listing (can merge into test_printer_crud.py)

*Existing infrastructure covers framework and conftest.py — no new infrastructure needed.*

---

## Manual-Only Verifications

| Behavior | Requirement | Why Manual | Test Instructions |
|----------|-------------|------------|-------------------|
| Alpine.js auto-populates port name from IP | PRNT-03 | Browser-side Alpine.js reactivity cannot be tested with pytest TestClient | 1. Open /printers/new 2. Type "192.168.1.100" in IP field 3. Verify port field shows "IP_192_168_1_100" 4. Edit port field manually 5. Change IP field 6. Verify port field keeps manual value |

---

## Nyquist Record

> Audited 2026-04-13 by Claude (gsd-executor, plan 08-03). One row per Phase 3 success criterion derived from `milestones/v1.0-ROADMAP.md` Phase 3 goal + plan outcomes (PRNT-01..10), cross-checked against `03-VERIFICATION.md` (10/10 observable truths verified 2026-04-10, 9 automated + 1 human-needed on PRNT-03) and `REQUIREMENTS.md` v1.0 PRNT-0x block. Evidence cites committed tests, source lines, and the dated VERIFICATION report. Status values: `pass` / `fail-fix-v1.1` / `deferred-v1.2` / `wont-do`.
>
> **Phase 3 goal (v1.0-ROADMAP.md):** *"Technicians configure all printer parameters, assign printers to clients, and regenerate saved configs without re-uploading drivers."*

| # | Success Criterion | Observable Check | Evidence | Status | Notes |
|---|-------------------|------------------|----------|--------|-------|
| 1 | **PRNT-01** — User can set a printer display name | `pytest tests/test_printer_crud.py::test_create_printer_persisted` verifies `name` field posted to `POST /printers` is persisted and rendered in `GET /printers` | `tests/test_printer_crud.py::test_create_printer_persisted`; `imptune/api/printers.py` `POST /printers` handler (commit 356c2ee); `imptune/templates/partials/printer_form.html` `name` input; 03-VERIFICATION.md row 1 (2026-04-10) | pass | |
| 2 | **PRNT-02** — User can set a printer IP address or hostname | `pytest tests/test_printer_crud.py::test_create_printer_persisted` includes `ip_address` field in POST body; persisted value round-trips through `GET /printers` | `tests/test_printer_crud.py::test_create_printer_persisted`; `imptune/api/printers.py` POST handler (`ip_address: str = Form(...)`); `printer_form.html` IP field with `x-model`; 03-VERIFICATION.md row 1 | pass | |
| 3 | **PRNT-03** — System auto-suggests port name from IP (user can override, manual edits preserved) | `pytest tests/e2e/test_port_autofill.py` — real Chromium via Playwright, types IP into form, asserts port field auto-fills with `IP_x_x_x_x`, then edits port manually, changes IP, asserts manual value preserved | `tests/e2e/test_port_autofill.py` (Phase 9 UX-02 commit 322fc20 test, 37a06da docs); `imptune/templates/partials/printer_form.html` Alpine.js `x-data`/`x-model`/`portEdited` guard (commit 356c2ee); `.planning/phases/09-ux-tech-debt-closure/09-02-SUMMARY.md` (UX-02 complete 2026-04-13); REQUIREMENTS.md v1.1 UX-02 = Complete; 03-VERIFICATION.md row 2 | pass | **Historical gap closed via Phase 9 / UX-02 fixing commits.** At v1.0 audit (03-VERIFICATION.md 2026-04-10) this was the sole `NEEDS HUMAN` truth — Alpine.js reactivity cannot execute inside FastAPI TestClient. Phase 9 Plan 02 added a Playwright headless-chromium live-browser e2e test that exercises the @input handler and the `portEdited` manual-edit lock. Closed as `pass` citing the fixing commits, consistent with the 08-01 (row 14 → Phase 10 RTVAL-01) and 08-02 (row 6 → Phase 9 UX-01) precedents. |
| 4 | **PRNT-04** — User can set duplex mode | `pytest tests/test_printer_crud.py::test_create_printer_duplex` posts `duplex_mode=LongEdge` and verifies persisted value | `tests/test_printer_crud.py::test_create_printer_duplex`; `printer_form.html` `duplex_mode` select (OneSided/LongEdge/ShortEdge); `imptune/api/printers.py` POST handler mapping; 03-VERIFICATION.md row 1 + PRNT-04 coverage row | pass | |
| 5 | **PRNT-05** — User can set color vs. grayscale default | `pytest tests/test_printer_crud.py::test_create_printer_color_mode` posts form without the `color_mode` checkbox and asserts the persisted value is `False` (checkbox-to-bool conversion) | `tests/test_printer_crud.py::test_create_printer_color_mode`; `printer_form.html` `color_mode` checkbox; `imptune/api/printers.py` checkbox-to-bool conversion in POST handler; 03-VERIFICATION.md PRNT-05 row | pass | |
| 6 | **PRNT-06** — User can set paper size | `pytest tests/test_printer_crud.py::test_create_printer_paper_size` posts `paper_size=A4` (plus Letter/Legal variants) and verifies persisted value | `tests/test_printer_crud.py::test_create_printer_paper_size`; `printer_form.html` `paper_size` select (A4/Letter/Legal); 03-VERIFICATION.md PRNT-06 row | pass | |
| 7 | **PRNT-07** — User can set collate on/off | `pytest tests/test_printer_crud.py::test_create_printer_collate` posts form without `collate` checkbox and asserts persisted value is `False` | `tests/test_printer_crud.py::test_create_printer_collate`; `printer_form.html` `collate` checkbox; `imptune/api/printers.py` checkbox-to-bool conversion; 03-VERIFICATION.md PRNT-07 row | pass | |
| 8 | **PRNT-08** — User can assign a printer to a client/tenant label | `pytest tests/test_printer_crud.py::test_printer_grouped_by_client` creates printers under distinct clients and asserts `GET /printers` renders `<h3>` group headers per client (LEFT OUTER JOIN) | `tests/test_printer_crud.py::test_printer_grouped_by_client`; `imptune/api/printers.py` `_render_printer_list` uses `Printer.select(Printer, Client).join(Client, JOIN.LEFT_OUTER)`; `printer_form.html` client select; `imptune/templates/partials/printer_list.html` group headers; 03-VERIFICATION.md rows 3 + 4 | pass | |
| 9 | **PRNT-09** — Printer configurations are persisted in SQLite across sessions | `pytest tests/test_printer_crud.py::test_printer_survives_page_refresh` (+ `test_create_printer_persisted`) — posts printer, re-queries via `GET /printers`, asserts DB count and rendered HTML both show the record | `tests/test_printer_crud.py::test_create_printer_persisted`, `::test_printer_survives_page_refresh`; `imptune/db/models.py` Printer model (Phase 1 schema); 03-VERIFICATION.md row 4 | pass | |
| 10 | **PRNT-10** — User can regenerate a package from saved config without re-uploading drivers (Phase 3 scope: detail page loads full config with driver FK intact; regenerate button placeholder until Phase 4) | `pytest tests/test_printer_crud.py::test_printer_detail_shows_driver` — `GET /printers/{id}` returns full-page detail with all 7 config fields and driver name (`HP Universal`) pre-populated; `::test_printer_detail_no_driver` covers missing-driver fallback | `tests/test_printer_crud.py::test_printer_detail_shows_driver`, `::test_printer_detail_no_driver`; `imptune/api/pages.py` `GET /printers/{id}` uses `.switch(Printer).join(Driver, JOIN.LEFT_OUTER)`; `imptune/templates/printer_detail.html` (disabled regenerate button, Phase-4 scoped); 03-VERIFICATION.md rows 6 + 7 + 8 | pass | Full regeneration workflow is a Phase 4 deliverable per 03 plan scope. Phase 3 scope = config retrievable with driver FK intact + placeholder button. Verified SATISFIED (partial) in 03-VERIFICATION.md; the "partial" refers to the Phase-4 button wiring, not a Phase 3 gap. |

**Audit outcome:** 10/10 rows `pass`. No `fail-fix-v1.1`, `deferred-v1.2`, or `wont-do` rows. Phase 3 is Nyquist-compliant: every PRNT-0x success criterion has exactly one observable check with cited, committed evidence. The PRNT-03 Alpine.js IP→port live-browser gap (only `NEEDS HUMAN` truth in 03-VERIFICATION.md) is captured as row 3 and closed via Phase 9 / UX-02 Playwright e2e fixing commits — fully honoring the CONTEXT.md locked-decision pattern.

---

## Validation Sign-Off

- [ ] All tasks have `<automated>` verify or Wave 0 dependencies
- [ ] Sampling continuity: no 3 consecutive tasks without automated verify
- [ ] Wave 0 covers all MISSING references
- [ ] No watch-mode flags
- [ ] Feedback latency < 5s
- [ ] `nyquist_compliant: true` set in frontmatter

**Approval:** pending
