← all branches

feat/energy-api

needs attentionviewing older commit
1446f7e · incrementalPR #207reviewed 2026-07-05 04:08 UTC2H · 4M · 7L
The branch
Purpose
Port the legacy electricity-data-api (billing + savings + metrics + webhooks + PDFs, Pulumi/DDB/Athena stack) into the platform, replacing all AWS plumbing with platform idioms.
Goal
Complete Phases 0-8 of the 10-phase port roadmap: metrics seam (Phase 1), CFE reference data (Phase 2), bill calculators (Phase 3), run orchestration pure core (Phase 4), webhooks pure core (Phase 5), savings pure core (Phase 6), public /v1 API (Phase 7), PDF generation (Phase 8).
Sub-goals
  • SG-1 Metrics adapter: Tinybird -> SiteMetrics (derived-at-read, integral transform, DQ)
  • SG-2 CFE rate values + TOU schedules as reference data
  • SG-3 Bill calculators: 4 strategies, THOR FAC, demand resolution
  • SG-4 Run orchestration: pure planner + runScheduledBillingShell
  • SG-5 Webhooks: WebhookEndpointFCIS + WebhookDeliveryFCIS
  • SG-6 Savings: SavingsFCIS.calculateSavingsReport with EBL leakage
  • SG-7 Public /v1 API: GET /sites/:id/energy-summary + monitoring + payment-status + webhooks
  • SG-8 PDFs: @batu/bill-pdf (react-pdf) + S3 persist
The changes (whole branch)
What
This commit: rate limiting on public v1 API catch-all (3 buckets: auth/read/write, SHA-256 keyed per credential), /i flag removed from SITE_ID_PATTERN in energy-summary and savings handlers, bill-calculator 0-vs-absent demand fix (measuredDemandForCharge returns undefined not 0 for absent measurement), new monitoring/payment-status/webhooks contracts wired in route.ts, migrations 0054-0056 (savings_configs, savings_reports, site_exception_states, tariff_jobs, tariff_tou_schedules tables + operation_start_date moved from site_locations to sites).
Why
Correctness fix: genuine 0 kW demand (solar fully-offset site) was treated identically to absent measurement, causing under-billing. Security hygiene: /i flag on regex lets lowercase IDs pass format validation but fail DB lookup, leaking a format-vs-not-found distinction. Rate limiting: public API surface now has 3 buckets to guard against abuse.
Areas
apps/platform+183034414domains/cross-domain+843513domains/utility+661228packages/database+25398domains/metrics+118014packages/api+114233
Blast
477 files, +49.8k/-4.7k across the whole branch. This commit: 20 branch-own files changed (rate limiting, billing calc fix, migration journal). Public API surface expanded with 3 new endpoint groups.
SSRF gap in webhook URL validation /i flag on WEBHOOK_PUBLIC_ID_PATTERN in webhooks.handler.ts deactivatePaymentMonitoringShell Result not checked in monitoring DELETE
CI· No statusCheckRollup available from GitHub at review timeCodeRabbit· No .coderabbit.yaml in repo

Findings · 13

security4

high

Webhook URL registration permits SSRF to internal endpoints

domains/core/src/webhook-endpoint/webhook-endpoint.decisions.ts:86

URL validation checks only protocol === https: but does not block loopback (127.0.0.1), link-local (169.254.169.254 EC2 IMDS), or RFC-1918 ranges. A customer can register these and the dispatcher will POST signed payloads to internal services.

low

deactivatePaymentMonitoringShell result silently swallowed

apps/platform/src/api/handlers/public-v1/monitoring.handler.ts:176

Result awaited and discarded. On shell failure handler returns 200 claiming payment_monitoring: false even though deactivation failed.

low

Anonymous requests collapse to shared ip:unknown rate-limit bucket

apps/platform/src/app/api/v1/[...ts-rest]/route.ts:203

When x-forwarded-for is absent all unauthenticated requests share one sliding window per instance.

low

WEBHOOK_PUBLIC_ID_PATTERN /i flag inconsistent with prior fix

apps/platform/src/api/handlers/public-v1/webhooks.handler.ts:40

Functionally harmless (lowercase IDs never exist in DB) but inconsistent with the /i removal applied in the same commit to other handlers.

conventions2

high

/i flag on WEBHOOK_PUBLIC_ID_PATTERN not removed

apps/platform/src/api/handlers/public-v1/webhooks.handler.ts:40

Same commit removed /i from energy-summary and savings handlers with an explanatory comment. Public IDs are uppercase Crockford base32; /i is dead permissiveness violating the pattern convention.

medium

tariff_jobs RLS SELECT policy un-scoped for all authenticated users

packages/database/drizzle/0054_illegal_human_cannonball.sql:178

USING (true) grants read access to every authenticated user with no org-scoping. If intentional for global reference data, add a comment; otherwise narrow the policy.

tests4

medium

0-vs-absent fix untested on tou_band demand path

domains/utility/src/bill-calculator/__tests__/bill-calculator.decisions.test.ts

New tests cover always-schedule branch (peak_15min / max_of_bands). The tou_band branch returns by_tou_band?.[band] — undefined for absent band — but no test exercises 0-vs-absent on this path.

medium

normalizeBodylessDelete has no unit tests

apps/platform/src/app/api/v1/[...ts-rest]/route.ts

Added to fix a real production SyntaxError. Three distinct code paths are trivially testable and regression-sensitive.

low

checkRateLimit sliding-window logic has no unit tests

packages/api/src/middleware/rate-limit.ts

At-limit boundary, window-expiry reset, and retryAfter calculation are correctness-sensitive edges with no coverage.

low

monitoring DELETE payment-monitoring shell failure uncovered by tests

apps/platform/src/api/handlers/public-v1/monitoring.handler.ts

Handler integration test for scope=all with failing payment-monitoring shell would catch the unchecked Result.

improvement3

medium

invalidRequest helper duplicated across monitoring + webhooks handlers

apps/platform/src/api/handlers/public-v1/monitoring.handler.ts

Identical invalidRequest() and database cast pattern copy-pasted into monitoring and webhooks. Move to apps/platform/src/api/utils/public-v1-validation.ts.

low

rateLimitBucket hashes full Authorization header including Bearer prefix

apps/platform/src/app/api/v1/[...ts-rest]/route.ts

Strip the prefix before hashing to avoid different buckets for same token with different casing.

low

listLatestPaymentStatusByContractPublicIds dedup could move to SQL

domains/utility/src/bill/bill.queries.ts

JS-side first-row-wins dedup transfers all bill rows per contract. DISTINCT ON (utilityContracts.publicId) CTE would emit exactly one row per contract.

History · 8 commits

  1. d6b4a35needs attentionincremental4H · 4M · 5L2026-07-05 20:29
  2. 678cb93safeincremental0H · 0M · 1L2026-07-05 17:40
  3. d219403needs attentionincremental1H · 3M · 4L2026-07-05 05:37
  4. 5ecd80cneeds attentionincremental1H · 2M · 1L2026-07-05 04:59
  5. 3fd1d03needs attentionincremental0H · 3M · 5L2026-07-05 04:36
  6. 1446f7eneeds attentionincremental2H · 4M · 7L2026-07-05 04:08current
  7. ca73a1bneeds attentionfull4H · 10M · 14L2026-07-05 02:46
  8. c24738eneeds attentionincremental3H · 10M · 9L2026-07-04 05:12