Files
SlabUploader/.audit/m3-build-plan.md
T
Ty 20f08bbc25 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.
2026-09-19 09:29:02 -07:00

5.7 KiB
Raw Blame History

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.