From 01aa4c52c3875f7dae00ce12ccb3b3d9f8bba03c Mon Sep 17 00:00:00 2001 From: Ty Balascio Date: Fri, 18 Sep 2026 21:02:06 -0700 Subject: [PATCH] docs(phase2): reconcile requirements and migrate capabilities/contracts Surface-matrix sweep of every source doc; added nine missed behaviors (METADATA-115, REVIEW-50, APP-55/60/65, SETTINGS-45 mode, Generate-text availability, defaults-off). 153 requirements, 0 coverage gaps. Migrated capabilities to docs/capabilities/ and contracts to docs/contracts/; added WOO-API, API, DATA-MODEL, PROMPTS, UX, TEST-SPEC. Populated MILESTONES; branch-neutral trunk docs; renamed umbrella term to Woo Slab Taxonomy. Archived reconciliation working records under .audit/archive/. --- .audit/2026-09-18-migration-todos.md | 48 -- .../{ => archive}/2026-09-18-coverage-gaps.md | 8 + .audit/archive/2026-09-18-migration-todos.md | 183 ++++++++ .../2026-09-18-reconciliation-decisions.md | 0 .../2026-09-18-session-handoff.md | 0 .audit/{ => archive}/docs-structure-pass.tsv | 0 .audit/coverage/cur-content-woo.md | 8 + .audit/coverage/cur-create.md | 9 + .audit/coverage/docs-readme.md | 19 + .audit/coverage/issues.md | 11 + .audit/coverage/matrix/architecture.md | 185 ++++++++ .audit/coverage/matrix/create.md | 158 +++++++ .audit/coverage/matrix/current-docs.md | 83 ++++ .audit/coverage/matrix/issues.md | 39 ++ .audit/coverage/matrix/openapi.md | 152 ++++++ .audit/coverage/matrix/prd.md | 115 +++++ .audit/coverage/matrix/tag-docs-remaining.md | 84 ++++ .audit/coverage/matrix/test-spec.md | 109 +++++ .audit/coverage/matrix/ux-design.md | 217 +++++++++ .audit/coverage/prd.md | 12 + .audit/coverage/product.md | 32 ++ .audit/coverage/settings-auth-health.md | 47 -- .audit/coverage/tag-architecture.md | 62 +++ .audit/coverage/tag-data-model.md | 50 ++ .audit/coverage/tag-deployment.md | 48 ++ .audit/coverage/tag-impl-plan.md | 82 ++++ .audit/coverage/tag-openapi.md | 79 ++++ .audit/coverage/tag-prompts.md | 36 ++ .audit/coverage/tag-tech-spec-pipeline.md | 51 +++ .audit/coverage/tag-test-spec.md | 29 ++ .audit/coverage/tag-ux.md | 78 ++++ .audit/coverage/uat-practice.md | 59 +++ AGENTS.md | 31 +- README.md | 6 +- docs/CONTENT-WOO.md | 201 -------- docs/CREATE.md | 431 ------------------ docs/MILESTONES.md | 41 +- docs/PRODUCT.md | 24 +- docs/README.md | 33 +- docs/REQUIREMENTS.md | 23 +- docs/STRUCTURE.md | 14 +- docs/UAT-PRACTICE.md | 36 +- docs/{ => capabilities}/CAPTURE.md | 23 +- docs/capabilities/CREATE.md | 118 +++++ docs/{ => capabilities}/PHOTOS.md | 19 +- docs/contracts/API.md | 191 ++++++++ docs/contracts/CONTENT-WOO.md | 86 ++++ docs/contracts/DATA-MODEL.md | 142 ++++++ docs/contracts/PROMPTS.md | 91 ++++ docs/contracts/README.md | 9 +- docs/contracts/TEST-SPEC.md | 152 ++++++ docs/contracts/UX.md | 97 ++++ docs/contracts/WOO-API.md | 142 ++++++ 53 files changed, 3186 insertions(+), 817 deletions(-) delete mode 100644 .audit/2026-09-18-migration-todos.md rename .audit/{ => archive}/2026-09-18-coverage-gaps.md (85%) create mode 100644 .audit/archive/2026-09-18-migration-todos.md rename .audit/{ => archive}/2026-09-18-reconciliation-decisions.md (100%) rename .audit/{ => archive}/2026-09-18-session-handoff.md (100%) rename .audit/{ => archive}/docs-structure-pass.tsv (100%) create mode 100644 .audit/coverage/cur-content-woo.md create mode 100644 .audit/coverage/cur-create.md create mode 100644 .audit/coverage/docs-readme.md create mode 100644 .audit/coverage/issues.md create mode 100644 .audit/coverage/matrix/architecture.md create mode 100644 .audit/coverage/matrix/create.md create mode 100644 .audit/coverage/matrix/current-docs.md create mode 100644 .audit/coverage/matrix/issues.md create mode 100644 .audit/coverage/matrix/openapi.md create mode 100644 .audit/coverage/matrix/prd.md create mode 100644 .audit/coverage/matrix/tag-docs-remaining.md create mode 100644 .audit/coverage/matrix/test-spec.md create mode 100644 .audit/coverage/matrix/ux-design.md create mode 100644 .audit/coverage/prd.md create mode 100644 .audit/coverage/product.md delete mode 100644 .audit/coverage/settings-auth-health.md create mode 100644 .audit/coverage/tag-architecture.md create mode 100644 .audit/coverage/tag-data-model.md create mode 100644 .audit/coverage/tag-deployment.md create mode 100644 .audit/coverage/tag-impl-plan.md create mode 100644 .audit/coverage/tag-openapi.md create mode 100644 .audit/coverage/tag-prompts.md create mode 100644 .audit/coverage/tag-tech-spec-pipeline.md create mode 100644 .audit/coverage/tag-test-spec.md create mode 100644 .audit/coverage/tag-ux.md create mode 100644 .audit/coverage/uat-practice.md delete mode 100644 docs/CONTENT-WOO.md delete mode 100644 docs/CREATE.md rename docs/{ => capabilities}/CAPTURE.md (84%) create mode 100644 docs/capabilities/CREATE.md rename docs/{ => capabilities}/PHOTOS.md (87%) create mode 100644 docs/contracts/API.md create mode 100644 docs/contracts/CONTENT-WOO.md create mode 100644 docs/contracts/DATA-MODEL.md create mode 100644 docs/contracts/PROMPTS.md create mode 100644 docs/contracts/TEST-SPEC.md create mode 100644 docs/contracts/UX.md create mode 100644 docs/contracts/WOO-API.md diff --git a/.audit/2026-09-18-migration-todos.md b/.audit/2026-09-18-migration-todos.md deleted file mode 100644 index 0e37444..0000000 --- a/.audit/2026-09-18-migration-todos.md +++ /dev/null @@ -1,48 +0,0 @@ -# Migration TODOs — source docs - -- Date: 2026-09-18 -- Branch: `docs/clean-main` -- Purpose: edits deferred until Phase 2 step 3, when the capability and contract docs are - migrated into `docs/capabilities/` and `docs/contracts/`, made branch-neutral, and - converted to cite requirement IDs. -- Rule: these docs are not edited now. `REQUIREMENTS.md` is the only normative text and is - already correct for everything below. - -## CAPTURE.md - -1. Line 16, "Length of the slab is aligned with the long side of the picture" — ruled dead. - Remove. -2. Lines 128–129, the `kind=inventory` role label — discarded. The "first inventory photo is - the measurement reference" half is `CAPTURE-115`; keep it as a citation, drop the label. -3. Line 140, editor/storefront alpha presentation — discarded. -4. The sensitivity mapping (line 69) and display/source-pixels rule (lines 58–62) are now - `CAPTURE-41` and `CAPTURE-141`; cite them instead of restating. -5. Line 82 busy mark ("more than a fraction of a second"; as-built is 300 ms) — ruled a - nit, not a requirement. Leave as design prose or drop. - -## CREATE.md - -1. Grade cardinality: change "1 or more" to 1 in all three places (attributes table, - ready set, data shape). Ruling: Grade is exactly one. -2. `fig-*` cardinality: change "1 or more / required" to optional in the tags table and the - ready set. Ruling: tags optional; the Figure attribute is at least one. -3. Ready-set images: change "at least one inventory PNG" to two (clean calibration plus its - dimension-overlay copy). Ruling: `REVIEW-5` is the PoR. -4. Remove "cap at 5 images" from the create sequence. -5. Remove "user typed widths/sqft/bdft wins"; the calculated fields are read-only. -6. Fix the Call 2 line so the model writes the title/description and the deterministic - template is the fallback, not the assembler. - -## CONTENT-WOO.md - -1. Em dash in the title template and example → hyphen. -2. "Templates (always on)" and "templates inject those numbers after the prose comes back" → - template is the fallback only; the model writes when Call 2 runs. -3. `fig-*` "1+ required" and the ready set including `fig-*` tags → tags optional. -4. "≤5 inventory photos" → no cap; drop the "only `kind=inventory`" clause. -5. The empty `attributes` element shape is the known deferred decision; keep the placeholder. - -## PHOTOS.md - -No changes. Overlay legibility and arrow/text drawing detail were ruled design, not -requirements. diff --git a/.audit/2026-09-18-coverage-gaps.md b/.audit/archive/2026-09-18-coverage-gaps.md similarity index 85% rename from .audit/2026-09-18-coverage-gaps.md rename to .audit/archive/2026-09-18-coverage-gaps.md index 24d0e2a..3beedb0 100644 --- a/.audit/2026-09-18-coverage-gaps.md +++ b/.audit/archive/2026-09-18-coverage-gaps.md @@ -66,3 +66,11 @@ seven distinct holes below. Each is a requirement the source material carries an - Proposed home: contract detail, not a requirement: record in `docs/contracts/` in step 3 and mark the ledger atom `dropped` with rationale. - Status: open. + +## GAP-8 — prompt storage + +- Atom: `tag:DATA-MODEL prompt-files` (PROMPTS source still to walk). +- Hole: the source docs put prompts in `backend/app/prompts/`, bind-mounted read-only, read fresh + per call. Ruling 2026-09-18: prompts live in the DB with all revisions. No PoR row yet. +- Proposed home: `METADATA` (prompt management) or `SETTINGS`; resolve during the PROMPTS walk. +- Status: resolved 2026-09-18 — `METADATA-105` (prompts stored in the DB with all revisions). diff --git a/.audit/archive/2026-09-18-migration-todos.md b/.audit/archive/2026-09-18-migration-todos.md new file mode 100644 index 0000000..1d15149 --- /dev/null +++ b/.audit/archive/2026-09-18-migration-todos.md @@ -0,0 +1,183 @@ +# Migration TODOs — source docs + +- Date: 2026-09-18 +- Branch: `docs/clean-main` +- Purpose: edits deferred until Phase 2 step 3, when the capability and contract docs are + migrated into `docs/capabilities/` and `docs/contracts/`, made branch-neutral, and + converted to cite requirement IDs. +- Rule: these docs are not edited now. `REQUIREMENTS.md` is the only normative text and is + already correct for everything below. + +## CAPTURE.md + +1. Line 16, "Length of the slab is aligned with the long side of the picture" — ruled dead. + Remove. +2. Lines 128–129, the `kind=inventory` role label — discarded. The "first inventory photo is + the measurement reference" half is `CAPTURE-115`; keep it as a citation, drop the label. +3. Line 140, editor/storefront alpha presentation — discarded. +4. The sensitivity mapping (line 69) and display/source-pixels rule (lines 58–62) are now + `CAPTURE-41` and `CAPTURE-141`; cite them instead of restating. +5. Line 82 busy mark ("more than a fraction of a second"; as-built is 300 ms) — ruled a + nit, not a requirement. Leave as design prose or drop. + +## CREATE.md + +1. Grade cardinality: change "1 or more" to 1 in all three places (attributes table, + ready set, data shape). Ruling: Grade is exactly one. +2. `fig-*` cardinality: change "1 or more / required" to optional in the tags table and the + ready set. Ruling: tags optional; the Figure attribute is at least one. +3. Ready-set images: change "at least one inventory PNG" to two (clean calibration plus its + dimension-overlay copy). Ruling: `REVIEW-5` is the PoR. +4. Remove "cap at 5 images" from the create sequence. +5. Remove "user typed widths/sqft/bdft wins"; the calculated fields are read-only. +6. Fix the Call 2 line so the model writes the title/description and the deterministic + template is the fallback, not the assembler. + +## CONTENT-WOO.md + +1. Em dash in the title template and example → hyphen. +2. "Templates (always on)" and "templates inject those numbers after the prose comes back" → + template is the fallback only; the model writes when Call 2 runs. +3. `fig-*` "1+ required" and the ready set including `fig-*` tags → tags optional. +4. "≤5 inventory photos" → no cap; drop the "only `kind=inventory`" clause. +5. The empty `attributes` element shape is the known deferred decision; keep the placeholder. + +## PHOTOS.md + +No changes. Overlay legibility and arrow/text drawing detail were ruled design, not +requirements. + +## TEST-SPEC.md (→ `docs/contracts/TEST-SPEC.md`) + +1. Migrate and cite requirement IDs; drop the pre-ID source-doc references. +2. Purge superseded cases: UI-1, UIE-9, UIE-10 (four knobs); V-3, API-8 (validation module); + C2-2 (`previous_response_id` / thread); C1-1 (image source); C2-4 (prompt files); TV-6, TV-7, + E2E-5 (80 percent / 1600 px); UIE-1, E2E-4 (computed override). +3. Add cases for the two new rows: `APP-60` (keyboard, focus ring, 320 px reflow) and `APP-65` + (no secret material in logs). + +## IMPL-PLAN.md (retired) + +1. Retire `docs/IMPL-PLAN.md`. Phases and Gates A/B/C are superseded by `MILESTONES.md` plus the + PoR; acceptance and test obligations are QUALITY rows. +2. Superseded: four knobs; "1–5 photos"; 80 percent / 1600 px; calculated-field override; portable + context and the stateful probe; prompt files. + +## ARCHITECTURE.md + +1. §1, §2, §4 "four knobs" including Edge offset and Feather — controls are exactly Sheet and + Sensitivity (`CAPTURE-40`). +2. §2 "1–5 photos" — one or more (`CAPTURE-25`). +3. §6.3 ready set requires 1+ `fig-*` tag and 1+ grade — tags are optional and grade is exactly + one (`METADATA-30`, `METADATA-15`). +4. §8/§9 shared validation module, `validation_hash`, 412, stateful/stateless mode, and + per-slab thread — ruled cut 2026-09-18; thread work is out of the MVP and test-inference no + longer returns mode. +5. Retire `docs/ARCHITECTURE.md`: its testable rules are APP/CAPTURE/SETTINGS rows, and the + topology is covered by those plus `deploy/`. +6. §3.4 Call 1 image input "all originals downscaled to 1024" → clean processed photos + (`METADATA-35`). + +## UX.md (→ `docs/contracts/UX.md`) and UX-DESIGN.md (retired) + +1. Migrate `docs/UX.md` to `docs/contracts/UX.md`: branch-neutral, cite requirement IDs, do not + restate rules. +2. Retire `docs/UX-DESIGN.md` (non-normative). Fold the "AI" badge, first-run card, separate + Title/Description screen, and error-state copy into the UX contract. +3. Superseded in UX.md: four knobs → two; "1 to 5 photos" → no cap; "no calibration photos" → the + calibration photo exists; 80 percent / 1600 px → 90 percent / 800 px; calculated-field override + path → read-only; grade 1+ → exactly one; `fig-*` 1+ → optional; "templates always run" → + template fallback; "title/short/description not required to reach ready" → required + (`REVIEW-10`); `short_title` → `short_description`. + +## TECH-SPEC-PIPELINE.md (retired) + +1. Retire `docs/TECH-SPEC-PIPELINE.md`. Its deterministic rules are CAPTURE; its ten vectors are + QUALITY. Fold any wanted algorithm detail into the CAPTURE capability doc. +2. Superseded: 80 percent fill and the 1600 px floor → 90 percent and 800 px; four knobs → two; + the auto-dimension overlay is no longer v1.5 (it shipped). + +## PROMPTS.md (→ `docs/contracts/PROMPTS.md`) + +1. Prompts move from server text files to the database with all revisions (`METADATA-105`): drop + the read-only mount, host-editing, and reset-to-default script. +2. Remove `previous_response_id`, the portable-context machinery, and the shared-validation rules + from the Call 2 inputs. +3. "1–5 inventory photos" → no cap (`CAPTURE-25`). +4. Example title uses an em dash and a multiplication sign → hyphen separator; snap `short_title` + to `short_description`. + +## DEPLOYMENT.md (retired) + +1. Retire `docs/DEPLOYMENT.md`. Its testable rules are OPS/SETTINGS; its operational facts live in + `deploy/` (`INVENTORY.md`, `docker-compose.yml`, `Caddyfile.fragment`, `RELEASE-PROCESS.md`). +2. Fold any still-useful prose into `deploy/` (the deploy README is the natural home). +3. Repoint the UAT-PRACTICE references from `docs/DEPLOYMENT.md` §1/§3/§5/§8 to `deploy/`. +4. Superseded: `output_px_min 1600` → 800 px floor (`CAPTURE-105`); "prompt files live in git and + need no backup" → prompts live in the DB with all revisions (GAP-8). + +## DATA-MODEL.md (→ `docs/contracts/DATA-MODEL.md`) + +1. Grade "1+" → exactly one; `fig_tag_ids` "1+" → optional. +2. `output_px_min 1600` and `fill_target 0.80` → 800 px floor and 90 percent fill. +3. "Only inventory photos. Calibration photos do not exist." → the calibration photo exists; other + batch photos are visual inventory only (`CAPTURE-115`). +4. "No hard deletes for slabs (audit)" → the local slab row is deleted on successful create + (`PUBLISH-25`). +5. Remove `inference_thread_id`; thread work is out of the MVP. +6. Prompt files: replace the bind-mounted text files with prompts stored in the DB, all revisions + (PoR row pending the PROMPTS walk). +7. `short_title` → `short_description` (Woo canonical). + +## OPENAPI.md (→ `docs/contracts/API.md`) + +1. Field name `short_title` → `short_description` (Woo canonical). Applies to the schema, read + model, Call 2 result, and examples. +2. `fill_target 0.80` and `output_px_min 1600` → 90 percent fill and an 800 px floor + (`CAPTURE-100`, `CAPTURE-105`). +3. Remove the validation module endpoint, `validation_hash`, and the 412 path (ruled cut). +4. Remove `previous_response_id`, the per-slab thread, and `mode` from test-inference; Call 2 is + a fresh full-context call. +5. Grade cardinality "1+" → exactly one; `fig-*` "1+" → optional. +6. The taxonomy-sync example key `synced_at` should be `taxonomy_last_sync_at`. +7. Call 1 image input for infer-taxon: original photos → clean processed photos (`METADATA-35`). + +## PRODUCT.md + +1. Line 19, "One or more `fig-*` tags" — tags are optional per `METADATA-30`. Change to + optional. +2. Line 23, "Measured values feed title and description templates" — template is the fallback; + the model writes when Call 2 runs. Cite `METADATA-60`, `METADATA-70`, `METADATA-80`. Same + conflict in the decision-rights table, line 37 ("Templates from listing facts"). +3. Line 20, "short title" — stale wording; the field is `short_description` (`REVIEW-10`). +4. Branch-neutral: line 1 "Milestone status on `m3-ds`", and line 66 "behavior ... taken from + `main`". Remove branch names. +5. Line 25, umbrella term "Woo attributes" → "Woo Slab Taxonomy" (see cross-cutting rename). + +## README.md + +1. Branch-neutral: line 1 title "Docs map (branch `m3-ds`)" and line 44 "copied into + `docs/CREATE.md` from `main`". Remove branch names. +2. Line 39, umbrella "Woo attributes" → "Woo Slab Taxonomy" (see cross-cutting rename). + +## UAT-PRACTICE.md + +1. Line 100, "four knobs: Sheet, Sensitivity, Edge offset, Feather" — controls are exactly Sheet + and Sensitivity; Edge offset and Feather are removed (`CAPTURE-40`). +2. Line 97, "1 to 5 photos" — no cap; `CAPTURE-25` is one or more. +3. Line 122, "at least one figure, at least one grade" — grade is exactly one (`METADATA-15`). +4. Line 126, "`fig-*` tags. Pick at least one." — tags are optional (`METADATA-30`). +5. Branch-neutral: line 14 "On branch `m3-ds`". Stale refs to `docs/DEPLOYMENT.md`, `docs/UX.md`, + `docs/TECH-SPEC-PIPELINE.md`, `docs/IMPL-PLAN.md` in the "where each rule lives" table. + +## Cross-cutting: "Woo Slab Taxonomy" rename + +Ruling 2026-09-18: the umbrella package term is **Woo Slab Taxonomy**; canonical Woo +attributes (the five) keep the name. + +- Rename umbrella occurrences: `AGENTS.md:48`, `PRODUCT.md:25`, `CREATE.md:7`, `CREATE.md:34`, + `CREATE.md:50` (heading), `CREATE.md:261`, `CREATE.md:397`, `README.md:39`. +- Keep "Woo attributes" where it means the canonical five: `CREATE.md:61`, + `CONTENT-WOO.md:121`. +- Give the term one glossary home in the step-3 contract docs; make PRODUCT/AGENTS reference + it instead of redefining. diff --git a/.audit/2026-09-18-reconciliation-decisions.md b/.audit/archive/2026-09-18-reconciliation-decisions.md similarity index 100% rename from .audit/2026-09-18-reconciliation-decisions.md rename to .audit/archive/2026-09-18-reconciliation-decisions.md diff --git a/.audit/2026-09-18-session-handoff.md b/.audit/archive/2026-09-18-session-handoff.md similarity index 100% rename from .audit/2026-09-18-session-handoff.md rename to .audit/archive/2026-09-18-session-handoff.md diff --git a/.audit/docs-structure-pass.tsv b/.audit/archive/docs-structure-pass.tsv similarity index 100% rename from .audit/docs-structure-pass.tsv rename to .audit/archive/docs-structure-pass.tsv diff --git a/.audit/coverage/cur-content-woo.md b/.audit/coverage/cur-content-woo.md new file mode 100644 index 0000000..6cd775b --- /dev/null +++ b/.audit/coverage/cur-content-woo.md @@ -0,0 +1,8 @@ +# Coverage ledger — cur:CONTENT-WOO + +Source `cur:CONTENT-WOO` (`docs/CONTENT-WOO.md`, current branch). Seeded by the +settings/auth/health pilot 2026-09-18. + +| Source | Atom | Requirement | Disposition | PoR ID | Rationale | +|---|---|---|---|---|---| +| cur:CONTENT-WOO | httpx-timeouts | httpx connect 10s, read 60s | dropped | — | connection detail; recorded in docs/contracts in step 3 | diff --git a/.audit/coverage/cur-create.md b/.audit/coverage/cur-create.md new file mode 100644 index 0000000..d2f8dc2 --- /dev/null +++ b/.audit/coverage/cur-create.md @@ -0,0 +1,9 @@ +# Coverage ledger — cur:CREATE + +Source `cur:CREATE` (`docs/CREATE.md`, current branch). Seeded by the settings/auth/health +pilot 2026-09-18. + +| Source | Atom | Requirement | Disposition | PoR ID | Rationale | +|---|---|---|---|---|---| +| cur:CREATE | settings-url | WOO_BASE_URL from env, read-only; change means edit .env and recreate | ported | SETTINGS-5 | — | +| cur:CREATE | test-inference-shape | test-inference returns ok, mode, vision_capable, detail | ported | SETTINGS-45 | mode removed by ruling; SETTINGS-45 now returns ok, vision_capable, detail | diff --git a/.audit/coverage/docs-readme.md b/.audit/coverage/docs-readme.md new file mode 100644 index 0000000..9fcebeb --- /dev/null +++ b/.audit/coverage/docs-readme.md @@ -0,0 +1,19 @@ +# Coverage ledger — README.md (docs map) + +Source `cur:README` (`docs/README.md`, role: index). Walked 2026-09-18. Navigation-heavy; +only the testable statements carry PoR IDs. + +| Source | Atom | Requirement | Disposition | PoR ID | Rationale | +|---|---|---|---|---|---| +| cur:README | high-level-goal | one slab is one unit and one listing; capture flow | dropped | — | intent restatement; listing invariant covered by PUBLISH-60 | +| cur:README | implemented-m1-m2 | M1/M2 shipped, UAT on `:10881` | ported | OPS-40 | — | +| cur:README | no-woo-create-yet | no Woo create, backend stub, inference unwired | dropped | — | historical branch state, not a requirement | +| cur:README | m3-exit-criteria | ready set plus a real `SLAB-UAT-*` draft with inference off | ported | QUALITY-110, REVIEW-5, METADATA-70 | — | +| cur:README | app-password-server-side | WordPress application password, server-side only | ported | SETTINGS-15, SETTINGS-20, APP-25 | — | +| cur:README | sync-taxonomy | sync and use the synced taxonomy | ported | METADATA-5 | — | +| cur:README | pricing-species-rate | price from species `$/bdft` with user override | ported | REVIEW-25, SETTINGS-35 | — | +| cur:README | inference-settings-calls | inference Settings and Call 1 / Call 2 rules | ported | SETTINGS-30, SETTINGS-45, METADATA-35, METADATA-55 | — | +| cur:README | review-ready-create | review to the ready set, then create, force draft | ported | REVIEW-5, PUBLISH-10 | — | +| cur:README | copied-from-main | technical detail copied from `main` | dropped | — | branch-neutral migration, not a requirement | +| cur:README | folder-map | how to read the folder table | dropped | — | navigation | +| cur:README | deploy-reminders | UAT `:10881` not `:10880`; env vs encrypted settings | ported | OPS-40, OPS-10, SETTINGS-5, SETTINGS-20, SETTINGS-30 | — | diff --git a/.audit/coverage/issues.md b/.audit/coverage/issues.md new file mode 100644 index 0000000..2231d61 --- /dev/null +++ b/.audit/coverage/issues.md @@ -0,0 +1,11 @@ +# Coverage ledger — issues + +Source `issue:#n` (Gitea `Ty_Tech/SlabUploader` design issues). Seeded by the +settings/auth/health pilot 2026-09-18. The issue Source column distinguishes each issue. + +| Source | Atom | Requirement | Disposition | PoR ID | Rationale | +|---|---|---|---|---|---| +| issue:#5 | app-password | app password, dedicated user, AES-GCM, browser never holds it, sole caller | ported | SETTINGS-15, SETTINGS-20, APP-25 | — | +| issue:#11 | url-compose | WOO_BASE_URL declared in compose, not user-editable | ported | SETTINGS-5 | — | +| issue:#14 | taxonomy-refresh | taxonomy refresh triggers and anchor | ported | SETTINGS-50, SETTINGS-55, SETTINGS-60 | — | +| issue:#6 | instrument-health | health checks and instrumentation | ported | APP-45 | — | diff --git a/.audit/coverage/matrix/architecture.md b/.audit/coverage/matrix/architecture.md new file mode 100644 index 0000000..14a831f --- /dev/null +++ b/.audit/coverage/matrix/architecture.md @@ -0,0 +1,185 @@ +# Coverage matrix — ARCHITECTURE.md + +Source: `docs/ARCHITECTURE.md` at tag `pre-simplification`. +Method: subagent extracted every surface entry; PoR home assigned here. +Dispositions: `ported`, `superseded`, `dropped`, `ui`, `missing`. + +## SETTINGS KEYS + +| Entry | PoR | Disposition | Note | +|---|---|---|---| +| inference_enabled | SETTINGS-30, SETTINGS-90 | ported | default off added | +| Species $/bdft | SETTINGS-35 | ported | — | +| prompts (as settings) | METADATA-105 | superseded | prompts live in the DB, not settings | +| Inference endpoint base URL | SETTINGS-30 | ported | — | +| Inference API key | SETTINGS-30 | ported | — | +| Woo credentials | SETTINGS-15, SETTINGS-20 | ported | — | +| publish status | SETTINGS-65 | ported | — | +| Store URL env, read-only | SETTINGS-5 | ported | — | +| SLAB_AES_KEY | SETTINGS-25 | ported | — | +| WOO_BASE_URL | SETTINGS-5, SETTINGS-10 | ported | — | +| sheet mode | CAPTURE-40, CAPTURE-145 | ported | — | +| sensitivity | CAPTURE-41, CAPTURE-145 | ported | — | +| edge offset | CAPTURE-40 | superseded | removed control | +| feather | CAPTURE-40 | superseded | removed control | +| reset-to-default | CAPTURE-145 | ported | — | +| Species list with $/bdft | SETTINGS-35 | ported | — | +| Taxonomy snapshot | SETTINGS-50 | ported | — | + +## CONTROLS + +| Entry | PoR | Disposition | Note | +|---|---|---|---| +| New Wood 1–5 photos | CAPTURE-25 | superseded | no cap | +| Four mask knobs | CAPTURE-40 | superseded | two knobs | +| Mask edge OK | CAPTURE-50 | ported | — | +| Retune knobs / Retake | CAPTURE-50, CAPTURE-155 | ported | — | +| Length axis rotate/confirm | CAPTURE-60, CAPTURE-65 | ported | — | +| User length/thickness/SKU | CAPTURE-70 | ported | — | +| Call 1 pre-fill / manual taxonomy | METADATA-45, METADATA-50 | ported | — | +| Generate text button | METADATA-55, METADATA-110 | ported | availability added | +| Publish | PUBLISH-5 | ported | — | +| Settings "Refresh taxonomy" control | SETTINGS-60 | ui | optional; not required for POC | +| Inline nudges | REVIEW-20 | ported | — | + +## LISTING FIELDS + +| Entry | PoR | Disposition | Note | +|---|---|---|---| +| Photos 1–5 | CAPTURE-25 | superseded | no cap | +| SKU | REVIEW-45 | ported | — | +| length / thickness | CAPTURE-70 | ported | — | +| sqft / bdft / 6in widths | CAPTURE-80, CAPTURE-85, CAPTURE-90 | ported | — | +| 3:4 PNG | CAPTURE-95, CAPTURE-100 | ported | — | +| title / short_title / description | REVIEW-10 | ported | short_title naming migrated | +| species | METADATA-10 | ported | — | +| wood categories | METADATA-10 | ported | — | +| edge / figure | METADATA-15 | ported | — | +| grade | METADATA-15 | ported | cardinality superseded | +| feat-* | METADATA-30 | ported | — | +| fig-* | METADATA-30 | ported | cardinality superseded | +| price | REVIEW-25 | ported | — | +| moisture | METADATA-15 | ported | — | +| originals | PUBLISH-25, PUBLISH-40 | ported | purge after publish | +| woo_product_id | PUBLISH-25 | ported | — | + +## STATUSES + +| Entry | PoR | Disposition | Note | +|---|---|---|---| +| draft | APP-5 | ported | — | +| calibrated | APP-5 | ported | — | +| ready | APP-5, REVIEW-5 | ported | fig/grade cardinality superseded | +| publishing | APP-5 | ported | — | +| published | APP-5, PUBLISH-25 | ported | — | +| failed | APP-5, PUBLISH-30 | ported | — | +| transitions draft→...→published/failed | APP-5 | ported | — | + +## ENDPOINTS + +| Entry | PoR | Disposition | Note | +|---|---|---|---| +| /wp-json/wc/v3/ | SETTINGS-15 | ported | — | +| /api/v1/settings | SETTINGS-20, SETTINGS-30 | ported | — | +| /api/v1/admin/taxonomy | SETTINGS-50 | ported | — | +| POST /api/v1/settings/test-woo | SETTINGS-40, SETTINGS-80 | ported | — | +| GET /api/v1/validation/module | — | dropped | validation module ruled cut | +| POST /api/v1/settings/test-inference | SETTINGS-45 | ported | — | + +## ERROR CODES + +| Entry | PoR | Disposition | Note | +|---|---|---|---| +| 409 duplicate SKU only | PUBLISH-15 | ported | — | +| 422 stale value | PUBLISH-20 | ported | — | +| 412 validation hash | — | dropped | validation module ruled cut | +| "Never show 412" | PUBLISH-35 | dropped | moot after cut | + +## USER-FACING ERROR / EMPTY STATES + +| Entry | PoR | Disposition | Note | +|---|---|---|---| +| Call 1 runtime failure → retry or message | METADATA-115 | ported | recoverable retries, unrecoverable explains | +| Below 0.7 fields stay empty | METADATA-45 | ported | — | +| Duplicate SKU field error | PUBLISH-15 | ported | — | +| Stale value field error | PUBLISH-20 | ported | — | +| Review gate empty subset | REVIEW-5, REVIEW-20 | ported | — | +| Inline nudges, not submit-only | REVIEW-20 | ported | — | +| UI "Updating rules" | — | dropped | validation module cut | +| failed error detail stored, retry | PUBLISH-30 | ported | — | + +## INFERENCE + +| Entry | PoR | Disposition | Note | +|---|---|---|---| +| Call 1 auto-run only if inference_enabled | METADATA-35, SETTINGS-90 | ported | — | +| Call 1 skipped when disabled | METADATA-70 | ported | — | +| Call 1 input: all originals downscaled to 1024 | METADATA-35 | superseded | PoR sends the clean processed photos | +| Call 1 input: synced taxonomy only | METADATA-35, METADATA-40 | ported | — | +| Call 1 input: SKU/length/thickness | METADATA-35 | ported | — | +| Call 1 timeout 45s | METADATA-115 | ported | runtime failure triage | +| Threshold ≥0.7 pre-populate | METADATA-45 | ported | — | +| Below 0.7 empty | METADATA-45 | ported | — | +| Manual entry authoritative | METADATA-50 | ported | — | +| Call 2 no auto-fire | METADATA-55 | ported | — | +| Call 2 portable context / full resend | — | superseded | thread work out of MVP | +| previous_response_id | — | superseded | thread work out of MVP | +| Call 2 confirmed vs inferred | METADATA-60 | ported | — | +| per-slab thread, delete on submit/abandon | — | superseded | thread work out of MVP | +| test-inference mode stateful/stateless/fail | SETTINGS-45 | ported | — | +| fail blocks Call 2 only | SETTINGS-45 | ported | — | +| Call 1/2 assist-only, do not gate publish | METADATA-75 | ported | — | +| low-confidence does not auto-fill | METADATA-45 | ported | — | + +## PUBLISH PAYLOAD + +| Entry | PoR | Disposition | Note | +|---|---|---|---| +| Draft upload contents | CAPTURE-135 | ported | — | +| Originals needed for retry | PUBLISH-40 | ported | — | +| Post-publish deletion | PUBLISH-25 | ported | — | +| Duplicate SKU handling | PUBLISH-15 | ported | — | +| Stale value handling | PUBLISH-20 | ported | — | +| Product status per setting | SETTINGS-65, PUBLISH-10 | ported | — | +| Title/desc typed, generated, or templated | REVIEW-10, METADATA-70 | ported | templates are fallback | +| Publish poll | PUBLISH-5 | ported | — | +| woo_product_id stored | PUBLISH-25 | ported | — | +| Images purged | PUBLISH-25 | ported | — | +| Publish resync > 1 hour | SETTINGS-60 | ported | — | +| Submit carries validation_hash / 412 retry | — | dropped | validation module cut | +| Woo-shaped 422 store rejection | APP-50, PUBLISH-20 | ported | — | +| Taxonomy: categories/attributes/fig-* synced | SETTINGS-50, SETTINGS-51 | ported | scope narrowed to fig/feat | + +## CONSTANTS / NUMBERS + +| Entry | PoR | Disposition | Note | +|---|---|---|---| +| 1–5 photos | CAPTURE-25 | superseded | no cap | +| downscale 1024 | METADATA-35 | ported | source conflict noted | +| Call 1 timeout 45s | METADATA-115 | ported | runtime failure triage | +| confidence 0.7 | METADATA-45 | ported | — | +| 3:4 | CAPTURE-100 | ported | — | +| 80% fill | CAPTURE-100 | superseded | 90 percent | +| 6in widths | CAPTURE-80 | ported | — | +| SKU charset 3..24 | REVIEW-45 | ported | case widened | +| taxonomy staleness 1 hour | SETTINGS-60 | ported | — | +| ready cardinalities (PNG/species/wood/edge/figure/fig/grade/moisture/feat) | REVIEW-5 | ported | fig/grade superseded | +| five attributes | METADATA-15 | ported | — | + +## BROWSER / API SUPPORT + +| Entry | PoR | Disposition | Note | +|---|---|---|---| +| Browser does not call Woo or vision | APP-25 | ported | — | +| no CORS to store | APP-25 | ported | — | +| localStorage persistence | CAPTURE-145 | ported | — | +| client BG removal APIs | CAPTURE-140, CAPTURE-35 | ported | — | +| camera | CAPTURE-25, CAPTURE-20 | ported | — | +| no U2Net control (POC) | CAPTURE-165 | ported | — | +| U2Net deferred, not deleted | CAPTURE-165 | ported | — | +| title/desc Woo maxLength | REVIEW-50 | ported | Woo v3 documents no max; caps in `docs/contracts/WOO-API.md` §11 | + +## Findings + +None open. Runtime inference failure is `METADATA-115`; the Call 1 image source is +`METADATA-35`; title/description length caps are inherent Woo limits. diff --git a/.audit/coverage/matrix/create.md b/.audit/coverage/matrix/create.md new file mode 100644 index 0000000..637e809 --- /dev/null +++ b/.audit/coverage/matrix/create.md @@ -0,0 +1,158 @@ +# Coverage matrix — CREATE.md (current branch) + +Source: `docs/CREATE.md` (current working tree). +Method: every surface entry dispositioned to PoR IDs or a recorded drop. + +## Findings + +None new. Two open decisions in the doc are already settled elsewhere: the `attributes` element +shape is resolved in `docs/contracts/WOO-API.md` §4, and the crop fill/floor (90% / 800) is the +PoR. The client stack note ("Vite + Svelte SPA") is implementation, not a requirement. + +## Superseded (queued in migration TODOs) + +- ready set "at least one inventory PNG" → REVIEW-5 (two images) +- grade 1+ → METADATA-15 (exactly one) +- `fig-*` 1+ required → METADATA-30 (optional) +- `short_title` → REVIEW-10 (`short_description`) +- "user typed number wins if overridden" for widths/sqft/bdft → REVIEW-30, REVIEW-40 (read-only) +- "title/short/desc not required to reach ready" → REVIEW-10 +- "templates always available" and "server assembles from templates" → METADATA-70, METADATA-80 + (model writes when Call 2 runs; template is fallback) +- "Cap at 5 images" / "Cap ≤5" → PUBLISH-40 (no cap) +- Call 1 input "inventory photos downscaled to 1024" → METADATA-35 (clean processed photos) + +## SETTINGS KEYS + +- `WOO_BASE_URL` env, read-only → SETTINGS-5; HTTPS → SETTINGS-10 +- `SLAB_AES_KEY` env → SETTINGS-25 +- `woo_wp_username` / `woo_app_password` encrypted → SETTINGS-20 +- `woo_create_status` default draft → SETTINGS-65 +- `inference_base_url`, `inference_api_key`, `inference_model`, `inference_enabled` → SETTINGS-30 +- `content_llm_enabled` → SETTINGS-30, SETTINGS-90 +- `taxonomy_last_sync_at`, `taxonomy_anchor` → SETTINGS-55 +- `brand_voice`, `geo_context` → SETTINGS-30 + +## CONTROLS + +- store URL read-only → SETTINGS-5 +- credential entry → SETTINGS-20 +- Generate text disabled when endpoint down → METADATA-110, METADATA-75 +- Call 2 blocked when test-inference `fail` → SETTINGS-45 +- inline nudges → REVIEW-20 +- species picker synced only → METADATA-5, METADATA-10 +- attribute pickers synced only → METADATA-15 +- stale field / vanished id → PUBLISH-20, REVIEW-35 +- duplicate SKU actions → PUBLISH-15 +- create/publish trigger → PUBLISH-5 +- draft upload trigger → CAPTURE-135 + +## LISTING FIELDS + +- `sku` → REVIEW-45; `status` enum → APP-5 +- `length_in`, `thickness_in` 1/8 → CAPTURE-70 +- `sqft`, `bdft`, widths → CAPTURE-80, CAPTURE-85, CAPTURE-90 +- `species_id` 1 leaf → METADATA-10; `wood_category_ids` 1+ → METADATA-10 +- `edge_type_term_id` 1 → METADATA-15; `figure_term_ids` 1+ → METADATA-15 +- `thickness_term_id` round-up → METADATA-15; `moisture_term_id` default kiln-dried → METADATA-15 +- `feat_tag_ids` 0+ → METADATA-30 +- `price`, `price_per_bdft`, `price_source` → REVIEW-25 +- templates (title, short, description, `{character}`, `{geo_sentence}`) → contract detail in + `docs/contracts/CONTENT-WOO.md`; fallback only +- species/character/length/free-text thickness not attributes → METADATA-25 + +## STATUSES + +- draft/calibrated/ready/publishing/published/failed → APP-5 +- `woo_create_status` draft/publish → SETTINGS-65 +- test-inference mode stateful/stateless/fail → SETTINGS-45 +- device draft is not a Woo draft → APP-10 + +## ENDPOINTS + +- `/api/health` → APP-30, APP-45 +- slabs CRUD, photos, infer-taxon, infer-content, publish, taxonomy, pricing, settings → + `docs/contracts/API.md` +- Woo endpoints (`/wc/v3/products`, categories, tags, attributes, media) → + `docs/contracts/WOO-API.md` + +## ERROR CODES + +- 502 `woo_error` → SETTINGS-50, PUBLISH-30 +- 422 `unpublishable` → REVIEW-5 +- 409 `duplicate_sku` → PUBLISH-15 +- 202 publish → contract + +## USER-FACING ERROR / EMPTY STATES + +- sync failure keeps previous cache → SETTINGS-50 +- create blocked until ready → REVIEW-5, REVIEW-20 +- duplicate SKU block → PUBLISH-15 +- stale field, current options → PUBLISH-20 +- vanished id not silently swapped → REVIEW-35 +- failure: log, mark failed, keep photos, field-level error, no raw code → PUBLISH-30, PUBLISH-35 +- low-confidence not shown as defaults → METADATA-45 +- Generate text down → METADATA-110 +- no pricing rule → price empty, owner types → REVIEW-25 + +## INFERENCE + +- Call 1 auto only when enabled → METADATA-35 +- Call 1 input (photos, metadata, taxonomy) → METADATA-35 +- Call 1 output + confidence → METADATA-40 +- threshold 0.7 pre-fill / below empty → METADATA-45 +- moisture never inferred → METADATA-15 +- typed wins → METADATA-50 +- never blocks create / auto-publish → METADATA-75 +- Call 2 user-triggered; requires test-inference ok → METADATA-55, SETTINGS-45 +- model prose only, no dimensions → METADATA-65 +- templates fallback → METADATA-70 +- test-inference shape, fail blocks Call 2 only → SETTINGS-45 +- templates math from deterministic pipeline → METADATA-90 +- inference-off create → QUALITY-20, METADATA-70 +- FastAPI proxies inference → APP-25 + +## PUBLISH PAYLOAD / FLOW + +- payload fields (`name`, `type`, `status`, `sku`, `regular_price`, `description`, + `short_description`, `categories`, `tags`, `images`, `attributes`, `virtual`, `downloadable`, + `catalog_visibility`) → `docs/contracts/WOO-API.md`, PUBLISH-55 +- non-null only → PUBLISH-55 +- `SLAB-UAT-*` force draft → PUBLISH-10 +- images upload keep alpha, lineup order → PUBLISH-40, PUBLISH-45 +- published meaning (not live) → PUBLISH-50 +- success behavior (product id, purge, delete row, clear device draft) → PUBLISH-25, APP-15 +- failure behavior → PUBLISH-30 +- create sequence steps 1–4 → PUBLISH-5 +- `images alt` text → contract detail +- attributes element shape → resolved, `docs/contracts/WOO-API.md` §4 + +## CONSTANTS / NUMBERS + +- `per_page=100` → SETTINGS-50, WOO-API §7 +- connect 10s / read 60s → contract detail +- taxonomy staleness 1 hour → SETTINGS-60 +- SKU `[A-Z0-9-]{3,24}` → REVIEW-45 +- downscale 1024 → METADATA-35 +- confidence 0.7 → METADATA-45 +- bdft and price formulas → CAPTURE-90, REVIEW-25 +- 1/8 step → CAPTURE-70 +- five attributes → METADATA-15 +- UAT port `:10881`, do not touch `:10880` → OPS-40 +- fill 90% / 800px floor → CAPTURE-100, CAPTURE-105 + +## BROWSER / API SUPPORT + +- FastAPI only Woo caller; browser no creds → APP-25 +- app-password Basic auth, not consumer keys → SETTINGS-15 +- HTTPS only → SETTINGS-10 +- proxy inference, no key in browser → APP-25, SETTINGS-30 +- device draft IndexedDB → APP-10, CAPTURE-140 +- Woo source of truth, cache ids → METADATA-5 +- v1 no term creation → METADATA-20 +- taxonomy sync order, transactional, anchor, re-pull, triggers → SETTINGS-50, SETTINGS-55, + SETTINGS-60 +- pricing rules flat per species, seed, override sticks, manual allowed → SETTINGS-35, REVIEW-25 +- practice rules (`SLAB-UAT-*`, hidden, inference off) → PUBLISH-10, QUALITY-20 +- dishonest proxies → QUALITY-105 +- tag/leaf filters and feat mapping → SETTINGS-51, METADATA-10, METADATA-30, METADATA-100 diff --git a/.audit/coverage/matrix/current-docs.md b/.audit/coverage/matrix/current-docs.md new file mode 100644 index 0000000..04a4595 --- /dev/null +++ b/.audit/coverage/matrix/current-docs.md @@ -0,0 +1,83 @@ +# Coverage matrix — CONTENT-WOO, CAPTURE, PHOTOS (current branch) + +Sources: `docs/CONTENT-WOO.md`, `docs/CAPTURE.md`, `docs/PHOTOS.md` (current working tree). + +## Findings + +None new. Two notes: the copy-template storage location ("templates live in Settings") is +contract detail for `docs/contracts/CONTENT-WOO.md`, and the case-insensitive name-matching rule +is legacy — the review flow selects from pickers, so it is superseded by `METADATA-5`. + +## CONTENT-WOO.md + +Superseded: `short_title` → REVIEW-10; `fig-*` 1+ required → METADATA-30; "≤5 inventory photos" → +PUBLISH-40; "Only kind=inventory photos / calibration photos do not exist" → calibration exists +(CAPTURE-25, CAPTURE-30); "check against the shared rules (current copy)" → validation module cut +(REVIEW-5); case-insensitive name matching → METADATA-5. + +Ported: +- `content_llm_enabled`, `content_source` → SETTINGS-30, METADATA-66 +- `geo_sentence` from Settings, default none → SETTINGS-30 +- `woo_create_status` default draft → SETTINGS-65 +- `WOO_BASE_URL` env HTTPS; `SLAB_AES_KEY` → SETTINGS-5, SETTINGS-10, SETTINGS-25 +- templates title/short/description and `{character}` → contract detail + `docs/contracts/CONTENT-WOO.md` +- categories species 1 + wood 1+, tags fig/feat, five attributes → METADATA-10, METADATA-15, + METADATA-30 +- ready set → REVIEW-5 (fig/grade cardinality superseded) +- payload fields → `docs/contracts/WOO-API.md`, PUBLISH-55 +- `type simple`, `virtual false`, `downloadable false`, `catalog_visibility visible` → PUBLISH-55 +- image bytes keep alpha, `POST /media` → PUBLISH-40, WOO-API §5 +- `SLAB-UAT-*` force draft → PUBLISH-10 +- status `draft`/`publish`, published meaning → SETTINGS-65, PUBLISH-50 +- create sequence steps 1–7 → PUBLISH-5 +- dedupe every time → PUBLISH-15 +- post-create delete originals + processed → PUBLISH-25 +- failure: field message, mark failed, keep photos → PUBLISH-30, PUBLISH-35 +- partial-failure orphaned media manual cleanup → PUBLISH-31 +- taxonomy sync order, `per_page=100`, 1-hour reconcile, anchor → SETTINGS-50, SETTINGS-55, + SETTINGS-60 +- vision 0.7 fills; vision off typed; typed wins → METADATA-45, METADATA-50, METADATA-70 +- model writes prose only, synced choices only, no invented dimensions → METADATA-40, METADATA-65 +- httpx timeouts 10s/60s → contract detail +- wire `{field, code, message, actions[]}`, codes never shown → PUBLISH-35, API contract + +## CAPTURE.md + +Ported: +- Sheet control, Sensitivity 0–100 → CAPTURE-40, CAPTURE-41 +- immediate value update, 2.0 s recompute, last good overlay → CAPTURE-45 +- approve blocked with no slab pixels → CAPTURE-50 +- length axis rotate/approve → CAPTURE-60 +- length/thickness 1/8, Calculate → CAPTURE-70 +- scale `longest_axis_px / L` → CAPTURE-75 +- widths every 6 in, drop first/last 6, `L ≤ 12` all zero → CAPTURE-80 +- `sqft = mask_pixels / scale² / 144`, 2 dp → CAPTURE-85 +- `bdft = sqft × thickness_in`, 2 dp → CAPTURE-90 +- 3:4, 90% fill, shorter side ≥ 800, no upscale → CAPTURE-100, CAPTURE-105 +- PNG 8-bit RGBA, alpha kept → CAPTURE-95 +- browsers and hard stop → CAPTURE-10 +- client APIs and no OpenCV/ONNX/U2Net → CAPTURE-140 +- preview display-only, source pixels → CAPTURE-141 +- device draft, Resume/Continue/Start fresh → APP-10 +- 1/8 steps → CAPTURE-70; sensitivity maps green/black → CAPTURE-41 + +Dropped: busy-mark "fraction of a second" (nit, migration TODO); "other backgrounds out of M1" +(scope); M1 single reference photo superseded by batch (CAPTURE-25). + +## PHOTOS.md + +Ported: +- batch one or more, continue at least one → CAPTURE-25 +- pick calibration photo → CAPTURE-25 +- M1 states on the calibration photo only → CAPTURE-30 +- Photos ready lineup order → CAPTURE-130 +- per-photo empty mask / size floor excluded, rest proceed → CAPTURE-110 +- single-photo batch works → CAPTURE-25 +- crop rules reuse (3:4, 90%, PNG, floor) → CAPTURE-100, CAPTURE-105 +- overlay black; stations every 12 in from 6 in, while ≤ L−6; EXIF bottom → CAPTURE-120, + CAPTURE-125 +- 1–N photos → CAPTURE-25 +- no inference / Woo create on this path → APP-25 + +No findings. diff --git a/.audit/coverage/matrix/issues.md b/.audit/coverage/matrix/issues.md new file mode 100644 index 0000000..b4e16e6 --- /dev/null +++ b/.audit/coverage/matrix/issues.md @@ -0,0 +1,39 @@ +# Coverage matrix — Gitea design issues #1–#20 + +Source: `gitea.vps1.afterthedemo.com/Ty_Tech/SlabUploader` issues #1–#20 (design issues). +Method: every issue dispositioned to PoR IDs or a recorded drop. + +## Findings + +None open. Issue #6's structured logging, request id, and `LOG_LEVEL` are now `APP-55`. + +## Issue map + +| Issue | Atom | Disposition | PoR | +|---|---|---|---| +| #1 | portable multi-turn context (closed, superseded by #2) | superseded | thread work out; Call 2 fresh full context METADATA-60 | +| #2 | portable multi-turn context | ported | METADATA-60, SETTINGS-45 | +| #2 | per-slab thread, delete on submit/abandon | superseded | thread work out | +| #2 | confirmed vs inferred labels | ported | METADATA-60 | +| #3 | duplicate SKU means "any status" | ported | PUBLISH-15 | +| #4 | browser compatibility floor + hard stop + load-bearing APIs | ported | CAPTURE-10, CAPTURE-140 | +| #5 | Woo auth via application passwords, server-side, AES-GCM | ported | SETTINGS-15, SETTINGS-20, APP-25 | +| #6 | health checks DB + image-volume writability | ported | APP-45 | +| #6 | sync_log + inference_log written and queryable | ported | PUBLISH-30, METADATA-66 | +| #6 | structured logging, request id phone→FastAPI→Woo, LOG_LEVEL | ported | APP-55 | +| #7 | Prometheus metrics, alerting, log retention (V1) | dropped | out of M3 scope | +| #8 | Call 1 pre-populates review fields when enabled | ported | METADATA-45 | +| #9 | 0.7 threshold prefill; suite passes with inference off | ported | METADATA-45, QUALITY-20 | +| #10 | per-field inline nudges; UX presentation pass | ported | REVIEW-20; docs/contracts/UX.md | +| #11 | WOO_BASE_URL compose-only, read-only, HTTPS, not user-editable | ported | SETTINGS-5, SETTINGS-10, SETTINGS-85 | +| #12 | only synced species selectable | ported | METADATA-5 | +| #13 | store-admin creates a species leaf (V1) | dropped | out of M3 scope | +| #14 | taxonomy anchor, client re-pull, triggers | ported | SETTINGS-50, SETTINGS-55, SETTINGS-60 | +| #15 | BG knobs inline, live re-run, mask on source photo | ported | CAPTURE-45, CAPTURE-150 | +| #15 | four knobs | superseded | CAPTURE-40 | +| #15 | client-only POC; U2Net deferred | ported | CAPTURE-165 | +| #16 | V1 instrumentation | dropped | out of M3 scope | +| #17 | shared validation module, versioned, submit-checked | dropped | validation module cut | +| #18 | 409/422 mapped to field-level recovery, no top banners | ported | PUBLISH-15, PUBLISH-20, PUBLISH-35 | +| #19 | POC acceptance test cases | duplicate | TEST-SPEC owns the test plan | +| #20 | TEST-SPEC review feedback | dropped | process; TEST-SPEC reconciled last | diff --git a/.audit/coverage/matrix/openapi.md b/.audit/coverage/matrix/openapi.md new file mode 100644 index 0000000..0d9e27d --- /dev/null +++ b/.audit/coverage/matrix/openapi.md @@ -0,0 +1,152 @@ +# Coverage matrix — OPENAPI.md + +Source: `docs/OPENAPI.md` at tag `pre-simplification`. +Method: subagent extracted every surface entry; PoR home assigned here. +Dispositions: `ported`, `superseded`, `dropped` (contract detail), `ui`, `missing`. + +## Findings + +None. Three items are contract detail (photo 40 MB request cap, `GET /meta`, `inference_error` +field), and the schema conflicts are already resolved in the PoR. + +## SETTINGS KEYS + +- `woo_base_url` read-only → SETTINGS-5 +- `woo_base_url` not writable, reject write → SETTINGS-85 +- `woo_wp_username` / `woo_app_password` write-only, AES-GCM → SETTINGS-20 +- reject `woo_consumer_key` / `woo_consumer_secret` → SETTINGS-15 +- `woo_credentials_configured` boolean → SETTINGS-75 +- secrets masked, never echoed → SETTINGS-75 +- `inference_base_url`, `inference_model`, `inference_enabled`, `content_llm_enabled`, + `brand_voice`, `geo_context`, `woo_create_status` → SETTINGS-30, SETTINGS-65 +- `aspect_ratio` 3:4 → CAPTURE-100 +- `output_px` / `output_px_min` 1600, `fill_target` 0.80 → superseded CAPTURE-105 +- client prefs not in SettingsView → CAPTURE-145 +- PUT partial body, re-encrypt, no echo → SETTINGS-20, SETTINGS-30 +- `inference_enabled` default `true` in the example → superseded SETTINGS-90 (default off) + +## CONTROLS + +- pickers limited to synced taxonomy → METADATA-5 +- duplicate SKU actions `edit_sku` / `open_existing` → PUBLISH-15 +- stale field actions/options → PUBLISH-20 + +## LISTING FIELDS (SlabCreate, read model, results) + +- `id`, `client_rev` → APP-20 +- `sku` 3..24 uppercase → REVIEW-45 (case widened; Woo hard cap in WOO-API §9) +- `length_in`, `thickness_in` 1/8 → CAPTURE-70 +- `sqft`, `bdft`, widths → CAPTURE-80, CAPTURE-85, CAPTURE-90 +- `species_id`, `wood_category_ids` → METADATA-10 +- `edge_type_term_id`, `figure_term_ids`, `thickness_term_id`, `moisture_term_id` → METADATA-15 +- `grade_term_ids` (plural, 1+) → superseded METADATA-15 (exactly one, singular) +- `fig_tag_ids` (1+) → superseded METADATA-30 (optional) +- `feat_tag_ids` (0+) → METADATA-30 +- `price`, `price_source` → REVIEW-25 +- `price_per_bdft` on slab → contract detail (pricing rules SETTINGS-35) +- `title`, `description` → REVIEW-10 +- `short_title` → superseded REVIEW-10 (`short_description` canonical) +- `content_source` → METADATA-66 +- read model `status`, `inference_status`, `woo_product_id`, `published_at`, revisions → APP-5, + METADATA-35, PUBLISH-25, APP-20 +- `inference_error` → contract detail +- PhotoRead `kind` label → dropped (label discarded); urls → contract; 410 → PUBLISH-25 +- Call1 result ids/confidences → METADATA-40; free-text → METADATA-100 +- Call2 result → METADATA-55/60 +- PriceRule shape → SETTINGS-35 + +## STATUSES + +- `calibrated` on create → APP-5, CAPTURE-135 +- `ready` preconditions → REVIEW-5 (fig/grade cardinality superseded) +- `publishing` / `failed` / `published` → APP-5, PUBLISH-25, PUBLISH-30 +- `inference_status` enum → METADATA-35 +- duplicate check across any Woo status → PUBLISH-15 + +## ENDPOINTS + +- `GET /api/health` body → APP-45 +- `GET /api/v1/meta` → contract detail +- `POST/GET/PUT/DELETE /api/v1/slabs` → docs/contracts/API.md +- `POST .../u2net/{pid}` reserved → CAPTURE-165 +- `GET .../photos/...` → PUBLISH-25 +- `POST .../infer-taxon` → METADATA-35 +- `POST .../infer-content` → METADATA-55 +- `POST .../publish` → PUBLISH-5 +- taxonomy GET/sync → SETTINGS-50, SETTINGS-55, SETTINGS-60 +- pricing CRUD → SETTINGS-35 +- settings GET/PUT, test-woo, test-inference → SETTINGS-20, SETTINGS-30, SETTINGS-40, SETTINGS-45, + SETTINGS-75, SETTINGS-85 +- `GET /api/v1/validation/module` → dropped (module cut) + +## ERROR CODES + +- 400, 404, 500 → APP-35 +- 409 `revision_conflict` → APP-20 +- 409 `duplicate_sku` → PUBLISH-15 +- 422 `unpublishable` → REVIEW-5, REVIEW-20 +- 422 `stale_field` → PUBLISH-20 +- 422 test-inference gate → SETTINGS-45 +- 422 `woo_base_url` write → SETTINGS-85 +- 502 `woo_error` → PUBLISH-30, SETTINGS-50 +- 504 timeout → METADATA-115 +- 410 photos → PUBLISH-25 +- 412 validation module → dropped (module cut) + +## USER-FACING ERROR / EMPTY STATES + +- never show the code, no raw code → PUBLISH-35 +- unpublishable UI mapping → REVIEW-20 +- test-woo fails closed → SETTINGS-80 +- test-inference `fail` blocks Call 2 only → SETTINGS-45, METADATA-75 +- low-confidence no prefill → METADATA-45 +- Woo failure surfaces as failed + log → PUBLISH-30 +- taxonomy sync failure keeps previous cache → SETTINGS-50 + +## INFERENCE + +- Call 1 auto only if enabled → METADATA-35 +- Call 1 inputs "original photos downscaled to 1024" → superseded METADATA-35 (clean processed + photos) +- Call 1 timeout 45s → METADATA-115 +- confidence persist ≥0.7 → METADATA-45 +- free-text → feat-* mapping → METADATA-100 +- Call 2 default resend full context → METADATA-60 (no thread) +- `previous_response_id`, per-slab thread → superseded (thread work out) +- Call 2 requires test-inference ok → SETTINGS-45 +- validation module shared with Call 2 → dropped (module cut) + +## PUBLISH PAYLOAD / FLOW + +- preconditions → REVIEW-5 +- dedupe → PUBLISH-15 +- stale → PUBLISH-20 +- success (product id, purge, delete row) → PUBLISH-25 +- failure (failed, sync_log, poll 502) → PUBLISH-30 +- create-time Woo status → SETTINGS-65, PUBLISH-10 + +## CONSTANTS / NUMBERS + +- base path `/api/v1`, version bump → APP-30 +- timestamps UTC ISO-8601 → APP-20 +- photo request cap 40 MB → contract detail +- SKU pattern `[A-Z0-9-]{3,24}` → REVIEW-45 +- 1/8 step → CAPTURE-70 +- price 2 decimals → REVIEW-25 +- poll backoff 1/2/4/10s → contract detail +- 1024 downscale → METADATA-35 +- 0.7 threshold → METADATA-45 +- 45s timeout → METADATA-115 +- 3:4 → CAPTURE-100 +- 1600 / 0.80 → superseded CAPTURE-105 +- publish cardinalities → REVIEW-5 + +## BROWSER / API SUPPORT + +- client pins version → APP-30 +- no client auth → OPS-55 +- Woo HTTPS Basic Auth, application password, not consumer keys → SETTINGS-10, SETTINGS-15 +- u2net post-POC not exposed → CAPTURE-165 +- out of contract (bulk, staff, webhook, analytics) → dropped +- test-woo always resyncs → SETTINGS-40 +- anchor re-pull / bump rules → SETTINGS-55, SETTINGS-60 diff --git a/.audit/coverage/matrix/prd.md b/.audit/coverage/matrix/prd.md new file mode 100644 index 0000000..ba06733 --- /dev/null +++ b/.audit/coverage/matrix/prd.md @@ -0,0 +1,115 @@ +# Coverage matrix — PRD-2026-08.txt + +Source: `docs/archive/PRD-2026-08.txt` (frozen history). Method: every labeled atom dispositioned. + +## Findings + +None new. The two potential holes from this doc are already covered: inference timeout/error → +`METADATA-115`, and no app-level auth → `OPS-55`. The archive's own banner already flags FR17, +FR19, FR40, FR42, and the NFR server timing as non-authoritative. + +## Archive banner + +- Flags FR17, FR19, FR40, FR42, NFR server timing, and "Known stale" → each of those lines is + superseded by a current row (below). + +## Decisions log (2026-08-31) + +- Doc contradiction freeze → dropped (process). +- bdft identity, secrets AES-GCM, online-only, `woo_create_status` default draft, `SLAB-UAT-*` + force draft, hybrid happy path, inference off first → ported CAPTURE-90, SETTINGS-20/25, + CAPTURE-15, SETTINGS-65, PUBLISH-10, CAPTURE-5, QUALITY-20. +- Review package not in repo → dropped. + +## Decisions log (2026-08-27) + +| # | Atom | Disposition | PoR | +|---|---|---|---| +| 1 | no calibration photos | superseded | CAPTURE-25, CAPTURE-30 | +| 1 | manual SKU/length/thickness | ported | CAPTURE-70 | +| 2 | first photo is the measurement photo, top-down | ported | CAPTURE-30, CAPTURE-20 | +| 3 | all photos 80% crop | superseded | CAPTURE-100 | +| 4 | transparent PNG min 1600, 3:4 | superseded | CAPTURE-95, CAPTURE-105 | +| 5 | green/black auto-detect from borders | ported | CAPTURE-35 | +| 6 | species 1 leaf, wood 1+ leaf | ported | METADATA-10 | +| 7 | edge 1, figure 1+, thickness round-up | ported | METADATA-15 | +| 7 | grade 1+ | superseded | METADATA-15 | +| 8 | tags fig/feat 1+ | superseded | METADATA-30 | +| 9 | two-call inference | ported | METADATA-35, METADATA-55 | +| 10 | Call 1 auto only if enabled; review before Call 2 | ported | METADATA-35, METADATA-55 | +| 11 | timeout/non-200 error + retry | ported | METADATA-115 | +| 12 | create status default draft; SLAB-UAT force draft | ported | SETTINGS-65, PUBLISH-10 | +| 13 | prompts as server text files | superseded | METADATA-105 | +| 14 | brand voice + GEO into Call 2 | ported | METADATA-60, SETTINGS-30 | +| 15 | Woo creds server-side AES-GCM | ported | SETTINGS-15, SETTINGS-20 | +| 16 | inference endpoint configurable | ported | SETTINGS-30 | +| 17 | test connection text + vision | ported | SETTINGS-45, SETTINGS-80 | +| 18 | offline not required | ported | CAPTURE-15 | + +Open items: none → dropped. + +## Functional requirements + +- FR1 (1–5 photos) superseded → CAPTURE-25; first top-down ported → CAPTURE-20 +- FR2 manual SKU/length/thickness → CAPTURE-70 +- FR3 border BG detect → CAPTURE-35 +- FR4 scale/sqft/bdft, old divide-by-12 wrong → CAPTURE-75, CAPTURE-85, CAPTURE-90 +- FR5 80% crop / 1600 / 3:4 → superseded CAPTURE-100, CAPTURE-105 +- FR6 retake/abort → CAPTURE-155 +- FR7 deterministic compute → CAPTURE-85, CAPTURE-90 +- FR8 thickness manual authoritative → CAPTURE-70, METADATA-50 +- FR9 deterministic unit-tested → QUALITY-5, QUALITY-15 +- FR10 sync categories/attributes/tags → SETTINGS-50 +- FR11 species 1 leaf → METADATA-10 +- FR12 wood 1+ leaf → METADATA-10 +- FR13 edge 1 → METADATA-15 +- FR14 figure 1+ → METADATA-15 +- FR15 grade 1+ → superseded METADATA-15 +- FR16 thickness round-up → METADATA-15 +- FR17 fig-* 1+ → superseded METADATA-30 +- FR18 auto-assign + manual override → METADATA-40, METADATA-45, METADATA-50 +- FR19 category-weighted pricing → superseded SETTINGS-35 +- FR20 bdft pricing → REVIEW-25 +- FR21 price editable, stored authoritative → REVIEW-25 +- FR22 Call 1 vision with confidences → METADATA-35, METADATA-40 +- FR22a threshold 0.7 → METADATA-45 +- FR22b species bias full list → METADATA-40 +- FR22c free-text → feat-* → METADATA-100 +- FR23 Call 2 user-triggered; prose only; server assembles templates → METADATA-55, METADATA-65; + server-assembles superseded → METADATA-70, METADATA-80 +- FR24 prompts text files + reset script → superseded METADATA-105 +- FR25 progress indicator → dropped (presentation) +- FR26 timeout/non-200 error + retry → METADATA-115 +- FR27 results editable, no auto publish → METADATA-50, METADATA-75 +- FR28 endpoint configurable + test text/vision → SETTINGS-30, SETTINGS-45, SETTINGS-80 +- FR29 chroma-key, U2Net fallback → CAPTURE-35, CAPTURE-165 +- FR30 80% / 3:4 / 1600 → superseded CAPTURE-100, CAPTURE-105 +- FR31 transparent PNG → CAPTURE-95 +- FR32 original + processed, purge after publish → PUBLISH-25, PUBLISH-40 +- FR33 consumer keys + app password → superseded SETTINGS-15 +- FR34 publish blocked on existing SKU → PUBLISH-15 +- FR35 create product fields → PUBLISH-55 +- FR36 default draft; SLAB-UAT draft → SETTINGS-65, PUBLISH-10 +- FR37 log sync, surface errors, store product id → PUBLISH-25, PUBLISH-30 +- FR38 draft slab state → APP-10 +- FR39 single review screen, all editable → REVIEW-40 +- FR40 server-side processing → superseded CAPTURE-135, APP-40 +- FR41 settings UI → SETTINGS-15, SETTINGS-20, SETTINGS-30, SETTINGS-35 +- FR42 compose on `.201` Caddy → superseded OPS-5, OPS-15, OPS-40 +- FR43 secrets encrypted, HTTPS → SETTINGS-20, SETTINGS-25, OPS-5 + +## Non-functional requirements + +- performance budgets (1–2s, 3–5s, 45s) → dropped (server timing non-authoritative) +- reliability: unit-tested → QUALITY-5, QUALITY-15 +- security: encrypted/HTTPS → SETTINGS-20, SETTINGS-25, OPS-5 +- maintainability separation → dropped +- usability one-hand → dropped + +## Acceptance criteria + +- E2E capture→publish on iPhone/Android → QUALITY-115 +- area accuracy +/- 0.25 in² → dropped (sqin banned) +- normalization 80% / PNG / 1600 → superseded CAPTURE-100, CAPTURE-105 +- Woo product images/taxonomy/content → PUBLISH-40, PUBLISH-45, PUBLISH-55 +- deterministic unit tests + inference logged → QUALITY-5, METADATA-66 diff --git a/.audit/coverage/matrix/tag-docs-remaining.md b/.audit/coverage/matrix/tag-docs-remaining.md new file mode 100644 index 0000000..502c3a7 --- /dev/null +++ b/.audit/coverage/matrix/tag-docs-remaining.md @@ -0,0 +1,84 @@ +# Coverage matrix — DATA-MODEL, PROMPTS, TECH-SPEC-PIPELINE, DEPLOYMENT, IMPL-PLAN + +Sources: the five tag docs at `pre-simplification`. + +## Findings + +None new. The remaining entries are already ported, superseded by prior rulings, or process +detail. No new requirement is missing. + +## Superseded (common to several) + +- four knobs including edge offset and feather → CAPTURE-40 +- "1–5 photos" / "1–N photos" cap → CAPTURE-25 +- 80% fill / 1600 px floor → CAPTURE-100, CAPTURE-105 +- grade "1+" / `grade_term_ids` plural → METADATA-15 (exactly one, singular) +- `fig-*` 1+ → METADATA-30 (optional) +- `inference_thread_id`, portable context, `previous_response_id`, per-slab thread → thread work + out +- prompt files on disk, reset script, "prompts are server files" → METADATA-105 (DB with revisions) +- `output_px` / `output_px_min` 1600, `fill_target` 0.80 → CAPTURE-100, CAPTURE-105 +- validation module rules in Call 2 → module cut +- em dash title example → hyphen separator + +## DATA-MODEL.md + +- settings keys (`woo_wp_username`, `woo_app_password`, inference keys/flags, brand/GEO, + `woo_create_status` draft, taxonomy stamps) → SETTINGS-20, SETTINGS-30, SETTINGS-55, SETTINGS-65 +- `WOO_BASE_URL` not a settings row → SETTINGS-5; `SLAB_AES_KEY` env, `value_enc` nonce||ciphertext + → SETTINGS-25 +- taxonomy columns and cardinalities → METADATA-10, METADATA-15, METADATA-30 +- `sku`, `price`, `price_source`, `content_source` → REVIEW-45, REVIEW-25, METADATA-66 +- status machine, transitions, `sync_log.status` → APP-5, PUBLISH-30 +- published deletes row + photos, keeps sync_log → PUBLISH-25 +- IDs/timestamps/rounding conventions → APP-20, CAPTURE-160 +- `sync_log.payload_hash`, `woo_product_id`, `error`, `http_status` → contract detail +- `inference_log` append-only, `call` 1|2 → METADATA-66 +- `client_rev`/`server_rev` default 1 → APP-20 + +## PROMPTS.md + +- `inference_enabled`, `content_llm_enabled` gates → SETTINGS-90, METADATA-35, METADATA-70 +- Call 1 purpose, inputs, synced-only constraint, schema, threshold, prefill, manual-wins, + ready-not-defined, inference-off → METADATA-5, METADATA-35, METADATA-40, METADATA-45, + METADATA-50, METADATA-75 +- free-text → feat-* mapping → METADATA-100 +- Call 2 purpose, prose-only, no dimensions, `content_llm_enabled` false → METADATA-55, + METADATA-65, METADATA-70 +- brand/GEO + templates → SETTINGS-30, contract detail +- fail closed → SETTINGS-45, METADATA-115 +- re-run Call 1 endpoint → API contract + +## TECH-SPEC-PIPELINE.md + +- sheet/sensitivity knobs, persistence, immediate re-run, confirm, rotate, nearly square → + CAPTURE-40, CAPTURE-41, CAPTURE-45, CAPTURE-50, CAPTURE-60, CAPTURE-65, CAPTURE-145, CAPTURE-150 +- mask single contour, retake → CAPTURE-55 +- scale/sqft/bdft/widths/price formulas → CAPTURE-75, CAPTURE-80, CAPTURE-85, CAPTURE-90, + REVIEW-25 +- no-rule empty price, publish allowed → REVIEW-25 +- unsupported client hard stop, client APIs → CAPTURE-10, CAPTURE-140 +- factory constants (hue/sat/val, black threshold, border strip, inch step, decimals) → contract + detail (client constants), CAPTURE-35, CAPTURE-41 +- no inference in scope → CAPTURE-5 + +## DEPLOYMENT.md + +- `SLAB_AES_KEY`, `WOO_BASE_URL` env, creds encrypted → SETTINGS-25, SETTINGS-5, SETTINGS-20 +- `woo_create_status` draft, UAT draft → SETTINGS-65, PUBLISH-10 +- health, test-woo on rotate, inference remote → APP-45, SETTINGS-40, OPS-45 +- host inventory, ports, LAN/Tailscale, Caddy HTTPS, env mode → OPS-15, OPS-5, OPS-20 +- backup 7 daily / 4 weekly → OPS-30 +- probes/ports proposal, rekey command, backup schedule → dropped (operational detail) +- `output_px_min` floor line → superseded CAPTURE-105 + +## IMPL-PLAN.md + +- ground rules, inference-toggle suite, status machine → QUALITY-5, QUALITY-10, QUALITY-20, APP-5 +- phase deliverables restate CAPTURE/REVIEW/METADATA/PUBLISH/SETTINGS rows +- `woo_create_status`, dedupe, stale recovery, sync_log/purge, partial failure → PUBLISH-10, + PUBLISH-15, PUBLISH-20, PUBLISH-25, PUBLISH-30, PUBLISH-31 +- Woo client basic auth, no consumer keys → SETTINGS-15 +- E2E browsers, no OCR/ruler/offline → QUALITY-115, CAPTURE-15 +- coverage ≥95%, specific UAT slab set, LoRA, "no processing status" → dropped (process/quality + targets, not product requirements) diff --git a/.audit/coverage/matrix/test-spec.md b/.audit/coverage/matrix/test-spec.md new file mode 100644 index 0000000..b87d190 --- /dev/null +++ b/.audit/coverage/matrix/test-spec.md @@ -0,0 +1,109 @@ +# Coverage matrix — TEST-SPEC.md + +Source: `docs/TEST-SPEC.md` at tag `pre-simplification` (107 cases). Reconciled last, against the +finished PoR. + +## Findings + +Two behaviors were tested but not required; both are now PoR rows: + +- FUND-3, FUND-5, FUND-6, FUND-7 (keyboard tab order, visible focus ring, 320 px reflow) → `APP-60` +- FUND-24 (no secret material in logs) → `APP-65` + +## Superseded cases to purge on migration + +- UI-1, UIE-9, UIE-10 — four knobs (edge offset, feather) → two controls +- V-3, API-8 — shared validation module, version hash, 412 +- C2-2 — `previous_response_id` / per-slab thread +- C1-1 — "inventory photos at 1024" → clean processed photos +- C2-4 — prompt file edits without restart → prompts live in the DB +- TV-6, TV-7, E2E-5 — 80 percent fill / 1600 px → 90 percent / 800 px +- UIE-1, E2E-4 — manual override of computed fields → read-only + +## Case map + +FUND +- FUND-1..4, 8..11 — test conventions → dropped (test practice, not requirements) +- FUND-3, 5, 6, 7 — accessibility → APP-60 +- FUND-12, 13, 16 — SQLite constraints/migrations → dropped (engineering practice) +- FUND-14 — WAL stale reads → dropped (engineering practice) +- FUND-15 — illegal status transitions rejected → APP-5 +- FUND-17 — fresh nonce, tamper fails → SETTINGS-25 +- FUND-18 — ciphertext absent from GET → SETTINGS-75 +- FUND-19 — missing key refuses startup → SETTINGS-25 +- FUND-20 — Basic auth app passwords, HTTPS → SETTINGS-15 +- FUND-21 — 401/400/404 mapped → APP-50 +- FUND-22 — pre-create SKU lookup, race → PUBLISH-15 +- FUND-23 — timeouts/network errors graceful, no stack → PUBLISH-30, PUBLISH-35 +- FUND-24 — secrets redacted in logs → APP-65 +- FUND-25 — HTTPS, health via proxy, port collision → OPS-5, OPS-15, OPS-20 + +TV +- TV-1..TV-10 → QUALITY-55..QUALITY-100 (TV-6/TV-7 numeric values superseded) + +Validation +- V-1 — SKU charset → REVIEW-45 +- V-2 — title/description limits → REVIEW-50 +- V-3 — module hash / 412 → dropped + +AES / health / draft +- AES-1 → QUALITY-125, SETTINGS-75 +- AES-2 → SETTINGS-25 +- H-1 → APP-45 +- D-1 → API contract, APP-5 + +Gate C / Woo +- GC-1 → PUBLISH-10; GC-2 → PUBLISH-15; GC-3 → SETTINGS-40; GC-4 → SETTINGS-85; GC-5 → SETTINGS-10 +- W-1 → PUBLISH-25; W-2 → PUBLISH-15; W-3 → SETTINGS-55; W-4 → SETTINGS-40; W-5 → PUBLISH-30 + +Inference +- INF-1 → METADATA-75, QUALITY-20 +- INF-2, INF-3 → SETTINGS-45 (mode is kept) +- INF-4 → SETTINGS-45 + +UI (capture) +- UI-1 → superseded CAPTURE-40 +- UI-2 → CAPTURE-50, CAPTURE-55, CAPTURE-165 +- UI-3 → CAPTURE-60, CAPTURE-65 +- UI-4 → CAPTURE-70, CAPTURE-80, CAPTURE-85, CAPTURE-90 +- UI-5 → REVIEW-5, REVIEW-20 + +Ops +- OPS-1 → APP-45; OPS-2 → OPS-15; OPS-3 → SETTINGS-40, SETTINGS-65, APP-45; OPS-4 → OPS-30; + OPS-5 → OPS-35 + +API +- API-1 → APP-20; API-2 → APP-40; API-3 → SETTINGS-75; API-4 → METADATA-35, METADATA-115; + API-5 → METADATA-45; API-6 → REVIEW-5; API-7 → SETTINGS-50, SETTINGS-55; API-8 → dropped; + API-9 → API contract; API-10 → SETTINGS-80 +- TAX-1 → SETTINGS-60; TAX-2 → METADATA-5; TAX-3 → SETTINGS-60 + +UI end-to-end +- UIE-1 → superseded REVIEW-30, REVIEW-40 +- UIE-2 → METADATA-45, REVIEW-20 +- UIE-3 → PUBLISH-15; UIE-4 → PUBLISH-20; UIE-5 → PUBLISH-25; UIE-6 → PUBLISH-30 +- UIE-7 → METADATA-70, METADATA-110 +- UIE-8 → SETTINGS-5, SETTINGS-15, SETTINGS-20, SETTINGS-30, SETTINGS-35, SETTINGS-65 +- UIE-9, UIE-10 → superseded CAPTURE-40, CAPTURE-45, CAPTURE-145 + +Calls +- C1-1 → superseded METADATA-35 (clean processed photos) +- C1-2, C1-3 → METADATA-45, REVIEW-5 +- C2-1 → METADATA-55, METADATA-60 +- C2-2 → superseded (thread work out) +- C2-3 → METADATA-65 +- C2-4 → METADATA-66, METADATA-105 +- OFF-1 → QUALITY-20; OFF-2 → SETTINGS-45 + +Instrumentation +- INS-1 → APP-45; INS-2 → APP-55; INS-3 → METADATA-66; INS-4 → PUBLISH-30 + +E2E +- E2E-1..3 → QUALITY-115 +- E2E-4 → superseded REVIEW-30, REVIEW-40 +- E2E-5 → superseded CAPTURE-100, CAPTURE-105 + +## Test harness (contract detail) + +Harness layout, allowed result values, evidence rules, and the Gitea-issue bug log are engineering +practice. They belong in `docs/contracts/TEST-SPEC.md`, not the PoR. diff --git a/.audit/coverage/matrix/ux-design.md b/.audit/coverage/matrix/ux-design.md new file mode 100644 index 0000000..7b70b3b --- /dev/null +++ b/.audit/coverage/matrix/ux-design.md @@ -0,0 +1,217 @@ +# Coverage matrix — UX-DESIGN.md (pilot) + +Purpose: prove every entry on a reference surface in the source doc has a home in the PoR. +Source: `docs/UX-DESIGN.md` at tag `pre-simplification` (non-normative wireframes). +Method: subagent extracted every entry mechanically; this file assigns the PoR home and disposition. +Dispositions: `ported`, `superseded`, `dropped`, `ui` (presentation-only, not a requirement), +`missing` (a finding). + +Pilot proof: the three entries fixed in SETTINGS-45 / SETTINGS-90 / METADATA-110 are marked +`ported` below. Before that fix they had no home, which is exactly what this matrix surfaces. + +## SETTINGS KEYS + +| Entry | PoR | Disposition | Note | +|---|---|---|---| +| Store URL field | SETTINGS-5 | ported | env, read-only | +| WP Username field | SETTINGS-20 | ported | — | +| Application Password field | SETTINGS-20 | ported | — | +| Inference Endpoint field | SETTINGS-30 | ported | — | +| Inference API Key field | SETTINGS-30 | ported | — | +| Enable Vision toggle, default off | SETTINGS-90 | ported | fixed | +| Enable Content toggle, default off | SETTINGS-90 | ported | fixed | +| Pricing Rules | SETTINGS-35 | ported | — | +| Publish Status dropdown, default Draft | SETTINGS-65 | ported | — | +| Brand Voice field | SETTINGS-30 | ported | — | +| GEO Context field | SETTINGS-30 | ported | — | +| Sheet dropdown | CAPTURE-40 | ported | — | +| Edge Offset control | CAPTURE-40 | superseded | removed | +| Feather control | CAPTURE-40 | superseded | removed | +| Knob persistence localStorage | CAPTURE-145 | ported | — | + +## CONTROLS + +| Entry | PoR | Disposition | Note | +|---|---|---|---| +| Test Connection (WP) | SETTINGS-40, SETTINGS-80 | ported | resync and failure | +| Test Connection (Inference) two-turn probe, stateful/stateless/fail | SETTINGS-45 | ported | fixed | +| Enable Vision / Enable Content buttons | SETTINGS-90 | ported | — | +| Add Rule / Edit (pricing) | SETTINGS-35 | ported | — | +| Reset Capture Knobs | CAPTURE-145 | ported | — | +| Refresh Store Lists | SETTINGS-60 | ported | — | +| Got it (first-run card) | — | ui | presentation | +| R retake shortcut | CAPTURE-50 | ported | — | +| Sheet dropdown | CAPTURE-40 | ported | — | +| Sensitivity slider | CAPTURE-41, CAPTURE-45 | ported | — | +| Edge Offset slider | CAPTURE-40 | superseded | removed | +| Feather slider | CAPTURE-40 | superseded | removed | +| Retake (capture) | CAPTURE-50, CAPTURE-155 | ported | — | +| Confirm (capture) | CAPTURE-50 | ported | — | +| Rotate (axis) | CAPTURE-60, CAPTURE-65 | ported | — | +| Confirm (axis) | CAPTURE-60 | ported | — | +| SKU input | REVIEW-45 | ported | — | +| Length input | CAPTURE-70 | ported | — | +| Thickness input | CAPTURE-70 | ported | — | +| Done (type values) | APP-5 | ported | status calibrated | +| X exit review | APP-10 | ported | — | +| Species dropdown | METADATA-10 | ported | — | +| Wood Cat dropdown | METADATA-10 | ported | — | +| Edge Type dropdown | METADATA-15 | ported | — | +| Figure dropdown | METADATA-15 | ported | — | +| Grade dropdown | METADATA-15 | ported | cardinality superseded | +| Fig Tags dropdown | METADATA-30 | ported | cardinality superseded | +| Feat Tags dropdown | METADATA-30 | ported | — | +| Thickness Band dropdown | METADATA-15 | ported | — | +| Moisture dropdown | METADATA-15 | ported | — | +| Price input | REVIEW-25 | ported | — | +| Review Title / Desc button | REVIEW-10 | ported | — | +| Publish button disabled until ready | REVIEW-20 | ported | — | +| Title input | REVIEW-10 | ported | — | +| Short Title input | REVIEW-10 | ported | naming migrated | +| Description input | REVIEW-10 | ported | — | +| Generate Text button, availability | METADATA-110 | ported | fixed | +| Done (title/desc) | REVIEW-40 | ported | — | +| Open in Store | — | ui | candidate: store link is presentation | +| Done (publish success) | PUBLISH-25 | ported | — | +| Retry (publish failed) | PUBLISH-30, PUBLISH-37 | ported | — | +| Back to Review | PUBLISH-30 | ported | retry from same screen | +| Retake (mask failure) | CAPTURE-50, CAPTURE-55 | ported | — | +| Rotate (axis check) | CAPTURE-60 | ported | — | +| Retake (source too small) | CAPTURE-105 | ported | — | +| Edit SKU | PUBLISH-15 | ported | — | +| Open Existing | PUBLISH-15 | ported | — | +| Refresh Store Lists (stale) | PUBLISH-20 | ported | — | +| Retry / Open Settings (unreachable) | PUBLISH-30, PUBLISH-36, PUBLISH-38 | ported | — | +| Update Browser | CAPTURE-10 | ported | — | + +## LISTING FIELDS + +| Entry | PoR | Disposition | Note | +|---|---|---|---| +| SKU | REVIEW-45, PUBLISH-15 | ported | — | +| Length | CAPTURE-70 | ported | — | +| Thickness | CAPTURE-70 | ported | — | +| sqft (computed) | CAPTURE-85 | ported | — | +| bdft (computed) | CAPTURE-90 | ported | — | +| widths (computed) | CAPTURE-80 | ported | — | +| Photos, "at least one inventory PNG" | REVIEW-5 | superseded | ready set is two images | +| Species exactly one | METADATA-10 | ported | — | +| Wood category 1+ | METADATA-10 | ported | — | +| Edge type one | METADATA-15 | ported | — | +| Figure 1+ | METADATA-15 | ported | — | +| Fig tags 1+ | METADATA-30 | superseded | tags optional | +| Feat tags 0+ | METADATA-30 | ported | — | +| Grade 1+ | METADATA-15 | superseded | grade exactly one | +| Thickness Band | METADATA-15 | ported | — | +| Moisture | METADATA-15 | ported | — | +| Price | REVIEW-25 | ported | — | +| Title | REVIEW-10 | ported | — | +| Short Title | REVIEW-10 | ported | naming migrated | +| Description | REVIEW-10 | ported | — | +| Store product ID | PUBLISH-25 | ported | display is presentation | + +## STATUSES + +| Entry | PoR | Disposition | Note | +|---|---|---|---| +| Publish Status Draft | SETTINGS-65 | ported | — | +| calibrated | APP-5 | ported | — | +| publishing | APP-5 | ported | — | +| published | APP-5, PUBLISH-50 | ported | — | +| failed | APP-5 | ported | — | +| Woo product draft | PUBLISH-10 | ported | — | +| SKU "any status" | PUBLISH-15 | ported | — | +| probe stateful/stateless/fail | SETTINGS-45 | ported | fixed | + +## ENDPOINTS + +| Entry | PoR | Disposition | Note | +|---|---|---|---| +| WP REST v3 auth | SETTINGS-15, SETTINGS-80 | ported | — | +| Inference endpoint | SETTINGS-30 | ported | — | +| Inference endpoint reachability | METADATA-110, METADATA-75 | ported | fixed | + +## ERROR CODES + +| Entry | PoR | Disposition | Note | +|---|---|---|---| +| green check / red error | SETTINGS-80 | ported | — | +| stateful/stateless/fail | SETTINGS-45 | ported | fixed | + +## USER-FACING ERROR / EMPTY STATES + +| Entry | PoR | Disposition | Note | +|---|---|---|---| +| Mask failure "Cannot isolate the slab edge..." | CAPTURE-55 | ported | — | +| Axis misalignment "Rotate the axis..." | CAPTURE-60 | ported | — | +| Source too small "Photo will be too small..." | CAPTURE-105 | ported | — | +| SKU duplicate "This SKU already exists..." | PUBLISH-15 | ported | — | +| Stale taxonomy "This value is no longer valid..." | PUBLISH-20, REVIEW-35 | ported | — | +| Store unreachable "Cannot reach the store..." | PUBLISH-30, PUBLISH-36 | ported | — | +| Publish failed "The store rejected the listing..." | PUBLISH-30, PUBLISH-35 | ported | — | +| Publishing in progress | PUBLISH-5 | ported | — | +| Publish success | PUBLISH-25 | ported | — | +| Browser gate "update Safari or Chrome" | CAPTURE-10 | superseded | matrix includes PC | +| Species/Figure/Wood/Edge nudges | REVIEW-20, METADATA-10, METADATA-15 | ported | — | +| Generic inline nudge | REVIEW-20 | ported | — | +| First-run guidance text | — | ui | presentation | +| Vision-off empty taxonomy | METADATA-45, METADATA-70 | ported | — | + +## INFERENCE + +| Entry | PoR | Disposition | Note | +|---|---|---|---| +| Call 1 flag default off | SETTINGS-90, METADATA-35 | ported | fixed | +| Call 2 flag default off | SETTINGS-90, METADATA-55 | ported | fixed | +| Generate Text triggers Call 2 | METADATA-55 | ported | — | +| Pre-populate threshold 0.7 | METADATA-45 | ported | — | +| Below-threshold leaves empty | METADATA-45 | ported | — | +| Below-threshold manual fill to ready | REVIEW-5, REVIEW-20 | ported | — | +| Two-turn probe stateful/stateless/fail | SETTINGS-45 | ported | fixed | +| AI badge on Call 1 fields | — | ui | presentation | +| Generate Text availability | METADATA-110 | ported | fixed | +| Call 2 prose only, templates numbers | METADATA-65, METADATA-70 | ported | — | +| Vision off state | METADATA-70 | ported | — | + +## PUBLISH PAYLOAD / FLOW + +| Entry | PoR | Disposition | Note | +|---|---|---|---| +| Publish action, poll | PUBLISH-5 | ported | — | +| Draft creation | PUBLISH-10 | ported | — | +| SLAB-UAT-* always draft | PUBLISH-10 | ported | — | +| Publish Status default draft | SETTINGS-65 | ported | — | +| Duplicate SKU behavior | PUBLISH-15 | ported | — | +| Stale taxonomy behavior | PUBLISH-20 | ported | — | +| Success result, open in store | PUBLISH-25 | ported | link is presentation | +| Failure behavior, keep draft, retry | PUBLISH-30 | ported | — | +| Product ID stored, images purged | PUBLISH-25 | ported | — | +| Publish button gating | REVIEW-20 | ported | — | +| Submit-time validation after all fields | REVIEW-20 | superseded | per-field nudges, no submit list | +| Taxonomy freshness on test | SETTINGS-40 | ported | — | + +## CONSTANTS / NUMBERS + +| Entry | PoR | Disposition | Note | +|---|---|---|---| +| SKU pattern `[A-Z0-9-]{3,24}` | REVIEW-45 | ported | case widened | +| Length / thickness 1/8 step | CAPTURE-70 | ported | — | +| Confidence threshold 0.7 | METADATA-45 | ported | — | +| Width sample every 6 inches | CAPTURE-80 | ported | — | +| Wireframe width 375px | — | ui | sketch detail | +| Sample values (sqft 0.64, bdft 0.96, widths, price, id) | — | ui | examples | +| Mandatory counts list | REVIEW-5 | ported | grade/fig superseded | +| Optional feat-* zero fine | METADATA-30 | ported | — | +| Photo thumbnail count 5 | CAPTURE-25 | superseded | no cap | +| SLAB-UAT-* pattern | PUBLISH-10 | ported | — | + +## BROWSER / API SUPPORT + +| Entry | PoR | Disposition | Note | +|---|---|---|---| +| Gate on missing APIs | CAPTURE-10 | ported | — | +| Supported browsers | CAPTURE-10 | ported | matrix worded phone-only, superseded | +| Hard-stop screen | CAPTURE-10 | ported | — | +| Client-side mask re-run | CAPTURE-140, CAPTURE-150 | ported | — | +| Camera capture | CAPTURE-20, CAPTURE-25 | ported | — | +| localStorage | CAPTURE-145 | ported | — | diff --git a/.audit/coverage/prd.md b/.audit/coverage/prd.md new file mode 100644 index 0000000..b62ca72 --- /dev/null +++ b/.audit/coverage/prd.md @@ -0,0 +1,12 @@ +# Coverage ledger — prd + +Source `prd` (`docs/archive/PRD-2026-08.txt`, archived). Seeded by the settings/auth/health +pilot 2026-09-18. The archived PRD is walked in full later in the order. + +| Source | Atom | Requirement | Disposition | PoR ID | Rationale | +|---|---|---|---|---|---| +| prd | decision-15 | Woo credentials are server-side only, never in the browser | ported | SETTINGS-15, SETTINGS-20 | — | +| prd | decision-16 | inference endpoint is configurable: base URL, key, model | ported | SETTINGS-30 | — | +| prd | decision-17 | test connection verifies text and vision support | ported | SETTINGS-80, SETTINGS-45 | — | +| prd | no-app-auth | no app-level auth; HTTPS plus trusted network | ported | OPS-55 | — | +| prd | FR41 | Settings UI for inference, Woo creds, pricing, brand/GEO, prompts | ported | SETTINGS-15, SETTINGS-20, SETTINGS-30, SETTINGS-35 | prompts are post-M3, audit section 11 | diff --git a/.audit/coverage/product.md b/.audit/coverage/product.md new file mode 100644 index 0000000..18ac4eb --- /dev/null +++ b/.audit/coverage/product.md @@ -0,0 +1,32 @@ +# Coverage ledger — PRODUCT.md + +Source `cur:PRODUCT` (`docs/PRODUCT.md`, role: intent). Walked 2026-09-18 section by section +against the PoR. Term ruling: the umbrella "Woo attributes" package is renamed **Woo Slab +Taxonomy**; canonical Woo attributes (the five) keep their name. + +| Source | Atom | Requirement | Disposition | PoR ID | Rationale | +|---|---|---|---|---|---| +| cur:PRODUCT | one-slab-one-listing | one slab is one unit and one listing | ported | PUBLISH-60 | — | +| cur:PRODUCT | photos-metadata-inventory | photos and metadata are the inventory record | dropped | — | intent framing; realized by CAPTURE/PHOTOS/CREATE, not a discrete rule | +| cur:PRODUCT | green-black-sheet | photographs taken on a green or black sheet | ported | CAPTURE-20 | — | +| cur:PRODUCT | inventory-png-3-4-transparent | inventory photo(s), uniform 3:4 transparent PNG | ported | CAPTURE-95, CAPTURE-100 | — | +| cur:PRODUCT | listing-fields-user-owned | SKU, length, thickness, price | ported | CAPTURE-70, REVIEW-25 | SKU has no standalone row; folded into PUBLISH-5/PUBLISH-15 | +| cur:PRODUCT | taxon-wood-attributes | species, wood category, edge, figure, grade | ported | METADATA-10, METADATA-15 | — | +| cur:PRODUCT | thickness-band-moisture | thickness band, moisture | ported | METADATA-15 | — | +| cur:PRODUCT | fig-tags-cardinality | one or more `fig-*` tags | superseded | METADATA-30 | source says one or more; ruled optional, Figure attribute is at least one | +| cur:PRODUCT | title-shorttitle-description | title, short title, description | ported | REVIEW-10 | "short title" is stale wording for short_description | +| cur:PRODUCT | feat-tags-optional | zero or more `feat-*` tags | ported | METADATA-30 | — | +| cur:PRODUCT | woo-slab-taxonomy-term | "Woo attributes" locked package name | ported | METADATA-5 | renamed Woo Slab Taxonomy; umbrella rule lives in METADATA-5 | +| cur:PRODUCT | measured-values-templates | measured values feed title and description templates | superseded | METADATA-60, METADATA-70, METADATA-80 | template is the fallback; the model writes when Call 2 runs | +| cur:PRODUCT | decision-rights-table | decision rights for mask/crop, measures, taxonomy, price, prose | ported | CAPTURE-50, CAPTURE-80, CAPTURE-85, CAPTURE-90, CAPTURE-100, METADATA-40, METADATA-45, REVIEW-25, REVIEW-40 | row-for-row mapping; L37 carries the template conflict | +| cur:PRODUCT | override-precedence | user entries override computed or inferred values | ported | METADATA-50 | — | +| cur:PRODUCT | milestones-1-2-browser-only | M1/M2 browser-only, no backend or inference URL | ported | APP-25, OPS-45 | — | +| cur:PRODUCT | m1-handoff-device-draft | M1 handoff is the device draft contents | ported | APP-10 | — | +| cur:PRODUCT | m2-extends-draft | M2 batch, calibration, per-photo PNGs, overlay copy | ported | CAPTURE-25, CAPTURE-110, CAPTURE-115, CAPTURE-130 | — | +| cur:PRODUCT | m3-fastapi-client | M3 FastAPI Woo client and inference proxy, secrets server-side | ported | APP-25, SETTINGS-15, SETTINGS-20, SETTINGS-30 | — | +| cur:PRODUCT | slab-uat-forces-draft | `SLAB-UAT-*` creates a Woo draft | ported | PUBLISH-10 | — | +| cur:PRODUCT | duplicate-sku-blocks | duplicate SKU blocks create | ported | PUBLISH-15 | — | +| cur:PRODUCT | create-inference-off | a listing can be created with inference off | ported | METADATA-70, METADATA-75, QUALITY-20 | — | +| cur:PRODUCT | device-draft-not-woo-draft | device draft is separate from a WooCommerce draft | ported | APP-10 | — | +| cur:PRODUCT | milestone-status-table | milestone status table, UAT on `:10881` | ported | OPS-40 | only host/port is testable; rest navigation; branch-neutral edit queued | +| cur:PRODUCT | out-of-m3-scope | anything beyond CREATE is out of M3 | dropped | — | scope prose, not a requirement; MILESTONES.md owns scope | diff --git a/.audit/coverage/settings-auth-health.md b/.audit/coverage/settings-auth-health.md deleted file mode 100644 index c05de9d..0000000 --- a/.audit/coverage/settings-auth-health.md +++ /dev/null @@ -1,47 +0,0 @@ -# Coverage ledger — settings / auth / health (pilot) - -Seed slice proving the method. Sources: the `pre-simplification` tag, the archived PRD, -the current branch docs, and the Gitea issues. - -| Source | Atom | Requirement | Disposition | PoR ID | Rationale | -|---|---|---|---|---|---| -| tag:TEST-SPEC | GC-1 | SLAB-UAT-* creates a Woo draft regardless of woo_create_status | ported | PUBLISH-10 | — | -| tag:TEST-SPEC | GC-2 | duplicate SKU returns 409 with edit_sku and open_existing | ported | PUBLISH-15 | — | -| tag:TEST-SPEC | GC-3 | test-woo returns ok with taxonomy and always resyncs on success | ported | SETTINGS-40 | — | -| tag:TEST-SPEC | GC-4 | WOO_BASE_URL compose-only, GET read-only, PUT rejects a write with 422 | gap | — | compose-only and read-only live in SETTINGS-5; the API-level 422 rejection has no row | -| tag:TEST-SPEC | GC-5 | FastAPI refuses to start on missing or http WOO_BASE_URL | ported | SETTINGS-10 | — | -| tag:TEST-SPEC | W-3 | taxonomy anchor unchanged on no-op, bumps only on stored-subset hash change | ported | SETTINGS-55 | sync and rebuild live in SETTINGS-50 | -| tag:TEST-SPEC | W-4 | test-woo always runs a taxonomy resync after a successful test | duplicate | SETTINGS-40 | same rule as GC-3 | -| tag:TEST-SPEC | AES-1 | credential encrypt/decrypt round-trips; ciphertext never appears in GET | gap | — | redaction lives in SETTINGS-75; the round-trip assertion has no row | -| tag:TEST-SPEC | AES-2 | missing SLAB_AES_KEY refuses startup, no plaintext fallback | ported | SETTINGS-25 | — | -| tag:TEST-SPEC | API-10 | test-woo fails closed on missing credentials or non-HTTPS URL | gap | — | no row defines test-woo's failure behavior | -| tag:TEST-SPEC | FUND-21 | 401/400/404 mapped to an actionable error, not raw passthrough | gap | — | publish fields are mapped by PUBLISH-20 and PUBLISH-35; the settings handshake mapping is absent | -| tag:TEST-SPEC | H-1 | health 200 with woo_reachable false when credentials are not configured | gap | — | APP-30 names /api/health only | -| tag:TEST-SPEC | INS-1 | health non-200 on DB or volume failure; woo_reachable false when the store is unreachable | gap | — | not represented | -| tag:TEST-SPEC | UIE-8 | Settings screen: WP creds and test-woo, inference and test, pricing, brand/GEO, create status, read-only URL | ported | SETTINGS-5, SETTINGS-15, SETTINGS-20, SETTINGS-30, SETTINGS-35, SETTINGS-65 | screen composition itself belongs to the contract/UX docs | -| tag:TEST-SPEC | OPS-3 | compose health, test-woo ok, create status draft at deploy | ported | QUALITY-110 | — | -| tag:OPENAPI | health | GET /api/health returns status, version, woo_reachable | gap | — | APP-30 names the path; the body is absent | -| tag:OPENAPI | settings-PUT-reject | PUT /settings cannot write woo_base_url, 422 | duplicate | — | same rule as GC-4 | -| tag:OPENAPI | settings-secrets | secrets are write-only and never echoed | ported | SETTINGS-75 | — | -| tag:OPENAPI | test-woo-shape | test-woo returns ok, detail, taxonomy_anchor, taxonomy_last_sync_at | gap | — | return shape absent | -| tag:OPENAPI | taxonomy-GET | taxonomy GET grouped, with anchor and last_sync_at | ported | SETTINGS-50, SETTINGS-55, SETTINGS-60 | — | -| tag:ARCHITECTURE | s5-server-secrets | Woo creds, inference key, AES key, WOO_BASE_URL are server-side only | ported | SETTINGS-5, SETTINGS-15, SETTINGS-20, SETTINGS-25, SETTINGS-30 | — | -| tag:ARCHITECTURE | s5-client-prefs | sheet and sensitivity preferences persist in localStorage | ported | CAPTURE-145 | — | -| tag:ARCHITECTURE | s7-taxonomy | last_sync_at, anchor, and sync triggers | ported | SETTINGS-50, SETTINGS-55, SETTINGS-60 | — | -| tag:ARCHITECTURE | s3-woo-only-fastapi | Woo is called only by FastAPI | ported | APP-25 | — | -| tag:DATA-MODEL | settings-table | settings.value_enc is AES-GCM; WOO_BASE_URL is not a settings row | ported | SETTINGS-5, SETTINGS-20, SETTINGS-30 | — | -| tag:DEPLOYMENT | env-table | env carries WOO_BASE_URL, Woo credentials, inference key | ported | OPS-10, SETTINGS-5, SETTINGS-30 | — | -| tag:DEPLOYMENT | rotate-creds | rotate Woo credentials then test-woo | duplicate | SETTINGS-40 | rotation is procedure; the verification is test-woo | -| tag:DEPLOYMENT | aes-rekey | SLAB_AES_KEY change requires re-encrypting settings | deferred | — | deferred ruling, audit section 18 item 7 | -| prd | decision-15 | Woo credentials are server-side only, never in the browser | ported | SETTINGS-15, SETTINGS-20 | — | -| prd | decision-16 | inference endpoint is configurable: base URL, key, model | ported | SETTINGS-30 | — | -| prd | decision-17 | test connection verifies text and vision support | gap | — | SETTINGS-45 covers the inference probe; the Woo side of test connection is only SETTINGS-40 | -| prd | no-app-auth | no app-level auth; HTTPS plus trusted network | gap | — | no row states the absence of app-level auth | -| prd | FR41 | Settings UI for inference, Woo creds, pricing, brand/GEO, prompts | ported | SETTINGS-15, SETTINGS-20, SETTINGS-30, SETTINGS-35 | prompts are post-M3, audit section 11 | -| cur:CREATE | settings-url | WOO_BASE_URL from env, read-only; change means edit .env and recreate | ported | SETTINGS-5 | — | -| cur:CREATE | test-inference-shape | test-inference returns ok, mode, vision_capable, detail | ported | SETTINGS-45 | — | -| cur:CONTENT-WOO | httpx-timeouts | httpx connect 10s, read 60s | gap | — | not represented | -| issue:#5 | app-password | app password, dedicated user, AES-GCM, browser never holds it, sole caller | ported | SETTINGS-15, SETTINGS-20, APP-25 | — | -| issue:#11 | url-compose | WOO_BASE_URL declared in compose, not user-editable | ported | SETTINGS-5 | — | -| issue:#14 | taxonomy-refresh | taxonomy refresh triggers and anchor | ported | SETTINGS-50, SETTINGS-55, SETTINGS-60 | — | -| issue:#6 | instrument-health | health checks and instrumentation | gap | — | same hole as H-1 and INS-1 | diff --git a/.audit/coverage/tag-architecture.md b/.audit/coverage/tag-architecture.md new file mode 100644 index 0000000..6ead9cd --- /dev/null +++ b/.audit/coverage/tag-architecture.md @@ -0,0 +1,62 @@ +# Coverage ledger — tag:ARCHITECTURE + +Source `tag:ARCHITECTURE` (`docs/ARCHITECTURE.md` at tag `pre-simplification`). Seeded by the +settings/auth/health pilot 2026-09-18. + +| Source | Atom | Requirement | Disposition | PoR ID | Rationale | +|---|---|---|---|---|---| +| tag:ARCHITECTURE | s5-server-secrets | Woo creds, inference key, AES key, WOO_BASE_URL are server-side only | ported | SETTINGS-5, SETTINGS-15, SETTINGS-20, SETTINGS-25, SETTINGS-30 | — | +| tag:ARCHITECTURE | s5-client-prefs | sheet and sensitivity preferences persist in localStorage | ported | CAPTURE-145 | — | +| tag:ARCHITECTURE | s7-taxonomy | last_sync_at, anchor, and sync triggers | ported | SETTINGS-50, SETTINGS-55, SETTINGS-60 | — | +| tag:ARCHITECTURE | s3-woo-only-fastapi | Woo is called only by FastAPI | ported | APP-25 | — | +| tag:ARCHITECTURE | topology-hybrid | phone happy-path; server stores/proxies; reverse proxy HTTPS | ported | CAPTURE-5, APP-25, OPS-5 | — | +| tag:ARCHITECTURE | u2net-deferred | server U2Net deferred from POC, not deleted | ported | CAPTURE-165 | — | +| tag:ARCHITECTURE | keys-server-side | Woo and LLM keys stay on the server; browser never calls Woo or vision | ported | APP-25, SETTINGS-15, SETTINGS-20, SETTINGS-30 | — | +| tag:ARCHITECTURE | new-wood-1-5-photos | new wood: 1-5 photos | superseded | CAPTURE-25 | PoR is one or more; no cap in code | +| tag:ARCHITECTURE | bg-four-knobs | sheet / sensitivity / edge / feather | superseded | CAPTURE-40 | controls are exactly Sheet and Sensitivity | +| tag:ARCHITECTURE | length-axis-confirm | length axis overlay, rotate/confirm | ported | CAPTURE-60, CAPTURE-65 | — | +| tag:ARCHITECTURE | user-length-thickness-sku | user length, thickness, SKU | ported | CAPTURE-70 | — | +| tag:ARCHITECTURE | client-measure-png | sqft, bdft, 6in widths, 3:4 PNGs | ported | CAPTURE-80, CAPTURE-85, CAPTURE-90, CAPTURE-95, CAPTURE-100 | — | +| tag:ARCHITECTURE | call1-prefill | review: Call 1 pre-fill or manual taxonomy | ported | METADATA-35, METADATA-40, METADATA-45 | — | +| tag:ARCHITECTURE | generate-text-tap | tap Generate text | ported | METADATA-55 | — | +| tag:ARCHITECTURE | call2-proxy-text | Call 2 proxy title, description, short description | ported | METADATA-55 | — | +| tag:ARCHITECTURE | happy-path-phone | happy path stays on phone through PNG and numbers; FastAPI idle until confirmed mask | ported | CAPTURE-5, CAPTURE-135 | — | +| tag:ARCHITECTURE | mask-knobs-client | mask knobs on capture; each change re-runs client BG removal; U2Net deferred | ported | CAPTURE-45, CAPTURE-150, CAPTURE-165 | — | +| tag:ARCHITECTURE | draft-upload-delete | draft upload sends originals, PNGs, numbers; delete after publish | ported | CAPTURE-135, PUBLISH-25, PUBLISH-40 | — | +| tag:ARCHITECTURE | call1-auto-body | Call 1 server-side, auto when enabled, 1024 + synced taxonomy, 45s, 0.7 | ported | METADATA-35, METADATA-40, METADATA-45 | — | +| tag:ARCHITECTURE | call2-no-auto | Call 2 does not auto-fire; user taps Generate text | ported | METADATA-55 | — | +| tag:ARCHITECTURE | call2-thread-context | portable context, previous_response_id, per-slab thread | superseded | — | thread ids pushed out of MVP; Call 2 is a fresh call | +| tag:ARCHITECTURE | woo-only-409-422 | Woo only via FastAPI; duplicate SKU 409 on field; stale value 422 on field; draft stays | ported | APP-25, PUBLISH-15, PUBLISH-20, PUBLISH-30 | — | +| tag:ARCHITECTURE | settings-server | settings on the server; store URL env, read-only | ported | SETTINGS-5, SETTINGS-30, SETTINGS-65 | — | +| tag:ARCHITECTURE | what-runs-where-table | what runs where | duplicate | CAPTURE-5, APP-25 | restates the topology and swimlane rows | +| tag:ARCHITECTURE | s5s1-basic-auth | WordPress Basic auth, app password, AES-GCM, not consumer keys | ported | SETTINGS-15, SETTINGS-20 | — | +| tag:ARCHITECTURE | s5s2-inference-key | inference base URL and key encrypted; server proxies | ported | SETTINGS-30, APP-25 | — | +| tag:ARCHITECTURE | s5s3-aes-key | AES master key env, never in the DB | ported | SETTINGS-25 | — | +| tag:ARCHITECTURE | s5s4-woo-url | WOO_BASE_URL env, HTTPS, not user-editable, read-only | ported | SETTINGS-5, SETTINGS-10 | — | +| tag:ARCHITECTURE | s5b2-species-rate | species list with $/bdft read from server settings sync | ported | SETTINGS-35 | — | +| tag:ARCHITECTURE | s5b3-taxonomy-snapshot | taxonomy snapshot read from server, no auth | ported | SETTINGS-50 | — | +| tag:ARCHITECTURE | s5f1-deploy-setup | deploy sets WOO_BASE_URL; operator creates low-priv WP user | ported | SETTINGS-5, SETTINGS-15 | — | +| tag:ARCHITECTURE | s5f2-enter-creds | user enters credentials in Settings; URL read-only | ported | SETTINGS-20 | — | +| tag:ARCHITECTURE | s5f3-encrypt | browser POSTs credentials; server encrypts AES-GCM, stores ciphertext | ported | SETTINGS-20 | — | +| tag:ARCHITECTURE | s5f4-sync-cache | server syncs taxonomy, caches in SQLite | ported | SETTINGS-50 | — | +| tag:ARCHITECTURE | s5f5-client-read | client reads taxonomy, no auth | ported | SETTINGS-50 | — | +| tag:ARCHITECTURE | s5f6-server-woo | all Woo calls go server-to-Woo | ported | APP-25 | — | +| tag:ARCHITECTURE | status-machine | draft, calibrated, ready, publishing, published, failed | ported | APP-5 | — | +| tag:ARCHITECTURE | ready-set | ready set fields | ported | REVIEW-5 | — | +| tag:ARCHITECTURE | ready-set-fig-grade | ready set requires 1+ fig-* tag and 1+ grade | superseded | METADATA-15, METADATA-30 | grade exactly one; tags optional | +| tag:ARCHITECTURE | anchor-bump | anchor bumps only on stored-subset change including deletions | ported | SETTINGS-55 | — | +| tag:ARCHITECTURE | anchor-repull | client re-pulls full taxonomy iff the anchor advanced | ported | SETTINGS-60 | — | +| tag:ARCHITECTURE | species-exclusivity | species pickers and Call 1 lists are exactly the cached set | ported | METADATA-5 | — | +| tag:ARCHITECTURE | trigger-test-woo | test-woo always resyncs taxonomy | ported | SETTINGS-40 | — | +| tag:ARCHITECTURE | trigger-publish-1h | publish resync if the last sync is older than 1 hour | ported | PUBLISH-5, SETTINGS-60 | — | +| tag:ARCHITECTURE | trigger-optional-launch | optional app-launch or Settings refresh | dropped | — | optional, not required for POC | +| tag:ARCHITECTURE | trigger-no-nav-sync | do not sync on every screen navigation | dropped | — | design guidance, not a requirement | +| tag:ARCHITECTURE | validation-module | shared validation module, version hash, 412 | dropped | — | ruled cut 2026-09-18 | +| tag:ARCHITECTURE | sku-charset | SKU charset [A-Z0-9-]{3,24} | ported | REVIEW-45 | case widened to alphanumeric by ruling | +| tag:ARCHITECTURE | validation-empty-optional | field-exit local check; empty optional is valid | dropped | — | review gate covers required fields; no separate row | +| tag:ARCHITECTURE | call1-call2-context | Call 1 to Call 2 portable context default | superseded | — | thread work pushed out of MVP | +| tag:ARCHITECTURE | test-inference-probe | test-inference two-turn probe records stateful, stateless, fail | ported | SETTINGS-45 | mode removed; probe keeps ok, vision_capable, detail | +| tag:ARCHITECTURE | per-slab-thread | per-slab thread id, delete on publish or abandon | superseded | — | cut 2026-09-18 | +| tag:ARCHITECTURE | inference-runtime-failure | Call 1 timeout 45s returns error plus retry on the phone | ported | METADATA-115 | — | +| tag:ARCHITECTURE | call1-image-source | all originals downscaled to 1024 | superseded | METADATA-35 | PoR sends the clean processed photos | +| tag:ARCHITECTURE | title-desc-maxlength | Woo maxLength caps title and descriptions | dropped | — | inherent: bound by Woo limits | diff --git a/.audit/coverage/tag-data-model.md b/.audit/coverage/tag-data-model.md new file mode 100644 index 0000000..b1ae0d8 --- /dev/null +++ b/.audit/coverage/tag-data-model.md @@ -0,0 +1,50 @@ +# Coverage ledger — tag:DATA-MODEL + +Source `tag:DATA-MODEL` (`docs/DATA-MODEL.md` at tag `pre-simplification`, role: schema spec). +Walked 2026-09-18. Column mechanics belong in `docs/contracts/DATA-MODEL.md`; the testable +rules carry PoR IDs. + +| Source | Atom | Requirement | Disposition | PoR ID | Rationale | +|---|---|---|---|---|---| +| tag:DATA-MODEL | settings-table | settings.value_enc is AES-GCM; WOO_BASE_URL is not a settings row | ported | SETTINGS-5, SETTINGS-20, SETTINGS-30 | — | +| tag:DATA-MODEL | ids-client-uuid | slab ids are client-generated UUIDv4 | ported | APP-20 | — | +| tag:DATA-MODEL | timestamps-utc | timestamps are UTC ISO-8601 | ported | APP-20 | — | +| tag:DATA-MODEL | status-enum | status draft/calibrated/ready/publishing/published/failed | ported | APP-5 | — | +| tag:DATA-MODEL | rounded-values-stored | rounded values are what gets stored | ported | CAPTURE-160 | — | +| tag:DATA-MODEL | inches-eighth | inches stored as 1/8 increments | ported | CAPTURE-70 | — | +| tag:DATA-MODEL | sqft-bdft-2dp | sqft and bdft to 2 decimals | ported | CAPTURE-85, CAPTURE-90 | — | +| tag:DATA-MODEL | sku-charset | sku `[A-Z0-9-]{3,24}` | ported | REVIEW-45 | — | +| tag:DATA-MODEL | species-leaf-1 | one species leaf | ported | METADATA-10 | — | +| tag:DATA-MODEL | wood-category-1plus | one or more wood-category leaves | ported | METADATA-10 | — | +| tag:DATA-MODEL | edge-1 | one edge type | ported | METADATA-15 | — | +| tag:DATA-MODEL | figure-1plus | one or more figure terms | ported | METADATA-15 | — | +| tag:DATA-MODEL | grade-1plus | one or more grade terms | superseded | METADATA-15 | grade is exactly one | +| tag:DATA-MODEL | thickness-roundup | thickness term by round-up | ported | METADATA-15 | — | +| tag:DATA-MODEL | moisture-default | moisture defaults kiln-dried, not inferred | ported | METADATA-15 | — | +| tag:DATA-MODEL | fig-tags-1plus | fig-* tags 1+ | superseded | METADATA-30 | tags optional | +| tag:DATA-MODEL | feat-tags-0plus | feat-* tags 0+ | ported | METADATA-30 | — | +| tag:DATA-MODEL | price-2dp-source | price 2 dp, price_source recommendation or override | ported | REVIEW-25 | — | +| tag:DATA-MODEL | inference-thread-id | optional stateful inference thread id | superseded | — | thread work out of MVP | +| tag:DATA-MODEL | state-machine | status transitions draft through failed | ported | APP-5 | — | +| tag:DATA-MODEL | ready-set | ready set fields | ported | REVIEW-5 | — | +| tag:DATA-MODEL | published-delete-row | published deletes the row and photos, keeps sync_log | ported | PUBLISH-25 | — | +| tag:DATA-MODEL | failed-error-retry | failed stores error detail, retry available | ported | PUBLISH-30 | — | +| tag:DATA-MODEL | slab-photos-only-inventory | only inventory photos; calibration photos do not exist | superseded | CAPTURE-115 | calibration photo exists; other batch photos are visual inventory | +| tag:DATA-MODEL | no-hard-deletes | no hard deletes for slabs (audit) | superseded | PUBLISH-25 | the local slab row is deleted on success | +| tag:DATA-MODEL | pricing-rules-flat | one flat price_per_bdft per species, no tiers | ported | SETTINGS-35 | — | +| tag:DATA-MODEL | woo-categories-filter | only species and wood-category leaves are offered | ported | METADATA-5, METADATA-10 | — | +| tag:DATA-MODEL | woo-attributes-five | only the five first-class attributes | ported | METADATA-15 | — | +| tag:DATA-MODEL | woo-tags-scope | only fig-* and feat-*; Woo is authoritative | ported | METADATA-30 | — | +| tag:DATA-MODEL | settings-credentials | woo_wp_username and woo_app_password settings | ported | SETTINGS-20 | — | +| tag:DATA-MODEL | settings-store-url-env | store URL is not a settings row | ported | SETTINGS-5 | — | +| tag:DATA-MODEL | settings-inference | inference base URL, key, model, enabled flags | ported | SETTINGS-30 | — | +| tag:DATA-MODEL | settings-brand-geo | brand_voice and geo_context settings | ported | SETTINGS-30 | — | +| tag:DATA-MODEL | settings-create-status | woo_create_status defaults draft | ported | SETTINGS-65 | — | +| tag:DATA-MODEL | settings-taxonomy-stamps | taxonomy_last_sync_at and taxonomy_anchor | ported | SETTINGS-55, SETTINGS-60 | — | +| tag:DATA-MODEL | settings-crop-1600-080 | output_px_min 1600, fill_target 0.80 | superseded | CAPTURE-100, CAPTURE-105 | PoR is 90 percent fill and an 800 px floor | +| tag:DATA-MODEL | settings-client-prefs | sheet and slider prefs in client localStorage | ported | CAPTURE-145 | — | +| tag:DATA-MODEL | settings-aes | AES master key env, value_enc = nonce plus ciphertext | ported | SETTINGS-25 | — | +| tag:DATA-MODEL | sync-log | append-only sync_log, not deleted on publish | ported | PUBLISH-30 | — | +| tag:DATA-MODEL | inference-log | append-only inference_log audit | ported | METADATA-66 | — | +| tag:DATA-MODEL | contract-details | money float internals, JSON arrays, payload_hash, soft-delete mechanics | dropped | — | schema contract detail | +| tag:DATA-MODEL | prompt-files | prompts bind-mounted as text files, read fresh, reset script | superseded | METADATA-105 | ruling: prompts live in the DB with all revisions | diff --git a/.audit/coverage/tag-deployment.md b/.audit/coverage/tag-deployment.md new file mode 100644 index 0000000..a0c5c3b --- /dev/null +++ b/.audit/coverage/tag-deployment.md @@ -0,0 +1,48 @@ +# Coverage ledger — tag:DEPLOYMENT + +Source `tag:DEPLOYMENT` (`docs/DEPLOYMENT.md` at tag `pre-simplification`, role: deploy +playbook). Vetted 2026-09-18. Testable rules are OPS/SETTINGS; operational facts live in +`deploy/`. Doc retired (see migration TODOs). + +| Source | Atom | Requirement | Disposition | PoR ID | Rationale | +|---|---|---|---|---|---| +| tag:DEPLOYMENT | env-table | env carries WOO_BASE_URL, Woo credentials, inference key | ported | OPS-10, SETTINGS-5, SETTINGS-30 | — | +| tag:DEPLOYMENT | rotate-creds | rotate Woo credentials then test-woo | duplicate | SETTINGS-40 | rotation is procedure; the verification is test-woo | +| tag:DEPLOYMENT | aes-rekey | SLAB_AES_KEY change requires re-encrypting settings | deferred | — | deferred ruling, audit section 18 item 7 | +| tag:DEPLOYMENT | preamble-hybrid-sot | client owns measure/crop; server-side measure/crop scaffolding not done | ported | CAPTURE-5, CAPTURE-135 | — | +| tag:DEPLOYMENT | preamble-uat-draft | Woo UAT is a draft with `SLAB-UAT-*` on the production store | ported | PUBLISH-10 | — | +| tag:DEPLOYMENT | preamble-inference-remote | inference is a remote configurable endpoint, not on the app host | ported | OPS-45 | — | +| tag:DEPLOYMENT | lab-host-example | historical lab host `192.168.1.201` | dropped | — | non-normative historical example | +| tag:DEPLOYMENT | host-inventory | docker ps, Caddy config, free port, volume convention | ported | OPS-15 | — | +| tag:DEPLOYMENT | reachability | confirm the hostname is reachable from the phone network | ported | OPS-5 | — | +| tag:DEPLOYMENT | inventory-before-bind | write values into deploy/INVENTORY.md and host .env before compose up | ported | OPS-15 | — | +| tag:DEPLOYMENT | repo-layout | repository layout at scaffold | dropped | — | scaffold detail | +| tag:DEPLOYMENT | store-url-env | WOO_BASE_URL in deploy/.env, injected into FastAPI | ported | SETTINGS-5, OPS-10 | — | +| tag:DEPLOYMENT | store-url-https | HTTPS only; FastAPI refuses missing or http at startup | ported | SETTINGS-10 | — | +| tag:DEPLOYMENT | store-url-not-settings-row | not a Settings input and not a SQLite settings row | ported | SETTINGS-5 | — | +| tag:DEPLOYMENT | store-url-readonly | Settings UI shows the URL read-only | ported | SETTINGS-5 | — | +| tag:DEPLOYMENT | app-password-out-of-compose | Woo application password stays out of compose; entered in Settings | ported | SETTINGS-20 | — | +| tag:DEPLOYMENT | house-rules-localhost-caddy | services bind 127.0.0.1; Caddy is the only public surface | ported | OPS-20 | — | +| tag:DEPLOYMENT | house-rules-pinned-images | no latest tags; restart unless-stopped; mem_limit | ported | OPS-25 | — | +| tag:DEPLOYMENT | caddy-fragment | Caddy fragment contents | dropped | — | deploy detail | +| tag:DEPLOYMENT | secret-aes-key | SLAB_AES_KEY env, mode 600, 32-byte base64, never in the repo | ported | SETTINGS-25, OPS-10 | — | +| tag:DEPLOYMENT | secret-woo-creds | Woo credentials AES-GCM in SQLite, dedicated low-priv user, not consumer keys | ported | SETTINGS-15, SETTINGS-20 | — | +| tag:DEPLOYMENT | secret-inference-key | inference API key AES-GCM in SQLite | ported | SETTINGS-30 | — | +| tag:DEPLOYMENT | woocred-https-store | store must already be on HTTPS for Application Passwords | ported | SETTINGS-15 | — | +| tag:DEPLOYMENT | woocred-dedicated-user | dedicated low-privilege WordPress user | ported | SETTINGS-15 | — | +| tag:DEPLOYMENT | woocred-generate-password | generate the application password in the WP profile | dropped | — | procedure | +| tag:DEPLOYMENT | woocred-enter-creds | enter credentials in Settings; AES-GCM; browser never keeps them | ported | SETTINGS-20 | — | +| tag:DEPLOYMENT | backup-sqlite-images | WAL-safe SQLite backup plus images tar; 7 daily and 4 weekly | ported | OPS-30 | — | +| tag:DEPLOYMENT | backup-prompt-files | prompt files live in git and need no separate backup | superseded | — | prompts move to the DB with all revisions (GAP-8) | +| tag:DEPLOYMENT | backup-offbox | mirror the backup to an off-box share | ported | OPS-30 | — | +| tag:DEPLOYMENT | restore-tested | stop, restore DB and images, start; tested once | ported | OPS-35 | — | +| tag:DEPLOYMENT | runbook | operations runbook | dropped | — | procedure | +| tag:DEPLOYMENT | smoke-health | health ok from the phone over HTTPS | ported | APP-45 | — | +| tag:DEPLOYMENT | smoke-test-woo | test-woo ok, create status draft, inference off | ported | SETTINGS-40, SETTINGS-65 | — | +| tag:DEPLOYMENT | smoke-capture-to-ready | create a slab, upload fixtures, poll to ready | ported | APP-5 | — | +| tag:DEPLOYMENT | smoke-bdft-price | verify bdft and price against the fixture | ported | CAPTURE-90, REVIEW-25 | — | +| tag:DEPLOYMENT | smoke-uat-draft | publish a `SLAB-UAT-*` slab; Woo product must be draft | ported | PUBLISH-10 | — | +| tag:DEPLOYMENT | smoke-cleanup | leave or delete the UAT draft | dropped | — | procedure | +| tag:DEPLOYMENT | scaling-postgres-redis | Postgres/Redis/GPU scaling paths | dropped | — | post-POC | +| tag:DEPLOYMENT | scaling-never-lower-1600 | never lower output_px_min below 1600 | superseded | CAPTURE-105 | PoR floor is 800 px | +| tag:DEPLOYMENT | scaling-rembg | RemBG is not part of the hybrid happy path | dropped | — | design note | diff --git a/.audit/coverage/tag-impl-plan.md b/.audit/coverage/tag-impl-plan.md new file mode 100644 index 0000000..12092d2 --- /dev/null +++ b/.audit/coverage/tag-impl-plan.md @@ -0,0 +1,82 @@ +# Coverage ledger — tag:IMPL-PLAN + +Source `tag:IMPL-PLAN` (`docs/IMPL-PLAN.md` at tag `pre-simplification`). Vetted 2026-09-18. +Process history and duplicates; doc retired (see migration TODOs). + +| Source | Atom | Requirement | Disposition | PoR ID | Rationale | +|---|---|---|---|---|---| +| tag:IMPL-PLAN | ground-rules | deterministic core tested first, no feature without a passing test, inference toggleable | ported | QUALITY-5, QUALITY-10, QUALITY-20 | — | +| tag:IMPL-PLAN | gates-abc | Gate A docs freeze, Gate B host, Gate C first draft listing | dropped | — | superseded by MILESTONES and the PoR | +| tag:IMPL-PLAN | phase0-client-modules | client pure modules for the deterministic core | ported | CAPTURE-5, CAPTURE-80, CAPTURE-85, CAPTURE-90 | — | +| tag:IMPL-PLAN | phase0-fastapi-skeleton | app factory, health, SQLite, settings AES, draft store stub | ported | APP-30, APP-45, SETTINGS-20, SETTINGS-25, APP-40 | — | +| tag:IMPL-PLAN | phase0-tests-tv | unit tests for TV-1 through TV-10 | ported | QUALITY-15 | — | +| tag:IMPL-PLAN | phase0-fixture-set | fixture photo set for Gate B review | dropped | — | process | +| tag:IMPL-PLAN | phase0-no-server-recompute | server stores client numbers, no server recompute | ported | CAPTURE-135, APP-40 | — | +| tag:IMPL-PLAN | phase0-u2net-stub | real U2Net stubbed, deferred | ported | CAPTURE-165 | — | +| tag:IMPL-PLAN | phase0-species-synced-only | species offered are exactly the synced leaves | ported | METADATA-5 | — | +| tag:IMPL-PLAN | phase0-offline-deferred | offline PWA deferred | ported | CAPTURE-15 | — | +| tag:IMPL-PLAN | phase0-no-fake-upscale | undersized source warns and requires retake | ported | CAPTURE-105 | — | +| tag:IMPL-PLAN | phase0-acceptance-math | bdft identity, no divide-by-12, rounding | ported | CAPTURE-90, QUALITY-100 | — | +| tag:IMPL-PLAN | phase0-health-compose | health up in compose after INVENTORY is filled | ported | OPS-15, APP-45 | — | +| tag:IMPL-PLAN | phase0-settings-aes | AES round-trip, secrets never in GET | ported | SETTINGS-75, QUALITY-125 | — | +| tag:IMPL-PLAN | phase0-draft-store | draft stores uploads without server recompute | ported | APP-40 | — | +| tag:IMPL-PLAN | phase0-coverage-property | coverage target and property checks | ported | QUALITY-40, QUALITY-45 | — | +| tag:IMPL-PLAN | phase0-exit-gate | Ty confirms the shown output | ported | QUALITY-30 | — | +| tag:IMPL-PLAN | phase1-capture-1-5 | online capture of 1-5 photos | superseded | CAPTURE-25 | no cap | +| tag:IMPL-PLAN | phase1-four-knobs | four knobs inline with live re-run | superseded | CAPTURE-40 | two knobs | +| tag:IMPL-PLAN | phase1-client-measure-png | client sqft/bdft/widths and 3:4 PNG prep | ported | CAPTURE-80, CAPTURE-85, CAPTURE-90, CAPTURE-95 | — | +| tag:IMPL-PLAN | phase1-manual-fields | manual SKU, length, thickness, taxonomy | ported | CAPTURE-70 | — | +| tag:IMPL-PLAN | phase1-calibrated-upload | calibrated draft upload, server stores only | ported | CAPTURE-135 | — | +| tag:IMPL-PLAN | phase1-uat-draft | Woo draft create with `SLAB-UAT-*` on the production store | ported | PUBLISH-10 | — | +| tag:IMPL-PLAN | phase1-inference-off | inference remains off for the slice | ported | QUALITY-20 | — | +| tag:IMPL-PLAN | phase1-routers-openapi | routers per OPENAPI, generated openapi.yaml | dropped | — | contract detail | +| tag:IMPL-PLAN | phase1-optimistic-concurrency | client_rev/server_rev, 409 paths | ported | APP-20 | — | +| tag:IMPL-PLAN | phase1-status-machine-queue | status machine, queue only for publish and inference | ported | APP-5 | — | +| tag:IMPL-PLAN | phase1-integration-tests | create to ready, revision conflict, duplicate SKU, encryption, anchor | ported | QUALITY-35 | — | +| tag:IMPL-PLAN | phase1-put-stores-derived | PUT stores derived fields, does not recompute | ported | APP-40 | — | +| tag:IMPL-PLAN | phase1-taxonomy-anchor | anchor advances only on real change; client re-pulls | ported | SETTINGS-55, SETTINGS-60 | — | +| tag:IMPL-PLAN | phase2-offline | offline capture deferred | ported | CAPTURE-15 | — | +| tag:IMPL-PLAN | phase3-review-override-paths | widths/sqft/bdft shown with manual override paths | superseded | REVIEW-30, REVIEW-40 | calculated fields are read-only | +| tag:IMPL-PLAN | phase3-show-pngs | show processed PNGs, originals while retained | ported | PUBLISH-40 | — | +| tag:IMPL-PLAN | phase3-inference-ui | 0.7 pre-fill, below empty, full ready set | ported | METADATA-45, REVIEW-5 | — | +| tag:IMPL-PLAN | phase3-capture-four-knobs | four knobs on the mask screen | superseded | CAPTURE-40 | two knobs | +| tag:IMPL-PLAN | phase3-publish-poll | publish button, poll, surface error detail | ported | PUBLISH-5, PUBLISH-30 | — | +| tag:IMPL-PLAN | phase3-settings-ui | settings screen for creds, inference, pricing, brand/GEO, create status | ported | SETTINGS-5, SETTINGS-15, SETTINGS-20, SETTINGS-30, SETTINGS-35, SETTINGS-65 | — | +| tag:IMPL-PLAN | phase3-secrets-masked | secrets never appear in GET | ported | SETTINGS-75 | — | +| tag:IMPL-PLAN | phase4-woo-client | Woo client with Basic auth, taxonomy sync | ported | SETTINGS-15, SETTINGS-50 | — | +| tag:IMPL-PLAN | phase4-auto-assign | auto-assign categories, five attributes, fig/feat tags | ported | METADATA-10, METADATA-15, METADATA-30 | — | +| tag:IMPL-PLAN | phase4-media-create | media upload of processed PNGs, product create per status | ported | PUBLISH-40, PUBLISH-10 | — | +| tag:IMPL-PLAN | phase4-dedupe | dedupe by SKU before create, 409 recovery | ported | PUBLISH-15 | — | +| tag:IMPL-PLAN | phase4-stale-recovery | 422 stale-field recovery with current options | ported | PUBLISH-20 | — | +| tag:IMPL-PLAN | phase4-sync-log-purge | sync_log, set published, purge images | ported | PUBLISH-25, PUBLISH-30 | — | +| tag:IMPL-PLAN | phase4-taxonomy-sync | taxonomy sync endpoint against the real store | ported | SETTINGS-50, SETTINGS-55 | — | +| tag:IMPL-PLAN | phase4-integration-respx | integration tests against a Woo mock | ported | QUALITY-35 | — | +| tag:IMPL-PLAN | phase4-partial-failure | orphaned media logged, manual cleanup note | ported | PUBLISH-31 | — | +| tag:IMPL-PLAN | phase5-call1-proxy | Call 1 proxy, all photos at 1024, taxonomy, 0.7 | ported | METADATA-35, METADATA-40, METADATA-45 | — | +| tag:IMPL-PLAN | phase5-prefill-rules | at or above threshold pre-fill, below empty | ported | METADATA-45 | — | +| tag:IMPL-PLAN | phase5-call2-proxy | Call 2 proxy, never auto, prose only, templates inject numbers | ported | METADATA-70, METADATA-65 | — | +| tag:IMPL-PLAN | phase5-portable-context | portable Call 1 context, previous_response_id | superseded | — | thread work out of MVP | +| tag:IMPL-PLAN | phase5-test-inference | test-inference probe | ported | SETTINGS-45 | mode removed | +| tag:IMPL-PLAN | phase5-prompt-files | prompt files read fresh each call | superseded | METADATA-105 | prompts live in the DB with all revisions | +| tag:IMPL-PLAN | phase5-no-invention | no numeric invention, LoRA deferred | ported | METADATA-65 | — | +| tag:IMPL-PLAN | phase6-host-inventory | host inventory pass | ported | OPS-15 | — | +| tag:IMPL-PLAN | phase6-deploy-backup | deploy, backup, restore tested | ported | OPS-5, OPS-30, OPS-35 | — | +| tag:IMPL-PLAN | phase6-uat-suite | UAT suite on real slabs | ported | QUALITY-110 | — | +| tag:IMPL-PLAN | phase6-e2e-browsers | end-to-end capture to publish on phone and PC browsers | ported | QUALITY-115 | — | +| tag:IMPL-PLAN | phase6-manual-length-override | manual length override recalculates widths/sqft/bdft | superseded | REVIEW-30, REVIEW-40 | calculated fields are read-only | +| tag:IMPL-PLAN | phase6-crop-80-1600 | crop 80 percent, shorter side 1600 | superseded | CAPTURE-100, CAPTURE-105 | PoR is 90 percent and 800 px | +| tag:IMPL-PLAN | phase6-woo-product | Woo product with processed PNGs and taxonomy | ported | PUBLISH-40, PUBLISH-45 | — | +| tag:IMPL-PLAN | phase6-unit-tests-logs | deterministic functions tested, inference calls logged | ported | QUALITY-5, METADATA-66 | — | +| tag:IMPL-PLAN | risks-green-sheet | chroma key plus inline knobs, retake, never lower pixel floor | ported | CAPTURE-35, CAPTURE-41, CAPTURE-155 | — | +| tag:IMPL-PLAN | risks-never-lower-1600 | never lower output_px_min below 1600 | superseded | CAPTURE-105 | PoR floor is 800 px | +| tag:IMPL-PLAN | risks-no-vision | inference toggleable, manual fully functional | ported | METADATA-75 | — | +| tag:IMPL-PLAN | risks-partial-publish | orphaned media, retained images, manual cleanup | ported | PUBLISH-31 | — | +| tag:IMPL-PLAN | risks-sqlite | SQLite contention under concurrent polls | dropped | — | single owner, low volume | +| tag:IMPL-PLAN | risks-host-port | host port or Caddy collision | ported | OPS-15 | — | +| tag:IMPL-PLAN | risks-ruler-deferred | ruler detection deferred; manual length is authoritative | ported | CAPTURE-15 | — | +| tag:IMPL-PLAN | dod-phases | all phases exit-gated by Ty | dropped | — | process | +| tag:IMPL-PLAN | dod-acceptance | hybrid online acceptance met in Phase 6 UAT | ported | QUALITY-110 | — | +| tag:IMPL-PLAN | dod-tests-green-off | pytest, integration, UAT green with inference off | ported | QUALITY-20 | — | +| tag:IMPL-PLAN | dod-deploy-backup | deployed, HTTPS, backed up, restore tested | ported | OPS-5, OPS-30, OPS-35 | — | +| tag:IMPL-PLAN | dod-gitea-source | repo on gitea is source of truth | ported | OPS-50 | — | +| tag:IMPL-PLAN | dod-archive-frozen | archive is frozen history; live docs are the SoT | dropped | — | process | diff --git a/.audit/coverage/tag-openapi.md b/.audit/coverage/tag-openapi.md new file mode 100644 index 0000000..1333ceb --- /dev/null +++ b/.audit/coverage/tag-openapi.md @@ -0,0 +1,79 @@ +# Coverage ledger — tag:OPENAPI + +Source `tag:OPENAPI` (`docs/OPENAPI.md` at tag `pre-simplification`, role: contract). Walked +2026-09-18. Wire detail (endpoints, schemas, examples) is contract material for +`docs/contracts/API.md`; only the testable rules carry PoR IDs. + +| Source | Atom | Requirement | Disposition | PoR ID | Rationale | +|---|---|---|---|---|---| +| tag:OPENAPI | auth-no-client-auth | no client auth; trusted LAN/Tailscale plus HTTPS | ported | OPS-55 | — | +| tag:OPENAPI | error-envelope | uniform error envelope { code, message, detail } | ported | APP-35 | — | +| tag:OPENAPI | error-400-404 | 400 validation; 404 not_found | ported | APP-35 | — | +| tag:OPENAPI | error-409-duplicate-sku | 409 duplicate_sku, SKU exists in Woo any status | ported | PUBLISH-15 | — | +| tag:OPENAPI | error-409-revision-conflict | 409 revision_conflict with current_server_rev | ported | APP-20 | — | +| tag:OPENAPI | error-422-unpublishable | 422 unpublishable with field keys, no raw code | ported | REVIEW-5, REVIEW-20 | — | +| tag:OPENAPI | error-412-validation-stale | 412 validation_module_stale | dropped | — | validation module ruled cut 2026-09-18 | +| tag:OPENAPI | error-502-woo | 502 woo_error carries the Woo error body | ported | PUBLISH-30 | — | +| tag:OPENAPI | error-500-internal | 500 internal | ported | APP-35 | — | +| tag:OPENAPI | revisions | client_rev/server_rev, mismatch 409 revision_conflict | ported | APP-20 | — | +| tag:OPENAPI | photos-multipart-40mb | multipart, max 40 MB per request | dropped | — | contract detail | +| tag:OPENAPI | photos-meta-role-kind | meta { kind: inventory, role, seq } | dropped | — | role/kind label discarded per CAPTURE migration | +| tag:OPENAPI | pagination-full-arrays | lists return full arrays | ported | APP-35 | — | +| tag:OPENAPI | api-version | /api/v1, breaking changes bump v2 | ported | APP-30 | — | +| tag:OPENAPI | schema-sku | SKU string 3..24 | ported | REVIEW-45 | — | +| tag:OPENAPI | schema-length-thickness | length_in and thickness_in 1/8 step | ported | CAPTURE-70 | — | +| tag:OPENAPI | schema-derived | sqft, bdft, widths computed by client | ported | CAPTURE-80, CAPTURE-85, CAPTURE-90 | — | +| tag:OPENAPI | schema-species-woodcategory | species leaf 1, wood categories 1+ | ported | METADATA-10 | — | +| tag:OPENAPI | schema-edge-figure | edge 1, figure 1+ | ported | METADATA-15 | — | +| tag:OPENAPI | schema-grade-cardinality | grade 1+ | superseded | METADATA-15 | grade is exactly one | +| tag:OPENAPI | schema-thickness-roundup | thickness term by round-up | ported | METADATA-15 | — | +| tag:OPENAPI | schema-moisture-default | moisture defaults kiln-dried | ported | METADATA-15 | — | +| tag:OPENAPI | schema-fig-tags-cardinality | fig-* 1+ | superseded | METADATA-30 | tags optional | +| tag:OPENAPI | schema-feat-tags | feat-* 0+ | ported | METADATA-30 | — | +| tag:OPENAPI | schema-price | price 2 dp, price_source recommendation or override | ported | REVIEW-25 | — | +| tag:OPENAPI | schema-short-title | short_title field | superseded | REVIEW-10 | Woo canonical name is short_description | +| tag:OPENAPI | slab-read-model | status, inference_status, woo_product_id, published_at, revisions | ported | APP-5 | — | +| tag:OPENAPI | photo-read-410 | original/processed urls, 410 after publish | ported | PUBLISH-25 | — | +| tag:OPENAPI | call1-result-shape | Call 1 taxonomy ids plus confidences | ported | METADATA-40 | — | +| tag:OPENAPI | call1-free-text-to-feat | free-text observations mapped to synced feat-* tags | ported | METADATA-100 | — | +| tag:OPENAPI | call2-result-shape | title, short_title, description | ported | METADATA-55 | short_title naming migrated to short_description | +| tag:OPENAPI | price-rule-shape | flat price_per_bdft per species | ported | SETTINGS-35 | — | +| tag:OPENAPI | settings-view-shape | settings read model; secrets masked | ported | SETTINGS-5, SETTINGS-30, SETTINGS-35, SETTINGS-65, SETTINGS-75 | — | +| tag:OPENAPI | settings-view-crop | fill_target 0.80, output_px_min 1600 | superseded | CAPTURE-100, CAPTURE-105 | PoR is 90 percent fill and an 800 px floor | +| tag:OPENAPI | meta-endpoint | GET /meta version, templates_configured | dropped | — | contract detail | +| tag:OPENAPI | create-slab-calibrated | POST /slabs returns 201 calibrated, stores client numbers | ported | CAPTURE-135, APP-5 | — | +| tag:OPENAPI | create-slab-auto-call1 | auto-run Call 1 only if inference_enabled | ported | METADATA-35 | — | +| tag:OPENAPI | create-slab-confidence-persist | persist inferred values only at or above 0.7 | ported | METADATA-45 | — | +| tag:OPENAPI | create-slab-dup-sku | 409 duplicate_sku on create | ported | PUBLISH-15 | — | +| tag:OPENAPI | get-slabs-list | GET /slabs most recent first | dropped | — | contract detail | +| tag:OPENAPI | get-slab-poll | GET /slabs/{id} poll with backoff | dropped | — | contract detail | +| tag:OPENAPI | put-slab-stores-derived | PUT stores client derived fields, never recomputes | ported | APP-40 | — | +| tag:OPENAPI | put-slab-dup-sku-actions | duplicate SKU actions edit_sku/open_existing, never raw code | ported | PUBLISH-15, PUBLISH-35 | — | +| tag:OPENAPI | put-slab-stale-field | 422 stale_field with current options | ported | PUBLISH-20 | — | +| tag:OPENAPI | put-slab-412 | 412 validation module stale | dropped | — | validation module ruled cut | +| tag:OPENAPI | delete-slab-soft | DELETE soft, unpublished drafts only | dropped | — | contract detail | +| tag:OPENAPI | u2net-endpoint | POST u2net post-POC, not exposed | ported | CAPTURE-165 | — | +| tag:OPENAPI | photo-serve-410 | photo original/processed, 410 after publish | ported | PUBLISH-25 | — | +| tag:OPENAPI | infer-taxon-endpoint | POST infer-taxon, prompt fresh, 1024 plus taxonomy, 202 | ported | METADATA-35 | — | +| tag:OPENAPI | infer-taxon-image-source | original photos downscaled to 1024 | superseded | METADATA-35 | PoR sends the clean processed photos | +| tag:OPENAPI | infer-taxon-timeout | timeout over 45s returns 504 | ported | METADATA-75 | — | +| tag:OPENAPI | infer-content-endpoint | POST infer-content, 202, requires test-inference ok | ported | METADATA-55 | — | +| tag:OPENAPI | infer-content-context-thread | full resend default, previous_response_id, per-slab thread | superseded | — | thread work out of MVP | +| tag:OPENAPI | publish-preconditions | publish requires the full ready set, else 422 | ported | REVIEW-5 | — | +| tag:OPENAPI | publish-dup-sku | 409 duplicate_sku before create | ported | PUBLISH-15 | — | +| tag:OPENAPI | publish-stale | 422 stale_field, draft stays | ported | PUBLISH-20, PUBLISH-30 | — | +| tag:OPENAPI | publish-success | success sets woo_product_id, purges images, deletes draft | ported | PUBLISH-25 | — | +| tag:OPENAPI | publish-woo-fail | Woo failure returns failed, sync_log, 502 | ported | PUBLISH-30 | — | +| tag:OPENAPI | taxonomy-sync | sync rebuilds cache, anchor only on change, previous retained on failure | ported | SETTINGS-50, SETTINGS-55 | — | +| tag:OPENAPI | pricing-crud | pricing rule CRUD | ported | SETTINGS-35 | — | +| tag:OPENAPI | settings-get | GET settings returns SettingsView | ported | SETTINGS-75 | — | +| tag:OPENAPI | settings-put | PUT settings partial, secrets re-encrypted, password never echoed | ported | SETTINGS-20, SETTINGS-30, SETTINGS-65 | — | +| tag:OPENAPI | settings-put-woo-url-reject | woo_base_url not writable | ported | SETTINGS-85 | — | +| tag:OPENAPI | settings-put-consumer-key-reject | reject consumer key/secret payload | ported | SETTINGS-15 | — | +| tag:OPENAPI | test-woo-fail-closed | test-woo fails closed on missing credentials or non-https | ported | SETTINGS-80 | — | +| tag:OPENAPI | test-inference-shape | test-inference ok, mode, vision_capable, detail | ported | SETTINGS-45 | mode removed by ruling | +| tag:OPENAPI | validation-module-endpoint | GET validation/module, hash, 412 on mismatch | dropped | — | ruled cut 2026-09-18 | +| tag:OPENAPI | examples-worked | worked request/response examples | dropped | — | non-normative examples | +| tag:OPENAPI | example-taxonomy-sync-key | example uses synced_at while spec says taxonomy_last_sync_at | dropped | — | contract detail inconsistency | +| tag:OPENAPI | versioning-client-pin | client pins the version it was built against | ported | APP-30 | — | +| tag:OPENAPI | out-of-contract-v1 | bulk, staff/role, webhook push, analytics out of contract | dropped | — | contract detail | diff --git a/.audit/coverage/tag-prompts.md b/.audit/coverage/tag-prompts.md new file mode 100644 index 0000000..e774ee1 --- /dev/null +++ b/.audit/coverage/tag-prompts.md @@ -0,0 +1,36 @@ +# Coverage ledger — tag:PROMPTS + +Source `tag:PROMPTS` (`docs/PROMPTS.md` at tag `pre-simplification`, role: prompt spec). +Walked 2026-09-18. Prompt templates are contract material for `docs/contracts/PROMPTS.md`; +the testable rules carry PoR IDs. + +| Source | Atom | Requirement | Disposition | PoR ID | Rationale | +|---|---|---|---|---|---| +| tag:PROMPTS | management-location | prompts are server text files under backend/app/prompts, mounted read-only | superseded | METADATA-105 | ruling: prompts live in the DB with all revisions | +| tag:PROMPTS | management-edit-host | edit the text files on the host, no restart | superseded | — | prompts move to the DB | +| tag:PROMPTS | management-reset-script | python -m app.prompts.reset restores defaults | superseded | — | replaced by DB revision history | +| tag:PROMPTS | management-version-log | inference_log stores prompt content or hash per call | ported | METADATA-66 | — | +| tag:PROMPTS | call1-purpose | classify species, edge, figure, grade from photos using synced lists only | ported | METADATA-40 | — | +| tag:PROMPTS | call1-inputs-photos | 1-5 inventory photos downscaled to 1024 | ported | METADATA-35 | downsample rule | +| tag:PROMPTS | call1-photo-cap | 1-5 inventory photos | superseded | CAPTURE-25 | no photo cap | +| tag:PROMPTS | call1-inputs-taxonomy | species, edge, figure, grade lists with ids | ported | METADATA-35, METADATA-40 | — | +| tag:PROMPTS | call1-inputs-metadata | user metadata SKU, length, thickness | ported | METADATA-35 | — | +| tag:PROMPTS | call1-system-constraint | only use provided taxonomy values, do not invent | ported | METADATA-40 | — | +| tag:PROMPTS | call1-schema | taxonomy ids plus confidences and free-text observations | ported | METADATA-40 | — | +| tag:PROMPTS | call1-free-text-to-feat | free-text observations mapped to synced feat-* tags | ported | METADATA-100 | — | +| tag:PROMPTS | call1-prepopulation | Call 1 is the primary pre-fill source when inference is enabled | ported | METADATA-45 | — | +| tag:PROMPTS | call1-confidence-gate | at or above 0.7 pre-fill, below leaves empty | ported | METADATA-45 | — | +| tag:PROMPTS | call1-ready-not-defined | Call 1 does not define the ready gate | ported | METADATA-75 | — | +| tag:PROMPTS | call1-manual-wins | manual entry wins; Call 1 is assist only | ported | METADATA-50 | — | +| tag:PROMPTS | call2-purpose | the model writes descriptive prose only | ported | METADATA-60 | — | +| tag:PROMPTS | call2-inputs-context | portable Call 1 context, previous_response_id | superseded | — | thread work out of MVP | +| tag:PROMPTS | call2-confirmed-inferred | explicit confirmed versus inferred labels | ported | METADATA-60 | PoR states metadata confirmed or changed in review | +| tag:PROMPTS | call2-deterministic-measurements | deterministic measurements, never invented | ported | METADATA-60, METADATA-65 | — | +| tag:PROMPTS | call2-brand-geo | brand voice, GEO context, templates | ported | METADATA-60 | — | +| tag:PROMPTS | call2-validation-rules | shared-validation rules in Call 2 assembly | dropped | — | validation module ruled cut | +| tag:PROMPTS | call2-system-no-invention | measurements are not invented; only provided data is used | ported | METADATA-65 | — | +| tag:PROMPTS | call2-template-assembly | server assembles title and description from templates | ported | METADATA-70, METADATA-80 | — | +| tag:PROMPTS | call2-llm-not-dimensions | the LLM never touches dimensions | ported | METADATA-65 | — | +| tag:PROMPTS | call2-llm-disabled | content_llm_enabled false skips the LLM, templates are used | ported | METADATA-70 | — | +| tag:PROMPTS | tuning-workflow | iterate prompts against real slabs | dropped | — | procedure | +| tag:PROMPTS | title-example-emdash | example title uses an em dash and a multiplication sign | superseded | — | title separator is the hyphen; migration cleanup | diff --git a/.audit/coverage/tag-tech-spec-pipeline.md b/.audit/coverage/tag-tech-spec-pipeline.md new file mode 100644 index 0000000..dcb24c9 --- /dev/null +++ b/.audit/coverage/tag-tech-spec-pipeline.md @@ -0,0 +1,51 @@ +# Coverage ledger — tag:TECH-SPEC-PIPELINE + +Source `tag:TECH-SPEC-PIPELINE` (`docs/TECH-SPEC-PIPELINE.md` at tag `pre-simplification`). +Vetted 2026-09-18. Rules are CAPTURE, vectors are QUALITY. Doc retired (see migration TODOs). + +| Source | Atom | Requirement | Disposition | PoR ID | Rationale | +|---|---|---|---|---|---| +| tag:TECH-SPEC-PIPELINE | invariants-determinism | pure function of pixels and user inputs; no network in the math | ported | CAPTURE-5 | — | +| tag:TECH-SPEC-PIPELINE | invariants-manual-authoritative | user-entered length, thickness, SKU, price always win | ported | METADATA-50 | — | +| tag:TECH-SPEC-PIPELINE | invariants-fallback-chain | every computed value has a defined fallback; never silently guesses | ported | CAPTURE-155 | — | +| tag:TECH-SPEC-PIPELINE | invariants-units | inches in 1/8 steps; sqft and bdft 2 decimals | ported | CAPTURE-70, CAPTURE-160 | — | +| tag:TECH-SPEC-PIPELINE | invariants-client-authoritative | all deterministic processing in the browser; server stores | ported | CAPTURE-135, APP-40 | — | +| tag:TECH-SPEC-PIPELINE | browser-compat-floor | iOS Safari, Android Chrome, desktop Chrome/Edge/Firefox; hard stop | ported | CAPTURE-10 | — | +| tag:TECH-SPEC-PIPELINE | load-bearing-apis | canvas or OffscreenCanvas, image decode/crop/encode, fetch | ported | CAPTURE-140 | — | +| tag:TECH-SPEC-PIPELINE | units-table | per-quantity source and rounding | ported | CAPTURE-70, CAPTURE-85, CAPTURE-90, REVIEW-25, CAPTURE-160 | — | +| tag:TECH-SPEC-PIPELINE | bdft-not-rounded-whole | bdft is never rounded up to whole board feet | ported | CAPTURE-90 | — | +| tag:TECH-SPEC-PIPELINE | pixels-hidden | pixel-to-inch scale is an implementation detail | dropped | — | internal detail | +| tag:TECH-SPEC-PIPELINE | bg-removal-load-bearing | the mask defines the measurement footprint; the eye is the validator | ported | CAPTURE-50 | — | +| tag:TECH-SPEC-PIPELINE | sheet-detect | 2-5 percent border strip, HSV green or black, else user picks | ported | CAPTURE-35 | — | +| tag:TECH-SPEC-PIPELINE | overlay-source-photo | overlay the mask on the source photo and confirm the edge | ported | CAPTURE-50 | — | +| tag:TECH-SPEC-PIPELINE | knobs-inline-live | knobs inline on capture, live re-run, persisted, no server round trip | ported | CAPTURE-45, CAPTURE-145, CAPTURE-150 | — | +| tag:TECH-SPEC-PIPELINE | knobs-four | sheet, sensitivity, edge offset, feather | superseded | CAPTURE-40 | controls are exactly Sheet and Sensitivity | +| tag:TECH-SPEC-PIPELINE | algorithms-floodfill | chroma key or threshold with border-connected flood fill | ported | CAPTURE-35, CAPTURE-41 | — | +| tag:TECH-SPEC-PIPELINE | bg-poc-client-u2net-deferred | client-only mask with retake; U2Net deferred, contract preserved | ported | CAPTURE-165 | — | +| tag:TECH-SPEC-PIPELINE | edge-quality-contiguous | the mask must be one contiguous slab contour, else retake | ported | CAPTURE-55 | — | +| tag:TECH-SPEC-PIPELINE | contour-length-axis | min-area bounding rect; long side is the length axis, prevailing length | ported | CAPTURE-60 | — | +| tag:TECH-SPEC-PIPELINE | axis-rotate-confirm | user rotates and confirms; nearly square picks the axis | ported | CAPTURE-60, CAPTURE-65 | — | +| tag:TECH-SPEC-PIPELINE | scale-formula | scale = longest_axis_px / user length | ported | CAPTURE-75 | — | +| tag:TECH-SPEC-PIPELINE | widths | sample every 6 inches perpendicular; min, max, average | ported | CAPTURE-80 | — | +| tag:TECH-SPEC-PIPELINE | sqft-formula | sqft = mask_pixels / scale squared / 144, 2 decimals | ported | CAPTURE-85 | — | +| tag:TECH-SPEC-PIPELINE | bdft-formula | bdft = sqft times thickness_in, 2 decimals | ported | CAPTURE-90 | — | +| tag:TECH-SPEC-PIPELINE | normalization-3-4-png | 3:4 portrait, transparent PNG, slab centered | ported | CAPTURE-95, CAPTURE-100 | — | +| tag:TECH-SPEC-PIPELINE | normalization-80-fill-1600 | 80 percent fill and 1600 px floor | superseded | CAPTURE-100, CAPTURE-105 | PoR is 90 percent fill and an 800 px floor | +| tag:TECH-SPEC-PIPELINE | normalization-every-photo | applies to every inventory photo | ported | CAPTURE-30, CAPTURE-110 | — | +| tag:TECH-SPEC-PIPELINE | dimension-overlay-v1_5 | auto-dimension overlay is v1.5, out of this spec | superseded | CAPTURE-120, CAPTURE-125 | the overlay is shipped | +| tag:TECH-SPEC-PIPELINE | pricing | species $/bdft, recommendation, override, no-rule behavior | ported | REVIEW-25, SETTINGS-35 | — | +| tag:TECH-SPEC-PIPELINE | constants-code | FILL_TARGET, ASPECT, OUTPUT_PX_MIN, WIDTH_SAMPLE, INCH_STEP, decimals | superseded | CAPTURE-100, CAPTURE-105 | fill and pixel floor superseded | +| tag:TECH-SPEC-PIPELINE | factory-bg-defaults | sheet auto, green hue/sat/val, black threshold | ported | CAPTURE-35, CAPTURE-41 | — | +| tag:TECH-SPEC-PIPELINE | tv1-green-sheet | interior green patch not touching the border survives; U2Net not invoked | ported | QUALITY-55 | — | +| tag:TECH-SPEC-PIPELINE | tv2-black-sheet | dark interior wood survives; flood fill isolates border-connected regions | ported | QUALITY-60 | — | +| tag:TECH-SPEC-PIPELINE | tv3-scale-sqft-bdft | 96 in, 1.5 in, 0.64 sqft yields 0.96 bdft | ported | QUALITY-65 | — | +| tag:TECH-SPEC-PIPELINE | tv4-width-samples | 6 inch stations on a 96 inch axis report min, max, average | ported | QUALITY-70 | — | +| tag:TECH-SPEC-PIPELINE | tv5-angled-end | one square end and one 45 degree cut; axis follows prevailing length | ported | QUALITY-75 | — | +| tag:TECH-SPEC-PIPELINE | tv6-crop | 3:4, fill at extremes, transparent PNG, shorter side floor | ported | QUALITY-80 | PoR uses 90 percent fill and the 800 px floor | +| tag:TECH-SPEC-PIPELINE | tv7-too-small | undersized source warns, is not upscaled, not publishable until retake | ported | QUALITY-85 | PoR floor is 800 px | +| tag:TECH-SPEC-PIPELINE | tv8-pricing | 0.96 bdft at 12.50 gives 12.00; override stored | ported | QUALITY-90 | — | +| tag:TECH-SPEC-PIPELINE | tv9-thickness-range | 1 in rounds up into the synced Woo thickness band | ported | QUALITY-95 | — | +| tag:TECH-SPEC-PIPELINE | tv10-rounding | half-up 2 decimal rounding cases | ported | QUALITY-100 | — | +| tag:TECH-SPEC-PIPELINE | assumptions | top-down first photo, user length, declared thickness, projected area | ported | CAPTURE-20, CAPTURE-70, CAPTURE-85, CAPTURE-90, CAPTURE-135 | — | +| tag:TECH-SPEC-PIPELINE | cross-reference-table | cross-references to other docs | dropped | — | stale refs; migration | +| tag:TECH-SPEC-PIPELINE | issue4-version-pins | exact minimum version pins tracked under issue #4 | dropped | — | tracked as an issue, not a requirement | diff --git a/.audit/coverage/tag-test-spec.md b/.audit/coverage/tag-test-spec.md new file mode 100644 index 0000000..be05192 --- /dev/null +++ b/.audit/coverage/tag-test-spec.md @@ -0,0 +1,29 @@ +# Coverage ledger — tag:TEST-SPEC + +Source `tag:TEST-SPEC` (`docs/TEST-SPEC.md` at tag `pre-simplification`). Rows seeded by the +settings/auth/health pilot 2026-09-18. TEST-SPEC is reconciled LAST, against a finished PoR. + +| Source | Atom | Requirement | Disposition | PoR ID | Rationale | +|---|---|---|---|---|---| +| tag:TEST-SPEC | GC-1 | SLAB-UAT-* creates a Woo draft regardless of woo_create_status | ported | PUBLISH-10 | — | +| tag:TEST-SPEC | GC-2 | duplicate SKU returns 409 with edit_sku and open_existing | ported | PUBLISH-15 | — | +| tag:TEST-SPEC | GC-3 | test-woo returns ok with taxonomy and always resyncs on success | ported | SETTINGS-40 | — | +| tag:TEST-SPEC | GC-4 | WOO_BASE_URL compose-only, GET read-only, PUT rejects a write with 422 | ported | SETTINGS-85 | — | +| tag:TEST-SPEC | GC-5 | FastAPI refuses to start on missing or http WOO_BASE_URL | ported | SETTINGS-10 | — | +| tag:TEST-SPEC | W-3 | taxonomy anchor unchanged on no-op, bumps only on stored-subset hash change | ported | SETTINGS-55 | sync and rebuild live in SETTINGS-50 | +| tag:TEST-SPEC | W-4 | test-woo always runs a taxonomy resync after a successful test | duplicate | SETTINGS-40 | same rule as GC-3 | +| tag:TEST-SPEC | AES-1 | credential encrypt/decrypt round-trips; ciphertext never appears in GET | ported | SETTINGS-75, QUALITY-125 | — | +| tag:TEST-SPEC | AES-2 | missing SLAB_AES_KEY refuses startup, no plaintext fallback | ported | SETTINGS-25 | — | +| tag:TEST-SPEC | API-10 | test-woo fails closed on missing credentials or non-HTTPS URL | ported | SETTINGS-80 | — | +| tag:TEST-SPEC | FUND-21 | 401/400/404 mapped to an actionable error, not raw passthrough | ported | APP-50 | — | +| tag:TEST-SPEC | H-1 | health 200 with woo_reachable false when credentials are not configured | ported | APP-45 | — | +| tag:TEST-SPEC | INS-1 | health non-200 on DB or volume failure; woo_reachable false when the store is unreachable | ported | APP-45 | — | +| tag:TEST-SPEC | UIE-8 | Settings screen: WP creds and test-woo, inference and test, pricing, brand/GEO, create status, read-only URL | ported | SETTINGS-5, SETTINGS-15, SETTINGS-20, SETTINGS-30, SETTINGS-35, SETTINGS-65 | screen composition itself belongs to the contract/UX docs | +| tag:TEST-SPEC | OPS-3 | compose health, test-woo ok, create status draft at deploy | ported | QUALITY-110 | — | +| tag:TEST-SPEC | FUND-3, FUND-5, FUND-6, FUND-7 | keyboard tab order, visible focus ring, 320 px reflow | ported | APP-60 | found by TEST-SPEC reconciliation | +| tag:TEST-SPEC | FUND-24 | no secret material in logs | ported | APP-65 | found by TEST-SPEC reconciliation | +| tag:TEST-SPEC | V-3, API-8 | shared validation module hash, 412 | dropped | — | validation module ruled cut | +| tag:TEST-SPEC | C2-2 | previous_response_id, per-slab thread | superseded | — | thread work out of MVP | +| tag:TEST-SPEC | UI-1, UIE-9, UIE-10 | four knobs | superseded | CAPTURE-40 | two controls | +| tag:TEST-SPEC | UIE-1, E2E-4 | manual override of computed fields | superseded | REVIEW-30, REVIEW-40 | computed fields read-only | +| tag:TEST-SPEC | TV-6, TV-7, E2E-5 | 80 percent fill, 1600 px | superseded | CAPTURE-100, CAPTURE-105 | 90 percent, 800 px | diff --git a/.audit/coverage/tag-ux.md b/.audit/coverage/tag-ux.md new file mode 100644 index 0000000..4909086 --- /dev/null +++ b/.audit/coverage/tag-ux.md @@ -0,0 +1,78 @@ +# Coverage ledger — tag:UX and tag:UX-DESIGN + +Sources: `tag:UX` (`docs/UX.md`, SIGNED presentation spec) and `tag:UX-DESIGN` +(`docs/UX-DESIGN.md`, non-normative wireframes). Walked 2026-09-18. UX migrates to +`docs/contracts/UX.md`; UX-DESIGN is retired. + +| Source | Atom | Requirement | Disposition | PoR ID | Rationale | +|---|---|---|---|---|---| +| tag:UX | jobs | five jobs: photo to listing, practice drafts, retake, fill fields, publish | ported | CAPTURE-5, PUBLISH-10, CAPTURE-50, METADATA-50, PUBLISH-5 | — | +| tag:UX | photos-1-to-5 | 1 to 5 inventory photos | superseded | CAPTURE-25 | no cap | +| tag:UX | no-calibration-photos | there are no calibration photos; only inventory photos exist | superseded | CAPTURE-25, CAPTURE-30 | the calibration photo exists | +| tag:UX | first-photo-topdown | first photo top-down, lens parallel to the broad face | ported | CAPTURE-20 | — | +| tag:UX | mask-overlay-only-check | the overlay on the source photo is the only quality check | ported | CAPTURE-50 | — | +| tag:UX | knobs-four | sheet, sensitivity, edge offset, feather | superseded | CAPTURE-40 | controls are exactly Sheet and Sensitivity | +| tag:UX | knobs-live-persist | live re-run, no apply step, no server round trip, persisted | ported | CAPTURE-45, CAPTURE-145, CAPTURE-150 | — | +| tag:UX | u2net-deferred | server matting deferred, no control | ported | CAPTURE-165 | — | +| tag:UX | length-axis-confirm | bounding rect and axis, rotate, nearly square choice | ported | CAPTURE-60, CAPTURE-65 | — | +| tag:UX | numbers-manual | length, thickness, SKU entered by hand | ported | CAPTURE-70 | — | +| tag:UX | widths-min-max-avg | widths every 6 inches; min, max, average | ported | CAPTURE-80 | — | +| tag:UX | sqft-bdft-2dp | square feet and board feet to two decimals | ported | CAPTURE-85, CAPTURE-90 | — | +| tag:UX | bdft-identity | one square foot at one inch is one board foot, no divide-by-12 | ported | CAPTURE-90 | — | +| tag:UX | server-no-recompute | computed in the browser and stored as sent | ported | CAPTURE-135, APP-40 | — | +| tag:UX | photo-prep-80-1600 | 3:4 at 80 percent fill, shorter side 1600 or more | superseded | CAPTURE-100, CAPTURE-105 | PoR is 90 percent fill and an 800 px floor | +| tag:UX | continue-calibrated | Continue uploads originals, PNGs, numbers; status calibrated | ported | CAPTURE-135, APP-5 | — | +| tag:UX | vision-fill-threshold | Call 1 pre-fills at or above 0.7 after the draft uploads | ported | METADATA-35, METADATA-45 | — | +| tag:UX | inference-off-empty | with inference off every field starts empty | ported | METADATA-70 | — | +| tag:UX | inference-never-blocks | inference never blocks publish and never auto-publishes | ported | METADATA-75 | — | +| tag:UX | call2-not-auto | Call 2 fires only when the owner taps it | ported | METADATA-55 | — | +| tag:UX | sku-charset | SKU `[A-Z0-9-]{3,24}` | ported | REVIEW-45 | PoR allows alphanumeric (lowercase too) | +| tag:UX | length-thickness-editable | length and thickness editable in review | ported | REVIEW-40 | — | +| tag:UX | calc-fields-override | square feet, board feet, widths have a manual override path | superseded | REVIEW-30, REVIEW-40 | calculated fields are read-only | +| tag:UX | species-exactly-one | exactly one species leaf from the synced list | ported | METADATA-10 | — | +| tag:UX | wood-categories-1plus | at least one wood category | ported | METADATA-10 | — | +| tag:UX | edge-one | one edge type | ported | METADATA-15 | — | +| tag:UX | figure-1plus | at least one figure term | ported | METADATA-15 | — | +| tag:UX | grade-1plus | at least one grade term | superseded | METADATA-15 | grade is exactly one | +| tag:UX | thickness-band-roundup | thickness band rounds up into the synced Woo range | ported | METADATA-15 | — | +| tag:UX | moisture-default | moisture defaults kiln-dried, never inferred | ported | METADATA-15 | — | +| tag:UX | fig-tags-1plus | at least one fig-* tag | superseded | METADATA-30 | tags are optional | +| tag:UX | feat-tags-0plus | zero or more feat-* tags | ported | METADATA-30 | — | +| tag:UX | price-reco-override | recommendation from species $/bdft, override sticks | ported | REVIEW-25 | — | +| tag:UX | price-no-rule-manual | no rule leaves the price empty; the owner types it | ported | REVIEW-25 | price is required before create | +| tag:UX | title-desc-not-required-to-ready | title, short title, description not required to reach ready | superseded | REVIEW-10 | they are required on the product | +| tag:UX | templates-always-run | templates always run and inject the numbers | superseded | METADATA-70, METADATA-80 | the model writes when Call 2 runs; template is the fallback | +| tag:UX | inline-nudges | per-field inline nudge; no single submit-time list | ported | REVIEW-20 | — | +| tag:UX | generate-text-button | Generate text runs Call 2 and fills title, short title, description | ported | METADATA-55 | — | +| tag:UX | draft-vs-live | `woo_create_status` default draft; SLAB-UAT-* always draft | ported | PUBLISH-10, SETTINGS-65 | — | +| tag:UX | published-not-live | published means the store create succeeded, not that it is live | ported | PUBLISH-50 | — | +| tag:UX | publish-success | success cleans up the local draft and photos | ported | PUBLISH-25 | — | +| tag:UX | publish-dup-sku | duplicate SKU on the field, edit SKU or open existing | ported | PUBLISH-15 | — | +| tag:UX | publish-stale-value | stale value on the field, pick from the current list | ported | PUBLISH-20 | — | +| tag:UX | publish-store-unreachable | plain message, no status number or stack trace, retry | ported | PUBLISH-30, PUBLISH-35, PUBLISH-36, PUBLISH-38 | — | +| tag:UX | publish-log-retry | the server logs every attempt; retry from the same screen | ported | PUBLISH-30 | — | +| tag:UX | browser-matrix | iOS Safari, Android Chrome, desktop Chrome/Edge/Firefox | ported | CAPTURE-10 | — | +| tag:UX | unsupported-hard-stop | hard stop with an update or switch link, no polyfill | ported | CAPTURE-10 | — | +| tag:UX | deferred-features | photo quality, OCR, ruler, server measuring, U2Net, offline deferred | ported | CAPTURE-15, CAPTURE-165 | — | +| tag:UX | where-jobs-live-table | where each job lives | dropped | — | stale refs; migration | +| tag:UX-DESIGN | wireframes-non-normative | wireframes are non-normative and defer to UX.md | dropped | — | non-normative by its own text | +| tag:UX-DESIGN | settings-screen | settings fields, test connections, pricing, brand/GEO | ported | SETTINGS-5, SETTINGS-20, SETTINGS-30, SETTINGS-35, SETTINGS-65 | — | +| tag:UX-DESIGN | settings-vision-content-flags | Enable Vision and Enable Content default off | ported | SETTINGS-90 | — | +| tag:UX-DESIGN | settings-test-inference | test connection shows stateful/stateless/fail | ported | SETTINGS-45 | mode kept as diagnostic; Call 2 does not use thread IDs | +| tag:UX-DESIGN | generate-text-availability | Generate text appears only when content generation is enabled and reachable; disabled when the service is down | ported | METADATA-110 | — | +| tag:UX-DESIGN | settings-reset-knobs | reset capture knobs to default | ported | CAPTURE-145 | — | +| tag:UX-DESIGN | first-run-guidance | first-run card | dropped | — | presentation detail; fold into the UX contract | +| tag:UX-DESIGN | capture-knobs-four | four knobs on capture | superseded | CAPTURE-40 | two knobs | +| tag:UX-DESIGN | type-values-screen | SKU, length, thickness, computed values, done | ported | CAPTURE-70, CAPTURE-85, CAPTURE-90 | — | +| tag:UX-DESIGN | calc-values-override | typed number wins over the computed value | superseded | REVIEW-30, REVIEW-40 | calculated fields are read-only | +| tag:UX-DESIGN | review-screen-fields | all store fields on review | ported | REVIEW-5, METADATA-10, METADATA-15, METADATA-30, REVIEW-25 | minus grade/fig cardinality | +| tag:UX-DESIGN | review-grade-fig-mandatory | grade and fig-* mandatory | superseded | METADATA-15, METADATA-30 | grade exactly one; tags optional | +| tag:UX-DESIGN | ai-badge | AI badge on Call 1-filled fields | dropped | — | presentation detail; fold into the UX contract | +| tag:UX-DESIGN | per-field-nudges | per-field nudges as the user works | ported | REVIEW-20 | — | +| tag:UX-DESIGN | submit-time-validation | submit-time validation fires after all fields are filled | superseded | REVIEW-20 | no single submit-time list | +| tag:UX-DESIGN | title-desc-screen | separate Title/Description screen, exit review returns to calibrated | ported | APP-10, REVIEW-40 | — | +| tag:UX-DESIGN | publish-states | publishing, success, failed with retry | ported | PUBLISH-5, PUBLISH-25, PUBLISH-30 | — | +| tag:UX-DESIGN | error-states | mask, axis, too-small, duplicate SKU, stale value, unreachable | ported | CAPTURE-55, CAPTURE-105, PUBLISH-15, PUBLISH-20, PUBLISH-30 | — | +| tag:UX-DESIGN | vision-off-below-threshold | empty fields with nudges when vision is off or below threshold | ported | METADATA-45, METADATA-70 | — | +| tag:UX-DESIGN | browser-gate-message | hard-stop message says update Safari or Chrome | superseded | CAPTURE-10 | matrix includes PC browsers | +| tag:UX-DESIGN | uat-cases-derived | fourteen UAT cases derived from wireframes | duplicate | REVIEW-20, PUBLISH-10, PUBLISH-15, PUBLISH-20, METADATA-45, CAPTURE-105 | TEST-SPEC owns the test plan | diff --git a/.audit/coverage/uat-practice.md b/.audit/coverage/uat-practice.md new file mode 100644 index 0000000..dbb79a1 --- /dev/null +++ b/.audit/coverage/uat-practice.md @@ -0,0 +1,59 @@ +# Coverage ledger — UAT-PRACTICE.md + +Source `cur:UAT-PRACTICE` (`docs/UAT-PRACTICE.md`, role: practice). Walked 2026-09-18. +Four conflicts were ruled superseded; the rest port to the PoR. + +| Source | Atom | Requirement | Disposition | PoR ID | Rationale | +|---|---|---|---|---|---| +| cur:UAT-PRACTICE | inventory-filled | deploy/INVENTORY filled before deploy | ported | OPS-15 | — | +| cur:UAT-PRACTICE | lan-tailscale | reachable over LAN/Tailscale | ported | OPS-5 | — | +| cur:UAT-PRACTICE | https-only | HTTPS only, valid cert | ported | OPS-5 | — | +| cur:UAT-PRACTICE | health-passes | health endpoint returns ok | ported | APP-30 | — | +| cur:UAT-PRACTICE | store-url-set | WOO_BASE_URL in host .env, https | ported | SETTINGS-5 | — | +| cur:UAT-PRACTICE | wp-store-https | WordPress store must already be on HTTPS | ported | SETTINGS-10, SETTINGS-15 | — | +| cur:UAT-PRACTICE | dedicated-low-priv-user | dedicated low-privilege WP user | ported | SETTINGS-15 | — | +| cur:UAT-PRACTICE | generate-password | generate application password in WP profile | dropped | — | procedure, not a requirement | +| cur:UAT-PRACTICE | enter-creds-encrypted | username + application password in Settings, server-encrypted | ported | SETTINGS-20 | — | +| cur:UAT-PRACTICE | test-woo-resync | test-woo reports ok and resyncs taxonomy | ported | SETTINGS-40 | — | +| cur:UAT-PRACTICE | fastapi-sole-caller | FastAPI is the only store caller | ported | APP-25 | — | +| cur:UAT-PRACTICE | rotate-creds | rotate application password then test-woo | duplicate | SETTINGS-40 | rotation is procedure; the verification is test-woo | +| cur:UAT-PRACTICE | sku-prefix | practice SKU starts with `SLAB-UAT-` | ported | PUBLISH-10 | — | +| cur:UAT-PRACTICE | always-draft | `SLAB-UAT-*` always creates a Woo draft | ported | PUBLISH-10 | — | +| cur:UAT-PRACTICE | create-status-default-draft | `woo_create_status` defaults to draft | ported | SETTINGS-65 | — | +| cur:UAT-PRACTICE | customers-never-see | a draft is hidden from the storefront | ported | PUBLISH-10, PUBLISH-50 | — | +| cur:UAT-PRACTICE | supported-browsers | iOS Safari, Android Chrome, desktop Chrome/Edge/Firefox; hard stop | ported | CAPTURE-10 | — | +| cur:UAT-PRACTICE | matrix-issue-refs | signed matrix and issue #4 provenance | dropped | — | stale provenance, not a requirement | +| cur:UAT-PRACTICE | photo-count-1-to-5 | take 1 to 5 photos | superseded | CAPTURE-25 | PoR is one or more; no cap in code | +| cur:UAT-PRACTICE | first-photo-topdown | first photo top-down, lens parallel | ported | CAPTURE-20 | — | +| cur:UAT-PRACTICE | four-knobs | Sheet, Sensitivity, Edge offset, Feather | superseded | CAPTURE-40 | controls are exactly Sheet and Sensitivity | +| cur:UAT-PRACTICE | confirm-mask | confirm the mask | ported | CAPTURE-50 | — | +| cur:UAT-PRACTICE | length-axis | bounding rectangle, rotate, nearly square choice | ported | CAPTURE-60, CAPTURE-65 | — | +| cur:UAT-PRACTICE | enter-length-thickness-sku | length/thickness 1/8 inch, SKU typed | ported | CAPTURE-70 | — | +| cur:UAT-PRACTICE | computed-numbers | sqft, bdft, widths, bdft identity | ported | CAPTURE-80, CAPTURE-85, CAPTURE-90 | — | +| cur:UAT-PRACTICE | continue-upload-status | Continue uploads originals, PNGs, numbers; status calibrated | ported | APP-5, CAPTURE-135 | — | +| cur:UAT-PRACTICE | species-exactly-one | pick exactly one species | ported | METADATA-10 | — | +| cur:UAT-PRACTICE | wood-categories | pick at least one wood category | ported | METADATA-10 | — | +| cur:UAT-PRACTICE | grade-at-least-one | at least one grade | superseded | METADATA-15 | grade is exactly one | +| cur:UAT-PRACTICE | figure-at-least-one | at least one figure | ported | METADATA-15 | — | +| cur:UAT-PRACTICE | thickness-band-roundup | thickness band rounds up | ported | METADATA-15 | — | +| cur:UAT-PRACTICE | moisture-default | moisture default kiln-dried | ported | METADATA-15 | — | +| cur:UAT-PRACTICE | fig-tags-at-least-one | pick at least one `fig-*` tag | superseded | METADATA-30 | tags are optional | +| cur:UAT-PRACTICE | feat-tags-zero-or-more | zero or more `feat-*` tags | ported | METADATA-30 | — | +| cur:UAT-PRACTICE | price-reco-override | recommendation from species `$/bdft`, override sticks | ported | REVIEW-25 | — | +| cur:UAT-PRACTICE | price-missing-rate | no rate starts empty, type it, publish allowed | ported | REVIEW-25 | price required before create; manual entry when no rate | +| cur:UAT-PRACTICE | title-description-templates | type or leave for templates; templates inject numbers | ported | METADATA-70, REVIEW-10 | — | +| cur:UAT-PRACTICE | inline-nudges-ready | inline nudge; cannot reach ready until filled | ported | REVIEW-20 | — | +| cur:UAT-PRACTICE | sku-check-create | server checks SKU, creates if new | ported | PUBLISH-5 | — | +| cur:UAT-PRACTICE | success-cleanup | success message, local draft and photos gone, status published | ported | PUBLISH-25, PUBLISH-50 | — | +| cur:UAT-PRACTICE | published-not-live | published means store create succeeded, not live | ported | PUBLISH-50 | — | +| cur:UAT-PRACTICE | store-draft-fields | confirm draft status and fields in the store | ported | PUBLISH-10, PUBLISH-40, PUBLISH-45, PUBLISH-55, METADATA-15 | — | +| cur:UAT-PRACTICE | storefront-hidden | not visible on the live storefront | ported | PUBLISH-10, PUBLISH-50 | — | +| cur:UAT-PRACTICE | repeat-pc | repeat the walk on a desktop browser | ported | CAPTURE-10 | — | +| cur:UAT-PRACTICE | pass-fail-checklist | pass/fail checks table | duplicate | REVIEW-5, PUBLISH-10, CAPTURE-90 | restates rows above | +| cur:UAT-PRACTICE | duplicate-sku-recovery | duplicate SKU recovery, edit or open existing | ported | PUBLISH-15 | — | +| cur:UAT-PRACTICE | fail-no-success | no success message, plain error, server log, retry | ported | PUBLISH-30, PUBLISH-35, PUBLISH-37 | — | +| cur:UAT-PRACTICE | fail-stale-value | value no longer valid, pick from current list | ported | PUBLISH-20, REVIEW-35 | — | +| cur:UAT-PRACTICE | fail-store-unreachable | store unreachable, plain message, retry | ported | PUBLISH-30, PUBLISH-36, PUBLISH-38 | — | +| cur:UAT-PRACTICE | cleanup-draft | leave or delete the UAT draft; app does not delete store products | dropped | — | procedure | +| cur:UAT-PRACTICE | final-poc-publish | final POC publish is a separate gated step | dropped | — | scope/procedure | +| cur:UAT-PRACTICE | where-rules-live | "where each rule lives" table | dropped | — | stale doc refs; migration | diff --git a/AGENTS.md b/AGENTS.md index c46e932..6e92260 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,11 +1,11 @@ # AGENTS.md -## STOP — coding paused (Phase 2: requirements reconciliation) +## Status — Phase 2 reconciliation complete -Do not implement Milestone 3 or any feature right now. Trunk docs are being consolidated. -Scope authority is `docs/REQUIREMENTS.md` + `docs/MILESTONES.md`. M3 will be re-derived as a subset of requirement IDs. -**Current state, locked rulings, and next steps: read `.audit/2026-09-18-session-handoff.md` first.** -The Milestone 3 text below is the pre-reconciliation state and is not authoritative until Phase 2 completes. The "Agent context" and "Milestone 3" sections below describe paused intent, not the current task. +`docs/REQUIREMENTS.md` (153 rows) is the only normative text; scope is the M3 ID subset in `docs/MILESTONES.md`. +Capability and contract docs live in `docs/capabilities/` and `docs/contracts/`. +Reconciliation evidence is archived under `.audit/archive/`; the coverage ledger and matrices are in `.audit/coverage/`. +Starting implementation is Ty's call; when it starts, implement only the M3 ID subset. ## Structure contract (read before creating any doc) @@ -14,17 +14,22 @@ Before creating or moving any doc, look up its home in `docs/STRUCTURE.md` (arti If no home exists, stop and ask Ty — do not create a new artifact. Run `python tools/check_requirements.py` after any docs change; it must pass. +## Agent runtime rules + +- Subagents (`explore`, `general`) run on a single local GPU and cannot handle concurrency. Invoke only one subagent at a time; never issue parallel subagent calls. +- Subagents are for mechanical extraction only (read and list). They never write and never set a disposition; rulings stay with Ty. + ## Agent context -Read `docs/README.md` first, then this file, `docs/CAPTURE.md`, `docs/PHOTOS.md`, and `docs/CREATE.md`. +Read `docs/README.md` first, then this file, `docs/capabilities/CAPTURE.md`, `docs/capabilities/PHOTOS.md`, and `docs/capabilities/CREATE.md`. -Implement **Milestone 3** only. Specification: `docs/CREATE.md`. +Implement **Milestone 3** only. Specification: `docs/capabilities/CREATE.md`. Product overview: `docs/PRODUCT.md`. -**M3 scope** (what is in vs out) comes from Ty conversations and `docs/CREATE.md`. -**Behavior** for Woo sync, metadata, pricing, and inference is written into `docs/CREATE.md` from the technical requirements on `main`. Do not treat older milestone splits on `main` as scope authority. Do not code against `main` docs by cross-reference. Use `docs/CREATE.md` on this branch. +**M3 scope** (what is in vs out) comes from Ty conversations and the reconciled `docs/MILESTONES.md` + `docs/REQUIREMENTS.md`. +**Behavior** for Woo sync, metadata, pricing, and inference is in `docs/capabilities/CREATE.md` and the `docs/contracts/` set. `docs/REQUIREMENTS.md` is the only normative text. -Do not implement work outside `docs/CREATE.md` unless Ty expands scope in chat. +Do not implement work outside the M3 requirement subset unless Ty expands scope in chat. Do not touch deploy port `:10880`. UAT redeploy target is overnight Docker on ubudual5060 `:10881`. ## Milestone 3 @@ -33,8 +38,8 @@ Create. Exit criteria: a Photos-ready slab is **submission-ready for WooCommerce ## Milestone 1 and 2 (shipped inputs) -- Capture: `docs/CAPTURE.md` -- Photos: `docs/PHOTOS.md` (UAT on `:10881`) +- Capture: `docs/capabilities/CAPTURE.md` +- Photos: `docs/capabilities/PHOTOS.md` (UAT on `:10881`) ## Invariants @@ -45,7 +50,7 @@ Create. Exit criteria: a Photos-ready slab is **submission-ready for WooCommerce - `bdft = sqft × thickness_in` - Price proposal is `bdft × species $/bdft`. User may replace it. Missing rate leaves price empty. - User-entered length, thickness, SKU, price, and taxonomy override computed or inferred values. -- **Woo attributes** means synced species and wood categories; Edge, Figure, Grade, Thickness, Moisture; `fig-*` and `feat-*`. Store is source of truth. +- **Woo Slab Taxonomy** means synced species and wood categories; Edge, Figure, Grade, Thickness, Moisture; `fig-*` and `feat-*`. Store is source of truth. - Woo auth is WordPress application password (server-side). `WOO_BASE_URL` is compose/env. Inference URL is Settings. - SKUs matching `SLAB-UAT-*` create as Woo draft. - Create works with inference off. diff --git a/README.md b/README.md index bf106e4..ce8d6b5 100644 --- a/README.md +++ b/README.md @@ -12,9 +12,9 @@ Start at [`docs/README.md`](docs/README.md) for goals, what already shipped, and | Milestones (ID subsets) | `docs/MILESTONES.md` | | Product overview | `docs/PRODUCT.md` | | Invariants and agent context | `AGENTS.md` | -| Milestone 1 Capture (shipped) | `docs/CAPTURE.md` | -| Milestone 2 Photos (shipped) | `docs/PHOTOS.md` | -| Milestone 3 Create (paused — requirements reconciliation) | `docs/CREATE.md` | +| Milestone 1 Capture (shipped) | `docs/capabilities/CAPTURE.md` | +| Milestone 2 Photos (shipped) | `docs/capabilities/PHOTOS.md` | +| Milestone 3 Create (paused — requirements reconciliation) | `docs/capabilities/CREATE.md` | | UAT practice walk | `docs/UAT-PRACTICE.md` | | Hosting | `deploy/` | diff --git a/docs/CONTENT-WOO.md b/docs/CONTENT-WOO.md deleted file mode 100644 index f722cdc..0000000 --- a/docs/CONTENT-WOO.md +++ /dev/null @@ -1,201 +0,0 @@ -# Listing copy and the store - -> On branch `m3-ds`, Milestone 3 exit and scope live in `docs/CREATE.md`. This file is the listing-copy and Woo create/payload reference brought from `main` so coding does not need to open `main`. If anything conflicts with a Ty conversation lock in `CREATE.md`, `CREATE.md` wins. - - -How titles and descriptions are built, how we talk to WooCommerce, and the exact create payload. Templates always run. Generate text is optional and never publishes on its own. - -## 1. Titles and descriptions - -### 1.1 Templates (always on) -Templates live in Settings. Defaults below. Placeholders use `{field}`. - -**title** (default): -``` -{species} {length_ft}ft {length_in_remainder}in × {width_avg_in}in Wood Slab — {character} -``` -Example: `Black Walnut 16ft 0in × 18.2in Wood Slab — Cathedral` -- `length_ft` = floor(length_in / 12), `length_in_remainder` = length_in mod 12 (1 dp). - -**short_title** (default): -``` -{species} Slab — {character} -``` -Example: `Black Walnut Slab — Cathedral` - -**description** (default, multi-paragraph): -``` -A one-of-a-one {character} {species} slab, {length_ft}ft {length_in_remainder}in long with an -average width of {width_avg_in}in and {thickness_in}in thickness ({bdft} bdft). - -Each slab is naturally shaped — width varies along the length at {width_min_in}in to -{width_max_in}in. Sold by the slab; dimensions as measured. {geo_sentence} -``` -`geo_sentence` is the geography sentence from Settings, e.g. -`"Locally milled and shipped from the Pacific Northwest."` (default if unset: none). - -All template math fields come from the deterministic pipeline (never from the LLM). - -### 1.2 Generate text (optional) -The mill owner taps Generate text on review. It never runs by itself. It is off unless `content_llm_enabled` is true. - -The model writes prose only (character, color, grain, uses). Measured length, width, thickness, square feet, and board feet are facts in the prompt. The model does not invent numbers. Templates inject those numbers into the title and description after the prose comes back. - -The mill owner can always edit the result. `content_source` is `llm` when this path ran. - -If the text endpoint is missing or down, disable the button. Templates still work. Publish is never blocked because Generate text failed. - -### 1.3 Audit -Every content generation (template or LLM) is logged with inputs (template version, -slab facts), the LLM request/response payload when used (inference calls logged for audit), -and the final stored content. - -## 2. WooCommerce - -### 2.1 Auth & client -- REST v3 surface unchanged: `GET/POST/PATCH/DELETE {WOO_BASE_URL}/wp-json/wc/v3/…` -- Auth is **WordPress Application Passwords**, not WooCommerce consumer keys - (`ck_`/`cs_`) and not the user's login password. -- Create a **dedicated low-privilege WordPress user** (shop manager, or a custom - role with product create/edit + media upload). A dedicated low-privilege user, not the admin account, is used. -- Generate the application password under **Users → Profile → Application - Passwords**. WordPress requires **HTTPS** for that UI; the store must already - be on TLS (Caddy enforces HTTPS on the app side too). -- Basic auth shape: `woo_wp_username:woo_app_password` (username + application - password). Spaces in the generated password are ignored by WordPress; store - the value as entered. -- FastAPI is the sole Woo caller. Credentials live encrypted at rest in SQLite - (AES-GCM, `SLAB_AES_KEY` env). The browser never holds them. -- Rotation: revoke the application password in the WP user profile, enter a new - one in Settings UI, then `POST /api/v1/settings/test-woo`. -- Client: `httpx` with timeouts (connect 10s, read 60s for image-heavy create). -- Store URL is **`WOO_BASE_URL`** from Docker Compose / host `.env` (HTTPS only). - FastAPI refuses `http://` at startup. Not a Settings field; UI shows it read-only. - -### 2.2 Store lists (species, tags, attributes) -`POST /api/v1/admin/taxonomy/sync` pulls, in order: -1. `GET /products/categories?per_page=100` (paginate) → species + wood-category leaves -2. `GET /products/tags?per_page=100` → `fig-*` / `feat-*` only (other tags ignored) -3. `GET /products/attributes` (+ terms) → five locked attributes - -Rebuilds cache tables in one transaction. Failure → `502 woo_error`; previous -cache retained (never delete-then-fail). - -**Manufactured stamps.** Woo REST has **no** usable last-modified on categories, -attributes, or tags. FastAPI persists `taxonomy_last_sync_at` (every successful -pull) and `taxonomy_anchor` (bump only when stored-subset hash changes, including -deletions). Client re-pulls the full taxonomy iff the anchor advanced. No Woo-side -taxonomy timestamp; no per-navigation sync. - -**Triggers:** (1) successful `test-woo` **always** resyncs; (2) publish if -`now - taxonomy_last_sync_at > 1 hour` then reconcile (bump only on real change); -(3) optional app launch / Settings refresh. If a picked id vanished after a refresh, show that field as no longer valid. The selection is not silently swapped. - -**Species exclusivity.** UI pickers, pricing seeds, and Call 1 species lists are -**exactly** the synced species-category leaves. No hardcoded, invented, or fallback -species set. A species new in Woo is selectable only after the next sync stores it. - -### 2.3 What goes on the listing - -When vision is on and it is at least 0.7 sure, it fills species, wood categories, -edge, figure, grade, and feature tags (character, inclusions, voids, checks map -to existing `feat-*` tags only). The mill owner can change every filled value. -Typed values always win. - -If it is less sure, those fields stay empty. That empty-start subset is not -the submit list. Review will not continue until the full ready set is filled: -SKU, length, thickness, price, species, wood category, edge type, figure, -grade, thickness band, moisture, fig-* tags, and inventory PNG(s). Species -plus one figure is not enough. Presentation: `docs/UX.md`. -Low-confidence guesses are not filled. - -Names from vision or from typing match store ids case-insensitive, trimmed. No -match leaves the field empty so they can pick from the list. The store will still -reject a missing required field at publish. - - -Field rules: -- **Categories**: species leaf (1 required) plus wood-category leaf ids (1+). -- **Tags**: `fig-*` (1+ required) and `feat-*` (0+). Only documented - character or species product tags are used. Woo is authoritative for tag definitions. -- **Attributes**: the five locked Woo attributes only — Edge Type, Figure, Grade, - Thickness, Moisture — using term ids from the cache. Species, - character, length, or free-text thickness as product attributes. Missing - attributes are skipped, not created (v1: no attribute creation). - -Vision only suggests. It never blocks publish. With vision off, everything is typed. - -### 2.4 Create payload -`POST /products` — body (only non-null fields sent): -```jsonc -{ - "name": "{title}", - "type": "simple", - "status": "{woo_create_status}", // draft | publish; default draft; SLAB-UAT-* forced draft - "sku": "{sku}", - "regular_price": "{price}", // 2 dp string, e.g. "2730.00" - "description": "{description}", - "short_description": "{short_title}", - "categories": [ { "id": } ], - "tags": [ { "id": }, { "id": }, … ], - "images": [ - { "src": "", "alt": "{species} {character} slab" } - ], - "attributes": [ … if present … ], - "virtual": false, - "downloadable": false, - "catalog_visibility": "visible" -} -``` -**Image upload strategy** (Woo v3): `images[].src` accepts a URL or a data URL. -For reliability the server: -1. For each customer-facing inventory photo in `seq` order (processed transparent PNGs - from the client TECH-SPEC path): `POST /media` with the **PNG bytes** → get attachment - `source_url`. Woo accepts `image/*`; keep alpha. JPEG re-encoding drops alpha and must not happen. -2. Reference those `source_url` values in `images` (≤5 inventory photos). -3. Only `kind=inventory` photos. Calibration photos do not exist in this design. - -**Status** comes from Settings `woo_create_status` (`draft` or `publish`, default -`draft`). Practice SKUs `SLAB-UAT-*` are always created as drafts, even if Settings -say publish. Existing SKUs are not edited in this version. After a successful create, -the server deletes the original and processed photos. A slab marked published in the -app means the store create succeeded. That is not the same as WooCommerce "publish." -The force-draft rule is in `AGENTS.md`. This file only owns the payload and the steps. - -### 2.5 Duplicate SKU -Before create: `GET /products?sku={sku}` (any store status). -- Found: stop. The phone shows the SKU field with Edit SKU and Open existing listing. This version does not update the other product. -- Not found: create. Still do the check every time. - -### 2.6 After a failed publish - -The mill owner sees a message on the field, not a status number or a stack. The form stays on the phone. The server marks the slab failed so they can try again. - -If the SKU already exists: under SKU, "This SKU already exists in the store (any status)." Edit SKU, or open the existing listing (admin link when we have it). - -If a category, attribute, or price is no longer valid: under that field, "This value is no longer valid. Pick from the current list," with the current store options. - -The server still writes `sync_log`. Success still sets `woo_product_id`, marks published locally, and deletes photos. - -- **Partial-failure note** (risk, see IMPL-PLAN): if media uploads succeed but the - final product POST fails, orphaned Woo attachments may exist. v1: log it; surface - a manual cleanup note. Not auto-deleted. - -## 3. End-to-end publish sequence (server side) -``` -1. check the listing against the shared rules (current copy of the rules on submit) -2. look up the SKU in the store (any status). Stop if it already exists. -3. refresh store lists if the last successful pull is older than 1 hour. Only bump the client list-version if something actually changed. -4. resolve categories, tags, and attributes from the synced lists (vision names or typed names) -5. upload processed PNG inventory photos to media, keep the URLs -6. POST /products with draft/publish per Settings, always draft for SLAB-UAT-* SKUs -7. on success: log, mark published locally, delete originals and processed photos - on failure: log, mark failed, keep photos, put the error on the field (§2.6) -``` - -## 4. What the model may and may not do - -Templates, numbers, matching names to store ids, payload, dedupe, logging: code. -Generate text may write description prose only. It must not invent dimensions. It may only pick species and attributes that exist in the synced store lists. - -Presentation: `docs/UX.md`. The wire uses `{ field, code, message, actions[] }`. The mill owner never sees those codes. If the phone's copy of the rules is stale, refresh the copy and retry. That is not a duplicate-SKU conflict. diff --git a/docs/CREATE.md b/docs/CREATE.md deleted file mode 100644 index 9ed274d..0000000 --- a/docs/CREATE.md +++ /dev/null @@ -1,431 +0,0 @@ -# Milestone 3 Create - -## Purpose - -Milestone 3 takes a Photos-ready device draft and makes the slab **submission-ready for WooCommerce**. - -Submission-ready means the mill owner has filled the full store listing package against **live synced Woo attributes**, with price set, and can create the product in the store (practice SKUs as Woo drafts). Photos and measures from Milestones 1 and 2 are inputs. They are not enough by themselves. - -**Exit criteria.** On the M3 UAT deploy, a Photos-ready slab reaches submission-ready and a `SLAB-UAT-*` product exists in WooCommerce as a **draft** whose categories, attributes, tags, price, and inventory images match the reviewed ready set. Create still works with inference off. - -## How to read this file - -This file is the Milestone 3 source of truth on branch `m3-ds`. - -- **What is in M3** comes from Ty conversations on this branch (not from older milestone splits on `main`). -- **How Woo sync, metadata, pricing, and inference behave** is taken from the technical requirements on `main` (`CONTENT-WOO`, `DATA-MODEL`, `UX`, `ARCHITECTURE`, `OPENAPI`, `AGENTS`, `UAT-PRACTICE`, `DEPLOYMENT`) and written here so coding does not need to open `main`. - -Related shipped specs: `docs/CAPTURE.md`, `docs/PHOTOS.md`. Listing copy and Woo payload detail: `docs/CONTENT-WOO.md` (from `main`). Product overview: `docs/PRODUCT.md`. Folder map: `docs/README.md`. - -## Preconditions (already shipped) - -On this branch before M3 work: - -- Milestone 1 Capture works in the browser (`docs/CAPTURE.md`). -- Milestone 2 Photos works in the browser (`docs/PHOTOS.md`). UAT evidence on `:10881`. -- Device draft holds Photos-ready inventory PNGs, calibration measures, and related state. -- `frontend/` holds that client path. `backend/` does not yet implement the Woo client or inference proxy. - -M3 adds the server path, Settings, taxonomy sync, review/ready fields, pricing, inference dependency, and Woo create. - -## In scope - -1. WooCommerce auth with WordPress application password (server-side). -2. Sync of **Woo attributes** from the store and correct use of those lists for the listing. -3. Review UI and rules to reach submission-ready (full ready set). -4. Pricing proposal from species `$/bdft` rules and user override. -5. OpenAI-compatible inference endpoint as a Settings dependency, with Call 1 / Call 2 behavior when enabled. -6. Create product in WooCommerce (force draft for `SLAB-UAT-*`), then clear the device draft on success. - -## Out of scope unless Ty expands M3 in chat - -- Creating new Woo categories, attributes, terms, or tags from the app. -- Updating an existing store SKU in place. -- Bulk create. -- Touching deploy port `:10880`. -- Dumping work onto `m2-ds` or doc-simplification branches. - ---- - -## Woo attributes - -Locked name for the synced taxonomy package. WooCommerce is the source of truth. The app caches ids. It does not invent the lists. - -### Categories (product categories, leaf only) - -| Field | Cardinality | Rule | -|---|---|---| -| Species | exactly 1 | Synced species leaves only. No hardcoded or fallback species set. A new store species appears only after the next successful sync. | -| Wood category | 1 or more | Leaf ids. Parents are grouping only. | - -### Attributes (exactly five locked Woo attributes) - -| Attribute | Cardinality | Rule | -|---|---|---| -| Edge Type | 1 | Term id from sync. | -| Figure | 1 or more | Term ids from sync. | -| Grade | 1 or more | Term ids from sync. | -| Thickness | 1 | Measured thickness rounded **up** into the store band (example: 1" lands in the store band labeled like `1"-1 1/2"` when that is the store label). | -| Moisture | 1 | Defaults to kiln-dried. User sets or keeps it. Never inferred. | - -v1 does not create missing attributes or terms. If a term is missing in Woo, skip it. Do not invent it. - -Species, character, length, and free-text thickness are **not** Woo product attributes. Only the five locked attributes go on the create payload. - -### Tags - -| Prefix | Cardinality | Rule | -|---|---|---| -| `fig-*` | 1 or more | Required. Other Woo tags are ignored at sync. | -| `feat-*` | 0 or more | Optional. Character, inclusions, voids, and checks map onto existing `feat-*` tags only. | - ---- - -## Auth and store URL - -From `main` deploy and agents rules, adopted here: - -- FastAPI is the only Woo caller. The browser never holds store credentials. -- Auth is a WordPress **application password** for a dedicated low-privilege WP user (shop manager or custom role with product create/edit and media upload). Not WooCommerce consumer keys (`ck_` / `cs_`). Not the store admin login password. -- Basic auth shape: `woo_wp_username` + `woo_app_password`. -- Store URL is **`WOO_BASE_URL`** from `deploy/.env` / Docker Compose (`https://` only). Injected into the FastAPI container. Settings shows it read-only. Not a SQLite settings row. Change store means edit `.env` and recreate the FastAPI container. -- Username and application password are entered in Settings UI, stored AES-GCM encrypted in SQLite (`SLAB_AES_KEY` env). -- REST surface: `{WOO_BASE_URL}/wp-json/wc/v3/…` -- Client: `httpx` with connect timeout 10s and read timeout 60s for image-heavy create. -- Rotate: revoke the application password in WP Users → Profile, enter a new one in Settings, then run `test-woo`. - ---- - -## Taxonomy sync - -Endpoint and behavior from `main` OPENAPI / CONTENT-WOO / ARCHITECTURE: - -`POST /api/v1/admin/taxonomy/sync` pulls, in order: - -1. `GET /products/categories?per_page=100` (paginate) → species and wood-category leaves after app filter. -2. `GET /products/tags?per_page=100` → keep only `fig-*` and `feat-*`. -3. `GET /products/attributes` plus terms → the five locked attributes. - -Rebuild cache tables in one transaction. On failure return `502 woo_error` and **keep** the previous cache. Never delete-then-fail. - -**Stamps.** Woo REST has no usable last-modified for categories, attributes, or tags. FastAPI stores: - -- `taxonomy_last_sync_at` on every successful pull -- `taxonomy_anchor` bumped only when the stored-subset hash changes (including deletions) - -Client re-pulls the full taxonomy only when the anchor advances. - -**Triggers.** - -1. Successful `POST /api/v1/settings/test-woo` always resyncs. -2. On publish, if `now - taxonomy_last_sync_at > 1 hour`, reconcile (bump anchor only on real change). -3. Optional app launch or Settings refresh. - -If a picked id disappeared after refresh, show that field as no longer valid. Do not silently swap another value. - -`GET /api/v1/admin/taxonomy` returns the cached groups plus `taxonomy_anchor` and `taxonomy_last_sync_at`. - ---- - -## Submission-ready (ready set) - -A slab is submission-ready only when **all** of the following are present. Species plus one figure is not enough. - -Required: - -- At least one inventory PNG (from Milestone 2) -- SKU matching `[A-Z0-9-]{3,24}` (practice runs use `SLAB-UAT-*`) -- Length and thickness (from capture, still editable on review) -- Client-computed widths, sqft, and bdft (user typed number wins if overridden) -- Exactly one species leaf -- At least one wood-category leaf -- One edge type term -- At least one figure term -- At least one grade term -- Thickness band term (round-up from measured thickness) -- One moisture term (default kiln-dried) -- At least one `fig-*` tag -- Price (see Pricing) - -Optional: - -- `feat-*` tags (zero or more) -- Title, short title, description (may be typed, filled by templates at create, or written by Generate text when inference is on). Not required to reach ready. - -Inline per-field nudges show empty required fields as the owner works. The slab cannot reach ready until the full set is filled. - -Server status name for this state: `ready` (see data model below). Publish/create is blocked with `422 unpublishable` until then. - ---- - -## Pricing - -From `main` DATA-MODEL / UX / AGENTS: - -- Settings (or admin API) holds `pricing_rules`: one flat `$/bdft` per species name that matches a synced species leaf. No tiers in v1. -- Seed rule rows from synced species. The mill owner sets the rate per species. -- Proposal: `price = bdft × price_per_bdft` for the selected species. -- User may replace the proposal. Override sticks (`price_source` = `override` vs `recommendation`). -- If no rule exists for the species, price starts empty and the owner types it. -- Publish/create is allowed with a manual price. -- `bdft = sqft × thickness_in` (deterministic). - -Admin API surface from `main` OPENAPI: - -- `GET /api/v1/admin/pricing` -- `POST /api/v1/admin/pricing` -- `PUT /api/v1/admin/pricing/{id}` -- `DELETE /api/v1/admin/pricing/{id}` - ---- - -## Inference - -M3 includes the OpenAI-compatible endpoint as a real server dependency and the Call 1 / Call 2 rules below. Create must pass with inference **off**. - -### Settings (not compose) - -Stored encrypted in SQLite Settings (not `deploy/.env`): - -- `inference_base_url` -- `inference_api_key` -- `inference_model` -- `inference_enabled` -- `content_llm_enabled` - -FastAPI proxies all inference calls. The browser never holds the key. - -`POST /api/v1/settings/test-inference` returns `{ ok, mode: stateful|stateless|fail, vision_capable, detail }`. Mode `fail` blocks Call 2 enablement only. Inference-off create still works. - -### Call 1 (vision / taxonomy assist) - -- Runs only when `inference_enabled` is true (auto after calibrated draft upload when on, or via `POST /api/v1/slabs/{id}/infer-taxon`). -- Input: inventory photos downscaled to max 1024px, user metadata, synced Woo taxonomy lists. -- Output: species, wood categories, edge, figure, grade, and feature-tag suggestions constrained to synced lists, each with confidence. -- Default confidence threshold 0.7 (configurable). At or above threshold: pre-fill on review, still editable. Below: leave empty. Do not show low-confidence guesses as defaults. -- Moisture is never inferred. -- Typed values always win over model values. -- Vision never blocks create and never auto-publishes. - -### Call 2 (Generate text) - -- User-triggered only (`POST /api/v1/slabs/{id}/infer-content`). Never auto. -- Requires successful `test-inference` (`ok: true`). -- Model writes prose only (character, color, grain, uses). It must not invent dimensions. -- Server assembles title, short title, and description from templates with prose injected. -- If the endpoint is down, disable the button. Templates still work. Create is never blocked by Generate text failure. - -### Templates (always available) - -Full defaults and Woo payload text live in `docs/CONTENT-WOO.md`. Summary here: - -**title** - -``` -{species} {length_ft}ft {length_in_remainder}in × {width_avg_in}in Wood Slab - {character} -``` - -Example from CONTENT-WOO: `Black Walnut 16ft 0in × 18.2in Wood Slab - Cathedral` - -**short_title** - -``` -{species} Slab - {character} -``` - -**description** (multi-paragraph) - -``` -A one-of-a-one {character} {species} slab, {length_ft}ft {length_in_remainder}in long with an -average width of {width_avg_in}in and {thickness_in}in thickness ({bdft} bdft). - -Each slab is naturally shaped. Width varies along the length at {width_min_in}in to -{width_max_in}in. Sold by the slab; dimensions as measured. {geo_sentence} -``` - -**What `{character}` means (locked from CONTENT-WOO example + PROMPTS on `main`).** -The word after the dash in the example is `Cathedral`, which is a **Figure** attribute term. -Fill `{character}` with the primary (first) Figure term **name** from the ready set. -If somehow no figure is present, leave that slot empty (ready set normally requires ≥1 figure). -Do not confuse this with `feat-*` tags. Call 1 free-text "character / inclusions / voids / checks" map to `feat-*` tags, not to this title slot. - -All template math fields come from the deterministic pipeline, never from the LLM. - ---- - -## Review and create flow - -Mill-owner path after Photos-ready: - -1. Upload calibrated / Photos-ready draft to the server (`POST /api/v1/slabs` multipart) when the M3 client reaches that step. Status becomes `calibrated`. If inference is on, Call 1 may run. -2. **Review.** Owner fills or overrides every ready-set field. Pickers show only synced Woo attributes. -3. When the ready set is complete, status becomes `ready` (submission-ready). -4. Owner taps create/publish → `POST /api/v1/slabs/{id}/publish`. -5. Server runs the create sequence below. Client polls `GET /api/v1/slabs/{id}`. - -### Create sequence (server) - -1. Check the listing against the ready-set rules. -2. Look up SKU in the store (`GET /products?sku={sku}`, any status). Stop if found. -3. Refresh taxonomy if last successful pull is older than 1 hour. -4. Resolve categories, tags, and attributes from the synced lists. -5. Upload inventory PNGs via `POST /media`, keep alpha. Reference `source_url` values on the product. Cap at 5 images. **Woo image order (locked):** (1) clean calibration inventory PNG, (2) dimension-overlay copy of that calibration PNG, (3+) remaining batch inventory PNGs in the order they were added in the web app. The overlay is part of the submission. Do not upload non-inventory artifacts outside that lineup. -6. `POST /products` with status from Settings `woo_create_status` (`draft` or `publish`, default `draft`). **`SLAB-UAT-*` always forces draft.** -7. On success: log, set `woo_product_id`, mark published locally, delete original and processed photos, clear device draft. -8. On failure: log, mark failed, keep photos, put a field-level error on the phone. No raw status codes for the mill owner. - -### Create payload (shape) - -`POST /products` sends only non-null fields. Conceptual body: - -```jsonc -{ - "name": "{title}", - "type": "simple", - "status": "{woo_create_status}", - "sku": "{sku}", - "regular_price": "{price}", - "description": "{description}", - "short_description": "{short_title}", - "categories": [ { "id": }, { "id": }, ... ], - "tags": [ { "id": } ], - "images": [ { "src": "", "alt": "{species} {character} slab" } ], - "attributes": [ /* Edge, Figure, Grade, Thickness, Moisture term ids when present */ ], - "virtual": false, - "downloadable": false, - "catalog_visibility": "visible" -} -``` - -App "published" means the store create succeeded. That is not the same as the Woo product being live when status is draft. - -### Duplicate SKU - -Before create, if the SKU exists in any store status: block. Show Edit SKU and Open existing listing. This version does not update the other product. API: `409 duplicate_sku`. - -### Stale taxonomy on submit - -If a category, attribute, or price is no longer valid after refresh: under that field, tell the owner to pick from the current list and supply current options (`stale_field`). - ---- - -## Settings keys (M3) - -| Key | Where | Notes | -|---|---|---| -| `WOO_BASE_URL` | compose / `.env` | HTTPS store URL. Read-only in UI. | -| `SLAB_AES_KEY` | compose / `.env` | AES master key. Never in DB or repo. | -| `woo_wp_username` | Settings (encrypted) | Dedicated WP user. | -| `woo_app_password` | Settings (encrypted) | Application password. | -| `woo_create_status` | Settings | `draft` or `publish`. Default `draft`. | -| `inference_base_url` | Settings (encrypted) | OpenAI-compatible base URL. | -| `inference_api_key` | Settings (encrypted) | | -| `inference_model` | Settings | | -| `inference_enabled` | Settings | | -| `content_llm_enabled` | Settings | Gates Generate text. | -| `taxonomy_last_sync_at` | Settings / derived | | -| `taxonomy_anchor` | Settings / derived | | -| `brand_voice`, `geo_context` | Settings | Copy helpers. | - -Relevant Settings API: `GET/PUT /api/v1/settings`, `POST /api/v1/settings/test-woo`, `POST /api/v1/settings/test-inference`. - ---- - -## Data shape (submission-ready fields) - -Core slab fields M3 must persist (from `main` DATA-MODEL, adopted here): - -| Field | Rule | -|---|---| -| `sku` | Required. Unique. | -| `status` | `draft` → `calibrated` → `ready` → `publishing` → `published`, or `failed` | -| `length_in`, `thickness_in` | Required. 1/8" step. | -| `sqft`, `bdft`, widths | From client math. | -| `species_id` | 1 species leaf. | -| `wood_category_ids` | 1 or more. | -| `edge_type_term_id` | 1. | -| `figure_term_ids` | 1 or more. | -| `grade_term_ids` | 1 or more. | -| `thickness_term_id` | Round-up band. | -| `moisture_term_id` | 1. Default kiln-dried. | -| `fig_tag_ids` | 1 or more. | -| `feat_tag_ids` | 0 or more. | -| `price`, `price_per_bdft`, `price_source` | See Pricing. | -| `title`, `short_title`, `description` | Optional for ready. | - -Cache tables: `woo_categories`, `woo_attributes`, `woo_attribute_terms`, `woo_tags`, `pricing_rules`. - -Device draft (IndexedDB on the phone) is not a WooCommerce draft. Clear the device draft after successful create. - ---- - -## Practice / UAT rules - -- Every practice listing uses SKU prefix `SLAB-UAT-*`. -- `SLAB-UAT-*` always creates a WooCommerce draft, even if Settings say publish. -- Customers must not see practice listings on the storefront. -- Inference is **off** for the claimable exit walk. Optional ON path only after inference Settings pass `test-inference`. -- UAT deploy for this branch: ubudual5060 `http://100.64.0.9:10881`. Do not use `:10880`. - ---- - -## API surface M3 must implement (summary) - -Base path `/api/v1` (health may also be `/api/health`). - -| Area | Methods | -|---|---| -| Health | `GET /api/health` | -| Slabs | `POST/GET/PUT/DELETE /api/v1/slabs`, `GET /api/v1/slabs/{id}` | -| Photos | original/processed GET while retained | -| Inference | `POST .../infer-taxon`, `POST .../infer-content` | -| Publish | `POST .../publish` → 202, poll slab | -| Taxonomy | `GET/POST /api/v1/admin/taxonomy`, `.../sync` | -| Pricing | CRUD `/api/v1/admin/pricing` | -| Settings | `GET/PUT /api/v1/settings`, `test-woo`, `test-inference` | - -Full request and response field lists on historical `main` `docs/OPENAPI.md` are the detail reference if a field is missing here. Prefer extending this file over coding from `main` directly. - ---- - -## Done when (exit criteria) - -**Product state induced.** Overnight UAT app on ubudual5060 `http://100.64.0.9:10881` (or the current M3 image of this branch), with `WOO_BASE_URL` and Woo application password pointed at the intended store. - -**Evidence from that state.** - -1. `test-woo` succeeds and taxonomy sync fills Woo attributes into the app cache. Pickers offer only synced ids. -2. A Photos-ready slab can be filled to the **full ready set** (submission-ready) using those lists and pricing rules, with **inference off**. -3. Create with `SLAB-UAT-*` yields a WooCommerce **draft** whose categories, attributes, tags, price, and inventory images match the reviewed ready set. -4. Duplicate SKU is blocked. Device draft clears after success. -5. Inference Settings accept an OpenAI-compatible base URL, key, and model. `test-inference` runs. Create still succeeds with inference disabled. - -## Dishonest proxies (do not count as pass) - -- Hardcoded or fixture taxonomy lists treated as synced. -- Mock Woo client that never hits the real store for the exit walk. -- Unit tests alone without a real `SLAB-UAT-*` draft in the store. -- Consumer-key auth instead of application password. -- Inference "wired" as dead config with no server proxy, or create that requires inference on. -- Claiming submission-ready when required ready-set fields are still empty. -- Touching `:10880` or parking work on `m2-ds` / doc-simplification. - -## Open decisions (must lock before task breakdown) - -Locked from Ty conversation or from `docs/CONTENT-WOO.md`: - -1. **`categories` on `POST /products`.** **Locked from CONTENT-WOO field rules:** send every ready-set category id (species leaf + each wood-category leaf) as `[{"id": n}, ...]`. The single-id example in CONTENT-WOO §2.4 is incomplete relative to those field rules. - -2. **Template `{character}`.** **Locked from CONTENT-WOO example (`… Wood Slab - Cathedral`):** use the primary Figure term **name**. `feat-*` tags are separate (Call 1 free-text character/inclusions/voids/checks map there). - -3. **Dimension-overlay PNG.** **Locked (Ty 2026-09-16):** Woo image order is (1) clean calibration PNG, (2) dimension-overlay copy, (3+) other inventory PNGs in add order. Cap ≤5. - -Still open (CONTENT-WOO does not fully settle these): - -4. **`attributes` element shape on `POST /products`.** CONTENT-WOO still has `"attributes": [ … if present … ]` with no fields. Need a live Whidbey product JSON sample, or Ty pointing at the official Woo REST fields to use. Do not invent. - -5. **Server persistence.** CONTENT-WOO describes create sequence and payload. It does not choose full OpenAPI slab CRUD vs a thinner FastAPI path on this branch. Still need a lock before coding the server store. - -Non-blocking notes (shipped M1/M2 win on this branch): fill target 90% and 800px floor (not main's 80%/1600); Vite + Svelte SPA (not SvelteKit docs); overlay draw spacing may differ from measure spacing by design. - -If store-specific term labels differ from examples in this file, the synced store lists win. diff --git a/docs/MILESTONES.md b/docs/MILESTONES.md index f731810..707f91d 100644 --- a/docs/MILESTONES.md +++ b/docs/MILESTONES.md @@ -2,20 +2,47 @@ A milestone is an explicit subset of requirement IDs from `docs/REQUIREMENTS.md`. No prose scope here. -Populated in Phase 2. Until then every milestone is empty and coding is paused. - ## M1 — Capture (shipped) -- IDs: — +IDs: + +CAPTURE-5, CAPTURE-10, CAPTURE-20, CAPTURE-35, CAPTURE-40, CAPTURE-41, CAPTURE-45, CAPTURE-50, +CAPTURE-55, CAPTURE-60, CAPTURE-65, CAPTURE-70, CAPTURE-75, CAPTURE-80, CAPTURE-85, CAPTURE-90, +CAPTURE-95, CAPTURE-100, CAPTURE-105, CAPTURE-135, CAPTURE-140, CAPTURE-141, CAPTURE-145, +CAPTURE-150, CAPTURE-155, CAPTURE-160, +APP-5, APP-10, APP-25, APP-40, +QUALITY-5, QUALITY-10, QUALITY-15, QUALITY-25, QUALITY-30, QUALITY-40, QUALITY-45, QUALITY-55, +QUALITY-60, QUALITY-65, QUALITY-70, QUALITY-75, QUALITY-80, QUALITY-85, QUALITY-100, +OPS-5, OPS-10, OPS-15, OPS-20, OPS-25, OPS-45, OPS-50, OPS-55 ## M2 — Photos (shipped) -- IDs: — +IDs: -## M3 — Create (paused) +CAPTURE-25, CAPTURE-30, CAPTURE-110, CAPTURE-115, CAPTURE-120, CAPTURE-125, CAPTURE-130 -- IDs: — +## M3 — Create + +IDs: + +METADATA-5, METADATA-10, METADATA-15, METADATA-20, METADATA-25, METADATA-30, METADATA-35, +METADATA-40, METADATA-45, METADATA-50, METADATA-55, METADATA-60, METADATA-65, METADATA-66, +METADATA-70, METADATA-75, METADATA-80, METADATA-85, METADATA-90, METADATA-95, METADATA-100, +METADATA-105, METADATA-110, METADATA-115, +REVIEW-5, REVIEW-10, REVIEW-15, REVIEW-20, REVIEW-25, REVIEW-30, REVIEW-35, REVIEW-40, +REVIEW-45, REVIEW-50, +PUBLISH-5, PUBLISH-10, PUBLISH-15, PUBLISH-20, PUBLISH-25, PUBLISH-30, PUBLISH-31, PUBLISH-35, +PUBLISH-36, PUBLISH-37, PUBLISH-38, PUBLISH-40, PUBLISH-45, PUBLISH-50, PUBLISH-55, PUBLISH-60, +SETTINGS-5, SETTINGS-10, SETTINGS-15, SETTINGS-20, SETTINGS-25, SETTINGS-30, SETTINGS-35, +SETTINGS-40, SETTINGS-45, SETTINGS-50, SETTINGS-51, SETTINGS-55, SETTINGS-60, SETTINGS-65, +SETTINGS-70, SETTINGS-75, SETTINGS-80, SETTINGS-85, SETTINGS-90, +APP-15, APP-20, APP-30, APP-35, APP-45, APP-50, APP-55, APP-60, APP-65, +OPS-40, +QUALITY-20, QUALITY-35, QUALITY-50, QUALITY-90, QUALITY-95, QUALITY-105, QUALITY-110, +QUALITY-120, QUALITY-125 ## Later -- IDs: — +IDs: + +CAPTURE-15, CAPTURE-165, OPS-30, OPS-35, QUALITY-115 diff --git a/docs/PRODUCT.md b/docs/PRODUCT.md index 2ba2ef0..93d7562 100644 --- a/docs/PRODUCT.md +++ b/docs/PRODUCT.md @@ -16,13 +16,13 @@ A finished listing has: - SKU, length, thickness, price - Species, wood category, edge type, figure, grade - Thickness band, moisture -- One or more `fig-*` tags -- Title, short title, description +- Optional `fig-*` tags +- Title, short_description, description - Zero or more `feat-*` tags -Store categories, attributes, and `fig-*` / `feat-*` tags are the filters shoppers use. Measured values feed title and description templates. +Store categories, attributes, and `fig-*` / `feat-*` tags are the filters shoppers use. When content generation runs, the model writes the prose; deterministic templates are the fallback and inject the measured values. -The locked name for that synced taxonomy package is **Woo attributes**. Details live in `docs/CREATE.md`. +The locked name for that synced taxonomy package is **Woo Slab Taxonomy**. Details live in `docs/capabilities/CREATE.md`. ## Decision rights @@ -34,7 +34,7 @@ The locked name for that synced taxonomy package is **Woo attributes**. Details | Widths, sqft, bdft | App, from approved reference photo + length + thickness | | Species, wood category, edge, figure, grade, tags | App may propose; user may change | | Price | App proposal `bdft × species $/bdft`; user may replace | -| Title, short title, description | Templates from listing facts; user may edit | +| Title, short_description, description | Model or template from listing facts; user may edit | User-entered length, thickness, SKU, price, and taxonomy override computed or inferred values. @@ -46,23 +46,23 @@ Milestone 1 handoff is the device draft: source photo, approved mask, inventory Milestone 2 extends the draft with the photo batch, calibration choice, per-photo inventory PNGs, and the dimension-overlay calibration copy. -Milestone 3 introduces FastAPI as the Woo client and inference proxy. Store credentials and inference secrets stay on the server. Spec: `docs/CREATE.md`. +Milestone 3 introduces FastAPI as the Woo client and inference proxy. Store credentials and inference secrets stay on the server. Spec: `docs/capabilities/CREATE.md`. ## Create rules (summary) -Full behavior is in `docs/CREATE.md`. +Full behavior is in `docs/capabilities/CREATE.md`. - `SLAB-UAT-*` creates as a WooCommerce draft (store status). - Duplicate SKU blocks create. - A listing can be created with inference off. - A device draft in the app is separate from a WooCommerce draft. -## Milestone status on `m3-ds` +## Milestone status | Milestone | Spec | Status | |---|---|---| -| 1 Capture | `docs/CAPTURE.md` | Shipped | -| 2 Photos | `docs/PHOTOS.md` | Shipped (UAT `:10881`) | -| 3 Create | `docs/CREATE.md` | Current work. Exit: submission-ready for WooCommerce (`CREATE.md` Done when). Scope from Ty conversations; behavior for sync/metadata/pricing/inference taken from `main` into `CREATE.md`. | +| 1 Capture | `docs/capabilities/CAPTURE.md` | Shipped | +| 2 Photos | `docs/capabilities/PHOTOS.md` | Shipped (UAT `:10881`) | +| 3 Create | `docs/capabilities/CREATE.md` | Scope is the M3 ID set in `docs/MILESTONES.md`. Exit: submission-ready for WooCommerce. | -Anything beyond `docs/CREATE.md` is out of M3 unless Ty expands scope in chat. +Scope is the M3 requirement subset in `docs/MILESTONES.md`; `docs/REQUIREMENTS.md` is normative. diff --git a/docs/README.md b/docs/README.md index e405cfe..bf5e5db 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,14 +1,13 @@ -# Docs map (branch `m3-ds`) +# Docs map -> **Phase 2 in progress (2026-09-18).** Requirements are being reconciled on `docs/clean-main`. -> Current state, rulings, and next steps: `.audit/2026-09-18-session-handoff.md`. The Milestone 3 -> text in this file is the pre-reconciliation state, not the current task. +> **Phase 2 status (2026-09-18).** Requirements are reconciled; capability and contract docs are +> migrated into `docs/capabilities/` and `docs/contracts/`. `.audit/` holds the working records. Read this file first. It tells you what SlabUploader is for, what already ships, and what Milestone 3 must build. -**Who this is for.** Mill owner (Ty), humans reviewing scope, and agents implementing on this branch. +**Who this is for.** Mill owner (Ty), humans reviewing scope, and agents implementing. -**Source of truth for Milestone 3.** `docs/CREATE.md` plus the Ty conversations that wrote it. Older milestone and task splits on `main` are obsolete for M3. Do not open `main` for scope once you are coding on `m3-ds`. +**Source of truth for Milestone 3.** `docs/REQUIREMENTS.md` plus the M3 ID subset in `docs/MILESTONES.md`. Behavior is in `docs/capabilities/CREATE.md` and the `docs/contracts/` set. ## High-level project goal @@ -22,26 +21,26 @@ Store: https://www.whidbeywoodstore.com/ | Milestone | Spec | Status on this branch | |---|---|---| -| 1 Capture | `docs/CAPTURE.md` | Shipped. Browser-only mask, length axis, measure, inventory PNG, device draft. | -| 2 Photos | `docs/PHOTOS.md` | Shipped. Batch photos, pick calibration, apply mask/crop to the batch, dimension overlay, Photos-ready lineup. UAT on `:10881`. | +| 1 Capture | `docs/capabilities/CAPTURE.md` | Shipped. Browser-only mask, length axis, measure, inventory PNG, device draft. | +| 2 Photos | `docs/capabilities/PHOTOS.md` | Shipped. Batch photos, pick calibration, apply mask/crop to the batch, dimension overlay, Photos-ready lineup. UAT on `:10881`. | -There is no Woo create yet. There is no Complete-listing taxonomy UI yet. There is no FastAPI Woo client on this branch (`backend/` is a stub). Inference is not wired. +There is no Woo create yet. There is no Complete-listing taxonomy UI yet. There is no FastAPI Woo client (`backend/` is a stub). Inference is not wired. ## What Milestone 3 builds -Spec: `docs/CREATE.md`. +Spec: `docs/capabilities/CREATE.md`. **Exit criteria.** A Photos-ready slab becomes **submission-ready for WooCommerce**, and a practice `SLAB-UAT-*` draft exists in the store that matches the reviewed ready set. Create must still work with inference off. In plain terms, M3 connects the Photos-ready device draft to the real store: 1. Talk to WooCommerce with a WordPress application password (server-side only). -2. Sync and use **Woo attributes** (categories, five locked attributes, `fig-*` / `feat-*` tags) per the requirements taken from `main` and written into `CREATE.md`. +2. Sync and use the **Woo Slab Taxonomy** (categories, five locked attributes, `fig-*` / `feat-*` tags) per the requirements. 3. Pricing from species `$/bdft` rules, with user override. 4. Inference Settings (OpenAI-compatible URL, key, model) and Call 1 / Call 2 rules when enabled. 5. Review to the full ready set, then create the Woo product (force draft for `SLAB-UAT-*`). -Technical detail for Woo sync, metadata, pricing, and inference is copied into `docs/CREATE.md` from `main` so you do not need to open `main` while coding. Older milestone splits on `main` are still not the scope authority for what M3 includes. +Technical detail for Woo sync, metadata, pricing, and inference is in `docs/capabilities/CREATE.md` and `docs/contracts/`. ## How to read the folder @@ -49,11 +48,11 @@ Technical detail for Woo sync, metadata, pricing, and inference is copied into ` |---|---| | `README.md` (this file) | Map: goals, shipped, M3. | | `PRODUCT.md` | Product overview: complete listing, decision rights, seams. | -| `CAPTURE.md` | Milestone 1 Capture (shipped). | -| `PHOTOS.md` | Milestone 2 Photos (shipped). | -| `CREATE.md` | Milestone 3 Create (current work). Read this before task breakdown or coding. | -| `CONTENT-WOO.md` | Listing copy templates and Woo create/payload detail (from `main`). | -| `UAT-PRACTICE.md` | Human practice walk for a `SLAB-UAT-*` listing. Defers to `CREATE.md` on conflict. | +| `capabilities/CAPTURE.md` | Milestone 1 Capture (shipped). | +| `capabilities/PHOTOS.md` | Milestone 2 Photos (shipped). | +| `capabilities/CREATE.md` | Milestone 3 Create. Read this before task breakdown or coding. | +| `contracts/CONTENT-WOO.md` | Listing copy templates and Woo payload intent. | +| `UAT-PRACTICE.md` | Human practice walk for a `SLAB-UAT-*` listing. Defers to `capabilities/CREATE.md` on conflict. | | `archive/` | Historical only. Not M3 scope. | ## Deploy reminders for this branch diff --git a/docs/REQUIREMENTS.md b/docs/REQUIREMENTS.md index 48a4a05..6e69bd7 100644 --- a/docs/REQUIREMENTS.md +++ b/docs/REQUIREMENTS.md @@ -74,6 +74,10 @@ Sources reconciled 2026-09-18 from the current branch docs plus the `pre-simplif | METADATA-85 | The primary wood category selects a specialized Call 2 prompt. | M3 | — | | METADATA-90 | The dimension fact block carries exact strings for species, figure term, length, thickness, average width, width min to max, and board feet; there is no L by W by T rectangle assumption. | M3 | — | | METADATA-95 | The dimension-overlay image is never sent to the model. | M3 | — | +| METADATA-100 | Call 1 free-text observations (character, inclusions, voids, checks) are mapped to existing synced feat-* tags only; WooCommerce is the authority for feat-* definitions. | M3 | — | +| METADATA-105 | Inference prompts are stored in the database with all revisions; each call uses the active revision and the revision history is retained. | M3 | — | +| METADATA-110 | The Generate text control appears only when content generation is enabled and the inference endpoint is reachable, and is disabled when the text service is down. | M3 | — | +| METADATA-115 | A runtime inference failure is classified recoverable or unrecoverable for the owner: recoverable offers Retry, unrecoverable gives a plain message explaining it. | M3 | — | ## REVIEW — confirm, tweak, ready @@ -83,10 +87,12 @@ Sources reconciled 2026-09-18 from the current branch docs plus the `pre-simplif | REVIEW-10 | Title, short_description, and description are required on the product and are satisfied by typed, generated, or templated text. | M3 | — | | REVIEW-15 | In review the owner declares one primary wood category and any secondary ones; primary drives the title and primary plus secondary drive the description; the default primary is the first category selected. | M3 | — | | REVIEW-20 | Every empty required field shows an inline nudge as the owner works; the slab cannot reach ready until the full set is filled, and there is no single submit-time gap list. | M3 | — | -| REVIEW-25 | The price suggestion is bdft times the species rate from Settings; the owner may override and the override sticks; no rule leaves the price empty for manual entry. | M3 | — | +| REVIEW-25 | The price suggestion is bdft times the species rate from Settings; when no rate exists the price starts empty and the owner must enter one before create; the owner may override and the override sticks. | M3 | — | | REVIEW-30 | Widths, sqft, and bdft are read-only in production, derived from the mask, and are not overridable. | M3 | — | | REVIEW-35 | A picked taxonomy id that disappeared after a refresh shows as no longer valid and is never silently swapped for another value. | M3 | — | | REVIEW-40 | The owner can edit any field, including generated title and description, until create, except the calculated measurement fields (widths, sqft, bdft), which are not user mutable. | M3 | — | +| REVIEW-45 | SKU is 3 to 24 characters, alphanumeric and hyphen only. | M3 | — | +| REVIEW-50 | Title and short_description warn past 60 and 160 characters and never exceed the store's hard limits; description has no maximum. | M3 | docs/contracts/WOO-API.md | ## PUBLISH — store submission @@ -106,7 +112,8 @@ Sources reconciled 2026-09-18 from the current branch docs plus the `pre-simplif | PUBLISH-40 | Images upload as processed PNGs with alpha; the clean primary, the primary dimensioned copy, and every remaining batch photo the owner selected are uploaded, with no upper cap on the number of images. | M3 | — | | PUBLISH-45 | The Woo image order is the photos-ready lineup: clean calibration, dimension-overlay copy, then the rest in capture order. | M3 | — | | PUBLISH-50 | Published means the store create succeeded, not that the product is live when status is draft. | M3 | — | -| PUBLISH-55 | Only non-null fields are sent; the payload carries name, type simple, status, sku, regular_price, description, short_description, categories, tags, images, attributes, virtual false, downloadable false, catalog_visibility visible. | M3 | — | +| PUBLISH-55 | Only non-null fields are sent; the payload carries name, type simple, status, sku, regular_price, description, short_description, categories, tags, images, attributes, virtual false, downloadable false, catalog_visibility visible, sold_individually true. | M3 | docs/contracts/WOO-API.md | +| PUBLISH-60 | Each published product represents exactly one slab; WooCommerce quantity is one. | M3 | — | ## SETTINGS — configured once @@ -128,6 +135,9 @@ Sources reconciled 2026-09-18 from the current branch docs plus the `pre-simplif | SETTINGS-65 | woo_create_status defaults to draft. | M3 | — | | SETTINGS-70 | The vision pixel cap is the only user-facing vision setting; the JPEG quality lives in server config. | M3 | — | | SETTINGS-75 | Secrets never appear in any GET response; only a configured boolean is returned. | M3 | — | +| SETTINGS-80 | test-woo returns ok and detail; it fails closed when credentials are missing or the base URL is not HTTPS, and distinguishes an authentication rejection from an unreachable store. | M3 | — | +| SETTINGS-85 | The settings API rejects an attempt to write WOO_BASE_URL; the value is env-only and read-only. | M3 | — | +| SETTINGS-90 | inference_enabled and content_llm_enabled default off. | M3 | — | ## APP — device draft and state @@ -141,6 +151,11 @@ Sources reconciled 2026-09-18 from the current branch docs plus the `pre-simplif | APP-30 | The API base is /api/v1 and health is at /api/health. | M3 | — | | APP-35 | Errors use a uniform envelope and are JSON; lists are returned in full without pagination. | M3 | — | | APP-40 | The server stores the client-sent derived numbers and does not recompute them as source of truth. | shipped | — | +| APP-45 | Health returns status, version, and woo_reachable, and goes non-200 when the database or image volume is not writable. | M3 | — | +| APP-50 | Upstream WooCommerce 4xx responses are mapped into the uniform error envelope rather than passed through raw. | M3 | — | +| APP-55 | Structured JSON logs carry a request id threaded from the client through FastAPI to WooCommerce, and the log level is set by a LOG_LEVEL env. | M3 | — | +| APP-60 | On supported clients, interactive controls are reachable and operable by keyboard with a visible focus ring, and the layout reflows without clipping at 320 px. | M3 | — | +| APP-65 | No secret material (Woo credentials, inference key, or AES key) appears in any log output. | M3 | — | ## OPS — deploy and release @@ -156,6 +171,7 @@ Sources reconciled 2026-09-18 from the current branch docs plus the `pre-simplif | OPS-40 | The UAT environment is ubudual5060 at http://100.64.0.9:10881; port 10880 is off limits. | M3 | — | | OPS-45 | Inference is a remote, configurable endpoint, not part of the app host. | shipped | — | | OPS-50 | The repository on gitea-atd is the source of truth. | shipped | — | +| OPS-55 | The app has no login of its own; it relies on the trusted LAN or Tailscale network plus HTTPS. | shipped | — | ## QUALITY — verification @@ -178,10 +194,11 @@ Sources reconciled 2026-09-18 from the current branch docs plus the `pre-simplif | QUALITY-75 | Pipeline vector TV-5: one square end and one 45 degree cut, the length axis follows the prevailing length. | shipped | — | | QUALITY-80 | Pipeline vector TV-6: 3:4 crop, 90 percent fill at extremes, transparent PNG, shorter side at least 800 when the source allows. | shipped | — | | QUALITY-85 | Pipeline vector TV-7: an undersized source warns, is not upscaled, and is not publishable until retake. | shipped | — | -| QUALITY-90 | Pipeline vector TV-8: bdft 0.96 at 12.50 per bdft gives a 12.00 recommendation, an override of 150.00 is stored, and no rule leaves the price empty for manual entry. | M3 | — | +| QUALITY-90 | Pipeline vector TV-8: bdft 0.96 at 12.50 per bdft gives a 12.00 recommendation, and an override of 150.00 is stored. | M3 | — | | QUALITY-95 | Pipeline vector TV-9: a 1 inch thickness rounds up into the synced Woo thickness band, not a hardcoded one. | M3 | — | | QUALITY-100 | Pipeline vector TV-10: half-up 2 decimal rounding, including 96.04 to 96, 0.641 to 0.64, 11.525 to 11.53, 12.345 to 12.35. | shipped | — | | QUALITY-105 | Dishonest proxies that do not count as pass: hardcoded or fixture taxonomy treated as synced, a Woo mock that never hits the real store for the exit walk, unit tests alone without a real SLAB-UAT-* draft, consumer-key auth, inference wired as dead config or required for create, claiming ready with empty fields, or parking work on the wrong branch. | M3 | — | | QUALITY-110 | The M3 exit evidence is a real SLAB-UAT-* Woo draft matching the reviewed ready set with inference off, plus test-woo and test-inference results. | M3 | — | | QUALITY-115 | Browser end-to-end capture through publish is verified on iOS Safari, Android Chrome, and desktop Chrome, Edge, and Firefox. | later | — | | QUALITY-120 | No raw status code, stack trace, or internal code reaches the owner in any tested path. | M3 | — | +| QUALITY-125 | An encrypted credential stored by Settings decrypts back to the value it was given. | M3 | — | diff --git a/docs/STRUCTURE.md b/docs/STRUCTURE.md index bdd53f9..208c5eb 100644 --- a/docs/STRUCTURE.md +++ b/docs/STRUCTURE.md @@ -16,13 +16,19 @@ Every Markdown file under `docs/` is registered here. `docs/archive/**` and `doc | `docs/STRUCTURE.md` | meta | | `docs/REQUIREMENTS.md` | requirements | | `docs/MILESTONES.md` | scope | -| `docs/CAPTURE.md` | capability | -| `docs/PHOTOS.md` | capability | -| `docs/CREATE.md` | capability | -| `docs/CONTENT-WOO.md` | contract | +| `docs/capabilities/CAPTURE.md` | capability | +| `docs/capabilities/PHOTOS.md` | capability | +| `docs/capabilities/CREATE.md` | capability | +| `docs/contracts/CONTENT-WOO.md` | contract | | `docs/UAT-PRACTICE.md` | practice | | `docs/capabilities/README.md` | index | | `docs/contracts/README.md` | index | +| `docs/contracts/WOO-API.md` | contract | +| `docs/contracts/API.md` | contract | +| `docs/contracts/DATA-MODEL.md` | contract | +| `docs/contracts/PROMPTS.md` | contract | +| `docs/contracts/UX.md` | contract | +| `docs/contracts/TEST-SPEC.md` | contract | Roles: diff --git a/docs/UAT-PRACTICE.md b/docs/UAT-PRACTICE.md index 56af9f1..6dcee53 100644 --- a/docs/UAT-PRACTICE.md +++ b/docs/UAT-PRACTICE.md @@ -11,7 +11,7 @@ the inference endpoint is already configured. Inference stays off here. --- -> On branch `m3-ds`, Milestone 3 exit criteria and field rules live in `docs/CREATE.md`. This walk is the human practice path for a `SLAB-UAT-*` listing. Prefer `CREATE.md` if anything conflicts. +> Milestone 3 exit criteria and field rules live in `docs/capabilities/CREATE.md`. This walk is the human practice path for a `SLAB-UAT-*` listing. Prefer `docs/capabilities/CREATE.md` if anything conflicts. ## 1. Host and deploy preconditions @@ -94,12 +94,12 @@ hard stop. Switch to a supported browser. ### 4.1 Capture 1. Open the app on the phone. Start a new slab. -2. Take 1 to 5 photos of the slab. The first should be top-down, lens parallel to the broad +2. Take one or more photos of the slab (the batch); the first should be top-down, lens parallel to the broad face. A tilted shot makes the numbers wrong. Retake if it is tilted. -3. **Mask overlay and four knobs.** The app draws a mask over the photo. You see which pixels are - slab and which are not. Tune the four knobs: **Sheet** (auto/green/black), **Sensitivity**, - **Edge offset**, **Feather**. Each change re-runs the mask immediately. Look at the edge - against the real wood. If it is still off, retake the photo. +3. **Mask overlay and controls.** The app draws a mask over the photo. You see which pixels are + slab and which are not. Tune the controls: **Sheet** (auto/green/black) and **Sensitivity** + (0–100). A Sheet change runs immediately; a Sensitivity change waits 2000 ms. Each recomputes in + the browser. Look at the edge against the real wood. If it is still off, retake the photo. 4. **Confirm the mask.** Tap confirm. 5. **Length axis.** The app draws the bounding rectangle and the length axis. Confirm it follows the slab's long direction. Rotate if it is skewed. On a nearly square slab, you say which way @@ -119,11 +119,11 @@ hard stop. Switch to a supported browser. 2. **Species.** Pick exactly one from the synced store list. The list came from `test-woo`. Only species in the list may be typed. 3. **Wood categories.** Pick at least one from the synced list. -4. **Edge type, figure, grade.** Pick from the synced lists. At least one figure, at least one - grade. +4. **Edge type, figure, grade.** Pick from the synced lists. One edge type, at least one figure, + exactly one grade. 5. **Thickness band.** Pick the Woo band that the measured thickness rounds up into. 6. **Moisture.** Keep the default (kiln-dried) or change it. -7. **fig-* tags.** Pick at least one. +7. **fig-* tags.** Optional; pick any from the synced list. 8. **feat-* tags.** Zero or more. Only pick existing `feat-*` tags. 9. **Price.** A recommendation fills from the species $/bdft rule times board feet. You can override it. If there is no rule for the species, the price starts empty. Type it. Publish @@ -184,7 +184,7 @@ You can see all of these without reading code. If you publish the same SKU twice, the second time the app stops before creating a second product. Under the SKU field you see: "This SKU already exists in the store (any status)." Two paths: edit the SKU to a new value, or open the existing listing. The draft stays in the app. Nothing is -lost. This is the expected behavior, not a bug. See `docs/CONTENT-WOO.md` §2.5. +lost. This is the expected behavior, not a bug. See `docs/contracts/CONTENT-WOO.md` §2.5. ### What a fail looks like @@ -220,11 +220,11 @@ is gated on your review of one real listing. | Rule | Source | |---|---| | Practice SKU forces draft | `AGENTS.md` §5 | -| `woo_create_status` default draft | `AGENTS.md` §5, `docs/CONTENT-WOO.md` §2.4 | -| App Password setup, not consumer keys | `AGENTS.md` §4, `docs/DEPLOYMENT.md` §5 | -| Host inventory before deploy | `docs/DEPLOYMENT.md` §1, `deploy/INVENTORY.md` | -| Capture → review → publish flow | `docs/UX.md` (SIGNED 2026-09-06) | -| Board feet identity | `docs/TECH-SPEC-PIPELINE.md` | -| Duplicate SKU recovery | `docs/CONTENT-WOO.md` §2.5, `docs/UX.md` Publish | -| PC browser compatibility | `docs/UX.md` Browser compatibility | -| Gate C = early Phase 1 | `docs/IMPL-PLAN.md` Gates map and Phase 1 | +| `woo_create_status` default draft | `AGENTS.md` §5, `docs/contracts/CONTENT-WOO.md` | +| App Password setup, not consumer keys | `AGENTS.md` §4, `deploy/` | +| Host inventory before deploy | `deploy/INVENTORY.md` | +| Capture → review → publish flow | `docs/contracts/UX.md` | +| Board feet identity | `docs/REQUIREMENTS.md` (CAPTURE-90) | +| Duplicate SKU recovery | `docs/contracts/CONTENT-WOO.md`, `docs/contracts/UX.md` | +| PC browser compatibility | `docs/contracts/UX.md` | +| M3 scope and exit | `docs/MILESTONES.md`, `docs/REQUIREMENTS.md` | diff --git a/docs/CAPTURE.md b/docs/capabilities/CAPTURE.md similarity index 84% rename from docs/CAPTURE.md rename to docs/capabilities/CAPTURE.md index 078e2e6..dc16dd8 100644 --- a/docs/CAPTURE.md +++ b/docs/capabilities/CAPTURE.md @@ -3,8 +3,22 @@ Runs in the browser on phone and PC as ordinary client code. No inference URL. No model. No backend. Mask, rectangle, measurements, crop, and PNG encode are deterministic functions of the photo and user inputs. -Milestone 1 uses one reference photo. Further angles are later milestones. -Milestone 1 ends when the user sees the inventory PNG and measurements. Milestone 2 is Review. Milestone 3 is Create. This file does not create a WooCommerce product. +Milestone 1 uses one reference photo; Milestone 2 adds the batch. Milestone 1 ends when the user sees the inventory PNG and measurements. Milestone 2 is Photos (`docs/capabilities/PHOTOS.md`); Milestone 3 is Create (`docs/capabilities/CREATE.md`). This file does not create a WooCommerce product. + +## Requirements impact + +| Requirement | Impact | +|---|---| +| CAPTURE-5, CAPTURE-135, CAPTURE-140 | deterministic client core; server stores, never recomputes | +| CAPTURE-10 | supported clients and hard stop | +| CAPTURE-20, CAPTURE-35 | sheet setup and detection | +| CAPTURE-40, CAPTURE-41, CAPTURE-45, CAPTURE-50, CAPTURE-55 | mask controls, sensitivity, live re-run, overlay, retake | +| CAPTURE-60, CAPTURE-65 | length axis | +| CAPTURE-70, CAPTURE-75, CAPTURE-80, CAPTURE-85, CAPTURE-90 | inputs and measure math | +| CAPTURE-95, CAPTURE-100, CAPTURE-105 | inventory PNG | +| CAPTURE-141, CAPTURE-145, CAPTURE-150, CAPTURE-155, CAPTURE-160 | preview, knobs, failure, rounding | +| APP-10 | device draft and resume | +| QUALITY-40, QUALITY-45, QUALITY-55, QUALITY-60, QUALITY-65, QUALITY-70, QUALITY-75, QUALITY-80, QUALITY-85, QUALITY-100 | deterministic tests and vectors | ## Setup @@ -13,7 +27,6 @@ Photograph the slab on a green or black sheet. - Sheet fills the frame around the slab. - Slab is fully on the sheet. - First photo is top-down, lens parallel to the face. -- Length of the slab is aligned with the long side of the picture. The app expects that sheet. Background removal keys green or black. Other backgrounds are out of Milestone 1. @@ -125,8 +138,7 @@ Milestone 3 clears the device draft after Woo create. ## Inventory PNG -Role: `kind=inventory`. Filename is an implementation detail. -The first inventory photo is the measurement reference. Later inventory photos use the same file properties. +The calibration photo is the measurement reference (`CAPTURE-25`, `CAPTURE-115`). Filename is an implementation detail. | Property | Value | |---|---| @@ -137,4 +149,3 @@ The first inventory photo is the measurement reference. Later inventory photos u | Size | After crop, shorter side ≥ 800px when the source can supply it | Source below that floor: warn and require a new photo. No upscale from an undersized source. -Editor preview of alpha is a light gray checkerboard. The storefront uses the theme background. diff --git a/docs/capabilities/CREATE.md b/docs/capabilities/CREATE.md new file mode 100644 index 0000000..d52ae9b --- /dev/null +++ b/docs/capabilities/CREATE.md @@ -0,0 +1,118 @@ +# Create capability + +Status: SPEC · 2026-09-18 · Milestone M3. +Behavior and acceptance for turning a Photos-ready slab into a submitted WooCommerce product. This +doc cites requirement IDs; `docs/REQUIREMENTS.md` is the only normative text. Interface detail +lives in `docs/contracts/` (`API.md`, `WOO-API.md`, `DATA-MODEL.md`, `PROMPTS.md`, `UX.md`, +`CONTENT-WOO.md`). + +## Requirements impact + +| Requirement | Impact on this capability | +|---|---| +| REVIEW-5, REVIEW-10, REVIEW-15, REVIEW-20 | ready set, prose fields, primary category, inline nudges | +| REVIEW-25, REVIEW-30, REVIEW-35, REVIEW-40, REVIEW-45, REVIEW-50 | price, read-only math, stale picks, editability, SKU and caps | +| METADATA-5, METADATA-10, METADATA-15, METADATA-20, METADATA-25, METADATA-30 | synced taxonomy, cardinality, no term creation | +| METADATA-35, METADATA-40, METADATA-45, METADATA-50 | Call 1 inputs, results, confidence gate, precedence | +| METADATA-55, METADATA-60, METADATA-65, METADATA-66 | Call 2, context, dimension safety, logging | +| METADATA-70, METADATA-75, METADATA-80, METADATA-85, METADATA-90, METADATA-95 | template fallback, non-blocking, prompt selection, fact block | +| METADATA-100, METADATA-105, METADATA-110, METADATA-115 | feat mapping, prompt storage, Generate text control, failure triage | +| PUBLISH-5, PUBLISH-10, PUBLISH-15, PUBLISH-20 | create sequence, draft rule, duplicate, stale | +| PUBLISH-25, PUBLISH-30, PUBLISH-31, PUBLISH-35, PUBLISH-36, PUBLISH-37, PUBLISH-38 | success, failure, partial failure, triage | +| PUBLISH-40, PUBLISH-45, PUBLISH-50, PUBLISH-55, PUBLISH-60 | images, lineup, meaning of published, payload, quantity | +| SETTINGS-5, SETTINGS-10, SETTINGS-15, SETTINGS-20, SETTINGS-25, SETTINGS-30, SETTINGS-35 | store URL, auth, crypto, inference config, rates | +| SETTINGS-40, SETTINGS-45, SETTINGS-50, SETTINGS-51, SETTINGS-55, SETTINGS-60, SETTINGS-65 | tests, taxonomy sync and anchor, create status | +| SETTINGS-70, SETTINGS-75, SETTINGS-80, SETTINGS-85, SETTINGS-90 | vision cap, secret masking, test-woo, URL reject, flags | +| APP-15, APP-20, APP-30, APP-35, APP-40, APP-45, APP-50, APP-55, APP-60, APP-65 | device draft, revisions, API base, envelope, health, logs, accessibility, redaction | +| QUALITY-20, QUALITY-35, QUALITY-50, QUALITY-90, QUALITY-95, QUALITY-105, QUALITY-110, QUALITY-120, QUALITY-125 | M3 test bar and exit evidence | +| OPS-40 | UAT host | + +## 1. Review to ready + +Review is reached from a Photos-ready draft. The owner fills or confirms every field; each empty +required field shows an inline nudge as they work, and the slab cannot reach `ready` until the +full set is filled (`REVIEW-5`, `REVIEW-20`). Widths, sqft, and bdft are read-only client math +(`REVIEW-30`); every other field stays editable until create (`REVIEW-40`). + +The owner declares one primary wood category and any secondary ones; the first selected is +primary (`REVIEW-15`). Price is the bdft times the species rate; with no rate it starts empty and +the owner must enter one; an override sticks (`REVIEW-25`). SKU is 3–24 alphanumeric and hyphen +(`REVIEW-45`). Title, short_description, and description are required and satisfy typed, +generated, or templated text (`REVIEW-10`); title and short_description warn past 60 and 160 +characters (`REVIEW-50`). A picked id that vanished shows as no longer valid and is never +silently swapped (`REVIEW-35`). + +## 2. Inference assist + +Call 1 runs only when inference is enabled, after a calibrated draft uploads or on demand; it +sends the clean processed photos at 1024 px long edge or less plus user metadata and the synced +taxonomy, and returns suggestions constrained to those lists (`METADATA-35`, `METADATA-40`). At +or above the threshold (default 0.7) a field pre-fills and stays editable; below it the field +stays empty (`METADATA-45`). Typed values always win (`METADATA-50`). Free-text observations map +to synced `feat-*` tags only (`METADATA-100`). + +Call 2 is user-triggered only and produces name, short_description, and description +(`METADATA-55`). It is a fresh request carrying the full Call 1 context and the literal dimension +fact block; the model never invents a dimension (`METADATA-60`, `METADATA-65`, `METADATA-90`). +The Generate text control appears only when content generation is enabled and the endpoint is +reachable (`METADATA-110`). All generation is logged (`METADATA-66`). + +With inference off, deterministic templates produce the prose so create never lacks it +(`METADATA-70`); the fallback title uses the primary figure term with a hyphen separator +(`METADATA-80`). Prompts live in the database with all revisions (`METADATA-105`). Inference never +blocks create and never auto-publishes (`METADATA-75`); a runtime failure offers Retry when +recoverable or explains when it is not (`METADATA-115`). + +## 3. Settings prerequisite + +Store URL is env-only, HTTPS, read-only (`SETTINGS-5`, `SETTINGS-10`); the settings API rejects a +write to it (`SETTINGS-85`). Woo auth is a WordPress application password stored AES-GCM +(`SETTINGS-15`, `SETTINGS-20`). `SLAB_AES_KEY` is env-only (`SETTINGS-25`). Inference URL and key +are encrypted; flags, model, brand voice, and geo context are settings (`SETTINGS-30`); the flags +default off (`SETTINGS-90`). Species rates are one flat value per synced leaf (`SETTINGS-35`). +`woo_create_status` defaults to draft (`SETTINGS-65`). + +test-woo returns ok and detail, fails closed on missing credentials or non-HTTPS, and always +resyncs taxonomy on success (`SETTINGS-80`, `SETTINGS-40`). test-inference returns ok, mode +(stateful, stateless, or fail), vision_capable, and detail; fail blocks Call 2 only +(`SETTINGS-45`). Taxonomy sync keeps only fig-* and feat-* tag families (`SETTINGS-51`), rebuilds +in one transaction, keeps the previous cache on failure (`SETTINGS-50`), and bumps the anchor only +on a real stored-subset change (`SETTINGS-55`). The client re-pulls when the anchor advances, and +publish refreshes when the last sync is over an hour old (`SETTINGS-60`). + +## 4. Publish + +The sequence validates the ready set, looks up the SKU and stops if it exists, refreshes taxonomy +when stale, resolves categories, tags, and attributes, uploads the processed PNGs, calls +`POST /products` per `docs/contracts/WOO-API.md`, then finalizes (`PUBLISH-5`). A `SLAB-UAT-*` SKU +always creates a draft (`PUBLISH-10`). A duplicate SKU blocks create with edit-SKU or +open-existing (`PUBLISH-15`). A stale value is a field-level error showing current options +(`PUBLISH-20`). + +Success sets `woo_product_id`, purges photos, deletes the local row, and clears the device draft +(`PUBLISH-25`, `APP-15`). Failure keeps photos, writes `sync_log`, and shows a plain message on +the affected field with retry (`PUBLISH-30`); the failure is classified recoverable or +unrecoverable (`PUBLISH-36`, `PUBLISH-37`, `PUBLISH-38`) and never shows a raw code +(`PUBLISH-35`). Partial failure leaves orphaned media with a cleanup note (`PUBLISH-31`). Images +upload as processed PNGs in photos-ready order with no cap (`PUBLISH-40`, `PUBLISH-45`). Published +means the store create succeeded, not that it is live (`PUBLISH-50`). The payload is per +`docs/contracts/WOO-API.md` (`PUBLISH-55`) and each product is one slab (`PUBLISH-60`). + +## 5. Acceptance + +- The full suite passes with inference disabled and asserts zero inference calls (`QUALITY-20`). +- Integration tests cover success, error, duplicate SKU, stale field, and taxonomy anchor against a + Woo mock (`QUALITY-35`). +- UI and end-to-end tests run against a Compose stack with mocked Woo (`QUALITY-50`). +- Vectors TV-8 (pricing) and TV-9 (thickness band) pass (`QUALITY-90`, `QUALITY-95`). +- The M3 exit evidence is a real `SLAB-UAT-*` Woo draft matching the reviewed ready set, with + inference off, plus test-woo and test-inference results (`QUALITY-110`). +- No raw status code, stack trace, or internal code reaches the owner (`QUALITY-120`); encrypted + credentials round-trip (`QUALITY-125`). +- Dishonest proxies are rejected (`QUALITY-105`). + +## 6. Out of scope + +Offline capture and U2Net matting (`CAPTURE-15`, `CAPTURE-165`), backup and restore (`OPS-30`, +`OPS-35`), and cross-browser end-to-end verification (`QUALITY-115`). The V1 instrumentation work +is out of M3. diff --git a/docs/PHOTOS.md b/docs/capabilities/PHOTOS.md similarity index 87% rename from docs/PHOTOS.md rename to docs/capabilities/PHOTOS.md index 65ba246..640ca93 100644 --- a/docs/PHOTOS.md +++ b/docs/capabilities/PHOTOS.md @@ -1,10 +1,23 @@ # Milestone 2 — Photos -Builds on Milestone 1. Spec for calibration math and inventory PNG properties: `docs/CAPTURE.md`. +Builds on Milestone 1. Spec for calibration math and inventory PNG properties: `docs/capabilities/CAPTURE.md`. This milestone gets every photo of one slab background-removed and cropped store-ready. It does not fill Complete-listing taxonomy, SKU, price, or copy. It does not create a WooCommerce product. No inference URL. No model. Mask and crop for non-calibration shots reuse the approved calibration params as ordinary client code. +## Requirements impact + +| Requirement | Impact | +|---|---| +| CAPTURE-25 | batch of one or more; owner picks the calibration photo | +| CAPTURE-30 | calibration drives mask, axis, measure, crop; others reuse them | +| CAPTURE-100, CAPTURE-105 | crop rules and size floor reused on other photos | +| CAPTURE-110 | per-photo error; remaining photos proceed | +| CAPTURE-115 | measures tied to the calibration photo; others are visual inventory | +| CAPTURE-120, CAPTURE-125 | dimension overlay | +| CAPTURE-130 | photos-ready lineup order | +| APP-10 | device draft restores the batch and calibration choice | + ## Goal Phone capture of one slab → a lined-up set of Woo-compatible inventory PNGs, plus Milestone 1 calibration numbers on the reference shot, plus one annotated copy of the calibration PNG with dimension overlays. @@ -70,6 +83,6 @@ On a phone UAT build: ## Out of scope - Complete-listing fields (SKU, species, category, edge, figure, grade, thickness band, moisture, `fig-*` / `feat-*`, title, short title, description, price) -- WooCommerce create and submission-ready taxonomy (Milestone 3: `docs/CREATE.md`) -- Work beyond `docs/CREATE.md` unless Ty expands M3 in chat +- WooCommerce create and submission-ready taxonomy (Milestone 3: `docs/capabilities/CREATE.md`) +- Work beyond `docs/capabilities/CREATE.md` unless Ty expands M3 in chat - Changing Milestone 1 mask/axis/measure math except to reuse its outputs for batch apply and overlays diff --git a/docs/contracts/API.md b/docs/contracts/API.md new file mode 100644 index 0000000..e7908fd --- /dev/null +++ b/docs/contracts/API.md @@ -0,0 +1,191 @@ +# App API contract — client ↔ FastAPI + +Status: SPEC · 2026-09-18. +Base path `/api/v1`. Health at `/api/health`. All JSON unless noted. Timestamps UTC ISO-8601. +The outbound WooCommerce payload is owned by `docs/contracts/WOO-API.md`. + +## Requirements impact + +| Requirement | Impact on this design | +|---|---| +| APP-5 | slab status values and transitions | +| APP-20 | client ids, UTC timestamps, `client_rev`/`server_rev` | +| APP-25 | FastAPI is the only Woo and inference caller | +| APP-30 | `/api/v1` base path, `/api/health` | +| APP-35 | error envelope, full lists, JSON | +| APP-40 | server stores client numbers, never recomputes | +| APP-45 | health body and non-200 condition | +| APP-50 | upstream Woo 4xx mapped into the envelope | +| CAPTURE-15, CAPTURE-165 | offline and U2Net are out of scope | +| CAPTURE-25, CAPTURE-30, CAPTURE-115, CAPTURE-130 | calibration choice and batch photos | +| CAPTURE-70, CAPTURE-80, CAPTURE-85, CAPTURE-90 | length/thickness units and derived numbers | +| CAPTURE-95, CAPTURE-105, CAPTURE-135, CAPTURE-141, CAPTURE-145, CAPTURE-150, CAPTURE-155 | PNG output, upload, preview/controls | +| METADATA-5, METADATA-10, METADATA-15, METADATA-30 | taxonomy cardinality and scoping | +| METADATA-35, METADATA-40, METADATA-45, METADATA-50, METADATA-55 | Call 1 / Call 2 | +| METADATA-60, METADATA-65, METADATA-70, METADATA-75, METADATA-100, METADATA-105, METADATA-110, METADATA-115 | context, constraints, fallback, prompts, failure | +| OPS-55 | no client auth | +| PUBLISH-5, PUBLISH-10, PUBLISH-15, PUBLISH-20 | create sequence, draft rule, duplicate, stale | +| PUBLISH-25, PUBLISH-30, PUBLISH-31, PUBLISH-35, PUBLISH-40, PUBLISH-45, PUBLISH-50, PUBLISH-55, PUBLISH-60 | create outcomes, images, payload | +| REVIEW-5, REVIEW-10, REVIEW-15, REVIEW-20, REVIEW-25, REVIEW-30, REVIEW-40, REVIEW-45, REVIEW-50 | ready set, fields, limits | +| SETTINGS-5, SETTINGS-10, SETTINGS-15, SETTINGS-20, SETTINGS-25, SETTINGS-30 | env URL, credentials, crypto | +| SETTINGS-35, SETTINGS-40, SETTINGS-45, SETTINGS-50, SETTINGS-51, SETTINGS-55, SETTINGS-60, SETTINGS-65, SETTINGS-75, SETTINGS-80, SETTINGS-85, SETTINGS-90 | rates, tests, taxonomy, flags | + +## 1. Conventions + +- **Auth:** no client auth; the app relies on the trusted LAN/Tailscale network plus HTTPS. +- **Envelope:** every error is `{ "error": { "code": string, "message": string, "detail": object|null } }`. +- **Lists:** returned in full, no pagination. +- **Revisions:** create/update carry `client_rev`; the server echoes `server_rev`; a mismatch is + `409 revision_conflict`. +- **IDs and time:** slab ids are client-generated UUIDv4; timestamps are UTC ISO-8601. +- **Photos:** `multipart/form-data` with `data` (JSON), `files[]` (original images), and `meta[]` + (`{ "seq": n }`). One photo is the calibration photo, chosen by the owner. +- **Secrets:** never returned; only a configured boolean. +- **Error display:** raw status codes, stack traces, and internal codes never reach the owner. + +## 2. Error codes + +| Status | Code | Meaning | +|---|---|---| +| 400 | `validation` | payload failed validation | +| 404 | `not_found` | unknown slab, photo, or setting | +| 409 | `revision_conflict` | `client_rev`/`server_rev` mismatch; `detail.current_server_rev` | +| 409 | `duplicate_sku` | SKU exists in Woo under any status | +| 422 | `unpublishable` | slab not publishable or missing a required field | +| 422 | `stale_field` | a picked taxonomy id vanished; returns current options | +| 502 | `woo_error` | WooCommerce rejected the request; `detail` carries the Woo body | +| 500 | `internal` | unexpected | + +There is no `412 validation_module_stale` path. + +## 3. Health and meta + +- `GET /api/health` → `{ "status": "ok", "version": string, "woo_reachable": bool }`. Non-200 when + the database or image volume is not writable. +- `GET /api/v1/meta` → `{ "version": string }`. + +## 4. Slabs + +### `POST /api/v1/slabs` +Multipart create of a calibrated draft. Returns `201` with the slab at `status: "calibrated"`. +Stores the client-sent derived numbers and never recomputes them. Auto-runs Call 1 only when +`inference_enabled` is true; inferred values persist only at confidence ≥ 0.7. `409 duplicate_sku` +if the SKU already exists. + +`SlabCreate` fields: + +| Field | Type | Notes | +|---|---|---| +| `id` | uuid | client-generated | +| `sku` | string | 3–24, alphanumeric and hyphen | +| `length_in`, `thickness_in` | number | 1/8 inch steps | +| `sqft`, `bdft`, `width_min_in`, `width_max_in`, `width_avg_in` | number | client-computed | +| `species_id` | int | one species leaf | +| `wood_category_ids` | int[] | one or more | +| `edge_type_term_id` | int | one | +| `figure_term_ids` | int[] | one or more | +| `grade_term_id` | int | exactly one | +| `thickness_term_id` | int | round-up band | +| `moisture_term_id` | int | default kiln-dried | +| `fig_tag_ids` | int[] | optional | +| `feat_tag_ids` | int[] | optional | +| `price` | number | 2 decimals | +| `price_source` | string | `recommendation` \| `override` | +| `title`, `short_description`, `description` | string\|null | prose | +| `content_source` | string | `manual` \| `llm` \| `template` | +| `client_rev` | int | optimistic concurrency | + +Grade, edge, thickness, and moisture are singular; figure and the tag lists are arrays. + +### `GET /api/v1/slabs` +Returns `[Slab]`, most recent first. + +### `GET /api/v1/slabs/{id}` +Returns the `Slab`. Also the poll endpoint for inference and publish. The client backs off +(1s, 2s, 4s, cap 10s). + +### `PUT /api/v1/slabs/{id}` +Partial update of editable fields plus `client_rev`. Stores client-sent derived fields and does +not recompute them. `409 revision_conflict`, `409 duplicate_sku` (SKU field actions `edit_sku` / +`open_existing`), `422 stale_field` (current options included). + +### `DELETE /api/v1/slabs/{id}` +Soft delete of unpublished drafts only; `204`. Published slabs cannot be deleted from the app. + +## 5. Photos + +- `GET /api/v1/slabs/{id}/photos/{pid}/original` → `image/*` while retained. +- `GET /api/v1/slabs/{id}/photos/{pid}/processed` → `image/*` (transparent PNG) while retained. +- After publish both return `410 Gone`. +- `POST /api/v1/slabs/{id}/u2net/{pid}` is reserved post-POC and is not exposed. + +## 6. Inference + +### `POST /api/v1/slabs/{id}/infer-taxon` +Runs or retries Call 1. Sends the clean processed photos at 1024 px long edge or less, the synced +taxonomy, and the user metadata. Returns `202`; the client polls. A failure or timeout follows the +runtime failure rule (recoverable offers Retry, unrecoverable explains). + +### `POST /api/v1/slabs/{id}/infer-content` +Runs Call 2 on user action only. Fresh request carrying the full Call 1 context; no provider +thread id. Requires `test-inference` `ok`. Returns `202`; the client polls. The model writes prose +only and never touches dimensions. + +Call 1 result fields: `species_id`, `species_confidence`, `wood_category_ids`, +`edge_type_term_id`, `figure_term_ids`, `figure_confidence`, `grade_term_id`, `grade_confidence`, +`character`, `inclusions`, `voids`, `checks`. Free-text observations map to synced `feat-*` tags +only. + +Call 2 result fields: `title`, `short_description`, `description`. + +## 7. Publish + +### `POST /api/v1/slabs/{id}/publish` +Preconditions are the full ready set, else `422 unpublishable` with field keys. Sequence: validate +the ready set; look up the SKU and stop if present; refresh taxonomy when the last sync is over +one hour old; resolve categories, tags, and attributes; upload the processed PNGs to Woo media; +`POST /products` per `docs/contracts/WOO-API.md`; finalize. `SLAB-UAT-*` always creates a draft. +Returns `202`; the client polls. + +- Success: `woo_product_id` set, photos purged, local slab row deleted, device draft cleared. +- Failure: attempt marked failed, photos retained, `sync_log` written, plain message on the + affected field or action, retry from the same screen. +- Partial failure (media uploaded, product POST failed): orphaned media left, logged, manual + cleanup note surfaced. + +## 8. Taxonomy + +- `GET /api/v1/admin/taxonomy` → grouped categories, attributes with terms, and tags, plus + `taxonomy_anchor` and `taxonomy_last_sync_at`. Only fig-* and feat-* tag families are kept. + These lists are the only legal picker options. +- `POST /api/v1/admin/taxonomy/sync` → pulls from Woo, rebuilds the cache in one transaction, sets + `taxonomy_last_sync_at`, bumps `taxonomy_anchor` only when the stored-subset hash changes, and + on failure returns `502` while keeping the previous cache. + +## 9. Pricing + +- `GET /api/v1/admin/pricing` → `[PriceRule]` +- `POST /api/v1/admin/pricing` → `201` +- `PUT /api/v1/admin/pricing/{id}` → `200` +- `DELETE /api/v1/admin/pricing/{id}` → `204` + +One flat `price_per_bdft` per species leaf, no tiers. + +## 10. Settings + +- `GET /api/v1/settings` → `SettingsView` with secrets masked. +- `PUT /api/v1/settings` — partial update. `woo_base_url` is not writable; the server rejects the + write. Woo credentials are write-only and stored AES-GCM. Inference base URL and key are + encrypted; flags, brand voice, and geo context are settings. Secrets are never echoed. +- `POST /api/v1/settings/test-woo` → `{ "ok": bool, "detail": string, "taxonomy_anchor": string, + "taxonomy_last_sync_at": string }`. Fails closed on missing credentials or non-HTTPS; an auth + rejection is distinct from an unreachable store; on success always resyncs. +- `POST /api/v1/settings/test-inference` → `{ "ok": bool, "mode": "stateful"|"stateless"|"fail", + "vision_capable": bool, "detail": string }`. `fail` blocks Call 2 only. + +## 11. Removed from v1 + +- The shared validation module and its `412` path. +- Provider thread ids and `previous_response_id` (Call 2 is a fresh full-context call). +- The U2Net control; the endpoint is reserved only. +- Offline/IndexedDB sync. diff --git a/docs/contracts/CONTENT-WOO.md b/docs/contracts/CONTENT-WOO.md new file mode 100644 index 0000000..062efe5 --- /dev/null +++ b/docs/contracts/CONTENT-WOO.md @@ -0,0 +1,86 @@ +# Listing copy and payload intent + +Status: SPEC · 2026-09-18. +Owns how listing copy is produced and the intent of the create payload. The wire shape is +`docs/contracts/WOO-API.md`; the create sequence is `docs/contracts/API.md`; prompts are +`docs/contracts/PROMPTS.md`. + +## Requirements impact + +| Requirement | Impact on this design | +|---|---| +| REVIEW-10, REVIEW-15, REVIEW-40, REVIEW-50 | required prose, primary category, editability, limits | +| METADATA-40, METADATA-55, METADATA-60, METADATA-65, METADATA-66 | model scope, Call 2, context, dimension safety, logging | +| METADATA-70, METADATA-75, METADATA-80, METADATA-85, METADATA-90, METADATA-95, METADATA-110 | template fallback, non-blocking, prompt selection, fact block, overlay exclusion, control | +| PUBLISH-5, PUBLISH-10, PUBLISH-15, PUBLISH-20, PUBLISH-25, PUBLISH-30, PUBLISH-31, PUBLISH-40, PUBLISH-45, PUBLISH-50, PUBLISH-55, PUBLISH-60 | payload and create outcomes | +| SETTINGS-30, SETTINGS-65 | brand voice and geo context; create status | +| METADATA-10, METADATA-15, METADATA-30 | categories, attributes, tags on the payload | + +## 1. Copy sources + +Titles and descriptions come from one of three sources (`REVIEW-10`): + +1. **Typed** by the owner. +2. **Model** when Call 2 runs; the owner triggers it (`METADATA-55`). +3. **Template** as the fallback when Call 2 does not run (`METADATA-70`). + +The template guides style; when the model runs it writes the prose (`METADATA-80`). The model +writes prose only and never touches a dimension; every number comes from the dimension fact block +(`METADATA-60`, `METADATA-65`, `METADATA-90`). The dimension-overlay image is never sent +(`METADATA-95`). The owner can edit any result (`REVIEW-40`). All text respects the field limits +in `docs/contracts/WOO-API.md` §9 (`REVIEW-50`). + +The Generate text control appears only when content generation is enabled and the endpoint is +reachable (`METADATA-110`); a failed generation never blocks create (`METADATA-75`). Every +generation is logged (`METADATA-66`). + +## 2. Default templates + +Placeholders use `{field}`; all math fields come from the deterministic pipeline, never the model +(`METADATA-90`). + +**Title** (hyphen separator; `{character}` is the primary figure term name, `METADATA-80`): + +``` +{species} {length_ft}ft {length_in_remainder}in x {width_avg_in}in Wood Slab - {character} +``` + +`length_ft = floor(length_in / 12)`, `length_in_remainder = length_in mod 12` (1 dp). + +**Short description:** + +``` +{species} Slab - {character} +``` + +**Description** (multi-paragraph): + +``` +A one-of-a-one {character} {species} slab, {length_ft}ft {length_in_remainder}in long with an +average width of {width_avg_in}in and {thickness_in}in thickness ({bdft} bdft). + +Each slab is naturally shaped; width varies along the length at {width_min_in}in to +{width_max_in}in. Sold by the slab; dimensions as measured. {geo_sentence} +``` + +`geo_sentence` comes from Settings and is omitted when unset (`SETTINGS-30`). The primary wood +category selects a specialized Call 2 prompt (`METADATA-85`). + +## 3. Payload intent + +The exact wire shape is `docs/contracts/WOO-API.md`. Intent: + +- Only non-null fields are sent (`PUBLISH-55`). +- `status` comes from `woo_create_status` (default draft); `SLAB-UAT-*` always draft + (`SETTINGS-65`, `PUBLISH-10`). +- Categories are the species leaf plus each wood-category leaf; attributes are the five locked + attributes by synced term; tags are fig-* and feat-* (`METADATA-10`, `METADATA-15`, + `METADATA-30`). Missing attributes or terms are skipped, never created (`METADATA-20`). +- Images are the processed transparent PNGs in photos-ready order, with no cap (`PUBLISH-40`, + `PUBLISH-45`). +- `sold_individually` is true; each product is one slab (`PUBLISH-60`). +- Create checks the SKU first and blocks on a duplicate (`PUBLISH-15`); a stale value is a + field-level error (`PUBLISH-20`). Success purges photos and deletes the local row + (`PUBLISH-25`); failure retains photos and logs (`PUBLISH-30`), including the orphaned-media + cleanup note (`PUBLISH-31`). Published means the create succeeded, not that it is live + (`PUBLISH-50`). diff --git a/docs/contracts/DATA-MODEL.md b/docs/contracts/DATA-MODEL.md new file mode 100644 index 0000000..2df3b03 --- /dev/null +++ b/docs/contracts/DATA-MODEL.md @@ -0,0 +1,142 @@ +# Data model contract + +Status: SPEC · 2026-09-18. Store: SQLite. + +## Requirements impact + +| Requirement | Impact on this design | +|---|---| +| APP-5 | slab status enum and transitions | +| APP-20 | ids, timestamps, revisions | +| APP-40 | derived numbers stored, never recomputed | +| CAPTURE-25, CAPTURE-30, CAPTURE-70, CAPTURE-80, CAPTURE-85, CAPTURE-90, CAPTURE-95, CAPTURE-105, CAPTURE-115, CAPTURE-130, CAPTURE-135, CAPTURE-160 | slab and photo columns, units, PNG | +| METADATA-5, METADATA-10, METADATA-15, METADATA-30 | taxonomy cache tables and slab taxonomy columns | +| METADATA-35, METADATA-45, METADATA-55, METADATA-66, METADATA-105 | inference columns, prompts tables, logs | +| PUBLISH-25, PUBLISH-30 | purge on success, append-only log | +| REVIEW-10, REVIEW-25, REVIEW-45, REVIEW-50 | prose, price, SKU, field limits | +| SETTINGS-5, SETTINGS-10, SETTINGS-20, SETTINGS-25, SETTINGS-30, SETTINGS-35, SETTINGS-50, SETTINGS-51, SETTINGS-55, SETTINGS-65, SETTINGS-90 | settings, crypto, rates, taxonomy stamps | + +## 1. Conventions + +- Slab ids are client-generated UUIDv4; all other ids are integer autoincrement. +- Timestamps are UTC ISO-8601 text. +- Slab status is one of `draft`, `calibrated`, `ready`, `publishing`, `published`, `failed`. +- Inches are stored as the rounded 1/8-inch value the user entered. +- `sqft` and `bdft` are stored to 2 decimals half-up. +- `price` is stored to 2 decimals half-up. +- Multi-select taxonomy values are JSON arrays of Woo ids. +- Secrets are stored as `value_enc = nonce(12) || ciphertext`, AES-GCM; the master key is the env + `SLAB_AES_KEY` and is never in the DB. +- The local slab row is deleted on successful publish; `sync_log` and `inference_log` are + append-only and retained. + +## 2. `slabs` + +| Column | Type | Constraint / default | +|---|---|---| +| `id` | text | PK, client UUIDv4 | +| `status` | text | enum, default `draft` | +| `sku` | text | 3–24, alphanumeric and hyphen; unique locally | +| `length_in` | real | 1/8-inch step | +| `thickness_in` | real | 1/8-inch step | +| `sqft` | real | 2 dp | +| `bdft` | real | 2 dp | +| `width_min_in`, `width_max_in`, `width_avg_in` | real | 6-inch stations | +| `species_id` | int | FK `woo_categories`, one leaf | +| `wood_category_ids` | json | one or more leaf ids | +| `edge_type_term_id` | int | one term | +| `figure_term_ids` | json | one or more terms | +| `grade_term_id` | int | exactly one term | +| `thickness_term_id` | int | round-up band term | +| `moisture_term_id` | int | default kiln-dried term | +| `fig_tag_ids` | json | optional | +| `feat_tag_ids` | json | optional | +| `price` | real | 2 dp | +| `price_source` | text | `recommendation` \| `override` | +| `title`, `short_description`, `description` | text | required non-empty; title warn >60, short_description warn >160 | +| `content_source` | text | `manual` \| `llm` \| `template` | +| `species_confidence`, `figure_confidence`, `grade_confidence` | real | null below 0.7 | +| `inference_status` | text | null \| `inferring` \| `done` \| `failed` | +| `calibration_photo_id` | text | FK `slab_photos` | +| `client_rev`, `server_rev` | int | default 1 | +| `woo_product_id` | int | null until publish | +| `published_at` | text | null until publish | +| `created_at`, `updated_at` | text | UTC ISO-8601 | + +No `inference_thread_id` column exists; provider threads are not used. + +## 3. `slab_photos` + +| Column | Type | Constraint | +|---|---|---| +| `id` | text | PK, UUID | +| `slab_id` | text | FK `slabs` | +| `seq` | int | capture order | +| `is_calibration` | int (bool) | exactly one true per slab | +| `original_blob` | blob | retained while draft/retry | +| `processed_blob` | blob | transparent PNG, alpha preserved | +| `width_px`, `height_px` | int | processed dimensions | +| `created_at` | text | UTC ISO-8601 | + +The calibration photo exists; other batch photos are visual inventory only. Rows are purged on +successful publish. + +## 4. `pricing_rules` + +| Column | Type | Constraint | +|---|---|---| +| `id` | int | PK | +| `species_id` | int | FK `woo_categories`, UNIQUE | +| `price_per_bdft` | real | flat, no tiers | +| `created_at`, `updated_at` | text | UTC ISO-8601 | + +## 5. Taxonomy cache + +- `woo_categories` — species and wood-category leaves only; parents group and are never assigned. +- `woo_attributes` / `woo_attribute_terms` — exactly the five locked attributes Edge Type, Figure, + Grade, Thickness, Moisture, with their terms. +- `woo_tags` — only the fig-* and feat-* families; other store tags are ignored. + +Rebuild is one transaction; on failure the previous cache is kept. + +## 6. `settings` + +| Key | Storage | Default | +|---|---|---| +| `woo_wp_username` | plain | — | +| `woo_app_password` | `value_enc` | — | +| `inference_base_url` | `value_enc` | — | +| `inference_api_key` | `value_enc` | — | +| `inference_model` | plain | — | +| `inference_enabled` | plain | `false` | +| `content_llm_enabled` | plain | `false` | +| `brand_voice` | plain | — | +| `geo_context` | plain | — | +| `woo_create_status` | plain | `draft` | +| `taxonomy_last_sync_at` | plain | — | +| `taxonomy_anchor` | plain | — | + +`WOO_BASE_URL` is not a settings row; it is env, HTTPS only, read-only. + +## 7. `prompts` and `prompt_revisions` + +Prompts live in the database with all revisions; each call uses the active revision and the +history is retained. + +- `prompts`: `id`, `name` (`call1` | `call2`), `active_revision_id`, `created_at`, `updated_at`. +- `prompt_revisions`: `id`, `prompt_id` FK, `content` text, `created_at`, `created_by`. + +## 8. `sync_log` + +Append-only; never deleted on publish. Columns: `id`, `slab_id`, `woo_product_id`, `status` +(`success` | `failed`), `payload_hash` (sha256 of the outbound payload), `message`, `created_at`. + +## 9. `inference_log` + +Append-only audit. Columns: `id`, `slab_id`, `call` (1 | 2), `prompt_revision_id` FK, `model`, +`request_payload`, `response_payload`, `latency_ms`, `created_at`. + +## 10. Status transitions + +`draft` → `calibrated` → `ready` → `publishing` → `published`, with `failed` reachable from any +step and retryable. `calibrated` is set when the draft uploads; `ready` requires the full set. diff --git a/docs/contracts/PROMPTS.md b/docs/contracts/PROMPTS.md new file mode 100644 index 0000000..ee004fc --- /dev/null +++ b/docs/contracts/PROMPTS.md @@ -0,0 +1,91 @@ +# Prompts contract + +Status: SPEC · 2026-09-18. +Owns prompt storage, prompt inputs, and the two inference calls. + +## Requirements impact + +| Requirement | Impact on this design | +|---|---| +| METADATA-5, METADATA-10, METADATA-15, METADATA-30 | taxonomy choices and cardinality | +| METADATA-35, METADATA-40, METADATA-45, METADATA-50 | Call 1 inputs, constraints, pre-fill, precedence | +| METADATA-55, METADATA-60, METADATA-65, METADATA-70 | Call 2 trigger, context, constraints, fallback | +| METADATA-75, METADATA-80, METADATA-85, METADATA-90, METADATA-95 | non-blocking, fallback title, prompt selection, fact block, overlay exclusion | +| METADATA-100, METADATA-105, METADATA-110, METADATA-115 | feat mapping, prompt storage, button, failure | +| METADATA-66 | generation logging | +| SETTINGS-30 | brand voice and geo context | +| REVIEW-40, REVIEW-50 | editability and field limits | + +## 1. Storage and revisions + +- Prompts live in the database, not in text files, with all revisions retained. Tables are in + `docs/contracts/DATA-MODEL.md` §7. +- Each call reads the active revision fresh. +- Editing a prompt creates a new revision; history is never overwritten. +- Every generation is logged with its inputs, the request/response when a model was used, and the + final stored content. + +## 2. Call 1 — vision classification + +**When.** Runs automatically after a calibrated draft uploads only when `inference_enabled` is +true; also on demand or retry. With inference off it is skipped and the fields are filled by hand. + +**Inputs.** +- The clean processed photos at 1024 px long edge or less. +- The synced taxonomy: species leaves, Edge Type, Figure, Grade, Thickness, Moisture terms, each + with its Woo id. These are the only legal choices. +- User metadata: SKU, length, thickness. + +**Constraints.** +- The model may only choose values from the supplied synced lists; it never invents categories or + terms. +- Free-text observations (character, inclusions, voids, checks) are mapped to existing synced + `feat-*` tags only; Woo is authoritative for those tags. + +**Result fields.** `species_id` + `species_confidence`; `wood_category_ids`; +`edge_type_term_id`; `figure_term_ids` + `figure_confidence`; `grade_term_id` + +`grade_confidence`; `character`, `inclusions`, `voids`, `checks`. Grade, edge, thickness, and +moisture are singular. + +**Pre-fill.** At or above the threshold (default 0.7) the field is pre-filled and remains +editable; below it the field stays empty and is not shown as a low-confidence guess. Typed values +always win. Call 1 is assist-only and never gates publish. A runtime failure is classified +recoverable or unrecoverable for the owner. + +## 3. Call 2 — content prose + +**When.** User-triggered only, via the Generate text control; never automatic. The control appears +only when content generation is enabled and the endpoint is reachable. + +**Inputs.** +- A fresh, full Call 1 context: prompt, Call 1 photos, and the Call 1 result. No provider thread + id is used. +- The confirmed or changed metadata from review. +- The literal dimension fact block, exact strings for species, figure term, length, thickness, + average width, width min–max, and board feet; no length-by-width-by-thickness rectangle + assumption. +- Brand voice and geo context. + +**Constraints.** +- The model writes prose only and never invents or alters a dimension; every number comes from the + fact block. +- The dimension-overlay image is never sent. + +**Output.** `title`, `short_description`, `description`. The owner can edit the result. + +## 4. Fallback and templates + +- With inference off, deterministic templates produce name, short_description, and description so + create never lacks prose. +- The template is the fallback only; when Call 2 runs, the model writes and the template guides + style. +- The fallback title uses the primary figure term name as the character, with the hyphen + separator. +- The primary wood category selects a specialized Call 2 prompt. +- All generated text stays within the field limits in `docs/contracts/WOO-API.md` §9. + +## 5. Prompt management + +- The active Call 1 and Call 2 revisions are editable; saving creates a new revision. +- A revision identifies its content, author, and timestamp; `inference_log` records the revision + used for each call. diff --git a/docs/contracts/README.md b/docs/contracts/README.md index 459cd38..a39e34d 100644 --- a/docs/contracts/README.md +++ b/docs/contracts/README.md @@ -4,13 +4,14 @@ Cross-cutting interface detail shared by capabilities. Rules (`docs/STRUCTURE.md`): a contract doc **cites** requirement IDs from `docs/REQUIREMENTS.md`; it never defines or restates them. -Expected files (Phase 2 migration): +Contract docs (migrated): +- `WOO-API.md` — external WooCommerce REST API v3 surface (new; the dependency contract) - `API.md` — FastAPI HTTP contract (from tag `OPENAPI.md`) - `DATA-MODEL.md` — SQLite schema and status machine (from tag `DATA-MODEL.md`) - `UX.md` — screens, field recovery, outcomes (from tag `UX.md`) -- `PROMPTS.md` — Call 1 / Call 2 prompt text (from tag `PROMPTS.md`) -- `TEST-SPEC.md` — test pyramid, vectors, dishonest proxies (from tag `TEST-SPEC.md`) -- `CONTENT-WOO.md` — listing copy and Woo payload detail (migrated from `docs/CONTENT-WOO.md`) +- `PROMPTS.md` — Call 1 / Call 2 prompt text and inputs (from tag `PROMPTS.md`) +- `CONTENT-WOO.md` — listing copy and Woo payload intent (from `docs/CONTENT-WOO.md`) +- `TEST-SPEC.md` — test pyramid, vectors, dishonest proxies (from tag `TEST-SPEC.md`) — pending Template: `docs/_templates/contract.md`. diff --git a/docs/contracts/TEST-SPEC.md b/docs/contracts/TEST-SPEC.md new file mode 100644 index 0000000..4ce44b0 --- /dev/null +++ b/docs/contracts/TEST-SPEC.md @@ -0,0 +1,152 @@ +# Test spec contract + +Status: SPEC · 2026-09-18 · reconciled against the finished PoR. +Defines the affirmative test bar. It cites requirement IDs; `docs/REQUIREMENTS.md` is normative. +The human walkthrough is `docs/UAT-PRACTICE.md`. + +## Requirements impact + +| Requirement | Impact on this design | +|---|---| +| QUALITY-5, QUALITY-10, QUALITY-15 | TDD for the deterministic core; vectors TV-1..TV-10 | +| QUALITY-20, QUALITY-35, QUALITY-50 | inference-off suite, Woo-mock integration, Compose UI E2E | +| QUALITY-25, QUALITY-30 | evidence rules; Ty owns gate exit | +| QUALITY-40, QUALITY-45 | property checks and bdft identity | +| QUALITY-55 .. QUALITY-100 | pipeline vectors | +| QUALITY-105, QUALITY-110 | dishonest proxies; M3 exit evidence | +| QUALITY-115, QUALITY-120, QUALITY-125 | browser E2E, no raw codes, AES round-trip | +| APP-45, APP-55, APP-60, APP-65 | health, logging, accessibility, log redaction | +| SETTINGS-80, PUBLISH-30/31/35 | failure handling under test | + +## 1. Layers and harness + +| Layer | Written | Location | Rule source | +|---|---|---|---| +| Unit | TDD before the code exists | `backend/tests/unit/`, `frontend/src/__tests__/` | deterministic core and validation | +| API contract | with the feature | `backend/tests/contract/` (pytest + httpx TestClient) | `docs/contracts/API.md` | +| Integration | with the feature | `backend/tests/integration/` (respx Woo mock) | `docs/contracts/WOO-API.md`, `docs/contracts/CONTENT-WOO.md` | +| UI / E2E | with the feature | `frontend/e2e/` (Playwright, Compose stack, mocked Woo) | `docs/contracts/UX.md` | +| Ops | with deploy config | `deploy/tests/` (shell) | `deploy/` | + +Allowed results are `pass`, `fail`, `not run`, and optionally `blocked`. Every `pass` or `fail` +needs runner evidence; no evidence means `not run`. The only bug log is Gitea issues. Ty reads raw +results and owns gate exit; an agent never declares a gate green (`QUALITY-25`, `QUALITY-30`). + +## 2. Fundamentals (FUND) + +- FUND-1..4 — no browser/DOM/network deps in pure tests; semantic selectors; real Tab navigation; + reactivity flush. (test practice) +- FUND-5, FUND-6, FUND-7 — Tab order, 320 px reflow, visible focus ring → `APP-60` +- FUND-8..11 — app factory per test, lifespan, override cleanup, no real network (test practice) +- FUND-12, FUND-13, FUND-16 — FK and constraint enforcement, migration integrity (test practice) +- FUND-14 — WAL with no stale reads (test practice) +- FUND-15 — illegal status transitions rejected → `APP-5` +- FUND-17 — fresh nonce per encrypt; tamper fails → `SETTINGS-25` +- FUND-18 — ciphertext absent from GET; only configured booleans → `SETTINGS-75` +- FUND-19 — missing key refuses startup → `SETTINGS-25` +- FUND-20 — Basic Auth with application passwords over HTTPS, not consumer keys → `SETTINGS-15` +- FUND-21 — 401/400/404 mapped, not passed raw → `APP-50` +- FUND-22 — pre-create SKU lookup, duplicate race → `PUBLISH-15` +- FUND-23 — timeouts/network errors graceful, no stack → `PUBLISH-30`, `PUBLISH-35` +- FUND-24 — no secret material in logs → `APP-65` +- FUND-25 — HTTPS, health through proxy, port collision → `OPS-5`, `OPS-15`, `OPS-20` + +## 3. Deterministic vectors (TV) + +- TV-1 — green patch not touching the border survives; U2Net not invoked → `QUALITY-55` +- TV-2 — dark interior wood survives; flood fill isolates border-connected regions → `QUALITY-60` +- TV-3 — 96 in, 1.5 in, 0.64 sqft yields 0.96 bdft; 1 sqft at 1 in equals 1 bdft → `QUALITY-65` +- TV-4 — 6 in stations on a 96 in axis report min, max, average → `QUALITY-70` +- TV-5 — one square end, one 45 degree cut; axis follows prevailing length → `QUALITY-75` +- TV-6 — 3:4 crop, 90 percent fill at extremes, transparent PNG, 800 px floor → `QUALITY-80` +- TV-7 — undersized source warns, is not upscaled, not publishable until retake → `QUALITY-85` +- TV-8 — 0.96 bdft at 12.50 gives 12.00; a 150.00 override is stored → `QUALITY-90` +- TV-9 — 1 in rounds up into the synced Woo thickness band → `QUALITY-95` +- TV-10 — half-up 2 decimal rounding cases → `QUALITY-100` + +Validation: +- SKU charset 3–24 alphanumeric and hyphen → `REVIEW-45` +- title/short_description/description within limits → `REVIEW-50` + +AES: +- round-trip decrypt; ciphertext never in GET → `QUALITY-125`, `SETTINGS-75` +- missing key refuses startup → `SETTINGS-25` + +API contract / health / draft: +- `GET /api/health` shape and non-200 on DB/volume failure → `APP-45` +- `POST /api/v1/slabs` multipart returns 201 calibrated → `docs/contracts/API.md`, `APP-5` + +## 4. Gate C and Woo + +- `SLAB-UAT-*` creates a draft regardless of setting → `PUBLISH-10` +- duplicate SKU before create returns 409 with `edit_sku` / `open_existing` → `PUBLISH-15` +- test-woo ok, resyncs taxonomy → `SETTINGS-40` +- WOO_BASE_URL read-only, PUT rejected → `SETTINGS-5`, `SETTINGS-85` +- startup refuses missing or http WOO_BASE_URL → `SETTINGS-10` +- Woo create stores `woo_product_id`, purges images → `PUBLISH-25` +- Woo 409 mapped to field recovery, draft retained → `PUBLISH-15` +- taxonomy anchor unchanged on no-op, bumps on change → `SETTINGS-55` +- failed publish marks failed, keeps photos, writes sync_log, retries to success → `PUBLISH-30` + +## 5. Inference + +- with inference off: Call 1 skipped, no inference calls, suite passes → `METADATA-75`, `QUALITY-20` +- test-inference returns ok, mode stateful/stateless, vision_capable → `SETTINGS-45` +- test-inference fail blocks Call 2 only → `SETTINGS-45` +- Call 1 at or above 0.7 pre-fills; below stays empty; override wins → `METADATA-45`, `METADATA-50` +- Call 1 sends clean processed photos at 1024, synced taxonomy, user metadata → `METADATA-35` +- Call 2 user-triggered only, fresh full context → `METADATA-55`, `METADATA-60` +- model does not invent dimensions → `METADATA-65` +- generation logged → `METADATA-66` +- prompts stored with revisions → `METADATA-105` + +## 6. UI end-to-end (UIE / E2E) + +- capture controls, overlay, axis, numbers, ready gates → `CAPTURE-40`, `CAPTURE-45`, `CAPTURE-50`, + `CAPTURE-60`, `CAPTURE-70`, `CAPTURE-80`, `CAPTURE-85`, `CAPTURE-90`, `REVIEW-5`, `REVIEW-20` +- review pre-fill and editability → `METADATA-45`, `REVIEW-40` +- duplicate SKU, stale value, success, failure messages → `PUBLISH-15`, `PUBLISH-20`, `PUBLISH-25`, + `PUBLISH-30`, `PUBLISH-35` +- inference-off content path, Generate text disabled → `METADATA-70`, `METADATA-110` +- settings screen and read-only URL → `SETTINGS-5`, `SETTINGS-15`, `SETTINGS-20`, `SETTINGS-30`, + `SETTINGS-35`, `SETTINGS-65` +- cross-browser capture through publish → `QUALITY-115` + +## 7. API surface + +- revisions and conflict → `APP-20` +- PUT stores derived, no recompute → `APP-40` +- secrets absent from GET → `SETTINGS-75` +- infer-taxon/content 202 and poll; timeout → `METADATA-35`, `METADATA-115` +- publish preconditions → `REVIEW-5` +- taxonomy sync, anchor, cache retention → `SETTINGS-50`, `SETTINGS-55` +- delete soft, published undeletable → `docs/contracts/API.md` +- test-woo fails closed → `SETTINGS-80` +- taxonomy re-pull and 1 hour publish refresh → `SETTINGS-60` +- species picker synced only → `METADATA-5` + +## 8. Ops and instrumentation + +- compose health, inventory, test-woo, draft status → `OPS-15`, `SETTINGS-40`, `SETTINGS-65`, `APP-45` +- backup and restore retention and cycle → `OPS-30`, `OPS-35` +- health non-200 on DB/volume, `woo_reachable` false → `APP-45` +- request id in logs → `APP-55` +- inference_log and sync_log written → `METADATA-66`, `PUBLISH-30` + +## 9. Purged cases + +Removed because the behavior was superseded: four-knob cases (edge offset, feather), the shared +validation module and its 412 path, `previous_response_id` and the per-slab thread, "inventory +photos" as the Call 1 input, prompt files edited without restart, 80 percent / 1600 px crop values, +and manual override of the computed fields. + +## 10. New cases + +- keyboard tab order, focus ring, and 320 px reflow → `APP-60` +- no secret material in any log output → `APP-65` + +## 11. Rules that do not change + +Inference is optional and the full suite passes with it off. `bdft = sqft × thickness_in`, no +divide-by-12. The client is authoritative for deterministic math; the server stores the numbers. +No new product locks live in this document. diff --git a/docs/contracts/UX.md b/docs/contracts/UX.md new file mode 100644 index 0000000..7f344ef --- /dev/null +++ b/docs/contracts/UX.md @@ -0,0 +1,97 @@ +# UX contract — mill-owner flow + +Status: SPEC · 2026-09-18. Presentation spec for the mill-owner flow on phone and PC browsers. + +## Requirements impact + +| Requirement | Impact on this design | +|---|---| +| CAPTURE-10 | supported browsers, hard stop | +| CAPTURE-15 | online only | +| CAPTURE-20, CAPTURE-25, CAPTURE-30 | photo intake and calibration choice | +| CAPTURE-40, CAPTURE-41, CAPTURE-45, CAPTURE-50, CAPTURE-55 | sheet/sensitivity controls, overlay, retake | +| CAPTURE-60, CAPTURE-65, CAPTURE-70, CAPTURE-80, CAPTURE-85, CAPTURE-90 | axis, entry, derived numbers | +| CAPTURE-105, CAPTURE-135, CAPTURE-141, CAPTURE-145, CAPTURE-150, CAPTURE-155 | crop guard, upload, preview/controls, failure | +| METADATA-10, METADATA-15, METADATA-30 | required taxonomy fields and cardinality | +| METADATA-45, METADATA-55, METADATA-110 | pre-fill, Generate text availability | +| PUBLISH-10, PUBLISH-15, PUBLISH-20, PUBLISH-25, PUBLISH-30, PUBLISH-35, PUBLISH-36, PUBLISH-37, PUBLISH-38, PUBLISH-50 | publish outcomes and messaging | +| REVIEW-5, REVIEW-10, REVIEW-15, REVIEW-20, REVIEW-25, REVIEW-30, REVIEW-40, REVIEW-45, REVIEW-50 | ready set, gating, fields, limits | +| SETTINGS-65 | create-status default | + +## 1. Shape + +One column of screens: capture → review → publish. No dashboard and no batch view. The reader is a +mill owner photographing one slab at a time, online. Capture often starts on a phone; the same +flow runs on a PC browser. + +## 2. Capture + +1. **Photos.** Take one or more photos; the first is top-down with the lens parallel to the broad + face. The owner chooses which photo is the calibration photo. +2. **Mask overlay and controls.** The app draws the mask on the source photo. The controls are + exactly **Sheet** (auto, green, black) and **Sensitivity** (0–100). A Sheet change runs + immediately; a Sensitivity change debounces 2000 ms; a worker recomputes and the last good + overlay stays visible. There is no apply step and no server round trip; values persist until + reset. +3. **Confirm the mask.** The owner confirms the edge; approval is blocked when the mask has no + slab pixels. A broken or non-contiguous mask resolves as a retake. +4. **Length axis.** The app draws the bounding rectangle and the length axis; the owner rotates it + to the prevailing length and confirms. On a nearly square slab the owner chooses the axis. +5. **Numbers.** The owner enters length and thickness in 1/8-inch steps and the SKU. The browser + computes widths, sqft, and bdft. These are display only; the server never recomputes them. +6. **Continue.** Uploads originals, processed PNGs, and the numbers as a calibrated draft; status + becomes `calibrated`. + +The preview is display-only and never drives the math. Failures such as a broken mask or an +undersized source never guess; the resolution is retake. + +## 3. Review + +- **Vision pre-fill.** At or above the threshold (default 0.7) a field is pre-filled and shown + with an "AI" badge; below it the field starts empty. The badge is presentation only and clears + on edit. +- **Required set.** Exactly one species and at least one wood-category leaf; one Edge Type, at + least one Figure, exactly one Grade, one Thickness band, one Moisture; fig-* and feat-* tags + optional; price; title, short_description, and description. +- **Primary category.** The owner declares one primary wood category and any secondary ones; the + default is the first selected. +- **Inline nudges.** Every empty required field shows an inline nudge as the owner works; there is + no single submit-time gap list. +- **Calculated fields.** Widths, sqft, and bdft are read-only and not overridable. +- **Generate text.** The control appears only when content generation is enabled and the endpoint + is reachable; it is disabled when the text service is down; it never runs automatically. + +## 4. Publish + +- A normal SKU uses `woo_create_status` (default draft); a `SLAB-UAT-*` SKU always creates a draft. + "Published" means the store create succeeded, not that it is live. +- **Success.** The local draft and photos are gone; the store product reference is shown. +- **Duplicate SKU.** "This SKU already exists in the store (any status)." with Edit SKU and Open + Existing. +- **Stale value.** "This value is no longer valid. Pick from the current list," with current + options. +- **Failure.** A plain message on the affected field or action, classified recoverable or + unrecoverable, with Retry while the draft and photos are retained; no raw code. + +## 5. Browser gate + +Supported families are iOS Safari and Android Chrome, plus desktop Chrome, Edge, and Firefox. An +unsupported client gets a hard stop with an update or switch link; there are no polyfills. + +## 6. Canonical error and empty copy + +| Situation | Copy | +|---|---| +| Mask failure | "Cannot isolate the slab edge. Adjust the controls or retake." | +| Axis check | "Rotate the axis to match the prevailing length of the slab." | +| Source too small | "Photo will be too small after cropping. Please retake with a closer shot." | +| Duplicate SKU | "This SKU already exists in the store (any status)." | +| Stale value | "This value is no longer valid. Pick from the current list." | +| Store unreachable | "Cannot reach the store. Check your connection and settings." | +| Missing field nudge | per-field, in place (e.g. "Select species") | +| Browser gate | "This app requires a recent browser. Please update or switch, then try again." | + +## 7. Presentation-only details + +First-run guidance card, "AI" badge styling, and the separate title/description step are +presentation detail, not requirements. diff --git a/docs/contracts/WOO-API.md b/docs/contracts/WOO-API.md new file mode 100644 index 0000000..c8da102 --- /dev/null +++ b/docs/contracts/WOO-API.md @@ -0,0 +1,142 @@ +# WooCommerce API contract + +Status: SPEC · 2026-09-18 · external dependency: WooCommerce REST API v3. +Source: official WooCommerce REST API v3 documentation +(https://woocommerce.github.io/woocommerce-rest-api-docs/). + +This contract pins the exact WooCommerce surface the app codes against. +`docs/contracts/CONTENT-WOO.md` owns the listing copy and payload intent; this file owns the wire +shape and the external constraints. + +## Requirements impact + +| Requirement | Impact on this design | +|---|---| +| SETTINGS-5, SETTINGS-10, SETTINGS-15 | base URL, HTTPS, app-password auth | +| SETTINGS-35, SETTINGS-50, SETTINGS-51, SETTINGS-65 | pricing, taxonomy sync, tag scope, create status | +| METADATA-5, METADATA-10, METADATA-15, METADATA-30 | categories, attributes, tags shapes | +| REVIEW-10, REVIEW-25, REVIEW-45, REVIEW-50 | name, price, SKU, field limits | +| PUBLISH-5, PUBLISH-10, PUBLISH-15, PUBLISH-20, PUBLISH-25, PUBLISH-30, PUBLISH-35 | create sequence, draft rule, errors | +| PUBLISH-40, PUBLISH-45, PUBLISH-55, PUBLISH-60 | images, payload fields, sold-individually | +| APP-35, APP-50 | error envelope mapping | + +## 1. Transport and auth + +- Base path: `${WOO_BASE_URL}/wp-json/wc/v3`. +- `WOO_BASE_URL` is HTTPS only and injected from env. +- Auth: WordPress application password over HTTP Basic. The consumer-key example in the Woo docs + is **not** used. +- Content type `application/json`. Dates ISO8601 (`YYYY-MM-DDTHH:MM:SS`). Monetary values are + strings with two decimals. + +## 2. Endpoints used + +| Purpose | Method and path | +|---|---| +| Sync categories | `GET /products/categories?per_page=100&page=N` | +| Sync tags | `GET /products/tags?per_page=100&page=N` | +| Sync attributes | `GET /products/attributes?per_page=100` | +| Sync attribute terms | `GET /products/attributes/{id}/terms?per_page=100` | +| SKU lookup | `GET /products?sku={sku}` | +| Create product | `POST /products` | +| Upload media | `POST /wp-json/wp/v2/media` (WordPress core) | + +## 3. Product create field alignment + +| Our value | Woo field | Woo type | Enum / constraint | +|---|---|---|---| +| title | `name` | string | none documented | +| — | `type` | string | `simple` (default) | +| create status | `status` | string | `draft`, `pending`, `private`, `publish`; default `publish` | +| sku | `sku` | string | unique; no documented format | +| price | `regular_price` | string | monetary string, 2 decimals | +| description | `description` | string | none documented | +| short_description | `short_description` | string | none documented | +| species + wood categories | `categories` | array of `{id: integer}` | leaf ids | +| fig-* / feat-* tags | `tags` | array of `{id: integer}` | tag ids | +| uploaded PNGs | `images` | array of `{id: integer}` | attachment ids, lineup order | +| five locked attributes | `attributes` | array of attribute elements | see §4 | +| — | `virtual` | boolean | default `false` | +| — | `downloadable` | boolean | default `false` | +| — | `catalog_visibility` | string | `visible`, `catalog`, `search`, `hidden`; default `visible` | +| — | `sold_individually` | boolean | default `false`; set `true` | + +`sold_individually: true` is the Woo mechanism that enforces "one slab is one unit." + +## 4. `attributes` element shape + +Global attributes are referenced by `id` (synced from Woo), never created by name. Each element: + +```json +{ + "id": 8, + "position": 0, + "visible": true, + "variation": false, + "options": ["Live Edge"] +} +``` + +| Attribute | `options` | +|---|---| +| Edge Type | `[term name]` | +| Figure | `[term name, ...]` | +| Grade | `[term name]` | +| Thickness | `[term name]` | +| Moisture | `[term name]` | + +`options` carries term **names**, not ids. `visible` is `true`; `variation` is `false`. + +## 5. Categories, tags, images shapes + +- `categories`: `[{"id": 9}, {"id": 14}]` +- `tags`: `[{"id": 34}]` +- `images`: `[{"id": 42}, {"id": 793}]`, in photos-ready lineup order. +- Media upload: `POST /wp-json/wp/v2/media` as `multipart/form-data` with the PNG bytes; the + response `id` is the attachment id used in `images`. Keep alpha; PNG only. + +## 6. Error shape + +```json +{ "code": "woocommerce_rest_term_invalid", "message": "Resource doesn't exist.", "data": { "status": 404 } } +``` + +`401` auth, `400` bad request, `404` missing, `500` server. These map into the app's uniform +envelope and never reach the owner raw. + +## 7. Pagination + +`per_page` max 100, `page` 1-based. Read `X-WP-Total` and `X-WP-TotalPages`; follow pages until +exhausted. Taxonomy lists return in full to the client. + +## 8. No documented maximum lengths + +The Woo v3 docs state no maximum length for `name`, `description`, `short_description`, `sku`, or +`regular_price`. Do not invent caps. The app enforces non-empty required fields and its own local +formats. + +## 9. Field length limits + +Hard limits are verified from WordPress/WooCommerce source; the display caps are conventions +(Google states no numeric limit). + +| Field | Storage | Hard limit | Recommended display cap | Rationale | +|---|---|---|---|---| +| `name` | `wp_posts.post_title` (`text`) | 65,535 | 60 | on-screen title-tag width | +| `short_description` | `wp_posts.post_excerpt` (`text`) | 65,535 | 160 | search snippet / meta description width | +| `description` | `wp_posts.post_content` (`longtext`) | ~16.7M | none | no word-count target; write for the page | +| `sku` | `wc_product_meta_lookup.sku` (`varchar(100)`, index `sku(50)`) | 100 | 50 | index prefix length | + +Sources: WordPress `schema.php`, WooCommerce `class-wc-install.php`, +`class-wc-product-data-store-cpt.php`, and Google Search Central (title links, snippets, SEO +starter guide). + +## 10. Media size + +The store exposes its upload cap read-only as `max_upload_size` on system status. Validate the +processed PNG against it before upload and surface a plain, actionable error if exceeded. + +## 11. Versioning + +Woo `wc/v3` requires WooCommerce 3.5+ and WordPress 4.4+ with pretty permalinks. Breaking Woo API +changes are handled by the app's own `/api/v1` version, not by pinning the store.