docs: remove live ARCHITECTURE.md (see archive + main 13bfa792)

This commit is contained in:
Ty
2026-09-11 13:47:05 +00:00
parent 6e2b9b3008
commit b36e33e239
-230
View File
@@ -1,230 +0,0 @@
# 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. The one-photo contract stays in the architecture; the control is not shipped until post-POC.
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** — SKU, length, thickness, client sqft/bdft/widths, ≥1 inventory PNG,
exactly one species, ≥1 wood category, 1 edge type, ≥1 figure term, ≥1 fig-* tag,
≥1 grade, thickness store band (round-up), 1 moisture (default kiln-dried), price.
feat-* 0+. Title/description may be typed, generated, or templated at publish.
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 species, wood category, figure,
edge, grade, and feat-* empty. That subset is not the submit list. Inline nudges
on every still-required control, not submit-only. Presentation: `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 REST exposes **no usable last-modified** on categories, attributes, or tags
(only products carry `date_modified`). The client never talks to Woo. FastAPI is
the sole freshness source. It persists two UTC stamps: `taxonomy_last_sync_at`
(every successful Woo pull) and `taxonomy_anchor` (bumps only when the stored-subset
hash changes, including deletions).
- Anchor bumps **only** when a row-level change is detected in the **stored
subset** of cache fields (whatever columns the cache tables actually keep:
species/wood-category leaves, five attributes + terms, `fig-*`/`feat-*` tags).
Scope is schema-driven: adding a cache column later automatically includes it.
- Spelling/name edits, additions, and deletions all bump. Diff must catch missing
rows (deletions), not only changed rows.
- A no-op sync (stored subset unchanged) **does not** bump, so the client does
not churn.
- Client reads the anchor. If it advanced past the client's last value, re-pull
the **full** taxonomy cache. One timestamp governs categories, attributes, and
tags together.
- `taxonomy_anchor` is app-managed; no Woo-side timestamp or per-table anchor is used.
**Triggers**
1. `POST /api/v1/settings/test-woo` **always** resyncs taxonomy after a successful
connection test (unlocks the species list).
2. Publish / product submit: opportunistic resync if the last successful sync is
older than **1 hour**; bump anchor only if the stored subset changed.
3. Optional: app launch check (sync only if stale) and a Settings “Refresh
taxonomy” control. Not required for POC.
4. Do **not** sync on every screen navigation.
Species pickers and Call 1 option lists are **exactly** this cached set. No
hardcoded, invented, or fallback species list. A new Woo species is selectable
only after the next sync that lands it in the cache.
## 8. Shared validation module
One FastAPI module is source of truth (`GET /api/v1/validation/module` → `{ hash, rules[] }`).
Two consumers: review UI and Call 2 prompt builder. Client caches the module.
- SKU charset: `[A-Z0-9-]{3,24}` (printable ASCII subset). Title / short_title / description: UTF-8 within Woo `maxLength`. Not “ASCII-only” and not “extended ASCII”.
- Field-exit: local check against the cache. Empty optional is valid.
- Length: `maxLength` is a hard input cap; server still enforces.
- Submit carries `validation_hash`. Match → validate. Mismatch → HTTP **412** + fresh module; UI “Updating rules”; re-validate; retry. Never show 412. 409 is duplicate SKU only. Woo-shaped 422 is store rejection (#18).
## 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` two-turn probe records `stateful | stateless | fail`.
**Fail blocks Call 2 enablement only** (Gate C inference-OFF still works). Call 2 always carries the full Call 1 context; `previous_response_id` is optional after probe.
Per-slab thread on the **server** (client does not hold provider IDs). Best-effort delete on publish / abandon / fail; log and continue.
Presentation: `docs/UX.md`.