← all branches

feat/solis

needs attentionviewing older commit
c3cb64a · fullpre-PRreviewed 2026-07-07 21:29 UTC5H · 9M · 9L · 5I
The branch
Purpose
Port the SolisCloud (91-device brand) into the Batu v2 metrics engine so the universal metrics coordinator can dispatch it alongside eGauge, victron, growatt, helioscope, and the other 7 brands.
Goal
Three Lambda workers (metrics + connection + explore) with stateless HMAC-SHA1 signing, CDK stack, and probe-verified granularity decisions — completing SG-1 and SG-2 of the branch plan.
Sub-goals
  • SG-1: SolisCloud API probe + worker package (metrics/connection/explore) + 71 unit tests
  • SG-2: CDK IntegrationSolisLambdaStack + main.ts + coordinator SSM reads/invoke grants
The changes (whole branch)
What
New services/metrics/integrations/solis/ package (3 handlers, HMAC client, translation layer, domain types, 71 tests); CDK IntegrationSolisLambdaStack with IAM and SSM ARN params; solis manifest with corrected key/key_secret field names; seed-metrics-catalog and seed-integration-arns solis entries; SecretProvider union extended with device integration providers.
Why
SolisCloud is the largest un-ported brand (91 devices). Porting enables the universal coordinator to collect 5-min native data from all Solis plants via the same SFN/jobType dispatch as other brands.
Areas
services/metrics/integrations/solis/+22000infra/cdk/+45011packages/integration-manifests/+750160packages/database/+22413scripts/metrics/+14000domains/core/+151
Blast
53 files, +6461/−196 lines. All new code additive — no existing handlers modified. SecretProvider union is type-only. CDK stack is brand-new CloudFormation stack, no existing resources renamed.
new-integration hmac-signing secrets-manager cdk-new-stack
CI / typecheck· No CI run on pre-PR branch — scope.md shows 71/71 tests green locallyCDK synth· scope.md: synth clean, template verifiedCodeRabbit· No .coderabbit.yaml in repoprobe (2026-07-07)· 5m stationDay native confirmed; HMAC signing verified against prod payload

Findings · 22

correctness4

medium

monthRecordsToBatu: fieldSeen set before dateStr validation

services/metrics/integrations/solis/src/translation/points-to-batu.ts:317

fieldSeen=true fires before the dateStr regex check. A month record with valid energy but malformed dateStr sets fieldSeen true while being skipped — returns ok:true with points:[] instead of ok:false. Coverage window marked collected while nothing persists to Tinybird.

medium

powerToWatts: default pec=0.001 coupled to kW — silent 1000x error if vendor sends powerStr=W without powerPec

services/metrics/integrations/solis/src/translation/points-to-batu.ts:86

If vendor sends powerStr=W with no powerPec, defaults compute raw×0.001×1=raw/1000, under-reporting by 1000×. No runtime error; channel passes through silently wrong.

low

fetchAllStations: extra page fetch when plant count is exact multiple of 100

services/metrics/integrations/solis/src/engine/solis-client.ts:272

Termination: page.records.length < STATION_PAGE_SIZE. Exact-full-page case wastes one vendor call +100ms politeDelay.

low

31-day cap uses UTC days, not local calendar days

services/metrics/integrations/solis/src/handlers/metrics.lambda.ts:152

UTC-6 site: 31-day window spans 32 local days. 31d+1s window triggers cap on a legitimate collection. Use localDaysInWindow().length.

security3

medium

secretConfigArn forwarded to SM without ARN-format validation

services/metrics/integrations/solis/src/engine/solis-client.ts:295

No format check before GetSecretValueCommand. Blast radius bounded by IAM prefix wildcards (systemic across eGauge/Growatt too).

medium

IAM legacy path {env}/solis/* covers portal secrets

infra/cdk/src/stacks/services/metrics/integrations/solis/iam.ts:43

Prod holds /portal (username/password) variants under the same prefix. Scope to {env}/solis/oid=* for least privilege.

low

Raw vendor msg in InvocationError.message → CloudWatch Logs

services/metrics/integrations/solis/src/engine/solis-client.ts:174

May include account-identifying strings. Internal boundary only. Acceptable for ops.

conventions4

high

INFRA_DESIGN.md missing from CDK stack directory

infra/cdk/src/stacks/services/metrics/integrations/solis/

INTEGRATION_STANDARDS.md §8 checklist item 1. eGauge and Growatt both carry this file.

high

Descriptor not registered in descriptors-catalog.ts

services/metrics/integrations/solis/src/

INTEGRATION_STANDARDS.md §8 item 8. metrics-pipeline.md marks descriptors dead/legacy — decision needs to be explicit.

medium

mapListError uses non-exhaustive default: switch

services/metrics/integrations/solis/src/handlers/explore.lambda.ts:99

Use SolisFetchFailure["reason"] param type + exhaustive switch matching mapFetchError pattern.

medium

probeFailurePayload credentialValid: false for timeout/unreachable

services/metrics/integrations/solis/src/handlers/connection.lambda.ts:96

May trigger false credential-rotation alerts on transient network errors. Fix: reason===auth-failed ? false : true.

tests7

high

HTTP 408 (clock-skew) classification untested

services/metrics/integrations/solis/src/engine/solis-client.ts:144

>15-min Date skew → HTTP 408 routes to UpstreamUnavailable (infinite SFN retry) not UpstreamAuthFailed. No test pins this.

high

DST fall-back transition not tested in dayPointsToBatu

services/metrics/integrations/solis/src/__tests__/points-to-batu.test.ts:65

Code handles fall-back dedup; spring-forward tested but fall-back not. Add America/Tijuana 2026-11-01 test.

high

stationMonth failure path has zero handler-level coverage

services/metrics/integrations/solis/src/__tests__/metrics-handler.test.ts:218

Auth-failure test only exercises 5m/stationDay. The 1d branch failure return path is never tested.

medium

Only one HMAC Python cross-vector

services/metrics/integrations/solis/src/__tests__/signing.test.ts:16

Missing: empty body, non-ASCII body content.

medium

resolveChannels scalar-params fallback untested

services/metrics/integrations/solis/src/__tests__/metrics-handler.test.ts:41

All handler tests use sources[]. Fallback from flat params never exercised.

medium

Inverted-window and NaN-date validation paths not tested

services/metrics/integrations/solis/src/__tests__/metrics-handler.test.ts:92

Only absent from/to case tested. Inverted window and invalid date strings uncovered.

low

Explore pagination with >100 plants untested

services/metrics/integrations/solis/src/__tests__/explore-handler.test.ts:63

Multi-page loop (pageNo+1, politeDelay) has no dedicated test.

improvement4

low

windowDays float cap can misfire on fractional windows

services/metrics/integrations/solis/src/handlers/metrics.lambda.ts:152

31d+1s gives 31.000011, rejecting a legitimate collection. Use localDaysInWindow().length > MAX_WINDOW_DAYS[granularity].

low

resolveChannels scalar-params fallback is vestigial

services/metrics/integrations/solis/src/handlers/metrics.lambda.ts:81

Coordinator never sends the flat-key shape. Remove or comment as test-only.

low

mapFetchError and mapListError nearly identical

services/metrics/integrations/solis/src/handlers/explore.lambda.ts:99

A shared engine/error-map.ts would enforce exhaustiveness in both call sites.

info

CfnOutput exportNames are console-only

infra/cdk/src/stacks/services/metrics/integrations/solis/lambda.stack.ts:102

Coordinator reads ARNs via SSM not Fn::ImportValue. Comment would prevent accidental cross-stack coupling.

History · 2 commits

  1. de07ba1needs attentionfull2H · 11M · 10L2026-07-08 02:50
  2. c3cb64aneeds attentionfull5H · 9M · 9L2026-07-07 21:29current