claude/zealous-lovelace-cbc97f
needs attentionviewing older commit6fb3b0a · incrementalPR #215reviewed 2026-07-06 16:26 UTC0H · 1M · 3L · 3I- 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
- 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+13392−0domains/utility/src/utility-contract-overview+342−0.github/workflows+124−0scripts/db+67−0docs/ADRs+6−2
- 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.
Findings · 7
correctness1
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
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.
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
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.
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
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.
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
- df474d4needs attentionincremental2H · 3M · 6L2026-07-22 23:12
- 8b749afneeds attentionincremental0H · 1M · 3L2026-07-14 17:58
- 8f01e3bsafeincremental0H · 0M · 0L2026-07-07 16:58
- 6fb3b0aneeds attentionincremental0H · 1M · 3L2026-07-06 16:26current
- b7dc960needs attentionincremental0H · 1M · 4L2026-07-04 02:25
- c0927dfneeds attentionincremental0H · 2M · 3L2026-07-03 06:34
- 1328ef5needs attentionincremental1H · 4M · 5L2026-07-03 06:26
- 4c3d40dneeds attentionfull6H · 15M · 12L2026-07-03 06:05