docs(#12,#14,#18): synced species, taxonomy anchor, 409/422 recovery
Species UI and Call 1 use only Woo-synced leaves. Middle tier manufactures a taxonomy last-modified anchor (bump on stored-subset change only; test-woo always resyncs; publish if >1h). CONTENT-WOO field-level 409/422 recovery. IMPL acceptance and AGENTS locks updated.
This commit is contained in:
@@ -158,7 +158,8 @@ Payload and publish sequence depth: `docs/CONTENT-WOO.md` (cites this section fo
|
|||||||
- **Call 1 (vision) / Call 2 (text) only after Gate C is green, still inside overall POC** before “done” — not post-POC.
|
- **Call 1 (vision) / Call 2 (text) only after Gate C is green, still inside overall POC** before “done” — not post-POC.
|
||||||
- Call 1 auto only if `inference_enabled`; Call 2 never auto-fires (user taps Generate text).
|
- Call 1 auto only if `inference_enabled`; Call 2 never auto-fires (user taps Generate text).
|
||||||
- Call 1 body: all inventory photos @1024 (not top-down-only). Taxonomy lists sent to Call 1 are **exactly the Woo-synced species / attribute / tag sets**. No hardcoded or invented species.
|
- Call 1 body: all inventory photos @1024 (not top-down-only). Taxonomy lists sent to Call 1 are **exactly the Woo-synced species / attribute / tag sets**. No hardcoded or invented species.
|
||||||
- **Confidence 0.7 (default):** ≥ 0.7 → pre-populate species, wood categories, edge, figure, grade, and feat-* from Call 1 (**mutable**). < 0.7 → those fields stay **empty**; user is gated before ready. Mandatory: exactly one species, ≥1 wood category, ≥1 figure. Presentation (inline nudges): `docs/UX.md`. Inference never auto-publishes.
|
- **Confidence 0.7 (default):** ≥ 0.7 → pre-populate species, wood categories, edge, figure, grade, and feat-* from Call 1 (**mutable**). < 0.7 → those fields stay **empty**; user is gated before ready. Mandatory floor: species + ≥1 figure (product also requires ≥1 wood category). Presentation: `docs/UX.md`. Inference never auto-publishes.
|
||||||
|
- **Taxonomy cache:** FastAPI manufactures one last-modified anchor; bump only on stored-subset change; client re-pulls if advanced. `test-woo` always resyncs; publish if >1h. No Woo taxonomy timestamp.
|
||||||
- Call 2 must run on a provider that supports **portable Call 1→Call 2 context** (full history resend default; stateful `previous_response_id` only if probe says so). `test-inference` fails closed if neither path works.
|
- Call 2 must run on a provider that supports **portable Call 1→Call 2 context** (full history resend default; stateful `previous_response_id` only if probe says so). `test-inference` fails closed if neither path works.
|
||||||
- LoRA deferred. No numeric/dimension invention by LLM; templates inject deterministic numbers.
|
- LoRA deferred. No numeric/dimension invention by LLM; templates inject deterministic numbers.
|
||||||
|
|
||||||
|
|||||||
+49
-30
@@ -80,19 +80,29 @@ and the final stored content.
|
|||||||
FastAPI refuses `http://` at startup. Not a Settings field; UI shows it read-only.
|
FastAPI refuses `http://` at startup. Not a Settings field; UI shows it read-only.
|
||||||
|
|
||||||
### 2.2 Taxonomy sync (FR25)
|
### 2.2 Taxonomy sync (FR25)
|
||||||
`POST /api/admin/taxonomy/sync` pulls, in order:
|
`POST /api/v1/admin/taxonomy/sync` pulls, in order:
|
||||||
1. `GET /products/categories?per_page=100` (paginate) → `woo_taxonomy(kind=category)`
|
1. `GET /products/categories?per_page=100` (paginate) → species + wood-category leaves
|
||||||
2. `GET /products/tags?per_page=100` → `kind=tag`
|
2. `GET /products/tags?per_page=100` → `fig-*` / `feat-*` only (other tags ignored)
|
||||||
3. `GET /products/attributes` → `kind=attribute`
|
3. `GET /products/attributes` (+ terms) → five locked attributes
|
||||||
Rebuilds the cache table (delete + insert, single transaction). Returns counts plus
|
|
||||||
`taxonomy_updated_at`. **Bump that timestamp only if a stored-subset row changed.**
|
|
||||||
A no-op sync (identical rows) leaves the anchor alone. Client re-pulls when the
|
|
||||||
anchor is newer than its snapshot.
|
|
||||||
|
|
||||||
**When to sync:** (1) always after successful `test-woo`; (2) opportunistic on
|
Rebuilds cache tables in one transaction. Failure → `502 woo_error`; previous
|
||||||
publish if last bump is older than 1 hour; (3) optional launch / Settings refresh.
|
cache retained (never delete-then-fail).
|
||||||
|
|
||||||
Failure → `502 woo_error`; previous cache retained (never delete-then-fail).
|
**Manufactured anchor.** Woo REST has **no** usable last-modified on categories,
|
||||||
|
attributes, or tags. FastAPI manufactures one `taxonomy_updated_at` /
|
||||||
|
`cache_anchor` for the whole cache. Bump **only** when a row-level change hits the
|
||||||
|
**stored subset** of cache columns (schema-driven: new columns auto-enter the
|
||||||
|
diff). Catch adds, edits, and deletions. A no-op sync does **not** bump. Client
|
||||||
|
re-pulls the full taxonomy iff the anchor advanced past its last value. One
|
||||||
|
timestamp governs all metadata kinds. Do not invent a Woo-side taxonomy timestamp.
|
||||||
|
|
||||||
|
**Triggers:** (1) successful `test-woo` **always** resyncs; (2) publish path if last
|
||||||
|
successful sync older than **1 hour** (bump only on real change); (3) optional app
|
||||||
|
launch / Settings refresh. Not every navigation.
|
||||||
|
|
||||||
|
**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 Taxonomy assignment (FR26)
|
### 2.3 Taxonomy assignment (FR26)
|
||||||
|
|
||||||
@@ -160,37 +170,46 @@ only. Do not diverge from AGENTS on force-draft. Do not conflate Woo create stat
|
|||||||
slab lifecycle status `published` (means Woo create succeeded locally).
|
slab lifecycle status `published` (means Woo create succeeded locally).
|
||||||
|
|
||||||
### 2.5 Dedupe & idempotency (FR24a)
|
### 2.5 Dedupe & idempotency (FR24a)
|
||||||
Before create: `GET /products?sku={sku}`.
|
Before create: `GET /products?sku={sku}` (any Woo status).
|
||||||
- Found → `409 duplicate_sku` (v1 has no update path; human resolves).
|
- Found → `409 duplicate_sku` (v1 has no update path).
|
||||||
- Not found → proceed. (Single-owner, slab-by-slab, so no TOCTOU concern in practice;
|
- Not found → proceed. (Single-owner, slab-by-slab; check still required.)
|
||||||
the check is still done for correctness.)
|
|
||||||
|
### 2.6 Post-submission recovery (409 / 422) and logging (FR28)
|
||||||
|
|
||||||
|
Business-logic failures after the user taps publish map to **field-level recovery**
|
||||||
|
on the review screen. No raw HTTP codes or stack traces in the UI. The draft stays
|
||||||
|
on the phone across error-and-retry.
|
||||||
|
|
||||||
|
| Class | When | UI recovery |
|
||||||
|
|---|---|---|
|
||||||
|
| **409 Conflict** | SKU already exists in Woo, any status | Inline on the **SKU** field. Copy names the conflict. Two paths: **edit SKU** and retry, or **open the existing listing** (store admin URL / product id). Never "update in place" of the other product in MVP. |
|
||||||
|
| **422 Validation** | Stale or rejected value (category/attribute deleted since sync, price rule, etc.) | Inline on the **affected field**. Copy: value is no longer valid; show **current options** from the synced taxonomy cache (or re-pull if anchor advanced). |
|
||||||
|
|
||||||
|
Server still writes `sync_log` and sets slab `failed` with machine codes for pollers.
|
||||||
|
Success path unchanged: set `woo_product_id`, local `published`, purge images.
|
||||||
|
|
||||||
### 2.6 Sync result & logging (FR28)
|
|
||||||
- On success: set `slabs.woo_product_id`, `status=published`, `published_at`, write
|
|
||||||
`sync_log(status=success, woo_product_id, payload_hash)`.
|
|
||||||
- On Woo HTTP error: `status=failed`, `sync_log(status=failed, http_status, error,
|
|
||||||
detail=<Woo error body>)`. Surface in UI + poll response.
|
|
||||||
- **Partial-failure note** (risk, see IMPL-PLAN): if media uploads succeed but the
|
- **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; a manual
|
final product POST fails, orphaned Woo attachments may exist. v1: log it; surface
|
||||||
cleanup note is surfaced. Not auto-deleted (could delete a real image).
|
a manual cleanup note. Not auto-deleted.
|
||||||
|
|
||||||
## 3. End-to-end publish sequence (server side)
|
## 3. End-to-end publish sequence (server side)
|
||||||
```
|
```
|
||||||
1. validate slab publishable (length,width,thickness,price,species,char,≥1 photo)
|
1. validate via shared validation module (hash check on submit; see ARCHITECTURE §8)
|
||||||
2. dedupe by SKU (GET /products?sku=) → 409 duplicate_sku if exists
|
2. dedupe by SKU (GET /products?sku=, any status) → 409 duplicate_sku if exists
|
||||||
3. (re)sync taxonomy if cache older than 1h → best-effort; use existing cache on fail
|
3. (re)sync taxonomy if last sync >1h; bump cache_anchor only on stored-subset change
|
||||||
4. resolve category/tags/attributes (auto-assign + overrides)
|
4. resolve category/tags/attributes (Call 1 / manual + name-match; synced set only)
|
||||||
5. upload processed PNG inventory images → media → source_urls
|
5. upload processed PNG inventory images → media → source_urls
|
||||||
6. POST /products (status per AGENTS §5 + woo_create_status) → woo_product_id
|
6. POST /products (status per AGENTS §5 + woo_create_status) → woo_product_id
|
||||||
7. write sync_log(success), set slab published, purge originals + processed images
|
7. write sync_log(success), set slab published, purge originals + processed images
|
||||||
on any failure at 2–6: write sync_log(failed), slab=failed, (images retained for retry)
|
on failure: sync_log(failed), slab=failed, images retained; map 409/422 to fields (§2.6)
|
||||||
```
|
```
|
||||||
|
|
||||||
## 4. What is deterministic vs scoped-inference here
|
## 4. What is deterministic vs scoped-inference here
|
||||||
- Deterministic: templates, all dimension text, category/tag/attribute resolution,
|
- Deterministic: templates, all dimension text, category/tag/attribute resolution
|
||||||
payload assembly, dedupe, logging, title assembly.
|
against the Woo-synced cache only, payload assembly, dedupe, logging, title assembly.
|
||||||
- Scoped inference: optional LLM prose generation for description only. The LLM
|
- Scoped inference: optional LLM prose generation for description only. The LLM
|
||||||
never touches dimensions.
|
never touches dimensions and may only choose species/attributes present in the
|
||||||
|
synced taxonomy lists.
|
||||||
|
|
||||||
## 4. Field-level Woo errors (presentation in `docs/UX.md`)
|
## 4. Field-level Woo errors (presentation in `docs/UX.md`)
|
||||||
|
|
||||||
|
|||||||
+21
-10
@@ -86,7 +86,8 @@ Online hybrid capture only. No OCR, ruler, or offline.
|
|||||||
Deliverables:
|
Deliverables:
|
||||||
|
|
||||||
- Online capture of 1–5 inventory photos (no airplane-mode requirement).
|
- Online capture of 1–5 inventory photos (no airplane-mode requirement).
|
||||||
- Client sheet/sliders, mask overlay + user confirm, length axis overlay + confirm.
|
- Client sheet + **four knobs inline** on capture (live re-run, mask overlay on
|
||||||
|
source photo); user confirm; length axis overlay + confirm. No U2Net control.
|
||||||
- Client sqft / bdft / 6" widths; 3:4 transparent PNG prep per TECH-SPEC.
|
- Client sqft / bdft / 6" widths; 3:4 transparent PNG prep per TECH-SPEC.
|
||||||
- Manual SKU, length, thickness; manual taxonomy as needed for one listing.
|
- Manual SKU, length, thickness; manual taxonomy as needed for one listing.
|
||||||
- Calibrated draft upload: originals + processed PNGs + client-sent numbers. Server
|
- Calibrated draft upload: originals + processed PNGs + client-sent numbers. Server
|
||||||
@@ -97,11 +98,13 @@ Deliverables:
|
|||||||
|
|
||||||
Acceptance:
|
Acceptance:
|
||||||
|
|
||||||
- Ty captures a real slab on phone (online), confirms mask + axis, sees client numbers.
|
- Ty captures a real slab on phone (online), adjusts any mask knob and sees a fresh
|
||||||
|
overlay without leaving the screen, confirms mask + axis, sees client numbers.
|
||||||
|
- Species picker offers **only** Woo-synced species (after test-woo / sync).
|
||||||
- One `SLAB-UAT-*` product appears in Woo admin as **draft** with correct SKU/price/images
|
- One `SLAB-UAT-*` product appears in Woo admin as **draft** with correct SKU/price/images
|
||||||
enough for Ty to open and inspect.
|
enough for Ty to open and inspect.
|
||||||
- No Call 1/Call 2 required. No offline, OCR, or ruler path exercised.
|
- No Call 1/Call 2 required. No offline, OCR, ruler, or U2Net path exercised.
|
||||||
- Duplicate SKU → stop with error; no second product.
|
- Duplicate SKU → stop with field-level recovery; no second product.
|
||||||
|
|
||||||
Exit gate: Ty opens one draft listing in Woo admin. Gate C is green.
|
Exit gate: Ty opens one draft listing in Woo admin. Gate C is green.
|
||||||
|
|
||||||
@@ -113,12 +116,14 @@ No server happy-path math. No `processing` slab status.
|
|||||||
Deliverables:
|
Deliverables:
|
||||||
|
|
||||||
- Routers per OPENAPI: slabs (CRUD + upload + poll), photos (serve), pricing-rules,
|
- Routers per OPENAPI: slabs (CRUD + upload + poll), photos (serve), pricing-rules,
|
||||||
settings (AES-GCM + test stubs), taxonomy (cache read/write).
|
settings (AES-GCM + test stubs), taxonomy (cache read/write + manufactured
|
||||||
|
`taxonomy_updated_at` / `cache_anchor`).
|
||||||
- Optimistic concurrency (`client_rev` / `server_rev`, 409 paths).
|
- Optimistic concurrency (`client_rev` / `server_rev`, 409 paths).
|
||||||
- Status machine only: `draft → calibrated → ready → publishing → published | failed`.
|
- Status machine only: `draft → calibrated → ready → publishing → published | failed`.
|
||||||
Job queue only for publish and inference (later). U2Net-on-demand is deferred from POC. Never for measure/crop.
|
Job queue only for publish and inference (later). U2Net-on-demand is deferred from POC. Never for measure/crop.
|
||||||
- Integration tests (httpx TestClient): create calibrated → poll → ready (manual fields);
|
- Integration tests (httpx TestClient): create calibrated → poll → ready (manual fields);
|
||||||
revision conflict; duplicate SKU; settings encryption round-trip (secret never returned).
|
revision conflict; duplicate SKU; settings encryption round-trip (secret never returned);
|
||||||
|
taxonomy anchor bumps on stored-subset change and stays put on no-op sync.
|
||||||
|
|
||||||
Acceptance:
|
Acceptance:
|
||||||
|
|
||||||
@@ -126,6 +131,8 @@ Acceptance:
|
|||||||
- Integration tests green; secrets never leak into any GET response (asserted).
|
- Integration tests green; secrets never leak into any GET response (asserted).
|
||||||
- `docker compose up` (backend + a throwaway frontend stub) serves the API.
|
- `docker compose up` (backend + a throwaway frontend stub) serves the API.
|
||||||
- PUT stores client-sent derived fields; does not recompute sqft/bdft/widths as SoT.
|
- PUT stores client-sent derived fields; does not recompute sqft/bdft/widths as SoT.
|
||||||
|
- Taxonomy: anchor advances only on real stored-subset change; client re-pulls only
|
||||||
|
when anchor is newer; `test-woo` always resyncs; publish respects the 1h threshold.
|
||||||
|
|
||||||
Exit gate: Ty drives the API with curl through a full calibrated → ready cycle (manual).
|
Exit gate: Ty drives the API with curl through a full calibrated → ready cycle (manual).
|
||||||
|
|
||||||
@@ -200,11 +207,14 @@ Deliverables:
|
|||||||
- Media upload of processed PNG inventory photos; product create via `woo_create_status`
|
- Media upload of processed PNG inventory photos; product create via `woo_create_status`
|
||||||
(default **draft**). If SKU matches `SLAB-UAT-*`, force Woo **draft** even when
|
(default **draft**). If SKU matches `SLAB-UAT-*`, force Woo **draft** even when
|
||||||
Settings say publish.
|
Settings say publish.
|
||||||
- Dedupe by SKU before create; `409 duplicate_sku`; no edit-same-SKU path in MVP.
|
- Dedupe by SKU before create (any Woo status); `409 duplicate_sku` with field recovery
|
||||||
|
paths (edit SKU / open existing); no edit-same-SKU path in MVP.
|
||||||
|
- `422 stale_field` recovery with current options from cache (CONTENT-WOO §2.6).
|
||||||
- `sync_log` with payload_hash; on success set local `published`, purge originals +
|
- `sync_log` with payload_hash; on success set local `published`, purge originals +
|
||||||
processed images on server.
|
processed images on server.
|
||||||
- `POST /api/admin/taxonomy/sync` against the real store.
|
- `POST /api/v1/admin/taxonomy/sync` against the real store; manufactured anchor.
|
||||||
- Integration tests against a Woo mock (respx) for success + error + duplicate paths.
|
- Integration tests against a Woo mock (respx) for success + error + duplicate +
|
||||||
|
stale-field paths.
|
||||||
- Partial-failure handling (orphaned media noted, per CONTENT-WOO §2.6).
|
- Partial-failure handling (orphaned media noted, per CONTENT-WOO §2.6).
|
||||||
|
|
||||||
Acceptance:
|
Acceptance:
|
||||||
@@ -212,7 +222,8 @@ Acceptance:
|
|||||||
- Real UAT publish: product appears in store admin as **draft** under `SLAB-UAT-*`,
|
- Real UAT publish: product appears in store admin as **draft** under `SLAB-UAT-*`,
|
||||||
correct SKU/price/PNG images/category/tags/attributes; `woo_product_id` stored;
|
correct SKU/price/PNG images/category/tags/attributes; `woo_product_id` stored;
|
||||||
`sync_log` written with payload_hash.
|
`sync_log` written with payload_hash.
|
||||||
- Duplicate SKU publish → 409, no second product created.
|
- Duplicate SKU publish → 409 mapped to SKU field, no second product created.
|
||||||
|
- Stale taxonomy value → 422 on that field with current options; draft retained.
|
||||||
- Woo error (bad creds / 500) → slab `failed` with detail; retry path works.
|
- Woo error (bad creds / 500) → slab `failed` with detail; retry path works.
|
||||||
- All Woo integration tests green (mock + one live smoke).
|
- All Woo integration tests green (mock + one live smoke).
|
||||||
|
|
||||||
|
|||||||
+9
-5
@@ -120,11 +120,15 @@ only the descriptive portion — dimensions, species, and measurements are
|
|||||||
injected by deterministic templates.
|
injected by deterministic templates.
|
||||||
|
|
||||||
### Inputs assembled by the server
|
### Inputs assembled by the server
|
||||||
- Curated Call 1 results (species, figure, character, grade)
|
- Portable Call 1 context (default: **full history resend** of Call 1 turn +
|
||||||
- Deterministic measurements: length, widths, sqft, bdft, thickness
|
images metadata + assistant JSON). `previous_response_id` only if
|
||||||
- Brand voice
|
`test-inference` recorded stateful support.
|
||||||
- GEO context
|
- Explicit **confirmed vs inferred** labels for species, dimensions, and other
|
||||||
- Title template and short description template
|
attributes (user overrides marked confirmed).
|
||||||
|
- Deterministic measurements: length, widths, sqft, bdft, thickness (never invented)
|
||||||
|
- Brand voice, GEO context, title/short templates
|
||||||
|
- Shared validation module rules so Call 2 assembly rejects the same invalid
|
||||||
|
values the UI would
|
||||||
|
|
||||||
### System prompt (template)
|
### System prompt (template)
|
||||||
```
|
```
|
||||||
|
|||||||
Reference in New Issue
Block a user