Files
SlabUploader/docs/ARCHITECTURE.md
T
grokbot_ty 7b7ed712c2 docs(#10): UX interaction spec plus remaining POC issue write-back
UAT is POC, so #10 is in DOC COMPLETE. Capture knobs live on-screen, review
confidence presentation, 409/422 field recovery, taxonomy cache, portable
Call 1→Call 2, shared validation. U2Net Try harder deferred, not deleted.
2026-09-02 00:30:47 +00:00

207 lines
9.7 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Architecture
Status: SPEC · hybrid topology locked at Gate A freeze (2026-08-31) · SlabUploader
Normative for hybrid split, swimlane, what-runs-where, secrets **why**, and status
transitions. Ops depth lives in DEPLOYMENT. Schema enums live in DATA-MODEL.
Agent-facing hard rules live in AGENTS.md (verbatim locks stay there).
Companion: `docs/TECH-SPEC-PIPELINE.md`. Historical PRD: `docs/archive/PRD-2026-08.txt` (frozen; not a source for new work). Agent locks: `AGENTS.md`.
## 1. Topology (hybrid)
Phone does the happy-path pipeline (mask knobs, length axis, sqft/bdft, 3:4 PNG).
FastAPI stores drafts, proxies inference, talks to Woo. reverse proxy terminates HTTPS.
Server U2Net (“Try harder”) is **deferred from POC**, not deleted.
```
phone (SvelteKit)
capture, four knobs inline, live mask overlay on the source photo,
length axis, sqft/bdft, 3:4 PNG
(POC: retake if mask still bad — no U2Net control)
server
reverse proxy → frontend (static)
→ fastapi : store, settings, taxonomy cache, Woo, inference proxy, prompt files
→ U2Net endpoint reserved post-POC
```
- Happy path never leaves the phone.
- Mask knobs live on capture; changing a knob re-runs client BG removal immediately.
- Woo + LLM keys stay on the server. Browser does not call Woo or the vision endpoint directly.
## 2. Swimlane
```mermaid
flowchart TB
subgraph Phone["Phone — SvelteKit"]
P1[New Wood: 1–5 photos]
P2[BG overlay + sheet / sensitivity / edge / feather]
P3{Edge OK?}
P4[Length axis overlay — rotate / confirm]
P5[User length + thickness + SKU]
P6[sqft, bdft, 6in widths, 3:4 PNGs]
P7[Review: Call 1 pre-fill or manual taxonomy]
P8[Tap Generate text]
P9[Review title / desc — publish]
end
subgraph API["server — FastAPI"]
A1[U2Net reserved post-POC]
A2[Store draft: originals + PNGs + numbers]
A3[Call 1 proxy — vision]
A4[Call 2 proxy — text]
A5[Woo: taxonomy, SKU check, create product]
end
subgraph LLM["Your OpenAI-compatible endpoint"]
L1[Wood expert — all photos at 1024]
L2[Title / desc / short desc]
end
subgraph Woo["WooCommerce store"]
W1[Categories / attributes / fig-* tags]
W2[New product live or draft per setting]
end
P1 --> P2 --> P3
P3 -->|no — retune knobs or retake| P2
P3 -->|no — Retake| P1
P3 -->|yes| P4 --> P5 --> P6
P6 --> A2
A2 --> A3 --> L1 --> A3 --> P7
P7 --> P8 --> A4 --> L2 --> A4 --> P9
P9 --> A5
A5 --> W1
A5 --> W2
```
## 3. Clarifications
1. **Happy path** stays on the phone through PNG + numbers. FastAPI is idle until the user has a confirmed mask.
2. **Mask knobs** live on capture; each change re-runs client BG removal. Server U2Net “Try harder” is deferred from POC (keep the one-photo contract; do not ship the control).
3. **Draft upload** sends **originals + processed PNGs + length/thickness/SKU/sqft/bdft/widths**. Originals are needed if Call 1 or a later retry must not depend on the tab still being open. After successful Woo publish, server deletes both.
4. **Call 1** is server-side so the vision key never sits in the browser. Auto-run
on slab create **only if** `inference_enabled` is true. If disabled, Call 1
is skipped; user can manually fill taxonomy and still trigger Call 2. Body:
all originals downscaled to 1024 + **Woo-synced taxonomy only** + SKU/length/thickness.
Timeout 45s → error + retry on the phone. ≥0.7 confidence **pre-populates**
species, wood categories, edge/figure/grade, feat-* (mutable). Below 0.7 those
fields stay **empty**. Manual entry stays authoritative. Presentation: `docs/UX.md`.
5. **Call 2** does not auto-fire. User taps Generate text. Server must send
**portable Call 1 context** (default: full conversation resend). Stateful
`previous_response_id` only after `test-inference` proves the provider supports
it. Call 2 inputs distinguish confirmed user values from inferred ones. Per-slab
thread; delete on submit or abandon.
6. **Woo** is only FastAPI. Browser never sees the WordPress application
password. Duplicate SKU → 409 mapped on the SKU field (edit SKU or open existing).
Stale value → 422 on that field. Draft stays on the phone.
7. **Settings** (species $/bdft, prompts, inference endpoint, Woo credentials,
publish status) live on the server. Store URL is env, displayed read-only.
## 4. What runs where
| Job | Where |
|---|---|
| Camera, 1-5 photos | Client |
| Sheet detect, chroma-key / black threshold, four knobs, live re-run, flood-fill | Client |
| U2Net “Try harder” | Server, **deferred from POC** |
| Length axis overlay, 6" widths, sqft, bdft | Client |
| 3:4 PNG, 80% fill (configurable aspect) | Client |
| Draft + originals + processed PNGs | Server (after user continues) |
| Species $/bdft, Woo creds, prompts, brand/GEO | Server |
| Call 1 (vision, all photos @1024) | Server proxy |
| Call 2 (title/desc) | Server proxy, after Call 1 curation |
| Woo taxonomy sync + publish | Server |
## 5. Secrets and settings
**Server-side only (never in browser JS):**
1. WordPress username + **application password** for a dedicated low-privilege
WP user. Server uses HTTP Basic Auth (`username:application_password`) against
`/wp-json/wc/v3/`. Stored encrypted at rest in SQLite (AES-GCM). Not Woo
consumer keys. Not the account login password.
2. Inference endpoint base URL + API key. Stored encrypted. Server proxies all
inference calls.
3. AES master key. Env var on server (`SLAB_AES_KEY`), never in the DB.
4. `WOO_BASE_URL` env (compose). HTTPS only. Not user-editable; Settings displays it read-only.
**Browser-side (no secrets):**
1. User preferences: sheet mode, sensitivity, edge offset, feather. Persisted in localStorage, reset-to-default available.
2. Species list with $/bdft. Read from server settings sync.
3. Taxonomy snapshot (categories, attributes, tags). Read from server. No auth needed.
**Flow:**
1. Deploy sets `WOO_BASE_URL` (`https://`) in compose. Operator creates a
low-privilege WP user on that store (HTTPS required for Application
Passwords UI). Generates an application password under Users → Profile.
2. User enters that username + application password in Settings UI
(store URL shown read-only from env).
3. Browser POSTs them to `/api/v1/settings` on FastAPI. Server encrypts with
AES-GCM and stores ciphertext only.
4. Server syncs taxonomy from Woo, caches in SQLite.
5. Client reads taxonomy from `/api/v1/admin/taxonomy` — no auth needed.
6. All Woo calls go server-to-Woo. Browser never calls Woo REST directly.
Browser-to-Woo REST would need CORS on the store, which this design does not use.
## 6. Status machine
```
draft → calibrated → ready → publishing → published
│ │ ↑
└→ failed ────┴────────────────┘ (woo_product_id set)
any → failed (with error)
```
1. **draft** — user took photos, no mask confirmed yet.
2. **calibrated** — user confirmed the BG edge, confirmed length axis, entered length/thickness/SKU. Client computed sqft/bdft/widths. Draft uploaded to server.
3. **ready** — all mandatory fields populated (species, wood category, edge type,
figure, grade, thickness, price, title/desc/short desc, at least one photo).
Values can come from Call 1 (≥ 0.7 pre-fill the user kept), manual entry, or a mix.
**Review gate:** below-threshold Call 1 leaves that field empty. Mandatory before
ready: **exactly one species, ≥1 wood category, ≥1 figure**. Inline nudges, not
submit-only (`docs/UX.md`). Inference is optional; with inference OFF the user
fills everything by hand.
4. **publishing** — Woo create in flight. Poll for result.
5. **published** — Woo product created, woo_product_id stored. Images purged.
6. **failed** — pipeline, inference, or publish error. Error detail stored. Retry available.
Call 1 and Call 2 are assist-only. They do not gate publish. Low-confidence Call 1
results do not auto-fill. If the user skips inference, the path is: calibrated →
ready → publishing → published.
## 7. Taxonomy cache (manufactured last-modified)
Woo category/tag/attribute `date_modified` is not a reliable client cache key (the store does not bump it for every edit we care about). FastAPI **manufactures** a `taxonomy_updated_at` timestamp:
- Bump **only** when a stored-subset row actually changes (species/wood-category leaves, five attributes + terms, `fig-*`/`feat-*` tags).
- A no-op sync (same rows) **does not** bump.
- Client re-pulls if the returned anchor is newer than its snapshot.
**Triggers**
1. `POST /api/v1/settings/test-woo` always runs a taxonomy resync after a successful connection test.
2. Publish path: opportunistic resync if the last successful bump is older than **1 hour**.
3. Optional: app launch and a Settings “Refresh taxonomy” control.
Species pickers and Call 1 option lists are **exactly** this cached set. No hardcoded mill list.
## 8. Shared validation module
One FastAPI module is source of truth for field rules (required, length cap, charset, Woo option membership). Client caches `{ rules, hash }`.
- Field-exit: local check against the cache.
- Length: hard input cap (cannot type past Woo max).
- Hash is compared **on submit only**. Mismatch → refresh module → re-validate → retry. Draft stays.
## 9. Call 1 → Call 2 context
Providers differ. Default portable path: **resend the full Call 1 turn** (prompt + images metadata + assistant JSON) with Call 2. Opt into `previous_response_id` only after probe.
`POST /api/v1/settings/test-inference` fails closed (`ok: false`) if **neither** full-resend nor stateful id works. Do not ship a Call 2 that silently drops Call 1 context.
Per-slab thread. Delete stored thread on successful publish or user abandon.
Presentation of the review screen: `docs/UX.md`.