feat(slabs): S2 CRUD + optimistic concurrency + soft delete + slab list UI

- backend/slabs.py: create (calibrated, client-derived stored as sent), list (excl
  soft-deleted, most recent), get, update (single compare-and-set on server_rev,
  409 revision_conflict / 409 duplicate_sku / 422 stale_field, status server-derived),
  delete (soft, published rejected). Explicit column allowlists, parameterized SQL,
  JSON array fields, commits.
- backend/app.py: wire slabs router.
- frontend: src/api/slabs.ts client + create/slabs/SlabList.svelte + dev toggle.
- tests: 9 slabs tests (create/dup/list/get/update/rev-conflict/dup-sku/soft-delete/
  published-delete) with full taxonomy seeding.

Verified: pytest 23 passed; check_requirements + check_coverage exit 0; npm build ok.
This commit is contained in:
2026-09-19 16:29:06 -07:00
parent a0032ddd2d
commit cc5241f7da
6 changed files with 523 additions and 2 deletions
+8 -1
View File
@@ -90,7 +90,9 @@ def _enforce_startup_guards() -> None:
_enforce_startup_guards()
# --- Request ID middleware (threads X-Request-Id or generates) ---
# --- Request ID middleware (threads X-Request-Id or generates) --
class RequestIdMiddleware(BaseHTTPMiddleware):
async def dispatch(self, request: Request, call_next):
rid = request.headers.get("X-Request-Id") or str(uuid.uuid4())
@@ -178,5 +180,10 @@ async def put_settings(payload: dict[str, Any]) -> dict[str, Any]:
return settings_mod.update_settings(payload)
# expose for tests
from .slabs import router as slabs_router
app.include_router(slabs_router)
# expose for tests
__all__ = ["app", "__version__"]