From 20f08bbc25a318f58d2bb04cd21fb1e4475e58d2 Mon Sep 17 00:00:00 2001 From: Ty Balascio Date: Sat, 19 Sep 2026 09:29:02 -0700 Subject: [PATCH] docs(m3): record create build plan and decisions Working record for a fresh session: settled contract rulings (R1-R4), implementation decisions (stdlib sqlite3 + schema.sql, in-process job queue, BLOB photo store, DB-only prompts), slice order S0-S7, guardrails. Cites requirement IDs; defines none. --- .audit/m3-build-plan.md | 84 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 84 insertions(+) create mode 100644 .audit/m3-build-plan.md diff --git a/.audit/m3-build-plan.md b/.audit/m3-build-plan.md new file mode 100644 index 0000000..967421a --- /dev/null +++ b/.audit/m3-build-plan.md @@ -0,0 +1,84 @@ +# M3 Create — build plan (working record) + +Status: WORKING RECORD · 2026-09-19 · **not normative**. + +`docs/REQUIREMENTS.md` is the only normative text; `docs/MILESTONES.md` holds the M3 ID subset; +behavior is in `docs/capabilities/CREATE.md` and `docs/contracts/`. This file records decisions +made on 2026-09-19 so a new session does not re-litigate or drift from them. It **cites** +requirement IDs; it defines none. + +## 1. Scope + +Implement Milestone 3 only, per the M3 ID subset in `docs/MILESTONES.md` and +`docs/capabilities/CREATE.md`. Do not implement outside that subset unless the owner expands scope +in chat. + +## 2. Settled contract rulings (owner, 2026-09-19) + +| # | Question | Ruling | Now lives in | +|---|---|---|---| +| R1 | Soft delete representation | Add `deleted_at TEXT NULL`; deleted rows excluded from all GET responses; status enum unchanged | `docs/contracts/DATA-MODEL.md` §1–2, `docs/contracts/API.md` §4 | +| R2 | Publish completion vs the poll endpoint | The terminal poll returns `woo_product_id`; the local working slab row is then discarded; the device draft is cleared client-side; the app **never deletes from WooCommerce** | `docs/REQUIREMENTS.md` PUBLISH-25, `docs/contracts/API.md` §4 + §7, `docs/contracts/DATA-MODEL.md` §1 | +| R3 | Prompt storage | SQLite is the point of record (`prompts` + `prompt_revisions`). File-based prompts are **superseded**; no prompt files ship and the `backend/app/prompts:ro` compose mount was removed | `docs/contracts/PROMPTS.md` (unchanged), `deploy/docker-compose.yml`, `docs/DEPLOYMENT.md` | +| R4 | `/images` volume | Photo bytes are SQLite BLOBs (source of truth). `/images` holds only a health-probe file and short-lived staging (1024 px Call-1 JPEGs, Woo media uploads), purged after use. Health goes non-200 when the DB or `/images` is unwritable; `woo_reachable:false` stays 200 | `docs/contracts/DATA-MODEL.md` §1, APP-45 | + +## 3. Implementation decisions + +**Stack.** FastAPI + stdlib `sqlite3` + `schema.sql` (`CREATE TABLE IF NOT EXISTS`). No SQLAlchemy, +no Alembic. Pragmas `journal_mode=WAL`, `foreign_keys=ON`, `busy_timeout`, `synchronous=NORMAL`; +one process-wide connection behind a lock. The app factory takes `db_path` and `image_dir` so tests +get per-test isolation. Revisit an ORM/migrations only after real UAT data exists. + +**Async (202 + poll).** One in-process job queue with a single consumer — **not** FastAPI +`BackgroundTasks` (Starlette background tasks make TestClient 202 tests accidentally synchronous). +Status transitions CAS inside `BEGIN IMMEDIATE`. Tests run the queue in an inline mode so contract +tests stay deterministic. + +**Photos.** `slab_photos.original_blob` / `processed_blob` are the source of truth; `/images` per R4. + +**Prompts.** DB-only with revisions per R3. + +**Publish.** Per R2 and `docs/contracts/API.md` §7 + `docs/contracts/CONTENT-WOO.md`. On success: +set `woo_product_id`, purge photos, delete the working row after the terminal poll is served, clear +the device draft. `SLAB-UAT-*` always creates a draft. `max_upload_size` is checked before media +upload (`docs/contracts/WOO-API.md` §10). + +**Other.** +- `ready` is server-derived from the REVIEW-5 set. +- SKU is entered at the Measure step (UX §2.5); do not widen the Capture union. +- The server stores client-sent derived numbers and never recomputes them as source of truth (APP-40). +- PUT order: 404 → 409 `revision_conflict` → 409 `duplicate_sku` → 422 `stale_field` → apply → `server_rev++`. +- Call 1 fills empty fields only; background inference writes bump `server_rev` and the client adopts the polled rev. +- Frontend additions stay outside the Capture union: `frontend/src/api/*`, `frontend/src/create/*`. + +## 4. Slice order + +| Slice | Delivers | M3 IDs | +|---|---|---| +| S0 | factory, config/startup guards, health/meta, JSON logs, Dockerfile | APP-30, APP-45, APP-55, APP-65, SETTINGS-10, SETTINGS-25, OPS-25 | +| S1 | AES-GCM crypto + Settings (masked, write-only secrets) | SETTINGS-5/15/20/25/30/35/75/85/90, QUALITY-125 | +| S2 | slabs CRUD, optimistic concurrency, soft delete | APP-5/15/20/35/40, REVIEW-45 | +| S3 | photos (multipart → BLOBs, original/processed, 410 after purge) | CAPTURE-95/105/130, PUBLISH-40 | +| S4 | Woo client (app-password Basic) + taxonomy sync + test-woo | METADATA-5/10/15/20/30, SETTINGS-40/50/51/55/60/80, QUALITY-35 | +| S5 | pricing, deterministic templates, server-derived ready | REVIEW-5/10/15/20/25/30/35/40/50, METADATA-70/80/85/90, QUALITY-90/95 | +| S6 | publish (sequence, UAT draft, duplicate/stale/partial-failure) | PUBLISH-5/10/15/20/25/30/31/35/36/37/38/40/45/50/55/60 | +| S7 | inference last (Call 1/Call 2, prompt revisions, templates fallback) | METADATA-35/40/45/50/55/60/65/66/75/95/100/105/110/115, SETTINGS-45/70 | +| Cross | frontend API client + Review / Settings / Publish screens | (UI for the slices above) | +| Exit | inference-off suite, Woo-mock integration, Compose UI E2E, real UAT draft | QUALITY-20/35/50/105/110/120 | + +TDD every slice: the failing test precedes its implementation; never parallelize RED and GREEN of +the same scenario. + +## 5. Guardrails + +- Subagents run on a single local GPU: **one at a time, never in parallel**. +- Agents never declare a gate green; Ty owns gate exit (QUALITY-25, QUALITY-30). +- Run `python tools/check_requirements.py` after any docs change; it must pass. +- Ports `10880`–`10889` are this project's; never bind `:10880`. Dev-test runs on `:10881`, bound `0.0.0.0`. +- Create must work with inference off (zero inference calls) — QUALITY-20. +- No raw status codes, stack traces, or internal codes reach the owner — QUALITY-120. + +## 6. Not yet validated + +- Long-horizon multi-step agentic loops (more than one or two tool round-trips). +- M3 implementation has not started; this file is the pre-build record.