← all branches

feat/energia-det

safeviewing older commit
40b0c87 · incrementalPR #308reviewed 2026-07-16 14:18 UTC0H · 2M · 3L · 1I
The branch
Purpose
feat/energia-det implements the P1 energy anomaly detection engine for the Energía module — 10 corpus-verified detectors as pure functions writing into utility_contract_findings
Goal
Port 10 detectors to pure TypeScript wired into a detection shell; operator script for on-demand runs; nightly sweep Lambda in a separate infra PR
Sub-goals
  • SG-1: 10 pure detectors (billing_error + optimization)
  • SG-2: Detection shell — idempotent upsert, never-regress-status
  • SG-3: Operator runner script
  • SG-4 (this commit): Fix optimistic lock WHERE + counter; widen bill-facts concepts; SQL injection fix; integration tests
The changes (whole branch)
What
Version predicate added to updateEstimate/updateStatus WHERE clauses; shell counter fix; CONCEPT_NAMES widened (kw/totalBill/dap); integration tests for optimistic lock and bill-facts extraction; SQL injection fix in detect-findings.ts
Why
Optimistic lock WHERE clause lacked version predicate (silently inoperative); detect-findings.ts used sql.raw (injection vector); bill-facts concept extraction untested at integration level
Areas
domains/utility+42481packages/database+1740apps/platform+622scripts/energia+1810docs/specs+1220packages/api+11domains/core+11
Blast
26 files, +4789 / -5 lines; utility domain + operator script; no schema change
stacked-pr: base is feat/energia-led (#300)
ci· no CI checks returned for PR 308coderabbit· no .coderabbit.yaml in repo

Findings · 7

correctness1

low

Lock-lost updates not reflected in result counters

domains/utility/src/finding/finding.shells.ts

When updateEstimate returns null (lock rejected), the shell skips updated++ silently. Not data corruption — a monitoring transparency gap.

security1

info

SQL injection fix is correct and complete

scripts/energia/detect-findings.ts

sql.raw replaced with sql.join parameterized bindings. Each org ID is a bound parameter. No residual raw interpolation.

tests3

medium

Inner beforeAll has no explicit timeout (Vitest default 5s)

domains/utility/src/finding/__tests__/finding.integration.test.ts:689

The nested beforeAll inside fetchBillFactRowsForOrg uses Vitest 5s default. Add beforeAll(async () => { ... }, 30_000).

low

uniq('TAR').toUpperCase() is a no-op

domains/utility/src/finding/__tests__/finding.integration.test.ts

uniq() returns uppercase; .toUpperCase() is redundant and implies case normalisation that isn't actually tested.

low

Missing edge case: kwhPerDay when periodDays is 0

domains/utility/src/finding/__tests__/finding.integration.test.ts

No test for identical periodStart/periodEnd. If SQL divides by period length, result would be NULL or an error.

improvement2

medium

IN clause: prefer ANY(array) single binding over sql.join loop

scripts/energia/detect-findings.ts

sql.join builds N fragments. Drizzle accepts JS array as one binding: WHERE s.org_id = ANY(${orgs.map(o => o.id)}::uuid[])

low

publicId uses Math.random() while randomUUID() is imported on next line

domains/utility/src/finding/__tests__/finding.integration.test.ts

Use randomUUID() consistently to eliminate Math.random() + slice(0,30) truncation.

History · 3 commits

  1. 67d4f5csafeincremental0H · 0M · 1L2026-07-21 19:41
  2. 40b0c87safeincremental0H · 2M · 3L2026-07-16 14:18current
  3. af5476dneeds attentionfull5H · 10M · 8L2026-07-16 13:21