diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..fe45317 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,148 @@ +# AGENTS.md — SlabUploader + +Working contract for coding agents. Read before writing code. + +Status: **Gate A freeze** · signed 2026-08-31 PT · SoT: Gitea `Ty_Tech/SlabUploader` on **gitea-atd** (not mirrors). + +--- + +## 0. Doc precedence (hard) + +When docs disagree, use this order until stale docs are rewritten: + +1. **`docs/TECH-SPEC-PIPELINE.md`** — deterministic math, image prep, constants, test vectors +2. **`docs/ARCHITECTURE.md`** — hybrid topology, what-runs-where, secrets, status machine +3. This **`AGENTS.md`** — hard rules for agents +4. **`docs/DATA-MODEL.md`**, **`docs/OPENAPI.md`**, **`docs/CONTENT-WOO.md`**, **`docs/PROMPTS.md`**, **`docs/DEPLOYMENT.md`** +5. **`docs/IMPL-PLAN.md`** — Phase 0 hybrid rewrite is authoritative; Phases 1+ TBD rewrite — **do not implement old Phase 0–2** (server happy-path pipeline, offline PWA, OCR, ruler) +6. **PRD / README** — product intent only; do **not** implement lines that contradict TECH-SPEC or ARCHITECTURE + +Known stale / do-not-implement without rewrite: + +- PRD FR4 `bdft = sqft × thickness / 12` → **wrong**. TECH-SPEC: `bdft = sqft × thickness_in` (no `/12`). +- PRD decision 15 “creds client-side” → **wrong**. Server AES-GCM only; never browser storage for secrets. +- PRD / README “server does normalization / offline-first PWA / OCR / ruler” → superseded by hybrid + online-only MVP. +- IMPL-PLAN Phase 0–2 text that describes server `pipeline/` CLI, ruler, offline PWA, Tesseract, OpenCV.js ruler → obsolete (Phase 0 rewritten 2026-08-31). +- CONTENT-WOO / DEPLOYMENT smoke “pending” as default create → superseded: UAT uses **Woo draft** + `SLAB-UAT-*`. + +--- + +## 1. Topology (locked — hybrid) + +**Client (SvelteKit) owns the happy path:** + +- Capture 1–5 photos +- Sheet detect + chroma-key / black threshold + sliders (sensitivity, edge offset, feather) +- Mask overlay + **user confirm** before measurement +- Length axis overlay (rotate / confirm) +- sqft, bdft, 6" width samples +- 3:4 transparent PNG, 80% fill at extremes, shorter side ≥ 1600 when source allows +- Draft payload: originals + processed PNGs + length/thickness/SKU/sqft/bdft/widths + +**Server (FastAPI) owns:** + +- Store drafts / settings / taxonomy cache +- **U2Net on demand only** (“Try harder” / coverage failure) — one photo in, mask out; sliders still apply on client +- WooCommerce (taxonomy sync, SKU check, create product) — browser never calls Woo +- Inference **proxy** (Call 1 / Call 2) — keys never in browser +- Prompt files (server text files, read fresh) + +Happy path never leaves the phone until the user continues after confirmed mask + numbers. + +--- + +## 2. What NOT to build (early phases / MVP) + +| Deferred / killed for early phases | Notes | +|---|---| +| Offline capture / PWA / IndexedDB sync | Online-only MVP | +| OCR (SKU) | Manual SKU entry | +| Ruler detection / OpenCV on client for scale | Manual length; scale = longest_axis_px / L | +| Server-side happy-path pipeline | No server chroma-key / crop / bdft math as SoT | +| Fake upscale / inventing pixels | Warn + retake if < 1600 after crop | +| LoRA / fine-tune | Deferred | +| Figure-/category-weighted pricing | v1.5 | +| Auto-dimension overlay | v1.5 | +| Bulk publish, staff roles, analytics, SKU update path | Non-goals | + +--- + +## 3. Deterministic core (non-negotiable) + +Encode TECH-SPEC exactly: + +- **bdft = sqft × thickness_in** (identity: 1 sqft @ 1" = 1 bdft). **No `/12`.** +- Units: inches in 1/8" steps; sqft/bdft 2 decimals; price half-up 2 decimals. +- Manual length, thickness, SKU, price always win. +- Image prep: sheet/sliders → mask confirm → length axis confirm → 3:4 PNG / 80% fill / ≥1600px when source allows. +- **No inventing pixels. No fake upscale.** Undersized source → warn + retake; photo not publishable until retake. +- Pricing: species `$/bdft` from Settings, seeded by Woo species sync; `rec = bdft × price_per_bdft`; user override sticks; missing rule → empty rec, manual allowed. +- Port TECH-SPEC **TV-1…TV-9** (TV-10 only after TECH-SPEC defines it). Client (or shared pure modules the client uses) is authoritative for math; server **stores** client-sent numbers — does not recompute as SoT. + +--- + +## 4. Secrets & settings + +**Never in browser storage / JS bundles:** + +- Woo consumer key + secret +- Inference base URL + API key +- AES master key (`SLAB_AES_KEY` env only; never in DB or repo) + +**Server:** AES-GCM at rest in SQLite for Woo + inference secrets. Settings UI POSTs to FastAPI; server encrypts. All Woo and inference calls are server-side. + +**Browser may persist (non-secret):** sheet mode, sensitivity, edge offset, feather (localStorage OK; reset-to-default required). + +--- + +## 5. Woo UAT & create status (Ty locked 2026-08-31) + +- Store: **PROD** `www.whidbeywoodstore.com` +- Mid-phase UAT creates: Woo status **draft**, SKU prefix **`SLAB-UAT-*`** +- Settings field **`woo_create_status`**: **`draft | publish`**, default **`draft`** +- Hard rule: if SKU matches `SLAB-UAT-*`, force **Woo draft** even if Settings say publish +- Final POC success: one real (non-UAT) SKU → **Woo publish** only after Ty review +- Duplicate SKU → stop, surface error; no edit-same-SKU in MVP +- After successful publish: purge originals + processed images on server +- Do **not** conflate with slab lifecycle status (below) + +--- + +## 6. Inference (inside POC, sequenced) + +- Feature-flagged; **full suite must pass with inference OFF**. +- **Gate C** claimable path: inference **OFF**; manual taxonomy + deterministic numbers + Woo draft path. +- **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). +- LoRA deferred. No numeric/dimension invention by LLM; templates inject deterministic numbers. + +--- + +## 7. Slab status machine (app-internal only) + +`draft → calibrated → ready → publishing → published` (or `failed` with detail). + +- **calibrated:** mask + axis confirmed; length/thickness/SKU entered; client computed sqft/bdft/widths; draft uploaded. +- **ready:** mandatory fields filled (manual and/or Call 1); inference optional. +- Inference does not gate publish. +- Never confuse slab `draft` with **Woo draft** (`woo_create_status`). + +--- + +## 8. Test & delivery expectations + +- No feature without a passing test and shown execution output (PR/commit message). +- Deterministic core unit-tested before UI consumes it. +- One phase = reviewable, shippable increment; Ty exit-gates phases. +- Phase naming: Phase 0–6 stay delivery chapters. **Phase 0 = Gates A+B.** **Gate C = early Phase 1** claimable publish slice. +- Do not write secrets, real Woo keys, or AES keys into the repo. +- Prefer fixing docs when code and locked decisions diverge — do not silently reintroduce `/12`, offline, ruler, or server happy-path pipeline. + +--- + +## 9. Deploy sketch (see DEPLOYMENT.md) + +- Target: `.201`, Docker Compose (`frontend` + `fastapi`) + Caddy → `slab.tyubumini.local` (LAN/Tailscale). +- Inventory host ports/Caddy before binding (bolt.diy collision risk). +- Health: `GET /api/health`. +- Inference is a remote configurable endpoint, not on `.201` by default. diff --git a/docs/IMPL-PLAN.md b/docs/IMPL-PLAN.md index a499aea..9f01bfd 100644 --- a/docs/IMPL-PLAN.md +++ b/docs/IMPL-PLAN.md @@ -1,6 +1,7 @@ # Implementation Plan -Status: SPEC · 2026-08-24 · SlabUploader +Status: SPEC · Gate A freeze 2026-08-31 · SlabUploader +Phase 0 rewritten for hybrid topology (client-authoritative TECH-SPEC). Phases 1+ still contain stale offline/OCR/ruler/pending language — do not implement those lines; follow AGENTS.md. The build plan a professional (or a fresh Hermes session) executes against. Each phase has: goal, deliverables, acceptance criteria (testable), and exit gate. Ordering is by dependency — do not skip ahead. Definition of done for EVERY phase: @@ -14,27 +15,55 @@ Ground rules (from AGENTS.md): --- -## Phase 0 — Skeleton & deterministic core (server) -Goal: repo scaffold + the tested deterministic pipeline, no UI, no Woo. +## Phase 0 — Hybrid skeleton & client deterministic core (= Gates A+B) + +Goal: scaffold the hybrid app and ship a **tested client-authoritative** TECH-SPEC +pipeline (mask → axis → sqft/bdft → 3:4 PNG) plus a minimal FastAPI store/settings +shell. No Woo publish, no inference, no offline/OCR/ruler. + +**Gate A (docs):** AGENTS.md + this Phase 0 rewrite + review package locks (signed +2026-08-31). **Gate B (infra/code):** items below. + +Scope — in: +- Repo layout: `frontend/` (SvelteKit), `backend/` (FastAPI), `deploy/` stubs; docs stay SoT. +- Client pure modules implementing TECH-SPEC: constants, sheet/chroma/black + sliders, + mask confirm hooks, length axis, scale, 6" widths, sqft, **bdft = sqft × thickness_in**, + pricing rec from species $/bdft, 3:4 PNG / 80% fill / ≥1600 guard (**no upscale**). +- FastAPI: app factory, logging, config, `GET /api/health`, SQLite + alembic skeleton, + settings stub with AES-GCM round-trip (secrets never returned on GET), draft store stub + (accept client numbers + files — store only, do not recompute SoT). +- `deploy/docker-compose.yml`, `Caddyfile.fragment`, `.env.example` (placeholders only). +- Unit tests for TECH-SPEC **TV-1…TV-9** (esp. TV-3 bdft, TV-6/7 crop/no-upscale, TV-8 pricing). +- Fixture photo set for Ty Gate B review. + +Scope — out: +- Server happy-path pipeline / CLI that recomputes mask/crop/bdft as SoT. +- Real U2Net (stub OK; on-demand later). +- Woo create/publish, live taxonomy sync (mock/seed species list OK later). +- Inference Call 1/2, LoRA. +- Offline PWA, IndexedDB sync, OCR, ruler. +- Fake upscale / inventing pixels. Deliverables: -- `backend/` FastAPI skeleton: app factory, health endpoint, config, logging. -- `pipeline/` implementing TECH-SPEC: constants.py, ruler→scale, geometry, width - sampler, bdft math, pricing, green-removal, crop/normalize, merch filter. -- `db/`: schema.sql + alembic migration for all tables (DATA-MODEL). -- `tests/`: port every TECH-SPEC test vector (TV-1…TV-9) to pytest; add property - tests (bdft monotonic in inputs; width sampler returns 3–5 points; crop fill ∈ - [0.75,0.85]). -- Dockerfile for backend; runs `pytest` in CI/local. +- `frontend/` scaffold + deterministic modules + unit tests TV-1…TV-9. +- `backend/` FastAPI health + settings encryption + draft persistence stub + schema migration baseline. +- `deploy/` compose + `.env.example` + Caddy fragment. +- Shown output in PR/commit: printed TV-3, TV-6, and TV-7 (no-upscale/retake) on **real fixtures**. Acceptance: -- `pytest` green, covering all 9 TVs + property tests; coverage of `pipeline/` ≥ 95%. -- `GET /api/health` works on the dev container. -- A fixture photo set (3–5 real slab photos) runs end-to-end through the pipeline - CLI (`python -m app.pipeline.run `) and prints bdft/widths/normalized outputs - matching hand-computed expectations. +- Gate B: client (or shared pure) tests green for TECH-SPEC vectors; bdft identity holds + (no `/12`); undersized source warns and refuses fake upscale. +- `GET /api/health` up in compose over HTTPS hostname after `.201` inventory + Caddy merge. +- Settings AES round-trip; secret fields never appear in GET. +- Draft can store client-uploaded originals + PNGs + numbers without server recompute. +- Coverage target: deterministic modules ≥ 95% where practical; property checks + (bdft monotonic in thickness/sqft; fill band; no upscale path). -Exit gate: Ty reviews the pipeline CLI output on real photos; math confirmed. +Exit gate: Ty confirms Gate B math/image prep shown output on real fixtures; health + +encrypted settings + draft store stub demonstrated (curl or thin UI). + +**Next (not Phase 0):** Gate C = early Phase 1 claimable publish — Woo **draft** + +`SLAB-UAT-*` on prod, inference OFF. Call 1/2 after Gate C, still inside POC. ## Phase 1 — API surface (no Woo, no inference) Goal: full slab CRUD + photo upload + settings, backed by the Phase-0 core. @@ -55,6 +84,8 @@ Acceptance: Exit gate: Ty drives the API with curl through a full draft→ready cycle. ## Phase 2 — Frontend: capture & offline PWA +> **SUPERSEDED for POC (Gate A 2026-08-31):** online-only; no OCR/ruler/offline PWA. Do not implement this section as written. Capture UX moves to early Phase 1 / Gate C shape per AGENTS.md. + Goal: mobile-first capture flow, fully offline, with the local light pass. Deliverables: @@ -93,26 +124,27 @@ Acceptance: - Inference disabled → review screen fully functional with manual species/character. - Every field is editable and the override persists (asserted in a test). -Exit gate: Ty publishes a test slab to the real store as a **pending** product. +Exit gate: Ty publishes a test slab to the real store as a **Woo draft** (`SLAB-UAT-*`) product. +(Gate A lock: not pending/published for UAT.) ## Phase 4 — WooCommerce integration (real) Goal: production Woo sync, taxonomy, dedupe, logging (FR24–28). Deliverables: - Woo client (httpx, Basic auth, timeouts), taxonomy sync, auto-assign, media upload, - product create (status=pending default), dedupe, sync_log. + product create via `woo_create_status` (default **draft**; `SLAB-UAT-*` forces draft), dedupe, sync_log. - `POST /api/admin/taxonomy/sync` against the real store. - Integration tests against a Woo mock (respx) for success + error + duplicate paths. - Partial-failure handling (orphaned media noted, per CONTENT-WOO §2.6). Acceptance: -- Real publish: product appears in store admin as pending, correct SKU/price/images/ +- Real UAT publish: product appears in store admin as **draft** under `SLAB-UAT-*`, correct SKU/price/images/ category/tags; `woo_product_id` stored; `sync_log` written with payload_hash. - Duplicate SKU publish → 409, no second product created. - Woo error (bad creds / 500) → slab `failed` with detail; retry path works. - All Woo integration tests green (mock + one live smoke). -Exit gate: Ty verifies a real pending product in the store is correct. +Exit gate: Ty verifies a real **Woo draft** `SLAB-UAT-*` product in the store is correct. ## Phase 5 — Inference (vision + optional content) Goal: scoped, toggleable inference with guardrails.