← all branches

claude/energy-management-features-4fdefc

needs attention
4f59fe5 · fullpre-PRreviewed 2026-07-11 06:14 UTC2H · 6M · 5L · 4I
The branch
Purpose
Backtest CFE bill-audit detectors against the real historical corpus before productizing; add Energía module JTBD map and design documentation
Goal
Validate or kill each candidate detector (estimated_reading, fp_recompute, fp_regime_change, inactive_service, vat, multiplier, component_sum, rate_wrong_month, rate_wrong_zone, demanda_facturada) against 122k real XML bills before any customer-facing exposure
Sub-goals
  • SG-1: Foundation bill_facts TEMP TABLE + corpus census (00-01)
  • SG-2: Operational detectors — demand overage/oversized/tariff-mismatch, FP streaks, DAP/porteo (10-21)
  • SG-3: Estimated readings, multiplier check, period anomalies, YoY kWh, inactive-service, IVA anomaly, FP-recompute (30-36)
  • SG-4: Arithmetic recompute + rate verification + component calibration + demanda facturada (40-43)
  • SG-5: Energía JTBD map + design principles + detector verdict ledger + module spec (docs)
The changes (whole branch)
What
Added scripts/corpus-study/ (run.mjs runner + 20 SQL detector files), docs/design/ (JTBD map, design principles, detector ledger), docs/specs/ (Energía module spec PR1/PR2), .gitignore update, and .mcp.json new supabase production server entry
Why
Disciplined pre-ship validation: five candidate CFE-error detectors were killed by this study (multiplier, component_sum, rate_wrong_month, rate_wrong_zone, demanda_facturada). Only what survives XML-level verification ships to customers.
Areas
scripts/corpus-study+56170docs/design+6040docs/specs+4230.mcp.json+51.gitignore+30
Blast
30 files, +6652/-1 lines. No production code changed — entirely exploratory tooling and documentation. .mcp.json change affects all Claude Code sessions that load the project config.
mcp-config-change no-tests-by-design production-db-access-via-mcp
ci· No CI checks on pre-PR branchcoderabbit· No .coderabbit.yaml present

Findings · 17

correctness5

medium

90-prize-rollup.sql: comment in discoverFiles() misidentifies which guard covers it

scripts/corpus-study/run.mjs:361

The comment reads '// e.g. 90-prize-rollup.sql — harmless template' next to 'if (!prefix) return true'. But 90-prize-rollup.sql has prefix '90' (numeric), so it never hits this guard — it's filtered by wanted.has('90') when --only is used. The comment actively misleads maintainers about how the file is included.

medium

00-bill-facts.sql header says 'CREATE TEMP VIEW' but creates TEMP TABLE

scripts/corpus-study/sql/00-bill-facts.sql:4

The top-level header (line 4) states 'Only SELECT / CREATE TEMP VIEW.' The file actually issues CREATE TEMP TABLE. The internal comment at ~line 107 correctly documents the discrepancy, but the header is the first thing a contributor reads and states the wrong DDL type.

low

statementKind() does not recognize CREATE TEMP VIEW — would be refused as 'other'

scripts/corpus-study/run.mjs:290

The temp-ddl regex matches only CREATE TEMP|TEMPORARY TABLE. A CREATE TEMP VIEW in a foundation file would be classified 'other' and refused, halting that file. No current file uses TEMP VIEW, but the comment mismatch in 00-bill-facts.sql header creates a trap for future contributors.

low

41-rate-verification: RETIRED detectors (rate_wrong_month, rate_wrong_zone) still emit rows into findings.csv

scripts/corpus-study/sql/41-rate-verification.sql:8

Both rate_wrong_month and rate_wrong_zone are documented RETIRED/DISPROVEN in the file header. Their SELECTs still match the findings contract, so run.mjs aggregates their rows into findings.csv and summary.md. A reader of those outputs would see these retired finding types without knowing they're invalid.

low

stripLeadingComments() uses indexOf('*/') without depth tracking — inconsistent with splitStatements

scripts/corpus-study/run.mjs:268

splitStatements correctly tracks nested block comment depth; stripLeadingComments uses a plain indexOf('*/'), stopping at the first */ regardless of nesting. A leading nested block comment would leave residue classified as 'other' and refused. No current SQL file triggers this; consequence is conservative (refusal not misexecution).

security4

high

supabase MCP entry has no project_ref — implicitly connects to auth-token default (likely production)

.mcp.json

The new 'supabase' entry uses 'https://mcp.supabase.com/mcp?read_only=true' with no project_ref, unlike supabase-staging which pins to project_ref=xrgglyfbginnqgqoqirz. Without a project_ref the MCP server resolves to whichever project the authenticated user's token defaults to — for a founder's account that is production. Any AI agent with this MCP server loaded can issue queries against the production database. Should be pinned to an explicit project_ref.

high

read_only=true is an unverified URL parameter — not a documented Supabase MCP enforcement mechanism

.mcp.json

There is no evidence that mcp.supabase.com recognizes or enforces a read_only query parameter. If silently ignored, the 'supabase' MCP entry grants full read/write access to the production project. The only safe way to enforce read-only access is a Postgres role with GRANT SELECT only at the DB level, not a URL hint.

medium

No runtime check that DATABASE_URL is a read-only credential

scripts/corpus-study/run.mjs

The phased safety model switches to default_transaction_read_only=on after foundation files, but if a write-capable credential is passed (e.g. service_role), the 00-foundation phase runs without read-only enforcement. A startup check (e.g. verify current_role is not service_role/postgres) would catch accidental write-capable connections before any SQL runs.

medium

Output files contain customer PII and financial data — no handling guidance

scripts/corpus-study/run.mjs

findings.csv embeds org_name, rpu, financial amounts, and JSONB bill details. The directory is gitignored but the README gives no data-handling guidance (retention, deletion, sharing restrictions). For a multi-analyst team, ad-hoc CSV files of customer financial data represent a GDPR/data-governance risk.

conventions2

high

scope.md describes a completely different feature (feat/mod-gates)

.branch/scope.md

scope.md describes the soft-launch entitlement gate / ORG_MODULES widening. This branch adds corpus study tooling and Energía documentation with no overlap. Reviewers using scope.md to understand intent will be completely misled. Should be replaced with content describing this branch's actual purpose.

medium

.mcp.json missing trailing newline

.mcp.json

The diff shows '\ No newline at end of file'. POSIX text file convention requires a trailing newline. All other JSON files in the repo end with a newline.

tests1

medium

splitStatements(): E'...' escape-string syntax would be misparsed

scripts/corpus-study/run.mjs:120

The tokenizer handles '' doubling but not PostgreSQL's E'...' backslash-escape syntax (E'it\'s tricky'). In squote state, a backslash is treated as a literal, so the following ' closes the string early. No current SQL file uses this syntax, but adding E'...' to any future detector SQL would cause a silent statement split and a REFUSED error. Fix: detect E/B/U& prefixes before entering squote state.

improvement5

medium

01-census is always-included (discovery) but NOT foundation (safety allowlist) — silent asymmetry

scripts/corpus-study/run.mjs:360

discoverFiles() always keeps prefix '00' AND '01' as setup dependencies. But isFoundation only matches '00-'. A future '01-' file needing CREATE TEMP TABLE would be REFUSED with 'outside foundation files' despite the code comment grouping them together. Either expand isFoundation to include '01-', or document the intentional asymmetry.

medium

90-prize-rollup.sql runs unconditionally and produces confusing '0 statement(s)' output

scripts/corpus-study/run.mjs:361

The file is pure SQL comments, so it passes through with '0 executable statements' in the log. The null-prefix guard (the intended mechanism) never fires for it since its prefix is '90'. Options: rename with a non-numeric prefix (template-prize-rollup.sql), exclude it explicitly, or suppress '0 statement' files from the progress log.

low

sql.unsafe() usage lacks an inline explanation

scripts/corpus-study/run.mjs:396

sql.unsafe() is alarming to readers unfamiliar with postgres-js — it actually means 'no parameterization', safe here because every string is a static literal from a hard-coded file. A one-line comment at the first usage clarifying this would prevent future readers from second-guessing the choice.

low

No --help flag; CLI is undiscoverable without reading README

scripts/corpus-study/run.mjs:56

parseArgs() silently ignores --help. A simple --help branch printing the header usage block would make the script self-documenting for ad-hoc analysts.

info

energia-module-pr1-pr2.md hardcodes source-file line numbers that will drift

docs/specs/energia-module-pr1-pr2.md:29

The implementation checklist references specific line numbers in existing files. These will drift as the codebase evolves. Prefer context strings or anchor comments over line numbers in specs meant to be auditable by an independent agent.