← all branches

claude/zealous-lovelace-cbc97f

needs attentionviewing older commit
8b749af · incrementalPR #215reviewed 2026-07-14 17:58 UTC0H · 1M · 3L · 4I
The branch
Purpose
Add a nightly reconcile-and-repair backstop for the `utility_contract_overview` trigger-maintained read-model (ADR-020 follow-up), making drift observable and auto-repairable
Goal
Migration 0061 (re-slotted from 0057 after merge collision): pg_cron SECURITY DEFINER reconcile function + audit log table + CI drift watchdog. New `listOverviewKeys` query powers 'select all N matching filter' bulk selection in the contratos table.
Sub-goals
  • SG-1: Re-slot migration 0057→0061 after merge collision (purely mechanical number update across all references)
  • SG-2: Add `listOverviewKeys` query with window-function-based total for atomic snapshot consistency
  • SG-3: Refactor `buildFullWhere` to accept narrower `OverviewWhereInput` shared by page and keys queries
The changes (whole branch)
What
Incremental diff: (1) migration 0057 references updated to 0061 in watchdog workflow, ADR-020 doc, CLAUDE.md files; (2) `listOverviewKeys` function + `ListOverviewKeysInput`/`OverviewKey`/`OverviewKeysResult` types added to queries module and barrel-exported; (3) `buildFullWhere` signature widened to `OverviewWhereInput` (a `Pick` of `ListOverviewPageInput` filter fields)
Why
Migration re-slot required after merging with another branch that claimed migration number 0057. `listOverviewKeys` needed for bulk-select UI feature in contratos table.
Areas
domains/utility+4022packages/database+2890.github/workflows+1240scripts/db+670docs/ADRs+62
Blast
15 files, +14146/-4 total (bulk of additions is the drizzle/meta snapshot JSON); meaningful churn: 13 files +918/-4 across domains/utility, packages/database, .github/workflows, scripts/db, docs/ADRs
CI / check-runs· No CI checks recorded at this SHA yet (runner may not have picked it up)CodeRabbit· No .coderabbit.yaml in repo

Findings · 8

correctness1

low

`total: sql<number>` maps PostgreSQL bigint via JS Number

domains/utility/src/utility-contract-overview/utility-contract-overview.queries.ts:526

`count(*) OVER ()` returns a PostgreSQL `bigint`. `.mapWith(Number)` coerces it through JS Number, which loses precision past 2^53. Not a realistic concern at current org contract volumes, but the conventional pattern is `sql<string>` + `parseInt`. Low priority — but worth aligning with the rest of the codebase if other count queries use the string-then-parse pattern.

security3

medium

No upper bound on `limit` at query layer — potential DB pressure

domains/utility/src/utility-contract-overview/utility-contract-overview.queries.ts:530

`listOverviewKeys` accepts `limit` with no cap enforced inside the function. A misbehaving caller passing a very large value forces `count(*) OVER ()` to materialize the full matching result set (window functions enumerate all rows before LIMIT applies), which can be expensive on large orgs. The project's convention is validation at the shell/handler layer, but a `Math.min(input.limit, MAX_KEYS_LIMIT)` guard (e.g. 5000, matching other list endpoints) at the query layer would be defence-in-depth. No immediate bug — add as a follow-up or add a JSDoc note documenting the caller's responsibility.

low

Org isolation relies on WHERE clause, not RLS — established pattern, not new risk

domains/utility/src/utility-contract-overview/utility-contract-overview.queries.ts

`listOverviewKeys` accepts `DbOrTx` which may be a non-RLS handle. Cross-org isolation relies entirely on `orgId` in the WHERE clause from `buildFullWhere`. This is the established project pattern (shells own `createRLSDb` wrapping) and not a new risk introduced here — but worth noting that any new handler wiring must use an RLS-wrapped db.

info

Search parameter injection safety inherited from `buildFullWhere` — not a new risk

domains/utility/src/utility-contract-overview/utility-contract-overview.queries.ts

The `search` parameter flows to the existing `buildFullWhere` (already in production via `listOverviewPage`). As long as `buildFullWhere` uses Drizzle's parameterized SQL (not raw string interpolation), there is no injection risk. No action needed — confirmed existing production path.

tests1

info

`matchesNothing` fast-path not tested in isolation

domains/utility/src/utility-contract-overview/__tests__/utility-contract-overview.integration.test.ts

The `matchesNothing` early return (which skips the DB query entirely) is covered only incidentally by the integration test for empty phases. A lightweight unit test asserting `db.select` is never called when `matchesNothing=true` would make the fast-path contract explicit. Low priority — the integration test will catch a regression if the early return is deleted.

improvement3

low

Redundant rows.map() — strips `total` that could stay on the row

domains/utility/src/utility-contract-overview/utility-contract-overview.queries.ts:530

The `.map(r => ({ contractPublicId, contractNumber, serviceName }))` call exists only to drop the `total` column. Since Drizzle's `.select({...})` already types each row with the exact fields, the cleaner approach is to extract `total` from `rows[0]?.total` and return `rows` directly as `keys` (they already lack `total` in the mapped type if you name the select fields as `OverviewKey` fields). Minor clarity improvement — not a bug.

info

`ListOverviewKeysInput` could be `OverviewWhereInput & { limit }` to prevent structural drift

domains/utility/src/utility-contract-overview/utility-contract-overview.queries.ts:103

`ListOverviewKeysInput` manually replicates the same 6 fields as `OverviewWhereInput`. Defining it as `OverviewWhereInput & { readonly limit: number }` would make structural identity explicit — any new filter field added to `OverviewWhereInput` (e.g. a new filter axis) is automatically included in the keys query without a second edit.

info

`listOverviewPage` docstring could explain why window function was not used there

domains/utility/src/utility-contract-overview/utility-contract-overview.queries.ts

`listOverviewPage` uses two parallel queries (page + count), while `listOverviewKeys` uses `count(*) OVER ()` for snapshot consistency. A brief note on `listOverviewPage` explaining why the window approach was NOT used (window functions force a full scan before LIMIT, hurting pagination performance for large orgs) would prevent a future reader from 'simplifying' it into a window function and regressing performance.

History · 8 commits

  1. df474d4needs attentionincremental2H · 3M · 6L2026-07-22 23:12
  2. 8b749afneeds attentionincremental0H · 1M · 3L2026-07-14 17:58current
  3. 8f01e3bsafeincremental0H · 0M · 0L2026-07-07 16:58
  4. 6fb3b0aneeds attentionincremental0H · 1M · 3L2026-07-06 16:26
  5. b7dc960needs attentionincremental0H · 1M · 4L2026-07-04 02:25
  6. c0927dfneeds attentionincremental0H · 2M · 3L2026-07-03 06:34
  7. 1328ef5needs attentionincremental1H · 4M · 5L2026-07-03 06:26
  8. 4c3d40dneeds attentionfull6H · 15M · 12L2026-07-03 06:05