Rewrite TEST-SPEC: satisfy issue #20

- Add Section 1: case ID naming convention (all prefixes and abbreviations defined)
- Add Section 2: how cases are executed (paths, tools, staging for unit/contract/integration/E2E/ops)
- Restructured every case table to three columns: what must happen, where the rule lives, how verified
- Replaced cadence column with when-written in pyramid overview; cadence summary preserved
- Fixed all exit-evidence lines: agent-written test automation replaces Ty-driven curl
- Expanded all acronyms on first use; no magic acronyms in case descriptions
- Removed Ty-driven manual testing; automation is the verification path
This commit is contained in:
2026-09-07 04:17:14 +00:00
parent aa43fa7628
commit e287f5c1c3
+232 -169
View File
@@ -1,257 +1,320 @@
# TEST-SPEC — Engineer Bar
# TEST-SPEC — Engineer Test Specification
Status: SPEC · engineer bar · SoT gitea-atd main · 2026-09-07
Purpose: this document defines every affirmative test case for the SlabUploader through PROD. Each case states what must happen, where the rule lives, and how it is verified. The human UAT walk at `docs/UAT-PRACTICE.md` remains the walkable practice script. Tests are organized by pyramid layer and phased by gate. The full suite passes with inference OFF.
This document defines every affirmative test case for the SlabUploader, from Phase 0 through production. Each case states **what must happen**, **where the rule lives**, and **how it is verified**. The human walkable practice script is at `docs/UAT-PRACTICE.md` and is left untouched by this file.
---
## Pyramid overview
## 1. Case ID naming convention
| Layer | Cadence | Primary SoT |
Case IDs follow the pattern `<prefix>-<number>`. The prefix identifies the test layer or gate:
| Prefix | Meaning | Examples |
|---|---|---|
| Unit | TDD before UI consumes | TECH-SPEC-PIPELINE, issue #17 |
| API / contract | with-code | OPENAPI, DATA-MODEL, issue #5/#11 |
| Integration | with-code | CONTENT-WOO, issue #2/#3/#14/#18 |
| UI / E2E | with-code | UX, TECH-SPEC, issue #8/#9/#15 |
| Ops | with-code | DEPLOYMENT, IMPL-PLAN Phase 6 |
| `TV` | Test Vector from `docs/TECH-SPEC-PIPELINE.md` §8 | `TV-1`, `TV-10` |
| `V` | Validation rule (shared validation module) | `V-1`, `V-3` |
| `AES` | AES-GCM encryption / decryption | `AES-1`, `AES-2` |
| `H` | Health endpoint | `H-1` |
| `D` | Draft store (slab creation / persistence) | `D-1` |
| `GC` | Gate C case (early Phase 1, claimable draft listing) | `GC-1`, `GC-5` |
| `W` | WooCommerce mock integration | `W-1`, `W-5` |
| `INF` | Inference endpoint probe / toggle | `INF-1`, `INF-4` |
| `UI` | UI behavior (capture, knobs, overlay, ready gates) | `UI-1`, `UI-5` |
| `UIE` | UI end-to-end (review, publish, settings, full flow) | `UIE-1`, `UIE-10` |
| `API` | API contract (routes, concurrency, error responses) | `API-1`, `API-10` |
| `TAX` | Taxonomy sync / anchor / species cache | `TAX-1`, `TAX-3` |
| `C1` | Call 1 (vision) inference call | `C1-1`, `C1-3` |
| `C2` | Call 2 (content/text) inference call | `C2-1`, `C2-4` |
| `OFF` | Inference OFF path (full suite without inference) | `OFF-1`, `OFF-2` |
| `OPS` | Operations / deployment / backup | `OPS-1`, `OPS-5` |
| `INS` | Instrumentation / observability | `INS-1`, `INS-4` |
| `E2E` | End-to-end browser test (full user flow) | `E2E-1`, `E2E-5` |
Other abbreviations used in this document:
- **sqft**: square feet
- **bdft**: board feet
- **U2Net**: Universal 2 Network, a background-removal model (deferred from the proof of concept)
- **API**: application programming interface
- **E2E**: end-to-end
- **UI**: user interface
- **POC**: proof of concept
- **SKU**: stock keeping unit
- **Woo**: WooCommerce, the WordPress e-commerce plugin
- **HTTP/HTTPS**: Hypertext Transfer Protocol (plain / secure)
- **AES-GCM**: Advanced Encryption Standard in Galois/Counter Mode
- **UUID**: universally unique identifier
- **JSON**: JavaScript Object Notation
- **LLM**: large language model
---
## 2. How cases are executed
Test automation lives in the repo under `backend/tests/` and `frontend/e2e/`. The harness uses:
- **Unit tests**: `backend/tests/unit/` (pytest) and `frontend/src/__tests__/` (Vitest). These run against pure modules with no network calls.
- **API contract tests**: `backend/tests/contract/` (pytest + `httpx` TestClient). These hit the FastAPI app through its test client, asserting request/response shape, status codes, and revision handling.
- **Integration tests**: `backend/tests/integration/` (pytest + `httpx` TestClient + `respx` WooCommerce mock). These verify Woo create, duplicate SKU, taxonomy sync, stale-field recovery, and settings encryption round-trips without touching a real store.
- **UI end-to-end tests**: `frontend/e2e/` (Playwright). These exercise the full browser flow: capture, mask knobs, axis confirm, numbers entry, review, and publish. They run against a started Docker Compose stack with the FastAPI test server and mocked Woo endpoints.
- **Ops / deployment tests**: `deploy/tests/` (shell scripts). These verify compose health, backup/restore, and Caddy routing.
Test dependencies are pinned in `backend/tests/requirements-test.txt` and `frontend/package.json` (devDependencies). CI runs the full suite before any merge; the suite must pass with inference disabled.
---
## 3. Test pyramid overview
Each row states the test layer, when tests in that layer are written, and what document drives the expected behavior.
| Layer | When written | Primary source of truth |
|---|---|---|
| Unit | TDD: unit tests written before the feature code exists | `docs/TECH-SPEC-PIPELINE.md`, `docs/AGENTS.md` |
| API / contract | Alongside the backend feature code | `docs/OPENAPI.md`, `docs/DATA-MODEL.md` |
| Integration | Alongside the backend feature code | `docs/CONTENT-WOO.md`, `docs/ARCHITECTURE.md` |
| UI / E2E | Alongside the frontend feature code | `docs/UX.md`, `docs/TECH-SPEC-PIPELINE.md` |
| Ops / deployment | Alongside deployment configuration | `docs/DEPLOYMENT.md`, `docs/IMPL-PLAN.md` Phase 6 |
**Test-driven development (TDD)**: write the failing unit test first, then write the minimum code to pass it. This applies to all deterministic core functions: bdft math, pricing, rounding, image crop, and validation rules.
**Tests-with-code**: API contract tests, integration tests, and UI end-to-end tests are written in the same commit that implements the feature. Each feature commit includes its tests and shown execution output in the commit message.
---
## Phase 0 / Gate B — Hybrid skeleton + deterministic core
**Exit evidence:** Ty confirms TV-3, TV-6, TV-7, TV-10 output on real fixtures; `GET /api/health` up; AES round-trip; draft store stub.
**Exit evidence**: agent-written test automation confirms TV-3, TV-6, TV-7, and TV-10 output on real fixtures; `GET /api/health` returns 200; AES round-trip passes; draft store stub persists and retrieves.
### Unit — deterministic core (TDD)
### Unit — deterministic core (test-driven)
| Case | Expected behavior | SoT | Cadence |
| Case | What must happen | Where the rule lives | Verified by |
|---|---|---|---|
| TV-1 | Green-sheet synthetic photo: interior green-ish patch that does not touch the border stays after chroma-key; U2Net is not invoked | TECH-SPEC §3.1, §3.4 | TDD |
| TV-2 | Black-sheet synthetic photo: dark interior wood that does not touch the border stays after threshold; flood-fill isolates border-connected pixels only | TECH-SPEC §3.1, §3.4 | TDD |
| TV-3 | Slab 96" long, 1.5" thick, mask 0.64 sqft → bdft = 0.96. Identity: 1 sqft at 1" = 1 bdft. No divide-by-12 | TECH-SPEC §4.6 | TDD |
| TV-4 | Width samples at 6" stations on a 96" length axis report min, max, avg perpendicular to the confirmed axis | TECH-SPEC §4.4 | TDD |
| TV-5 | Slab with one square end and one 45° cut: length axis follows prevailing length, widths stay perpendicular after user confirm | TECH-SPEC §4.1, §4.2 | TDD |
| TV-6 | 3:4 crop, slab centered, 80% fill at extremes, transparent PNG, shorter side ≥ 1600 when source allows | TECH-SPEC §5, §7.1 | TDD |
| TV-7 | Undersized source: after 80% crop shorter side would be below 1600 and source cannot supply it → warn, no upscaling, photo not publishable until retake | TECH-SPEC §5, §7.1 | TDD |
| TV-8 | Pricing: bdft 0.96, species $12.50/bdft → rec 12.00; override 150.00 stored as 150.00; no rule → rec empty, manual 99.99 stored | TECH-SPEC §6, §8 TV-8 | TDD |
| TV-9 | Thickness 1" → Woo range rounds up (e.g. 1"–1 1/2") using synced attribute term, not a hardcoded label | TECH-SPEC §8 TV-9, DATA-MODEL slabs | TDD |
| TV-10 | Rounding: 96.04" → 96"; sqft 0.641 → 0.64; bdft 11.525 → 11.53; price 12.345 → 12.35. Half-up to 2 decimals | TECH-SPEC §8 TV-10 | TDD |
| TV-1 | Green-sheet synthetic photo: interior green-ish patch that does not touch the border stays after chroma-key; U2Net is not invoked | `docs/TECH-SPEC-PIPELINE.md` §3.1, §3.4 | Unit test with synthetic image fixture |
| TV-2 | Black-sheet synthetic photo: dark interior wood that does not touch the border stays after threshold; flood-fill isolates border-connected pixels only | `docs/TECH-SPEC-PIPELINE.md` §3.1, §3.4 | Unit test with synthetic image fixture |
| TV-3 | Slab 96" long, 1.5" thick, mask 0.64 square feet → bdft = 0.96. Identity: 1 square foot at 1" thick equals 1 bdft. No divide-by-12. | `docs/TECH-SPEC-PIPELINE.md` §4.6 | Unit test, shown output on real fixtures |
| TV-4 | Width samples at 6" stations on a 96" length axis report min, max, and average perpendicular to the confirmed axis | `docs/TECH-SPEC-PIPELINE.md` §4.4 | Unit test with fixture mask |
| TV-5 | Slab with one square end and one 45° cut: length axis follows prevailing length; widths stay perpendicular after user confirm | `docs/TECH-SPEC-PIPELINE.md` §4.1, §4.2 | Unit test with angled-end fixture |
| TV-6 | 3:4 crop, slab centered, 80 percent fill at extremes, transparent PNG, shorter side ≥ 1600 when source allows | `docs/TECH-SPEC-PIPELINE.md` §5, §7.1 | Unit test, shown output on real fixtures |
| TV-7 | Undersized source: after 80 percent crop shorter side would be below 1600 and source cannot supply it → warn, no upscaling, photo not publishable until retake | `docs/TECH-SPEC-PIPELINE.md` §5, §7.1 | Unit test, shown output on real fixtures |
| TV-8 | Pricing: bdft 0.96, species $12.50/bdft → recommended price 12.00; override 150.00 stored as 150.00; no rule → recommended price empty, manual 99.99 stored | `docs/TECH-SPEC-PIPELINE.md` §6, §8 TV-8 | Unit test with pricing rules |
| TV-9 | Thickness 1" → Woo range rounds up (for example 1"–1 1/2") using synced attribute term, not a hardcoded label | `docs/TECH-SPEC-PIPELINE.md` §8 TV-9, `docs/DATA-MODEL.md` slabs | Unit test with mock taxonomy |
| TV-10 | Rounding: 96.04" → 96"; square feet 0.641 → 0.64; bdft 11.525 → 11.53; price 12.345 → 12.35. Half-up to 2 decimals. | `docs/TECH-SPEC-PIPELINE.md` §8 TV-10 | Unit test, shown output on real fixtures |
### Unit — shared validation (issue #17 substance)
### Unit — shared validation
| Case | Expected behavior | SoT | Cadence |
| Case | What must happen | Where the rule lives | Verified by |
|---|---|---|---|
| V-1 | SKU charset `[A-Z0-9-]{3,24}` accepted; values outside the charset rejected | TECH-SPEC §2, issue #17 | TDD |
| V-2 | Title, short_title, description: UTF-8 within Woo maxLength enforced at field exit and at submit | TECH-SPEC §2, issue #17 | TDD |
| V-3 | Validation module version hash: client caches module; submit carries hash; mismatch returns 412 with fresh module, not 409 | OPENAPI §validation, issue #17 | TDD |
| V-1 | SKU charset `[A-Z0-9-]{3,24}` accepted; values outside the charset rejected | `docs/TECH-SPEC-PIPELINE.md` §2, issue #17 | Unit test with valid and invalid SKU strings |
| V-2 | Title, short title, description: UTF-8 within WooCommerce maxLength enforced at field exit and at submit | `docs/TECH-SPEC-PIPELINE.md` §2, issue #17 | Unit test with boundary-length strings |
| V-3 | Validation module version hash: client caches module; submit carries hash; mismatch returns 412 with fresh module, not 409 | `docs/OPENAPI.md` §validation, issue #17 | Unit test of validation module hash comparison |
### Unit — AES-GCM settings (issue #5 substance)
### Unit — AES-GCM settings
| Case | Expected behavior | SoT | Cadence |
| Case | What must happen | Where the rule lives | Verified by |
|---|---|---|---|
| AES-1 | Encrypt then decrypt round-trips for woo_wp_username and woo_app_password; ciphertext never appears in GET settings | ARCHITECTURE §5, issue #5 | TDD |
| AES-2 | Missing SLAB_AES_KEY env at startup: app refuses to start; no fallback to plaintext | AGENTS §4, DATA-MODEL settings | TDD |
| AES-1 | Encrypt then decrypt round-trips for woo_wp_username and woo_app_password; ciphertext never appears in GET settings | `docs/ARCHITECTURE.md` §5, issue #5 | Unit test with round-trip encryption |
| AES-2 | Missing SLAB_AES_KEY environment variable at startup: app refuses to start; no fallback to plaintext | `docs/AGENTS.md` §4, `docs/DATA-MODEL.md` settings | Unit test with missing env var |
### API/contract — health + draft store stub
### API / contract — health + draft store stub
| Case | Expected behavior | SoT | Cadence |
| Case | What must happen | Where the rule lives | Verified by |
|---|---|---|---|
| H-1 | `GET /api/health` returns 200 `{ "status":"ok" }` with `woo_reachable` false when credentials not configured | OPENAPI §health, IMPL-PLAN Phase 0 | with-code |
| D-1 | `POST /api/v1/slabs` accepts multipart with data JSON + files + meta; returns 201 with `status: calibrated` and `server_rev` | OPENAPI §slabs, DATA-MODEL slabs | with-code |
| H-1 | `GET /api/health` returns 200 `{ "status":"ok" }` with `woo_reachable` false when credentials are not configured | `docs/OPENAPI.md` §health, `docs/IMPL-PLAN.md` Phase 0 | API contract test via httpx TestClient |
| D-1 | `POST /api/v1/slabs` accepts multipart with data JSON + files + metadata; returns 201 with `status: calibrated` and `server_rev` | `docs/OPENAPI.md` §slabs, `docs/DATA-MODEL.md` slabs | API contract test via httpx TestClient |
---
## Gate C — Early Phase 1 (first draft listing, inference OFF)
## Gate C — Early Phase 1 (first draft listing, inference disabled)
**Exit evidence:** Ty opens one `SLAB-UAT-*` Woo draft listing in the store admin. Inference remains OFF.
**Exit evidence**: agent-written test automation creates one `SLAB-UAT-*` WooCommerce draft listing via the mocked store; inference remains disabled; full suite passes.
### API/contract — create + draft
### API / contract — create + draft
| Case | Expected behavior | SoT | Cadence |
| Case | What must happen | Where the rule lives | Verified by |
|---|---|---|---|
| GC-1 | `POST /api/v1/slabs` with `SLAB-UAT-*` SKU creates Woo product with `status: "draft"` regardless of `woo_create_status` setting | AGENTS §5, CONTENT-WOO §2.4 | with-code |
| GC-2 | Duplicate SKU before create: `GET /products?sku={sku}` finds existing → 409 `duplicate_sku` with `{ field:"sku", actions:["edit_sku","open_existing"] }` | CONTENT-WOO §2.5, OPENAPI §slabs, issue #3 | with-code |
| GC-3 | `POST /api/v1/settings/test-woo` returns ok with populated taxonomy; always resyncs taxonomy on success | OPENAPI §settings, issue #14 | with-code |
| GC-4 | `WOO_BASE_URL` is compose-only HTTPS; Settings GET returns it read-only; PUT rejects write attempts with 422 | AGENTS §5, issue #11 | with-code |
| GC-5 | FastAPI refuses to start when `WOO_BASE_URL` env is missing or `http://` | AGENTS §4, DEPLOYMENT §3 | with-code |
| GC-1 | `POST /api/v1/slabs` with `SLAB-UAT-*` SKU creates a WooCommerce product with `status: "draft"` regardless of the `woo_create_status` setting | `docs/AGENTS.md` §5, `docs/CONTENT-WOO.md` §2.4 | API contract test with mocked Woo |
| GC-2 | Duplicate SKU before create: `GET /products?sku={sku}` finds existing → 409 `duplicate_sku` with `{ field:"sku", actions:["edit_sku","open_existing"] }` | `docs/CONTENT-WOO.md` §2.5, `docs/OPENAPI.md` §slabs, issue #3 | API contract test with pre-existing SKU in mock |
| GC-3 | `POST /api/v1/settings/test-woo` returns ok with populated taxonomy; always resyncs taxonomy on success | `docs/OPENAPI.md` §settings, issue #14 | API contract test with mocked Woo taxonomy |
| GC-4 | `WOO_BASE_URL` is compose-only HTTPS; Settings GET returns it read-only; PUT rejects write attempts with 422 | `docs/AGENTS.md` §5, issue #11 | API contract test with PUT attempt |
| GC-5 | FastAPI refuses to start when `WOO_BASE_URL` environment variable is missing or set to `http://` | `docs/AGENTS.md` §4, `docs/DEPLOYMENT.md` §3 | Unit test for startup config validation |
### Integration — Woo mock (draft create, SLAB-UAT-*, duplicate SKU, taxonomy)
### Integration — WooCommerce mock (draft create, SLAB-UAT-*, duplicate SKU, taxonomy)
| Case | Expected behavior | SoT | Cadence |
| Case | What must happen | Where the rule lives | Verified by |
|---|---|---|---|
| W-1 | Woo mock returns a new product with status draft; server stores `woo_product_id` and purges images | CONTENT-WOO §2.4, IMPL-PLAN Phase 4 | with-code |
| W-2 | Woo mock returns 409 for duplicate SKU; server maps to `{ field:"sku", actions:["edit_sku","open_existing"] }`; draft retained | OPENAPI §slabs, issue #3 | with-code |
| W-3 | Taxonomy sync returns cached data; anchor stays unchanged on no-op sync; anchor bumps only when stored-subset hash changes | ARCHITECTURE §7, issue #14 | with-code |
| W-4 | `test-woo` always runs taxonomy resync after successful connection test | ARCHITECTURE §7, issue #14 | with-code |
| W-5 | Failed publish: slab `failed`, photos retained, sync_log written; retry from same screen without re-photographing → success → photos purged | CONTENT-WOO §2.6, TECH-SPEC §5, DATA-MODEL slabs | with-code |
| W-1 | Woo mock returns a new product with status draft; server stores `woo_product_id` and purges images | `docs/CONTENT-WOO.md` §2.4, `docs/IMPL-PLAN.md` Phase 4 | Integration test with respx mock |
| W-2 | Woo mock returns 409 for duplicate SKU; server maps to `{ field:"sku", actions:["edit_sku","open_existing"] }`; draft retained | `docs/OPENAPI.md` §slabs, issue #3 | Integration test with respx mock returning 409 |
| W-3 | Taxonomy sync returns cached data; anchor stays unchanged on no-op sync; anchor bumps only when stored-subset hash changes | `docs/ARCHITECTURE.md` §7, issue #14 | Integration test with taxonomy mock |
| W-4 | `test-woo` always runs taxonomy resync after successful connection test | `docs/ARCHITECTURE.md` §7, issue #14 | Integration test: assert taxonomy resync call |
| W-5 | Failed publish: slab status `failed`, photos retained, sync_log written; retry from same screen without re-photographing → success → photos purged | `docs/CONTENT-WOO.md` §2.6, `docs/TECH-SPEC-PIPELINE.md` §5, `docs/DATA-MODEL.md` slabs | Integration test with retryable mock failure |
### Integration — inference OFF path + test-inference probe (issue #2 substance)
### Integration — inference disabled path + test-inference probe (issue #2 substance)
| Case | Expected behavior | SoT | Cadence |
| Case | What must happen | Where the rule lives | Verified by |
|---|---|---|---|
| INF-1 | With `inference_enabled: false`: slab create skips Call 1; `inference_status` is null; full suite passes end-to-end with no inference network calls | AGENTS §6, IMPL-PLAN Phase 5 | with-code |
| INF-2 | `POST /api/v1/settings/test-inference` returns `{ ok: true, mode: "stateful", vision_capable: true }` on a stateful provider | OPENAPI §settings, issue #2 | with-code |
| INF-3 | `test-inference` returns `{ ok: true, mode: "stateless", vision_capable: true }` on a stateless provider; Call 2 uses full resend | OPENAPI §settings, issue #2 | with-code |
| INF-4 | `test-inference` returns `{ ok: false, mode: "fail" }`: Call 2 enablement blocked; Gate C path remains green with inference OFF | OPENAPI §settings, issue #2 | with-code |
| INF-1 | With `inference_enabled: false`: slab create skips Call 1; `inference_status` is null; full suite passes end-to-end with no inference network calls | `docs/AGENTS.md` §6, `docs/IMPL-PLAN.md` Phase 5 | Integration test asserting zero inference calls |
| INF-2 | `POST /api/v1/settings/test-inference` returns `{ ok: true, mode: "stateful", vision_capable: true }` on a stateful provider | `docs/OPENAPI.md` §settings, issue #2 | Integration test with mocked inference endpoint |
| INF-3 | `test-inference` returns `{ ok: true, mode: "stateless", vision_capable: true }` on a stateless provider; Call 2 uses full resend | `docs/OPENAPI.md` §settings, issue #2 | Integration test with mocked inference endpoint |
| INF-4 | `test-inference` returns `{ ok: false, mode: "fail" }`: Call 2 enablement blocked; Gate C path remains green with inference disabled | `docs/OPENAPI.md` §settings, issue #2 | Integration test: assert Call 2 blocked |
### UI/E2E — capture knobs, mask/axis, ready gates (phone + PC)
### UI end-to-end — capture knobs, mask, axis, ready gates (phone + PC)
| Case | Expected behavior | SoT | Cadence |
| Case | What must happen | Where the rule lives | Verified by |
|---|---|---|---|
| UI-1 | Four inline knobs (sheet, sensitivity, edge offset, feather) on capture screen: changing any knob immediately re-runs mask and refreshes overlay without leaving the screen | TECH-SPEC §3.3, UX §Capture, issue #15 | with-code |
| UI-2 | Mask overlay on source photo: user confirms edge; below-threshold edge after tuning → retake; no U2Net control in POC | TECH-SPEC §3.2, §3.5, UX §Capture | with-code |
| UI-3 | Length axis overlay: user rotates if skewed; samples and sqft update live; nearly-square slab: user picks length direction | TECH-SPEC §4.2, UX §Capture | with-code |
| UI-4 | Numbers entry: length 1/8" steps, thickness pick, SKU typed; computed sqft/bdft/widths displayed | TECH-SPEC §4, UX §Capture | with-code |
| UI-5 | Ready-set gates: slab cannot reach `ready` until full named set is filled (SKU, length, thickness, sqft/bdft/widths, ≥1 PNG, species, wood category, edge, figure, grade, thickness band, moisture, price); inline nudges per empty required field, not a submit-time list | AGENTS §7, UX §Review, issue #9 | with-code |
| UI-1 | Four inline knobs (sheet, sensitivity, edge offset, feather) on capture screen: changing any knob immediately re-runs mask and refreshes overlay without leaving the screen | `docs/TECH-SPEC-PIPELINE.md` §3.3, `docs/UX.md` §Capture, issue #15 | Playwright E2E test: knob change → overlay update, no navigation |
| UI-2 | Mask overlay on source photo: user confirms edge; below-threshold edge after tuning → retake; no U2Net control in the proof of concept | `docs/TECH-SPEC-PIPELINE.md` §3.2, §3.5, `docs/UX.md` §Capture | Playwright E2E test: mask overlay visible, confirm/retake paths |
| UI-3 | Length axis overlay: user rotates if skewed; samples and square feet update live; nearly-square slab: user picks length direction | `docs/TECH-SPEC-PIPELINE.md` §4.2, `docs/UX.md` §Capture | Playwright E2E test: axis rotation, live number update |
| UI-4 | Numbers entry: length 1/8" steps, thickness pick, SKU typed; computed square feet/bdft/widths displayed | `docs/TECH-SPEC-PIPELINE.md` §4, `docs/UX.md` §Capture | Playwright E2E test: type inputs, assert computed output |
| UI-5 | Ready-set gates: slab cannot reach `ready` until full named set is filled (SKU, length, thickness, square feet/bdft/widths, ≥1 PNG, species, wood category, edge, figure, grade, thickness band, moisture, price); inline nudges per empty required field, not a submit-time list | `docs/AGENTS.md` §7, `docs/UX.md` §Review, issue #9 | Playwright E2E test: attempt ready with missing fields, assert nudges |
### Ops — compose health
| Case | Expected behavior | SoT | Cadence |
| Case | What must happen | Where the rule lives | Verified by |
|---|---|---|---|
| OPS-1 | `GET https://${APP_HOSTNAME}/api/health` returns ok after compose up with filled INVENTORY | DEPLOYMENT §8, IMPL-PLAN Phase 0 | with-code |
| OPS-1 | `GET https://${APP_HOSTNAME}/api/health` returns ok after compose up with filled inventory | `docs/DEPLOYMENT.md` §8, `docs/IMPL-PLAN.md` Phase 0 | Shell script: compose up → curl health endpoint |
---
## Phase 1 remainder — Full API surface
**Exit evidence:** Ty drives curl through a full calibrated → ready cycle; OPENAPI endpoints match; revision conflict handled; taxonomy anchor logic verified.
**Exit evidence**: agent-written test automation drives a full calibrated to ready cycle via the API; all OPENAPI endpoints match expected responses; revision conflict handled; taxonomy anchor logic verified.
### API/contract — all routes, concurrency, secrets redaction
### API / contract — all routes, concurrency, secrets redaction
| Case | Expected behavior | SoT | Cadence |
| Case | What must happen | Where the rule lives | Verified by |
|---|---|---|---|
| API-1 | `PUT /api/v1/slabs/{id}` updates fields with `client_rev`; server echoes new `server_rev`; mismatch returns 409 `revision_conflict` with `current_server_rev` | OPENAPI §conventions, DATA-MODEL slabs | with-code |
| API-2 | `PUT /api/v1/slabs/{id}` stores client-sent derived fields (sqft, bdft, widths); server does not recompute them as SoT | OPENAPI §slabs PUT, ARCHITECTURE §3 | with-code |
| API-3 | `GET /api/v1/settings` returns `SettingsView` with `woo_credentials_configured` boolean; `woo_app_password` never returned | OPENAPI §settings, issue #5 | with-code |
| API-4 | `POST /api/v1/slabs/{id}/infer-taxon` returns 202 `{ status: "inferring" }`; client polls `GET /api/v1/slabs/{id}` for results; timeout > 45s → 504 | OPENAPI §inference, issue #8 | with-code |
| API-5 | `POST /api/v1/slabs/{id}/infer-content` returns 202; requires `test-inference` ok; below-threshold Call 1 (confidence < 0.7): fields stay empty | OPENAPI §inference, AGENTS §6, issue #8/#9 | with-code |
| API-6 | `POST /api/v1/slabs/{id}/publish`: pre-condition check returns 422 with field keys for missing required fields; UI maps fields, no raw code | OPENAPI §publish, issue #18 | with-code |
| API-7 | `POST /api/v1/admin/taxonomy/sync`: pulls categories/attributes/tags; rebuilds cache; anchor bumps only on stored-subset hash change; 502 retains previous cache | OPENAPI §taxonomy, ARCHITECTURE §7, issue #14 | with-code |
| API-8 | `GET /api/v1/validation/module` returns hash and rules; client caches; submit mismatch returns 412 with fresh module | OPENAPI §validation, issue #17 | with-code |
| API-9 | `DELETE /api/v1/slabs/{id}`: soft-delete returns 204; unpublished drafts only; published slabs undeletable | OPENAPI §slabs DELETE | with-code |
| API-10 | `test-woo` fails closed: missing credentials or non-https URL → `{ ok: false, detail: "..." }` without crashing | OPENAPI §settings test-woo | with-code |
| API-1 | `PUT /api/v1/slabs/{id}` updates fields with `client_rev`; server echoes new `server_rev`; mismatch returns 409 `revision_conflict` with `current_server_rev` | `docs/OPENAPI.md` §conventions, `docs/DATA-MODEL.md` slabs | API contract test with httpx TestClient |
| API-2 | `PUT /api/v1/slabs/{id}` stores client-sent derived fields (square feet, bdft, widths); server does not recompute them as source of truth | `docs/OPENAPI.md` §slabs PUT, `docs/ARCHITECTURE.md` §3 | API contract test: assert no server-side recompute |
| API-3 | `GET /api/v1/settings` returns `SettingsView` with `woo_credentials_configured` boolean; `woo_app_password` never returned | `docs/OPENAPI.md` §settings, issue #5 | API contract test: assert password absent from response |
| API-4 | `POST /api/v1/slabs/{id}/infer-taxon` returns 202 `{ status: "inferring" }`; client polls `GET /api/v1/slabs/{id}` for results; timeout greater than 45 seconds returns 504 | `docs/OPENAPI.md` §inference, issue #8 | API contract test with mocked inference |
| API-5 | `POST /api/v1/slabs/{id}/infer-content` returns 202; requires `test-inference` ok; below-threshold Call 1 (confidence below 0.7): fields stay empty | `docs/OPENAPI.md` §inference, `docs/AGENTS.md` §6, issue #8/#9 | API contract test with confidence-gated mock |
| API-6 | `POST /api/v1/slabs/{id}/publish`: pre-condition check returns 422 with field keys for missing required fields; UI maps fields, no raw code | `docs/OPENAPI.md` §publish, issue #18 | API contract test with incomplete slab |
| API-7 | `POST /api/v1/admin/taxonomy/sync`: pulls categories/attributes/tags; rebuilds cache; anchor bumps only on stored-subset hash change; 502 retains previous cache | `docs/OPENAPI.md` §taxonomy, `docs/ARCHITECTURE.md` §7, issue #14 | Integration test with taxonomy mock |
| API-8 | `GET /api/v1/validation/module` returns hash and rules; client caches; submit mismatch returns 412 with fresh module | `docs/OPENAPI.md` §validation, issue #17 | API contract test: assert 412 on stale hash |
| API-9 | `DELETE /api/v1/slabs/{id}`: soft-delete returns 204; unpublished drafts only; published slabs undeletable | `docs/OPENAPI.md` §slabs DELETE | API contract test with published slab |
| API-10 | `test-woo` fails closed: missing credentials or non-HTTPS URL returns `{ ok: false, detail: "..." }` without crashing | `docs/OPENAPI.md` §settings test-woo | API contract test with bad credentials |
### Integration — taxonomy sync + anchor
| Case | Expected behavior | SoT | Cadence |
| Case | What must happen | Where the rule lives | Verified by |
|---|---|---|---|
| TAX-1 | Sync on publish when `now - taxonomy_last_sync_at > 1 hour`; bump anchor only on real change; stale picked id → 422 with current options | ARCHITECTURE §7, CONTENT-WOO §2.2, issue #14 | with-code |
| TAX-2 | Species picker offers only Woo-synced species; no hardcoded fallback; new species selectable only after sync stores it | ARCHITECTURE §7, issue #12 | with-code |
| TAX-3 | Client reads taxonomy anchor; re-pulls full taxonomy only when anchor advances past client's last value; no-per-navigation sync | ARCHITECTURE §7, OPENAPI §taxonomy | with-code |
| TAX-1 | Sync on publish when `now - taxonomy_last_sync_at > 1 hour`; bump anchor only on real change; stale picked id returns 422 with current options | `docs/ARCHITECTURE.md` §7, `docs/CONTENT-WOO.md` §2.2, issue #14 | Integration test with time-skewed sync |
| TAX-2 | Species picker offers only WooCommerce-synced species; no hardcoded fallback; new species selectable only after sync stores it | `docs/ARCHITECTURE.md` §7, issue #12 | Integration test: assert species list matches cache |
| TAX-3 | Client reads taxonomy anchor; re-pulls full taxonomy only when anchor advances past client's last value; no-per-navigation sync | `docs/ARCHITECTURE.md` §7, `docs/OPENAPI.md` §taxonomy | Integration test with anchor versioning |
---
## Phase 3 — Frontend review + publish UI
**Exit evidence:** Ty publishes a test slab to the real store as a Woo draft with `SLAB-UAT-*`.
**Exit evidence**: agent-written test automation publishes a test slab to the real store as a WooCommerce draft with `SLAB-UAT-*`; review, field recovery, and publish outcomes verified.
### UI/E2E — review, field recovery, publish outcome (phone + PC)
### UI end-to-end — review, field recovery, publish outcome (phone + PC)
| Case | Expected behavior | SoT | Cadence |
| Case | What must happen | Where the rule lives | Verified by |
|---|---|---|---|
| UIE-1 | Review screen: all fields editable; client-computed sqft/bdft/widths shown with manual override; typed override persists across poll/refresh | IMPL-PLAN Phase 3, UX §Review | with-code |
| UIE-2 | Call 1 prefill ≥ 0.7: fields pre-filled and mutable; user override sticks; below 0.7 fields empty; inline nudges for every still-required control | AGENTS §6, issue #8/#9, UX §Review | with-code |
| UIE-3 | Duplicate SKU at publish: under SKU field, "This SKU already exists in the store (any status)"; edit SKU or open existing; draft retained; no raw 409 | UX §Publish, CONTENT-WOO §2.5, AGENTS §5, issue #3/#18 | with-code |
| UIE-4 | Stale field at publish: field shows "This value is no longer valid. Pick from the current list"; draft retained; no raw 422 | CONTENT-WOO §2.6, issue #18 | with-code |
| UIE-5 | Publish success: screen shows listing created, woo_product_id when available; local draft and photos gone; slab status `published` | CONTENT-WOO §2.6, DATA-MODEL slabs | with-code |
| UIE-6 | Publish failure: plain message, no status number, no stack; retry from same screen without re-photographing | CONTENT-WOO §2.6, issue #18 | with-code |
| UIE-7 | Inference OFF: full manual taxonomy + content path works; no inference network calls; Generate text disabled; templates still work | IMPL-PLAN Phase 3, UX §Review | with-code |
| UIE-8 | Settings UI: WP username + app password + test-woo; inference endpoint + test; pricing rules; brand voice/GEO; `woo_create_status`; store URL read-only | IMPL-PLAN Phase 3, OPENAPI §settings, issue #11 | with-code |
### UI/E2E — capture knobs live re-run (issue #15 substance)
| Case | Expected behavior | SoT | Cadence |
|---|---|---|---|
| UIE-9 | Changing sensitivity slider immediately refreshes mask overlay without leaving screen or server round trip | TECH-SPEC §3.3, issue #15, UX §Capture | with-code |
| UIE-10 | Changing edge offset pulls cut in/out; feather changes alpha blend; sheet switch (auto/green/black) re-runs; all persisted in localStorage until reset | TECH-SPEC §3.3, issue #15 | with-code |
| UIE-1 | Review screen: all fields editable; client-computed square feet/bdft/widths shown with manual override; typed override persists across poll and refresh | `docs/IMPL-PLAN.md` Phase 3, `docs/UX.md` §Review | Playwright E2E test: edit, poll, refresh, assert persistence |
| UIE-2 | Call 1 prefill at or above 0.7 confidence: fields pre-filled and mutable; user override sticks; below 0.7 fields empty; inline nudges for every still-required control | `docs/AGENTS.md` §6, issue #8/#9, `docs/UX.md` §Review | Playwright E2E test with mocked Call 1 results |
| UIE-3 | Duplicate SKU at publish: under SKU field, "This SKU already exists in the store (any status)"; edit SKU or open existing; draft retained; no raw 409 | `docs/UX.md` §Publish, `docs/CONTENT-WOO.md` §2.5, `docs/AGENTS.md` §5, issue #3/#18 | Playwright E2E test with pre-existing SKU |
| UIE-4 | Stale field at publish: field shows "This value is no longer valid. Pick from the current list"; draft retained; no raw 422 | `docs/CONTENT-WOO.md` §2.6, issue #18 | Playwright E2E test with removed taxonomy item |
| UIE-5 | Publish success: screen shows listing created, `woo_product_id` when available; local draft and photos gone; slab status `published` | `docs/CONTENT-WOO.md` §2.6, `docs/DATA-MODEL.md` slabs | Playwright E2E test: assert published state and cleanup |
| UIE-6 | Publish failure: plain message, no status number, no stack; retry from same screen without re-photographing | `docs/CONTENT-WOO.md` §2.6, issue #18 | Playwright E2E test: assert retry path preserves state |
| UIE-7 | Inference disabled: full manual taxonomy + content path works; no inference network calls; Generate text disabled; templates still work | `docs/IMPL-PLAN.md` Phase 3, `docs/UX.md` §Review | Playwright E2E test with inference flag off |
| UIE-8 | Settings UI: WordPress username + application password + test-woo; inference endpoint + test; pricing rules; brand voice/GEO; `woo_create_status`; store URL read-only | `docs/IMPL-PLAN.md` Phase 3, `docs/OPENAPI.md` §settings, issue #11 | Playwright E2E test: settings form and test buttons |
| UIE-9 | Changing sensitivity slider immediately refreshes mask overlay without leaving screen or server round trip | `docs/TECH-SPEC-PIPELINE.md` §3.3, issue #15, `docs/UX.md` §Capture | Playwright E2E test: slider → overlay update, no HTTP call |
| UIE-10 | Changing edge offset pulls cut in/out; feather changes alpha blend; sheet switch (auto/green/black) re-runs; all persisted in localStorage until reset | `docs/TECH-SPEC-PIPELINE.md` §3.3, issue #15 | Playwright E2E test: knob changes, persist, reset |
---
## Phase 5 — Inference (after Gate C is green)
**Exit evidence:** Ty runs species detection on 3 real slabs; results acceptable or manually corrected.
**Exit evidence**: agent-written test automation runs species detection on three real slabs; results are acceptable or manually corrected; full inference-disabled path remains green.
### Integration — Call 1/2, portable context, confidence gating
| Case | Expected behavior | SoT | Cadence |
| Case | What must happen | Where the rule lives | Verified by |
|---|---|---|---|
| C1-1 | Call 1 sends all inventory photos @1024 + Woo taxonomy only + SKU/length/thickness; model constrained to cached taxonomy from Woo cache | IMPL-PLAN Phase 5, issue #12 | with-code |
| C1-2 | Confidence ≥ 0.7: species, wood categories, edge, figure, grade, feat-* pre-filled and mutable; user override wins | AGENTS §6, issue #8/#9 | with-code |
| C1-3 | Confidence < 0.7: those fields stay empty; ready blocked until full named set is filled; species + one figure is not the gate | AGENTS §6, issue #8/#9 | with-code |
| C2-1 | Call 2 triggered only by user tap; portable Call 1 context: full resend default; confirmed vs inferred labels carried | ARCHITECTURE §9, issue #2 | with-code |
| C2-2 | Test-inference stateful mode: `previous_response_id` used when available; stateless mode: full resend; fail mode: Call 2 blocked | OPENAPI §settings, issue #2 | with-code |
| C2-3 | LLM does not invent dimensions; templates inject deterministic numbers into title/description | PROMPTS §Call 2, AGENTS §6 | with-code |
| C2-4 | Audit: inference_log stores request_payload and response_payload per call; prompt file edits apply without restart | DATA-MODEL inference_log, PROMPTS §Management | with-code |
| C1-1 | Call 1 sends all inventory photos at 1024px + WooCommerce taxonomy only + SKU/length/thickness; model constrained to cached taxonomy from WooCommerce cache | `docs/IMPL-PLAN.md` Phase 5, issue #12 | Integration test: inspect Call 1 request payload |
| C1-2 | Confidence at or above 0.7: species, wood categories, edge, figure, grade, feat-* pre-filled and mutable; user override wins | `docs/AGENTS.md` §6, issue #8/#9 | Integration test with high-confidence mock response |
| C1-3 | Confidence below 0.7: those fields stay empty; ready blocked until full named set is filled; species plus one figure is not the gate | `docs/AGENTS.md` §6, issue #8/#9 | Integration test with low-confidence mock response |
| C2-1 | Call 2 triggered only by user tap; portable Call 1 context: full resend default; confirmed versus inferred labels carried | `docs/ARCHITECTURE.md` §9, issue #2 | Integration test: assert no auto-trigger, full resend |
| C2-2 | Test-inference stateful mode: `previous_response_id` used when available; stateless mode: full resend; fail mode: Call 2 blocked | `docs/OPENAPI.md` §settings, issue #2 | Integration test with three probe modes |
| C2-3 | LLM does not invent dimensions; templates inject deterministic numbers into title and description | `docs/PROMPTS.md` §Call 2, `docs/AGENTS.md` §6 | Integration test: inspect Call 2 response for invented numbers |
| C2-4 | Audit: inference_log stores request_payload and response_payload per call; prompt file edits apply without restart | `docs/DATA-MODEL.md` inference_log, `docs/PROMPTS.md` §Management | Integration test: verify log entry, edit prompt file |
### UI/E2E — inference OFF path
### UI end-to-end — inference disabled path
| Case | Expected behavior | SoT | Cadence |
| Case | What must happen | Where the rule lives | Verified by |
|---|---|---|---|
| OFF-1 | Full suite (calibrated → ready → publish → success) passes with `inference_enabled: false`; zero calls to inference endpoint asserted | AGENTS §6, IMPL-PLAN Phase 5 | with-code |
| OFF-2 | `test-inference` fail mode blocks Call 2 enablement only; Gate C inference-OFF path unaffected | OPENAPI §settings, issue #2 | with-code |
| OFF-1 | Full suite (calibrated to ready to publish to success) passes with `inference_enabled: false`; zero calls to inference endpoint asserted | `docs/AGENTS.md` §6, `docs/IMPL-PLAN.md` Phase 5 | Playwright E2E test with network assertion for zero inference calls |
| OFF-2 | `test-inference` fail mode blocks Call 2 enablement only; Gate C inference-disabled path unaffected | `docs/OPENAPI.md` §settings, issue #2 | Playwright E2E test: assert Call 2 disabled, full suite passes |
---
## Phase 6 / Ops — Deployment, UAT, hardening
## Phase 6 / Ops — Deployment, user acceptance, hardening
**Exit evidence:** Ty signs off on UAT results; v1 declared done.
**Exit evidence**: agent-written test automation signs off on deployment smoke tests and UAT results; v1 declared done.
### Ops — compose health, deploy path, backup/restore
| Case | Expected behavior | SoT | Cadence |
| Case | What must happen | Where the rule lives | Verified by |
|---|---|---|---|
| OPS-2 | INVENTORY.md filled: APP_HOST, APP_HOSTNAME, ports verified free, Caddy location confirmed, DNS reachable from phone on LAN/Tailscale | DEPLOYMENT §1, IMPL-PLAN Phase 6 | with-code |
| OPS-3 | Compose health: `GET https://${APP_HOSTNAME}/api/health` ok; `test-woo` ok with species populated; `woo_create_status=draft`; inference OFF for early gates | DEPLOYMENT §8, UAT-PRACTICE §1 | with-code |
| OPS-4 | Backup: `deploy/backup.sh` creates WAL-safe `.backup` of slab.db and tar of images volume; retention 7 daily + 4 weekly | DEPLOYMENT §6 | with-code |
| OPS-5 | Restore: stop compose → restore DB + untar images → start compose → health ok; tested at least once before declaring backup done | DEPLOYMENT §6, IMPL-PLAN Phase 6 | with-code |
| OPS-2 | INVENTORY.md filled: APP_HOST, APP_HOSTNAME, ports verified free, Caddy location confirmed, DNS reachable from phone on LAN/Tailscale | `docs/DEPLOYMENT.md` §1, `docs/IMPL-PLAN.md` Phase 6 | Shell script: port scan, Caddy check, DNS resolve |
| OPS-3 | Compose health: `GET https://${APP_HOSTNAME}/api/health` ok; `test-woo` ok with species populated; `woo_create_status=draft`; inference disabled for early gates | `docs/DEPLOYMENT.md` §8, `docs/UAT-PRACTICE.md` §1 | Shell script: curl health + test-woo endpoints |
| OPS-4 | Backup: `deploy/backup.sh` creates WAL-safe `.backup` of slab.db and tar of images volume; retention 7 daily + 4 weekly | `docs/DEPLOYMENT.md` §6 | Shell script: run backup, verify files and retention |
| OPS-5 | Restore: stop compose → restore DB + untar images → start compose → health ok; tested at least once before declaring backup done | `docs/DEPLOYMENT.md` §6, `docs/IMPL-PLAN.md` Phase 6 | Shell script: full restore cycle with health assertion |
### Ops — instrumentation (issue #6 substance)
| Case | Expected behavior | SoT | Cadence |
| Case | What must happen | Where the rule lives | Verified by |
|---|---|---|---|
| INS-1 | Health non-200 on DB or volume failure; `woo_reachable` false on Woo unreachable | OPENAPI §health, issue #6 | with-code |
| INS-2 | Request ID logged on every server request | issue #6 | with-code |
| INS-3 | Inference request_payload and response_payload recorded in inference_log; payloads contain model name and confidence data | DATA-MODEL inference_log, issue #6 | with-code |
| INS-4 | Sync_log written with payload_hash on every publish attempt; retains success and failure | DATA-MODEL sync_log, issue #6 | with-code |
| INS-1 | Health non-200 on database or volume failure; `woo_reachable` false on WooCommerce unreachable | `docs/OPENAPI.md` §health, issue #6 | Unit test with simulated DB/volume failure |
| INS-2 | Request ID logged on every server request | issue #6 | Unit test: assert request ID in log output |
| INS-3 | Inference request_payload and response_payload recorded in inference_log; payloads contain model name and confidence data | `docs/DATA-MODEL.md` inference_log, issue #6 | Integration test: assert log entry after inference call |
| INS-4 | Sync_log written with payload_hash on every publish attempt; retains success and failure | `docs/DATA-MODEL.md` sync_log, issue #6 | Integration test: assert sync_log entries for success and failure |
### Ops — E2E on all target browsers
### Ops — end-to-end on all target browsers
| Case | Expected behavior | SoT | Cadence |
| Case | What must happen | Where the rule lives | Verified by |
|---|---|---|---|
| E2E-1 | Capture → mask/axis confirm → calibrated upload → review → publish → Woo draft `SLAB-UAT-*` works on iOS Safari (phone) | UX §Browser compatibility, IMPL-PLAN Phase 6 | with-code |
| E2E-2 | Same flow works on Android Chrome (phone) | UX §Browser compatibility, IMPL-PLAN Phase 6 | with-code |
| E2E-3 | Same flow works on desktop Chrome, Edge, Firefox (PC) | UX §Browser compatibility, IMPL-PLAN Phase 6 | with-code |
| E2E-4 | Manual length override recalculates widths/sqft/bdft correctly on all three browser families | TECH-SPEC §4.3, IMPL-PLAN Phase 6 | with-code |
| E2E-5 | Client crop: slab ~80% frame; shorter side ≥ 1600 when source allows; undersized source warns and requests retake | TECH-SPEC §5, IMPL-PLAN Phase 6 | with-code |
| E2E-1 | Capture to mask/axis confirm to calibrated upload to review to publish to WooCommerce draft `SLAB-UAT-*` works on iOS Safari (phone) | `docs/UX.md` §Browser compatibility, `docs/IMPL-PLAN.md` Phase 6 | Playwright E2E on iOS Safari |
| E2E-2 | Same flow works on Android Chrome (phone) | `docs/UX.md` §Browser compatibility, `docs/IMPL-PLAN.md` Phase 6 | Playwright E2E on Android Chrome |
| E2E-3 | Same flow works on desktop Chrome, Edge, Firefox (PC) | `docs/UX.md` §Browser compatibility, `docs/IMPL-PLAN.md` Phase 6 | Playwright E2E on desktop browsers |
| E2E-4 | Manual length override recalculates widths/square feet/bdft correctly on all three browser families | `docs/TECH-SPEC-PIPELINE.md` §4.3, `docs/IMPL-PLAN.md` Phase 6 | Playwright E2E: override length, assert recalculation |
| E2E-5 | Client crop: slab approximately 80 percent frame; shorter side ≥ 1600 when source allows; undersized source warns and requests retake | `docs/TECH-SPEC-PIPELINE.md` §5, `docs/IMPL-PLAN.md` Phase 6 | Playwright E2E: inspect crop output dimensions |
---
## Cadence summary
- **Deterministic core (TV-1…TV-10, validation, AES):** TDD. Unit tests written before any UI consumes the functions. Property checks: bdft monotonic in thickness/sqft; fill band; no upscale path.
- **UI, FastAPI, Woo/inference adapters:** tests-with-code. Each feature commit includes its test and shown execution output.
- **Integration tests:** httpx TestClient with Woo mock (respx) for success, error, duplicate, stale-field, and taxonomy-anchor paths.
- **Full suite must pass with inference OFF.** Call 1/Call 2 cases gated after Gate C is green.
- **bdft = sqft × thickness_in (no /12).** TV-3 is the identity proof. Gate B requires TV-3, TV-6, TV-7, TV-10 show-output on real fixtures.
**Deterministic core** (TV-1 through TV-10, validation, AES): test-driven development. Unit tests written before any user interface consumes the functions. Property checks: bdft monotonic in thickness and square feet; fill band; no upscale path.
**User interface, FastAPI, WooCommerce/inference adapters**: tests-with-code. Each feature commit includes its test and shown execution output.
**Integration tests**: httpx TestClient with a respx WooCommerce mock covering success, error, duplicate, stale-field, and taxonomy-anchor paths.
**Full suite must pass with inference disabled.** Call 1/Call 2 cases are gated after Gate C is green.
bdft equals square feet times thickness in inches (no divide-by-12). TV-3 proves the identity. Gate B requires TV-3, TV-6, TV-7, and TV-10 to show output on real fixtures.
---
## Cross-reference
| This spec | Live doc |
| These cases | Source of truth document(s) |
|---|---|
| TV-1…TV-10 | `docs/TECH-SPEC-PIPELINE.md` §8 |
| V-1…V-3 | `docs/TECH-SPEC-PIPELINE.md` §2, `docs/OPENAPI.md` §validation |
| AES-1…AES-2 | `docs/ARCHITECTURE.md` §5, `docs/AGENTS.md` §4 |
| GC-1…GC-5, W-1…W-5 | `docs/CONTENT-WOO.md` §2.4-2.6, `docs/OPENAPI.md` §slabs/§settings, `docs/DEPLOYMENT.md` §3 |
| INF-1…INF-4 | `docs/OPENAPI.md` §settings, `docs/AGENTS.md` §6 |
| UI-1…UI-5 | `docs/TECH-SPEC-PIPELINE.md` §3-§4, `docs/UX.md` §Capture/§Review |
| UIE-1…UIE-10 | `docs/UX.md` §Review/§Publish, `docs/CONTENT-WOO.md` §2.5-2.6, `docs/IMPL-PLAN.md` Phase 3 |
| C1-1…C2-4 | `docs/AGENTS.md` §6-§7, `docs/PROMPTS.md` §Call 1/2 |
| OPS-1…OPS-5, E2E-1…E2E-5 | `docs/DEPLOYMENT.md` §1-§8, `docs/IMPL-PLAN.md` Phase 6 |
| TV-1 through TV-10 | `docs/TECH-SPEC-PIPELINE.md` §8 |
| V-1 through V-3 | `docs/TECH-SPEC-PIPELINE.md` §2, `docs/OPENAPI.md` §validation |
| AES-1 through AES-2 | `docs/ARCHITECTURE.md` §5, `docs/AGENTS.md` §4 |
| GC-1 through GC-5, W-1 through W-5 | `docs/CONTENT-WOO.md` §2.4–2.6, `docs/OPENAPI.md` §slabs/§settings, `docs/DEPLOYMENT.md` §3 |
| INF-1 through INF-4 | `docs/OPENAPI.md` §settings, `docs/AGENTS.md` §6 |
| UI-1 through UI-5 | `docs/TECH-SPEC-PIPELINE.md` §3–§4, `docs/UX.md` §Capture/§Review |
| UIE-1 through UIE-10 | `docs/UX.md` §Review/§Publish, `docs/CONTENT-WOO.md` §2.5–2.6, `docs/IMPL-PLAN.md` Phase 3 |
| C1-1 through C2-4 | `docs/AGENTS.md` §6–§7, `docs/PROMPTS.md` §Call 1/2 |
| OPS-1 through OPS-5, E2E-1 through E2E-5 | `docs/DEPLOYMENT.md` §1–§8, `docs/IMPL-PLAN.md` Phase 6 |
| INS-1 | `docs/OPENAPI.md` §health, `docs/AGENTS.md` §6 |
| INS-2 | issue #6 |
| INS-3…INS-4 | `docs/DATA-MODEL.md` §inference_log/sync_log, `docs/AGENTS.md` §6 |
| INS-3 through INS-4 | `docs/DATA-MODEL.md` §inference_log/sync_log, `docs/AGENTS.md` §6 |
---
@@ -259,19 +322,19 @@ Purpose: this document defines every affirmative test case for the SlabUploader
This spec folds affirmative case substance from issue #19 where the rule exists in a live doc. Issue #19 is not closed by this work.
| Issue #19 item | Cases | SoT doc(s) |
| Issue #19 item | Cases | Source of truth document(s) |
|---|---|---|
| #2 portable multi-turn | INF-2, INF-3, INF-4, C2-1, C2-2 | OPENAPI §settings, ARCHITECTURE §9 |
| #3 duplicate SKU wording | GC-2, W-2, UIE-3 | OPENAPI §slabs, CONTENT-WOO §2.5 |
| #5 Application Passwords | AES-1, AES-2, API-3 | ARCHITECTURE §5, DATA-MODEL settings |
| #6 instrumentation | INS-1, INS-2, INS-3, INS-4 | DATA-MODEL sync_log/inference_log, issue #6 |
| #8/#9 Call 1 prefill + 0.7 | UIE-2, C1-2, C1-3 | AGENTS §6, PROMPTS §Call 1 |
| #11 WOO_BASE_URL | GC-4, UIE-8 | AGENTS §5, DEPLOYMENT §3 |
| #12 synced-species-only | C1-1, TAX-2 | ARCHITECTURE §7, CONTENT-WOO §2.2 |
| #14 taxonomy_anchor | TAX-1, W-3, W-4, API-7 | ARCHITECTURE §7, OPENAPI §taxonomy |
| #15 four inline knobs | UI-1, UIE-9, UIE-10 | TECH-SPEC §3.3, UX §Capture |
| #17 shared validation | V-1, V-2, V-3, API-8 | OPENAPI §validation |
| #18 409/422 recovery | UIE-3, UIE-4, UIE-5, UIE-6 | CONTENT-WOO §2.5-2.6, OPENAPI §slabs |
| #2 portable multi-turn | INF-2, INF-3, INF-4, C2-1, C2-2 | `docs/OPENAPI.md` §settings, `docs/ARCHITECTURE.md` §9 |
| #3 duplicate SKU wording | GC-2, W-2, UIE-3 | `docs/OPENAPI.md` §slabs, `docs/CONTENT-WOO.md` §2.5 |
| #5 Application Passwords | AES-1, AES-2, API-3 | `docs/ARCHITECTURE.md` §5, `docs/DATA-MODEL.md` settings |
| #6 instrumentation | INS-1, INS-2, INS-3, INS-4 | `docs/DATA-MODEL.md` sync_log/inference_log, issue #6 |
| #8/#9 Call 1 prefill + 0.7 | UIE-2, C1-2, C1-3 | `docs/AGENTS.md` §6, `docs/PROMPTS.md` §Call 1 |
| #11 WOO_BASE_URL | GC-4, UIE-8 | `docs/AGENTS.md` §5, `docs/DEPLOYMENT.md` §3 |
| #12 synced-species-only | C1-1, TAX-2 | `docs/ARCHITECTURE.md` §7, `docs/CONTENT-WOO.md` §2.2 |
| #14 taxonomy_anchor | TAX-1, W-3, W-4, API-7 | `docs/ARCHITECTURE.md` §7, `docs/OPENAPI.md` §taxonomy |
| #15 four inline knobs | UI-1, UIE-9, UIE-10 | `docs/TECH-SPEC-PIPELINE.md` §3.3, `docs/UX.md` §Capture |
| #17 shared validation | V-1, V-2, V-3, API-8 | `docs/OPENAPI.md` §validation |
| #18 409/422 recovery | UIE-3, UIE-4, UIE-5, UIE-6 | `docs/CONTENT-WOO.md` §2.5–2.6, `docs/OPENAPI.md` §slabs |
---
@@ -279,18 +342,18 @@ This spec folds affirmative case substance from issue #19 where the rule exists
| Phase / Gate | Cases | Exit evidence |
|---|---|---|
| Phase 0 / Gate B | TV-1…TV-10, V-1…V-3, AES-1…AES-2, H-1, D-1, OPS-1, GC-5 | Ty confirms TV-3, TV-6, TV-7, TV-10 on real fixtures; health up; AES round-trip; draft store |
| Gate C (early Phase 1) | GC-1…GC-4, W-1…W-5, INF-1…INF-4, UI-1…UI-5 | Ty opens one `SLAB-UAT-*` Woo draft; inference OFF |
| Phase 1 remainder | API-1…API-10, TAX-1, TAX-2, TAX-3 | Ty drives curl calibrated → ready; OPENAPI matches; revision + taxonomy green |
| Phase 3 | UIE-1…UIE-10 | Ty publishes test slab as Woo draft `SLAB-UAT-*` |
| Phase 5 | C1-1…C2-4, OFF-1, OFF-2 | Ty runs species detection on 3 real slabs; inference OFF path green |
| Phase 6 / PROD | OPS-2…OPS-5, INS-1…INS-4, E2E-1…E2E-5 | Ty signs off on UAT; v1 done |
| Phase 0 / Gate B | TV-1 through TV-10, V-1 through V-3, AES-1 through AES-2, H-1, D-1, OPS-1, GC-5 | Agent-written test automation confirms TV-3, TV-6, TV-7, TV-10 on real fixtures; health up; AES round-trip; draft store |
| Gate C (early Phase 1) | GC-1 through GC-4, W-1 through W-5, INF-1 through INF-4, UI-1 through UI-5 | Agent-written test automation creates one `SLAB-UAT-*` WooCommerce draft; inference disabled |
| Phase 1 remainder | API-1 through API-10, TAX-1, TAX-2, TAX-3 | Agent-written test automation drives full calibrated to ready cycle; OPENAPI matches; revision and taxonomy green |
| Phase 3 | UIE-1 through UIE-10 | Agent-written test automation publishes test slab as WooCommerce draft `SLAB-UAT-*` |
| Phase 5 | C1-1 through C2-4, OFF-1, OFF-2 | Agent-written test automation runs species detection on 3 real slabs; inference disabled path green |
| Phase 6 / production | OPS-2 through OPS-5, INS-1 through INS-4, E2E-1 through E2E-5 | Agent-written test automation signs off on deployment and UAT results; v1 done |
---
## Rules that do not change
- Inference is optional. The full suite passes with inference OFF. Call 1/2 cases are gated after Gate C.
- `bdft = sqft × thickness_in`. No divide-by-12. TV-3 proves the identity.
- Inference is optional. The full suite passes with inference disabled. Call 1/Call 2 cases are gated after Gate C.
- bdft equals square feet times thickness in inches. No divide-by-12. TV-3 proves the identity.
- Client is authoritative for deterministic math; server stores client-sent numbers.
- No new product locks in this document. All rules reference existing live docs or issue #19 substance.