[M3] POST /slabs: an extra payload key breaks the INSERT binding count; unvalidated columns/values raise raw DB errors instead of 400 #83

Closed
opened 2026-09-20 18:16:48 +00:00 by grokbot_ty · 1 comment
Owner

Summary

create_slab builds its column list from the allowlist but its value list from "payload keys that appear in that column list", so any extra key in the payload shifts the binding count and the INSERT dies with a driver error. Separately, create validates no enum values and no taxonomy foreign keys, so out-of-domain input becomes an unhandled IntegrityError. Both surface as 500s with a stack trace in the log, against QUALITY-120.

Found in a read-only review of main @ b99cbdf (2026-09-20). 63 backend tests pass and both docs gates pass at that commit; these are gaps the green suite does not cover.

Observed evidence

backend/slabs.py:108-111:

cols = [f for f in ALLOWED_FIELDS if f in data] + ["status", "client_rev", "server_rev", "deleted_at", "created_at", "updated_at"]
vals = [data[f] for f in cols if f in data] + ["calibrated", 1, 1, None, now, now]

If the payload carries status (or client_rev, created_at, ...), that key is picked up by the comprehension and supplied again by the trailing literal list. Reproduced against the real app:

POST /api/v1/slabs  (full valid payload + an extra "status": "published")
-> sqlite3.ProgrammingError: Incorrect number of bindings supplied.
   The current statement uses 35, and there are 36 supplied.

POST /api/v1/slabs  (valid payload, species_id = 999)
-> sqlite3.IntegrityError: FOREIGN KEY constraint failed

Contrast: update_slab does validate taxonomy ids (slabs.py:148-158), so create and update disagree about what a valid slab is. species_id: 999 is therefore persisted-able in principle and only fails later at Woo (publish.py:71 sends {"id": slab["species_id"]} as a category).

Acceptance

  • The payload is allowlisted before the INSERT (unknown keys dropped or rejected with a plain 400), and the column/value lists are built from one source so they cannot drift.
  • Enum-ish columns (price_source, inference_status, status) and the taxonomy FKs are validated on create at least as strictly as on update, returning validation/stale_field 400/422 — never a driver exception.
  • Tests: extra-key payload, out-of-domain enum, and non-existent taxonomy id each return a 4xx with the standard error envelope.

Related

  • #77 (the UI rebuild will call create; it must not be able to 500 the app)
  • Requirements: REVIEW-35, QUALITY-120, DATA-MODEL constraints
## Summary `create_slab` builds its column list from the allowlist but its value list from "payload keys that appear in that column list", so any extra key in the payload shifts the binding count and the INSERT dies with a driver error. Separately, create validates no enum values and no taxonomy foreign keys, so out-of-domain input becomes an unhandled `IntegrityError`. Both surface as 500s with a stack trace in the log, against `QUALITY-120`. Found in a read-only review of `main` @ `b99cbdf` (2026-09-20). 63 backend tests pass and both docs gates pass at that commit; these are gaps the green suite does not cover. ## Observed evidence `backend/slabs.py:108-111`: ```python cols = [f for f in ALLOWED_FIELDS if f in data] + ["status", "client_rev", "server_rev", "deleted_at", "created_at", "updated_at"] vals = [data[f] for f in cols if f in data] + ["calibrated", 1, 1, None, now, now] ``` If the payload carries `status` (or `client_rev`, `created_at`, ...), that key is picked up by the comprehension *and* supplied again by the trailing literal list. Reproduced against the real app: ``` POST /api/v1/slabs (full valid payload + an extra "status": "published") -> sqlite3.ProgrammingError: Incorrect number of bindings supplied. The current statement uses 35, and there are 36 supplied. POST /api/v1/slabs (valid payload, species_id = 999) -> sqlite3.IntegrityError: FOREIGN KEY constraint failed ``` Contrast: `update_slab` does validate taxonomy ids (`slabs.py:148-158`), so create and update disagree about what a valid slab is. `species_id: 999` is therefore persisted-able in principle and only fails later at Woo (`publish.py:71` sends `{"id": slab["species_id"]}` as a category). ## Acceptance - The payload is allowlisted before the INSERT (unknown keys dropped or rejected with a plain 400), and the column/value lists are built from one source so they cannot drift. - Enum-ish columns (`price_source`, `inference_status`, status) and the taxonomy FKs are validated on create at least as strictly as on update, returning `validation`/`stale_field` 400/422 — never a driver exception. - Tests: extra-key payload, out-of-domain enum, and non-existent taxonomy id each return a 4xx with the standard error envelope. ## Related - `#77` (the UI rebuild will call create; it must not be able to 500 the app) - Requirements: REVIEW-35, QUALITY-120, DATA-MODEL constraints
Author
Owner

Closed by M3 Option A: main 0c00d25 + UAT QUALITY-110 evidence.

  • Deploy: version 0.5.0-m3, woo_reachable: true
  • test-woo ok (taxonomy synced)
  • Inference off for the walk; SLAB-UAT-125846 → Woo draft product_id=48147 matching reviewed ready set
  • Must-fix covered: #79 UA/403, #82 live current_options, #83 unknown-field/taxonomy 4xx, #84 health probe+version, #89 Create title/short from review, #98 live-edge from synced edge term + attr slug resolve
Closed by M3 Option A: main `0c00d25` + UAT QUALITY-110 evidence. - Deploy: version `0.5.0-m3`, `woo_reachable: true` - `test-woo` ok (taxonomy synced) - Inference off for the walk; **SLAB-UAT-125846** → Woo draft `product_id=48147` matching reviewed ready set - Must-fix covered: #79 UA/403, #82 live `current_options`, #83 unknown-field/taxonomy 4xx, #84 health probe+version, #89 Create title/short from review, #98 live-edge from synced edge term + attr slug resolve
Sign in to join this conversation.
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: Ty_Tech/SlabUploader#83