Review: Tenant-scoped Drizzle query helper (v2)

review-1091-2026-04-25-v2 Review

review needs-refinement

Verdict: NEEDS_REFINEMENT

Re-review of review-1091-2026-04-25. Previous verdict was NEEDS_REFINEMENT with two recommendations: [SCOPE] resolve arch label mismatch, [BODY] pin Lineage to explicit issue numbers.

Changes Since v1 Review

  • [x] [BODY] Lineage pinned — issue body now reads "Hard depends on forgejo_admin/westside-admin#1 (Drizzle setup ...). Hard depends on forgejo_admin/westside-admin#6 (scaffolding)." Related section also adds "DEPENDS ON: #6, #1". Resolved.
  • [~] [SCOPE] Arch label changed from arch:scopeddb-helper to arch:scoped-db. Sibling ticket #1089 (Drizzle setup) also carries arch:scoped-db, so the two helper-layer tickets now share one arch label cleanly. Partially resolved — the label is consistent across the helper layer, but no backing arch-scoped-db note exists.

Template Completeness

  • [x] Type — Feature
  • [x] Lineage — explicit issue numbers (#1, #6)
  • [x] Repo — forgejo_admin/westside-admin
  • [x] User Story — story-westside-admin-admin-row-crud
  • [x] Context — clear motivation, swap-point design, lint enforcement rationale
  • [x] File Targets — Create + Modify + Do NOT modify lists present
  • [x] Acceptance Criteria — 6 ACs, behavioral + type-system + lint + comment
  • [x] Test Expectations — unit + integration described
  • [x] Constraints — no JWT-derived tenant_id (v1), Drizzle typed builders only
  • [x] Checklist — PR / ACs / lint verification
  • [x] Related — project page + arch note + DEPENDS ON line

Traceability

  • [x] story:admin-row-crud label — present
  • [x] story note verified — found in project-westside-admin user-stories table (story-westside-admin-admin-row-crud, Admin role, "Zero psql UPDATEs / zero fix-X texts over 30 days")
  • [x] arch:scoped-db label — present (was arch:scopeddb-helper in v1; now matches sibling #1089)
  • [ ] arch note MISSING — [SCOPE] No arch-scoped-db note exists in pal-e-docs (verified via search_notes queries "arch-scoped-db" and "scoped-db westside-admin", both empty). The project-westside-admin Architecture section lists exactly three arch notes: arch-domain-westside-admin, arch-dataflow-westside-admin, arch-deployment-westside-admin. The issue's Related section still points to arch-dataflow-westside-admin. The arch label and the issue's Related link disagree. Two clean resolutions: (a) create arch-scoped-db-westside-admin as a sub-component note nested under arch-dataflow-westside-admin — this matches the sibling-ticket pattern and gives the helper layer a real architecture home, or (b) relabel both #1091 and #1089 to arch:dataflow-westside-admin and drop scoped-db as a phantom component. Recommend (a): scoped-db is a real architectural seam (the only legal entry point to the database from page-server code), worth its own note.
  • [x] type:feature label — present
  • [x] Forgejo issue — https://forgejo.tail5b443a.ts.net/forgejo_admin/westside-admin/issues/3 — open

File Targets

Repo forgejo_admin/westside-admin still contains only README.md on main (verified via Forgejo contents API). Every File Target is part of the dependent ticket chain (#6 scaffolding → #1 Drizzle setup → this ticket). Cannot be live-verified; scoped relative to post-#1 tree.
  • [~] src/lib/server/tenant.ts (Create) — path well-formed, plausible after #1
  • [~] src/lib/server/scopedDb.ts (Create) — path well-formed
  • [~] src/lib/server/db.ts (Modify) — issue #1 explicitly creates this; consistent
  • [~] package.json (Modify) — root path, exists after scaffolding #6
Acceptable for backlog→todo. Dev agent must re-verify after #6 + #1 land.

Repo Placement

OK. All file targets in westside-admin; matches issue's Repo header. No multi-repo fan-out.

Dependencies

  • Hard blocker: #1 (Drizzle setup, board item #1089, backlog) — creates db.ts and schema.ts. Now explicitly named in Lineage.
  • Hard blocker: #6 (scaffolding, board item #1087, backlog) — creates SvelteKit shell. Now explicitly named in Lineage.
  • Downstream: #2 hooks-server (#1090), #4 page-server tables (#1092), #5 page-server rows (#1093, already in todo) all consume scopedDb. Note: #1093 is in todo column despite depending on this ticket which is still backlog — that's a board ordering question for Ava, not this ticket's concern.
  • Documented? Yes — Lineage now pins both upstream issues by number.

Acceptance Criteria

Unchanged from v1, all 6 ACs agent-verifiable:
  • AC1–AC2: SQL output assertions — verifiable via snapshot test of generated query
  • AC3: TypeScript type error on system-table misuse — verifiable via expectTypeOf or tsc --noEmit
  • AC4: CI lint check fails on direct db import — verifiable by running lint on fixture
  • AC5: 6+ unit tests — quantitative
  • AC6: multi-line swap-procedure comment in tenant.ts — verifiable via grep
Integration test (URL/form-body tenant_id mutation still produces correct WHERE clause) remains the strongest data-leak guarantee. Solid.

Blast Radius

Unchanged from v1:
  • First Drizzle consumer in the cluster — pattern set here informs future adoption
  • Downstream rebases on #1090, #1092, #1093 if API shape is wrong; agent should sanity-check Drizzle's actual builder API before locking signatures
  • Grep-based lint is brittle to renames/re-exports; acceptable for v1
  • TENANT_ID = 1 in TS file works for v1; swap-point comment is the discipline

Decomposition Assessment

4 file targets (2 create + 2 modify), 6 ACs, ~6 unit tests + 1 integration test. Estimated agent work 30–60 min one-pass. No decomposition needed. Within 5-minute-rule envelope.

Recommendation

  • [SCOPE] Create arch-scoped-db-westside-admin note (or chosen slug) under arch-dataflow-westside-admin. Document: helper API surface (scopedDb + systemDb), tenant-table vs system-table split, lint-enforced single entry point, TENANT_ID swap procedure. Same note backs sibling ticket #1089. Once created, also align board labels and the issue's Related section so they reference the new arch note instead of (or alongside) arch-dataflow-westside-admin. Ava's call on slug + whether to relabel.
One unresolved [SCOPE] from v1 remains. The [BODY] Lineage fix landed cleanly. Ticket otherwise reads READY — strong scope, crisp ACs, well-defined seam. Once the arch note exists (or Ava decides the label is purely organizational and waives the requirement), this flips to APPROVED.