feat(curtailment): add preview persistence and selection#141
feat(curtailment): add preview persistence and selection#141rongxin-liu wants to merge 3 commits intomainfrom
Conversation
🔐 Codex Security Review
Review SummaryOverall Risk: MEDIUM Findings[MEDIUM] Curtailment preview can read stale telemetry from a previous device/org after soft-delete reuse
[MEDIUM] Whole-org preview does uncached capability lookups for every device
[LOW] New curtailment store paths return raw database errors to clients
NotesI did not find an auth bypass, SQL injection, command injection, or pool/wallet redirection path in the reviewed diff. I did not run tests in this review environment. Generated by Codex Security Review | |
There was a problem hiding this comment.
Pull request overview
Adds the first end-to-end usable curtailment “preview” path to the Go backend by laying down persistence tables + sqlc query surface, a SQL-backed store, and a domain selector/service that filters/ranks miners and returns a PreviewCurtailmentPlan response. This is wired into fleetd while leaving the remaining curtailment RPCs intentionally Unimplemented.
Changes:
- Added curtailment persistence schema (events/targets/reconciler heartbeat) and a sqlc query to load preview candidates with telemetry + conflict/cooldown signals.
- Implemented curtailment preview domain service/selector (v1
FIXED_KW+ least-efficient-first selection) and a SQL store adapter. - Wired
PreviewCurtailmentPlanthrough the Connect handler andfleetdconfig/service construction.
Reviewed changes
Copilot reviewed 15 out of 18 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| server/sqlc/queries/curtailment.sql | Adds ListCurtailmentPreviewDevices query to load scoped preview candidates + telemetry/conflict/cooldown flags. |
| server/migrations/000039_create_curtailment.up.sql | Introduces curtailment event/target/heartbeat tables, constraints, and indexes. |
| server/migrations/000039_create_curtailment.down.sql | Drops new curtailment tables/trigger on rollback. |
| server/internal/handlers/curtailment/handler.go | Implements PreviewCurtailmentPlan delegation (keeps other RPCs unimplemented). |
| server/internal/handlers/curtailment/handler_test.go | Updates handler construction + adds delegation test for preview. |
| server/internal/domain/stores/interfaces/curtailment.go | Adds curtailment store interface + preview device DTO/params. |
| server/internal/domain/stores/sqlstores/curtailment.go | Implements SQL curtailment store (device-set validation + preview candidate mapping). |
| server/internal/domain/stores/sqlstores/curtailment_integration_test.go | DB integration coverage for preview candidate flags + efficiency conversion. |
| server/internal/domain/curtailment/service.go | Implements PreviewCurtailmentPlan orchestration (scope validation + store-backed candidate load). |
| server/internal/domain/curtailment/selector.go | Implements filtering + ranking + plan building and response mapping. |
| server/internal/domain/curtailment/modes/mode.go | Adds mode abstraction and candidate struct + helpers. |
| server/internal/domain/curtailment/modes/fixed_kw.go | Implements FIXED_KW selection logic + structured insufficient-load error. |
| server/internal/domain/curtailment/service_test.go | Adds unit tests for preview validation/filtering/selection semantics. |
| server/cmd/fleetd/config.go | Adds curtailment config block to fleetd config. |
| server/cmd/fleetd/main.go | Wires SQL curtailment store + preview service into the curtailment handler. |
| server/generated/sqlc/models.go | Regenerated sqlc models for new curtailment tables. |
| server/generated/sqlc/db.go | Regenerated sqlc prepared statement plumbing for new query. |
| server/generated/sqlc/curtailment.sql.go | Regenerated sqlc query implementation for ListCurtailmentPreviewDevices. |
3b4640f to
40ff61f
Compare
Closes #140.
Summary
Adds the curtailment persistence and preview-planning slice.
PreviewCurtailmentPlanis now backed by database-scoped candidate loading, full device-set scope validation, active-target conflict detection, a kW-only selector, and fleetd config for candidate eligibility thresholds and post-event cooldowns.This keeps start/update/stop/read curtailment RPCs intentionally stubbed. The persistence schema is laid down for later event lifecycle, dispatch, reconciliation, and restore work, while this PR makes preview selection usable end to end.
What changed
Persistence schema and sqlc
curtailment_event,curtailment_target, andcurtailment_reconciler_heartbeattables with constraints, active-event indexes, target-work indexes, idempotency/external-reference uniqueness, and reconciler singleton state.ListCurtailmentPreviewDevicessqlc query for whole-org, device-set, and explicit-device preview scopes.curtailment_targetkeyed for device/state/time lookups, while preserving the eventended_atfallback.GetDeviceSetTypesBatchquery.Preview selector and plan math
PreviewCurtailmentPlan, including session org scoping, request normalization, v1 validation, scope conversion, explicit-device resolution, device-set validation, and store-backed candidate loading.FIXED_KWwithLEAST_EFFICIENT_FIRST,FULL, andNORMAL/EMERGENCYpriorities.InvalidArgumentinstead of silently previewing a smaller scope; duplicate requested IDs do not cause false failures.FIXED_KWselection ranks candidates least-efficient first, accumulates current-power snapshots untiltarget_kwis met, allows explicitly positive tolerance for near-misses, and rejects insufficient curtailable load with structured details.UNKNOWNdevice status, maintenance acknowledgement, full-curtailment capability support, active curtailment target conflicts, post-event cooldown, current/recent power thresholds, and recent hashrate.Handler and fleetd wiring
PreviewCurtailmentPlanthrough the curtailment Connect handler while leaving the remaining Fleet curtailment RPCs asUnimplementedstubs.curtailment-/CURTAILMENT_, includingcandidate-min-power-wandpost-event-cooldownwith the existing 1500 W and 10m defaults.fleetdwith the loaded config and existing capability provider.Behavior changes
PreviewCurtailmentPlanis available in fleetd when the service is configured.InvalidArgumentwhen any requested set is outside the caller org or does not exist.InvalidArgumentwhen a requested device is outside the caller org or does not exist.active_curtailmentfor both normal and emergency previews.UNKNOWNstatus are treated as unreachable, matching the rest of the fleet behavior for paired miners without current status.efficiency_jh.Reviewer guide
Start with the persistence and candidate-loading path:
server/migrations/000039_create_curtailment.up.sqlserver/sqlc/queries/curtailment.sqlserver/internal/domain/stores/interfaces/curtailment.goserver/internal/domain/stores/sqlstores/curtailment.goserver/internal/domain/stores/sqlstores/curtailment_integration_test.goThen review the preview behavior:
server/internal/domain/curtailment/service.goserver/internal/domain/curtailment/selector.goserver/internal/domain/curtailment/modes/mode.goserver/internal/domain/curtailment/modes/fixed_kw.goserver/internal/handlers/curtailment/handler.goserver/cmd/fleetd/config.goserver/cmd/fleetd/main.goGenerated outputs live under
server/generated/sqlc,server/generated/grpc/curtailment/v1, andclient/src/protoFleet/api/generated/curtailment/v1.Out of scope
StartCurtailment,UpdateCurtailmentEvent,StopCurtailment,GetActiveCurtailment, andListCurtailmentEventsbusiness logic.Test coverage
Added or updated tests cover:
FIXED_KWselection math, tolerance echoing, least-efficient-first ordering, deterministic tie-breaking, last-miner overshoot, explicit tolerance near-miss, strict omitted/zero tolerance, and insufficient curtailable load.UNKNOWNstatus, maintenance, missing full-curtailment capability, active curtailment conflicts, cooldown, unreliable power telemetry, phantom load, and non-hashing miners.Focused checks run locally:
go test ./server/internal/domain/curtailmentgo test ./server/internal/domain/stores/sqlstores -shortcd server && golangci-lint fmt --diff -c .golangci.yamlcd server && just lintcd server && just buildgit diff --checkGitHub PR Gate is passing on latest head
4a14467.