docs: finish Gate A contradiction patches

Align PRD/README/CONTENT-WOO/DATA-MODEL/OPENAPI/DEPLOYMENT with AGENTS.md
and the POC review package: hybrid client SoT, bdft without /12, server AES
creds, woo_create_status draft|publish with SLAB-UAT-* firebreak, deploy smoke
and health path /api/health.
This commit is contained in:
2026-09-01 03:59:46 +00:00
parent 457711bd78
commit 4ec478f383
6 changed files with 68 additions and 47 deletions
+25 -23
View File
@@ -1,44 +1,46 @@
# SlabUploader
Mobile-first, self-hosted app for publishing one-of-a-one wood slabs to WooCommerce.
Field capture (offline) → deterministic measurement/pricing/normalization → publish a
single slab to the store as a pending product.
Phone capture (online) → hybrid measure/crop on device → review → WooCommerce create.
Owner: Ty (Whidbey). Store: www.whidbeywoodstore.com. Deploy: .201 (Caddy, Docker).
## Status
**Planning complete — ready for implementation.** All 20 product decisions locked
(PRD "Decisions log", 2026-08-24). Build plan in `docs/IMPL-PLAN.md` (phases 0–6).
**POC requirements freeze in progress (Gate A, 2026-08-31).** Read `AGENTS.md` before
any implementation. TECH-SPEC + ARCHITECTURE beat stale PRD/README lines. Do **not**
treat older “planning complete / ready for implementation” wording as a green light
to code the obsolete server-pipeline Phase 0.
POC bar: phone → customer-ready listing on the real store; UAT creates as **Woo draft**
with `SLAB-UAT-*` SKUs; one real SKU **Woo publish** after Ty review. Online-only for POC
(offline deferred). Build plan: `docs/IMPL-PLAN.md` (Phase 0 = hybrid Gates A+B).
## Documentation
| File | What |
|---|---|
| `Slab capture app PRD.txt` | Requirements + locked decisions (source of truth for "what") |
| `docs/ARCHITECTURE.md` | Services, client/server split, offline PWA, data flow |
| `docs/TECH-SPEC-PIPELINE.md` | Deterministic math + image pipeline, constants, 9 test vectors |
| `docs/DATA-MODEL.md` | SQLite schema + status state machine |
| `docs/OPENAPI.md` | Client↔server API contract (deliverable #2) |
| `AGENTS.md` | Working contract + hard rules (read first) |
| `Slab capture app PRD.txt` | Requirements + decisions (intent; superseded lines called out in AGENTS) |
| `docs/ARCHITECTURE.md` | Hybrid client/server split, data flow |
| `docs/TECH-SPEC-PIPELINE.md` | Deterministic math + image pipeline, constants, test vectors |
| `docs/DATA-MODEL.md` | SQLite schema + slab status state machine |
| `docs/OPENAPI.md` | Client↔server API contract |
| `docs/CONTENT-WOO.md` | Content templates + WooCommerce payload/integration |
| `docs/PROMPTS.md` | Call 1 vision prompt + Call 2 content prompt (server files) |
| `docs/DEPLOYMENT.md` | Compose, Caddy, backup/restore, key rotation (deliverable #4) |
| `docs/PROMPTS.md` | Vision + content prompts (server files) |
| `docs/DEPLOYMENT.md` | Compose, Caddy, backup/restore, key rotation |
| `docs/IMPL-PLAN.md` | Phased build plan with acceptance criteria + exit gates |
| `AGENTS.md` | Working contract + hard rules for anyone implementing |
## Quick orientation
- **Deterministic core** (measurements, pricing, normalization, Woo sync) is pure,
network-free, and unit-tested. Inference (species/character + optional content) is
the *only* scoped-LLM surface, and it's toggleable — the app fully works without it.
- **Offline-first PWA**: capture works with no network; drafts + photos persist in
IndexedDB and sync when online.
- **Hybrid:** phone owns mask/sliders/sqft/bdft/3:4 PNG; server stores, Woo, inference proxy, on-demand U2Net.
- **Deterministic core** is unit-tested and network-free. Inference is toggleable; app works with it off.
- **Online-only for POC** (offline PWA deferred).
- **Two containers** (`frontend`, `fastapi`) + Caddy. Single SQLite + image volumes.
- **v1 non-goals**: no versioning, no staff/roles, no bulk publish, no analytics,
no update path (create-only, SKU-deduped).
- **v1 non-goals:** no versioning, no staff/roles, no bulk publish, no analytics, no SKU update path.
## Repo remotes
- Canonical (source of truth): Gitea `Ty_Tech/SlabUploader` on **gitea-atd** (`https://gitea.vps1.afterthedemo.com/Ty_Tech/SlabUploader`)
- Canonical SoT: Gitea `Ty_Tech/SlabUploader` on **gitea-atd** (`https://gitea.vps1.afterthedemo.com/Ty_Tech/SlabUploader`)
- Optional later mirrors: `gitea-truenas`, GitHub — not SoT for this workstream
## Getting started (implementer)
1. Read `AGENTS.md` (hard rules) and `docs/IMPL-PLAN.md` (phases).
2. Start at Phase 0 — scaffold + the tested deterministic pipeline.
3. Don't skip the exit gates; Ty signs each phase.
1. Read `AGENTS.md` (hard rules) and Phase 0 in `docs/IMPL-PLAN.md`.
2. Start at Phase 0 — hybrid client TECH-SPEC modules + thin FastAPI health/settings (not a server happy-path pipeline).
3. Do not skip exit gates; Ty signs each phase via the delivery process.
+25 -10
View File
@@ -16,6 +16,13 @@ publish pipeline.
- Images are purged after successful publish (retained only while draft/retry).
- Deploy target: .201, Docker Compose + Caddy.
## Decisions log (2026-08-31, POC freeze via CoS/PPM/Dev Manager)
- Doc contradiction freeze: TECH-SPEC + ARCHITECTURE win over stale PRD/IMPL-PLAN lines.
- AGENTS.md added; bdft = sqft × thickness_in; secrets server AES-GCM; online-only POC;
woo_create_status draft|publish (default draft); SLAB-UAT-* force Woo draft on PROD;
hybrid client happy-path; inference OFF for first claimable publish, Call 1/2 after.
- See `/workspace/slabuploader-poc-review-package.md` review package (not in repo) and AGENTS.md.
## Decisions log (2026-08-27, Ty)
1. No calibration photos — user manually enters SKU, length, thickness.
2. First photo is the measurement photo (must be top-down, lens parallel to broad side).
@@ -32,10 +39,13 @@ publish pipeline.
10. Call 1 runs automatically after calibration **only if** inference is enabled.
User reviews/corrects before triggering Call 2.
11. Inference timeout >45s or non-200 → error with retry. User can fill fields manually.
12. Woo product status set to "published" by default (configurable in settings).
12. Woo create status: Settings key `woo_create_status` = draft | publish, default **draft**.
UAT on PROD uses Woo draft + SKU prefix SLAB-UAT-* (forced draft). Final POC: one real
SKU → Woo publish after Ty review. (Supersedes older "published by default" / pending smoke.)
13. Inference prompts are server-side text files, read fresh on each invocation.
14. Brand voice + GEO context injected into Call 2 prompt.
15. WooCommerce credentials (username + App Password) stored client-side in settings.
15. WooCommerce credentials stored server-side only (AES-GCM in SQLite via Settings UI).
Never in the browser. (Supersedes older client-side storage decision.)
16. Inference endpoint: configurable OpenAI-compatible API (user provides base URL, key, model).
17. Test connection verifies text completion + vision support.
18. Offline capture not required for MVP.
@@ -97,7 +107,8 @@ publish pipeline.
- **FR2:** User manually enters SKU (3-24 chars, [A-Z0-9-]), length (in), thickness (in).
- **FR3:** App detects background color from image borders (green or black sheet).
- **FR4:** First photo: BG removal - find longest dimension - compute scale
(longest_px / user-length) - compute area (sqft) - bdft = sqft * thickness / 12.
(longest_px / user-length) - compute area (sqft) - bdft = sqft * thickness_in
(TECH-SPEC authoritative; the old "/ 12" formula is wrong and must not be implemented).
- **FR5:** All photos: BG removal - crop to 80% frame fill - save as transparent
PNG, minimum 1600 px on smaller dimension, 3:4 portrait crop (configurable).
- **FR6:** User can retake the first photo or abort and start fresh.
@@ -169,7 +180,8 @@ publish pipeline.
- **FR34:** Publish is blocked if the slab SKU already exists (simple dedupe, no update path).
- **FR35:** Create WooCommerce product with SKU, title, description, short description,
price, categories, attributes, tags, and normalized PNG images.
- **FR36:** Default product status: "published" (configurable in settings).
- **FR36:** Default Woo create status: "draft" via `woo_create_status` (draft | publish).
SLAB-UAT-* SKUs always create as Woo draft. Real inventory may use publish after Ty review.
- **FR37:** Log sync results and surface errors; store WooCommerce product ID on success.
### Drafts, review, and publish
@@ -199,12 +211,14 @@ publish pipeline.
- SvelteKit (mobile-first)
- TailwindCSS for UI
- Camera API for capture and immediate preview
- No OCR, no OpenCV on client
- Hybrid client (ARCHITECTURE / TECH-SPEC): capture, sheet/sliders, mask confirm,
length axis, sqft/bdft/widths, 3:4 PNG normalization on the phone. No OCR/ruler
in POC. Offline PWA deferred.
**Server (self-hosted, containerized)**
- FastAPI (Python) for deterministic image pipeline, bdft math, pricing,
background removal, crop/normalize, WooCommerce REST integration,
inference wrappers, taxonomy sync
- FastAPI (Python): draft store, on-demand U2Net ("try harder"), WooCommerce REST,
inference proxy, taxonomy sync, AES-GCM settings. Server does **not** own the
happy-path measure/crop pipeline (client is math SoT).
- SQLite for v1 (slab records, pricing rules, taxonomy cache, sync history)
**Inference**
@@ -235,8 +249,9 @@ updated_at: timestamp
1. New slab: user stages slab and opens app.
2. Capture: take 1-5 inventory photos.
3. Enter metadata: SKU, length, thickness.
4. Calibration: server processes first photo (BG removal, area, bdft),
processes all photos (BG removal, crop, PNG).
4. Calibration: on phone — BG removal + sliders, confirm mask, confirm length axis,
enter length/thickness/SKU; client computes sqft/bdft/widths and 3:4 PNGs.
Optional server U2Net "try harder" on one photo if mask fails.
5. Inference: Call 1 auto-runs (vision), populating taxonomy/content.
User reviews/corrects. Call 2 user-triggered (text), generating
title/description/short description.
+7 -5
View File
@@ -92,7 +92,7 @@ Failure → `502 woo_error`; previous cache retained (never delete-then-fail).
{
"name": "{title}",
"type": "simple",
"status": "pending", // test-target decision: NEVER "publish" automatically
"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}",
@@ -115,9 +115,11 @@ For reliability the server:
2. Reference those `source_url` values in `images` (≤5, per PRD 1–5 inventory photos).
3. Calibration photos are excluded (FR2).
**Status = `pending`** by default (test-target decision: test products must never go
live without human action). The Settings key `woo_publish_status` (default `pending`)
controls this; Ty flips it to `publish` only when intentionally going live.
**Status** comes from Settings key **`woo_create_status`** (`draft` | `publish`, default
**`draft`**). UAT on PROD uses Woo **draft** plus SKU prefix `SLAB-UAT-*` (hard rule:
`SLAB-UAT-*` always creates as Woo draft even if Settings say publish). Flip to
`publish` only for intentional live inventory after Ty review. Do not conflate with
slab lifecycle status `published` (means Woo create succeeded locally).
### 2.5 Dedupe & idempotency (FR24a)
Before create: `GET /products?sku={sku}`.
@@ -141,7 +143,7 @@ Before create: `GET /products?sku={sku}`.
3. (re)sync taxonomy if cache older than 1h → best-effort; use existing cache on fail
4. resolve category/tags/attributes (auto-assign + overrides)
5. upload normalized inventory images → media → source_urls
6. POST /products (status=pending) → woo_product_id
6. POST /products (status=woo_create_status) → woo_product_id
7. write sync_log(success), set slab published, purge images
on any failure at 2–6: write sync_log(failed), slab=failed, (images retained for retry)
```
+1 -1
View File
@@ -179,7 +179,7 @@ Keys:
- `inference_base_url`, `inference_api_key`, `inference_model`
- `inference_enabled`, `content_llm_enabled`
- `brand_voice`, `geo_context`
- `publish_status` ('published' | 'draft')
- `woo_create_status` ('draft' | 'publish'), default `draft` — Woo product create status; not slab lifecycle
- `aspect_ratio` ('3:4'), `output_px` (1600), `output_px_min` (1600), `fill_target` (0.80)
- `user_sensitivity`, `user_edge_offset`, `user_feather`, `user_sheet_mode`
+8 -6
View File
@@ -2,6 +2,8 @@
Status: SPEC (deliverable #4) · 2026-08-24 · SlabUploader
Target: TyUBUMini at 192.168.1.201. Domain: `slab.tyubumini.local` (LAN/Tailscale
> Gate A freeze (2026-08-31): hybrid client owns measure/crop SoT. Do not scaffold a server ruler/pipeline as authoritative. Woo UAT = draft + SLAB-UAT-* on PROD.
only — Decision). Inference is NOT on .201; it's a remote configurable endpoint
(.202 or elsewhere).
@@ -39,7 +41,7 @@ SlabUploader/
└── backend/ # FastAPI app (Phase 0)
├── app/
│ ├── main.py
│ ├── pipeline/ # deterministic core (constants.py, ruler, geometry, widths, pricing, normalize)
│ ├── (no happy-path pipeline SoT here) # hybrid: TECH-SPEC modules live in frontend/ or shared pure TS; backend = store/Woo/inference/U2Net stub
│ ├── woo/ # Woo client, taxonomy, publish
│ ├── inference/ # vision + content wrappers (toggleable)
│ ├── prompts/ # call1.txt, call2.txt (shipped defaults)
@@ -146,12 +148,12 @@ provide `python -m app.security.rekey NEW_KEY` (reads old key from env
| Reset one slab | delete its photos dir + `UPDATE slabs SET status='draft'` (admin script) |
## 8. Smoke test (after every deploy)
1. `GET /api/health` → ok
2. Settings: Woo creds configured + `test-woo` ok
1. `GET /api/health` → ok (from phone over HTTPS once CA trust is proven)
2. Settings: Woo creds + `test-woo` ok; `woo_create_status=draft`; inference OFF for early gates
3. Create a slab → upload test photos (fixture set) → poll to `ready`
4. Verify bdft/price match the fixture's expected values (golden test)
5. Publish test slab → `published`, Woo product visible as **pending** in store admin
6. Delete/leave the pending test product as a staging artifact (never published)
4. Verify bdft/price match the fixture's expected values (golden test; bdft = sqft × thickness)
5. Publish test slab with SKU `SLAB-UAT-…` → slab may show local `published`; Woo product must be **draft**
6. Leave or delete the Woo draft UAT product (never customer-visible). Final live publish is a non-UAT SKU after Ty review.
## 9. Scaling path (not v1)
- Postgres when SQLite write contention appears (schema already portable).
+2 -2
View File
@@ -126,7 +126,7 @@ the call returns. WooCommerce is the authoritative source for `feat-*` definitio
"inference_model": "<model>", "inference_enabled": true,
"content_llm_enabled": false,
"brand_voice": "...", "geo_context": "...",
"publish_status": "published",
"woo_create_status": "draft",
"aspect_ratio": "3:4", "output_px": 1600, "output_px_min": 1600,
"fill_target": 0.80,
"user_sensitivity": 0.5, "user_edge_offset": 0, "user_feather": 1,
@@ -139,7 +139,7 @@ Secrets (keys) are never returned. Only a `*_configured` boolean.
## Endpoints
### Health & meta
- `GET /api/v1/health` → `{ "status":"ok", "version":"...", "woo_reachable": bool }`
- `GET /api/health` → `{ "status":"ok", "version":"...", "woo_reachable": bool }` (canonical deploy/smoke path; may also be exposed as `/api/v1/health`)
- `GET /api/v1/meta` → `{ "version":"...", "templates_configured": bool }`
### Slabs