feat(backend): S0 foundation (schema + stdlib sqlite3 db factory + health/meta + JSON logs)

- backend/db/schema.sql: all 12 tables from DATA-MODEL.md (taxonomy empty)
- backend/db: stdlib sqlite3 factory, WAL/FK/busy_timeout/synchronous pragmas,
  single locked connection, explicit SLAB_DB_PATH/SLAB_IMAGES_DIR
- backend/app.py: /api/health (non-200 on storage not writable), /api/v1/meta,
  JSON logging with request-id threading + secret redaction, startup guards
  (SETTINGS-10 WOO_BASE_URL https, SETTINGS-25 SLAB_AES_KEY)
- tests: schema-loads, health happy/failure, startup guards (TDD)
- docs/contracts: add requirement-ID citations to 12 sections so
  check_requirements.py passes (pre-existing gate debt)

Gates: check_requirements.py + check_coverage.py exit 0; pytest 5 passed
This commit is contained in:
2026-09-19 15:34:15 -07:00
parent 9b181a937b
commit 0eb2b0f697
12 changed files with 592 additions and 6 deletions
+6 -4
View File
@@ -75,7 +75,7 @@ There is no `412 validation_module_stale` path.
Multipart create of a calibrated draft. Returns `201` with the slab at `status: "calibrated"`.
Stores the client-sent derived numbers and never recomputes them. Auto-runs Call 1 only when
`inference_enabled` is true; inferred values persist only at confidence ≥ 0.7. `409 duplicate_sku`
if the SKU already exists.
if the SKU already exists. Cites APP-20, CAPTURE-135, METADATA-45, PUBLISH-15.
`SlabCreate` fields:
@@ -116,7 +116,7 @@ Partial update of editable fields plus `client_rev`. Stores client-sent derived
not recompute them. Checks run in order: `409 revision_conflict`, then `409 duplicate_sku` (SKU
field actions `edit_sku` / `open_existing`), then `422 stale_field` (current options included).
`status` becomes `ready` only when the full required set is present; it is server-derived and a
client never sets it.
client never sets it. Cites APP-20, REVIEW-40, PUBLISH-15, PUBLISH-20.
### `DELETE /api/v1/slabs/{id}`
Soft delete of unpublished drafts only (sets `deleted_at`); `204`. Deleted rows are excluded from
@@ -141,7 +141,7 @@ Runs Call 2 on user action only. Fresh request carrying the processed photos, th
metadata, the primary and secondary wood categories, the parent-category path, the dimension fact
block, and brand/geo context; no provider thread id and no raw Call 1 result. Requires
`test-inference` `ok`. Returns `202`; the client polls. The model writes the long description only
and never touches dimensions.
and never touches dimensions. Cites METADATA-55, METADATA-65.
Call 1 result fields: `species_id`, `species_confidence`, `wood_category_ids`,
`edge_type_term_id`, `figure_term_ids`, `figure_confidence`, `grade_term_id`, `grade_confidence`,
@@ -158,7 +158,7 @@ Preconditions are the full ready set, else `422 unpublishable` with field keys.
the ready set; look up the SKU and stop if present; refresh taxonomy when the last sync is over
one hour old; resolve categories, tags, and attributes; upload the processed PNGs to Woo media;
`POST /products` per `docs/contracts/WOO-API.md`; finalize. `SLAB-UAT-*` always creates a draft.
Returns `202`; the client polls.
Returns `202`; the client polls. Cites PUBLISH-5, PUBLISH-10, PUBLISH-15, PUBLISH-25.
- Success: `woo_product_id` set and returned on the terminal poll, photos purged, the local working
slab row discarded, device draft cleared. Nothing is deleted from WooCommerce.
@@ -187,6 +187,8 @@ One flat `price_per_bdft` per species leaf, no tiers.
## 10. Settings
Cites SETTINGS-15, SETTINGS-20, SETTINGS-25, SETTINGS-80, SETTINGS-85.
- `GET /api/v1/settings` → `SettingsView` with secrets masked.
- `PUT /api/v1/settings` — partial update. `woo_base_url` is not writable; the server rejects the
write. Woo credentials are write-only and stored AES-GCM. Inference base URL and key are
+6
View File
@@ -104,6 +104,8 @@ successful publish.
## 5. Taxonomy cache
Cites METADATA-5, SETTINGS-50, SETTINGS-51, SETTINGS-55.
- `woo_categories` — species and wood-category leaves only; parents group and are never assigned.
- `woo_attributes` / `woo_attribute_terms` — exactly the five locked attributes Edge Type, Figure,
Grade, Thickness, Moisture, with their terms.
@@ -148,6 +150,8 @@ revision it used (`METADATA-66`).
## 8. `sync_log`
Cites PUBLISH-25, PUBLISH-30.
Append-only; never deleted on publish. Columns: `id`, `slab_id`, `woo_product_id`, `status`
(`success` | `failed`), `payload_hash` (sha256 of the outbound payload), `message`, `created_at`.
@@ -158,5 +162,7 @@ Append-only audit. Columns: `id`, `slab_id`, `call` (1 | 2), `prompt_revision_id
## 10. Status transitions
Cites APP-5.
`draft` → `calibrated` → `ready` → `publishing` → `published`, with `failed` reachable from any
step and retryable. `calibrated` is set when the draft uploads; `ready` requires the full set.
+2
View File
@@ -39,6 +39,8 @@ The interface is reachable on the trusted network only until it is authenticated
## 3. Call 1 — vision classification
Cites METADATA-35, METADATA-40, METADATA-45, METADATA-50.
**When.** Runs automatically after a calibrated draft uploads only when `inference_enabled` is
true; also on demand or retry. With inference off it is skipped and the fields are filled by hand.
+8 -2
View File
@@ -11,10 +11,10 @@ Status: SPEC · 2026-09-18. Presentation spec for the mill-owner flow on phone a
| CAPTURE-20, CAPTURE-25, CAPTURE-30 | photo intake and calibration choice |
| CAPTURE-40, CAPTURE-41, CAPTURE-45, CAPTURE-50, CAPTURE-55 | sheet/sensitivity controls, overlay, retake |
| CAPTURE-60, CAPTURE-65, CAPTURE-70, CAPTURE-80, CAPTURE-85, CAPTURE-90 | axis, entry, derived numbers |
| CAPTURE-105, CAPTURE-135, CAPTURE-141, CAPTURE-145, CAPTURE-150, CAPTURE-155 | crop guard, upload, preview/controls, failure |
| CAPTURE-105, CAPTURE-130, CAPTURE-135, CAPTURE-141, CAPTURE-145, CAPTURE-150, CAPTURE-155 | crop guard, lineup order, upload, preview/controls, failure |
| METADATA-10, METADATA-15, METADATA-30 | required taxonomy fields and cardinality |
| METADATA-45, METADATA-55, METADATA-110 | pre-fill, Generate text availability |
| PUBLISH-10, PUBLISH-15, PUBLISH-20, PUBLISH-25, PUBLISH-30, PUBLISH-35, PUBLISH-36, PUBLISH-37, PUBLISH-38, PUBLISH-50 | publish outcomes and messaging |
| PUBLISH-5, PUBLISH-10, PUBLISH-15, PUBLISH-20, PUBLISH-25, PUBLISH-30, PUBLISH-35, PUBLISH-36, PUBLISH-37, PUBLISH-38, PUBLISH-50 | publish sequence, outcomes and messaging |
| REVIEW-5, REVIEW-10, REVIEW-15, REVIEW-20, REVIEW-25, REVIEW-30, REVIEW-40, REVIEW-45, REVIEW-50 | ready set, gating, fields, limits |
| SETTINGS-65 | create-status default |
@@ -26,6 +26,8 @@ flow runs on a PC browser.
## 2. Capture
Cites CAPTURE-20, CAPTURE-25, CAPTURE-40, CAPTURE-130.
1. **Photos.** Take one or more photos; the first is top-down with the lens parallel to the broad
face. The owner chooses which photo is the calibration photo.
2. **Mask overlay and controls.** The app draws the mask on the source photo. The controls are
@@ -47,6 +49,8 @@ undersized source never guess; the resolution is retake.
## 3. Review
Cites REVIEW-5, REVIEW-15, REVIEW-20, METADATA-45.
- **Vision pre-fill.** At or above the threshold (default 0.7) a field is pre-filled and shown
with an "AI" badge; below it the field starts empty. The badge is presentation only and clears
on edit.
@@ -63,6 +67,8 @@ undersized source never guess; the resolution is retake.
## 4. Publish
Cites PUBLISH-5, PUBLISH-10, PUBLISH-15, PUBLISH-25.
- A normal SKU uses `woo_create_status` (default draft); a `SLAB-UAT-*` SKU always creates a draft.
"Published" means the store create succeeded, not that it is live.
- **Success.** The local draft and photos are gone; the store product reference is shown.