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.
5.7 KiB
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.
readyis 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→ 409duplicate_sku→ 422stale_field→ apply →server_rev++. - Call 1 fills empty fields only; background inference writes bump
server_revand 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.pyafter any docs change; it must pass. - Ports
10880–10889are this project's; never bind:10880. Dev-test runs on:10881, bound0.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.