← all branches

claude/zealous-lovelace-cbc97f

needs attentionviewing older commit
6fb3b0a · incrementalPR #215reviewed 2026-07-06 16:26 UTC0H · 1M · 3L · 3I
The branch
Purpose
ADR-020 follow-up: add a nightly reconcile-and-repair backstop for utility_contract_overview drift caused by trigger-bypass writes, making drift observable (always expected 0) and auto-repaired.
Goal
Scheduled pg_cron job (utility_contract_overview_reconcile) detects and repairs missing/orphaned/mismatch rows in the bills/contratos read-model, logs an audit row per run, and alerts on non-zero drift via RAISE WARNING + pg_notify. A CI watchdog (GHA) independently checks the audit log nightly and opens a GitHub issue on anomalies.
Sub-goals
  • SG-1: SQL function utility_contract_overview_reconcile() — SECURITY DEFINER, repair-capped, scheduled via pg_cron
  • SG-2: utility_contract_overview_reconcile_runs audit table — backend-only RLS, service_role only
  • SG-3: runOverviewReconcile TS wrapper — mechanism-agnostic fallback for EventBridge Lambda path
  • SG-4: CI drift watchdog (.github/workflows/uco-drift-watchdog.yml) — independent NO_RECENT_RUN / DRIFT / RUN_FAILED alerting
  • SG-5: Integration + unit test coverage for drift-detect → repair path
The changes (whole branch)
What
This increment re-slots the reconcile migration from 0054 → 0057 (merge collision with three other migrations that landed on main), updates all documentation references, and adds missing schema/index.ts barrel exports for entities introduced by migrations 0055/0056.
Why
Three migrations were merged to main while this branch was in review, occupying slots 0054-0056. Re-slotting to 0057 is required for db:migrate to apply cleanly in order.
Areas
packages/database/drizzle+133920domains/utility/src/utility-contract-overview+3420.github/workflows+1240scripts/db+670docs/ADRs+62
Blast
15 files, +14085/-3 across DB schema/migrations, utility domain, GHA watchdog, and docs. Expand-only migration (no destructive DDL). No app code changed. pg_cron requires out-of-band enablement per environment.
pg_cron must be enabled out-of-band on staging+prod before the schedule takes effect migration re-slot assumes no environment has 0054_tense_gauntlet applied — verify staging/prod migration state before merging
ci· statusCheckRollup not accessible via personal tokencoderabbit· no .coderabbit.yaml in repo

Findings · 7

correctness1

medium

runOverviewReconcile accepts DbOrTx but SQL function is unsafe inside a caller-supplied transaction

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

utility_contract_overview_reconcile() uses CREATE TEMP TABLE _uco_drift ON COMMIT DROP and accumulates pg_advisory_xact_lock locks via utility_contract_overview_refresh(). If a caller passes an in-progress transaction as db, the temp table survives until the outer transaction commits and the advisory locks are held for the outer transaction's lifetime — potentially deadlocking other writers. All current call sites pass a raw db handle so this is not triggered today, but the DbOrTx signature communicates that wrapping in a transaction is safe, which it is not. The parameter type should be narrowed to Database.

security2

low

No explicit REVOKE on utility_contract_overview_reconcile_runs table

packages/database/drizzle/0057_chubby_starhawk.sql:15

The function ships REVOKE EXECUTE FROM authenticated, anon, public (line 187) but the audit table itself has only RLS + a service_role PERMISSIVE policy. Supabase auto-grants table-level SELECT to authenticated at creation, so RLS is the sole barrier against a future policy misconfiguration exposing cross-org (org_id, contract_id) pairs in the details JSONB. An explicit REVOKE SELECT, INSERT, UPDATE, DELETE ON utility_contract_overview_reconcile_runs FROM authenticated, anon would add the same defence-in-depth layer used for the SECURITY DEFINER functions.

info

CREATE TEMP TABLE uses unqualified name while DROP uses pg_temp-qualified name

packages/database/drizzle/0057_chubby_starhawk.sql:84

DROP TABLE IF EXISTS pg_temp._uco_drift (qualified) vs CREATE TEMP TABLE _uco_drift (unqualified). In plpgsql, CREATE TEMP TABLE always targets pg_temp regardless of search_path, so this is safe. All subsequent reads are schema-qualified. No exploitable path since the function is SECURITY DEFINER revoked from all client roles. Documenting for clarity.

conventions2

low

Mixed import quote styles in new schema/index.ts exports

packages/database/src/schema/index.ts:47

The new exports (tariffTouSchedules, savingsConfigs) use single-quote paths while tariffJobs and the reconcile-runs export use double-quote paths. The existing utility block at lines 41-56 uses single quotes uniformly. Not introduced by this commit alone but the re-slot bundles these additions without normalising the style.

info

DESC index missing .nullsLast() chain in TypeScript schema

packages/database/src/schema/utility-contract-overview-reconcile.ts:59

ranAtIdx uses .on(table.ranAt.desc()) without .nullsLast(). The existing utility_contract_overview.ts index (line 173) chains .desc().nullsLast() explicitly. Drizzle emits NULLS LAST by default for DESC, so the migration SQL is correct, but the TypeScript source doesn't declare the intent explicitly — inconsistent with the established convention in this schema package.

improvement2

low

c_repair_cap (2000) is hardcoded in SQL — not tunable without a migration

packages/database/drizzle/0057_chubby_starhawk.sql:64

The repair cap is a DECLARE constant inside plpgsql. If a large trigger-bypass disaster requires raising the cap for a one-off reconcile run, the only option is deploying a new migration. A GUC-backed default (current_setting('batu.reconcile_repair_cap', true)::integer with fallback to 2000) would allow a superuser to tune it at runtime without a deploy. Low priority given the cap is intentionally conservative and manual bulk reconcile is the documented human path.

info

Schema exports for 0055/0056 entities bundled into the 0057 re-slot commit

packages/database/src/schema/index.ts

Exports for tariffTouSchedules, savingsConfigs, savingsReports, tariffJobs, siteExceptionStates belong to migrations 0055/0056 merged from main. Bundling them into the re-slot commit conflates two concerns and makes git-bisect / blame less clean. For future re-slots, consider a separate commit for schema/index.ts export additions.

History · 8 commits

  1. df474d4needs attentionincremental2H · 3M · 6L2026-07-22 23:12
  2. 8b749afneeds attentionincremental0H · 1M · 3L2026-07-14 17:58
  3. 8f01e3bsafeincremental0H · 0M · 0L2026-07-07 16:58
  4. 6fb3b0aneeds attentionincremental0H · 1M · 3L2026-07-06 16:26current
  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