feat/energy-api
needs attentionviewing older commit1446f7e · incrementalPR #207reviewed 2026-07-05 04:08 UTC2H · 4M · 7L- 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
- 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+18303−4414domains/cross-domain+8435−13domains/utility+6612−28packages/database+2539−8domains/metrics+1180−14packages/api+1142−33
- 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.
Findings · 13
security4
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.
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.
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.
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
/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.
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
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.
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.
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.
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
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.
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.
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
- d6b4a35needs attentionincremental4H · 4M · 5L2026-07-05 20:29
- 678cb93safeincremental0H · 0M · 1L2026-07-05 17:40
- d219403needs attentionincremental1H · 3M · 4L2026-07-05 05:37
- 5ecd80cneeds attentionincremental1H · 2M · 1L2026-07-05 04:59
- 3fd1d03needs attentionincremental0H · 3M · 5L2026-07-05 04:36
- 1446f7eneeds attentionincremental2H · 4M · 7L2026-07-05 04:08current
- ca73a1bneeds attentionfull4H · 10M · 14L2026-07-05 02:46
- c24738eneeds attentionincremental3H · 10M · 9L2026-07-04 05:12