Skip to content

refactor(drivercontrol): move operator holds out of the API - #1997

Draft
hannesb90 wants to merge 3 commits into
srcfl:masterfrom
hannesb90:refactor/driver-control-holds
Draft

hannesb90 wants to merge 3 commits into
srcfl:masterfrom
hannesb90:refactor/driver-control-holds

Conversation

@hannesb90

Copy link
Copy Markdown
Contributor

Problem and result

Step 2 of the proposal in #1994 (ADR 0012), toward #520. Stacked on #1995;
only the last commit is new here.

The operator hold (one bounded value per driver, sent and released under one
per-driver lock, handed back to the driver's own default when it ends) lived
inside internal/api. A planned value will need the same lock and the same
expiry, or an operator hold and a planned one would each expire into a default
that undoes the other. This moves that code, unchanged, into
internal/drivercontrol, and does nothing else with it yet.

  • drivercontrol.Holds owns the per-driver state, Send, ArmLocked,
    Expire, SendDefault, Active, Peek and Clear. The bodies are the
    former Server methods, with the server's registry passed in as a small
    interface.
  • The API keeps its routes, status codes, response bodies and
    Server.SendDriverDefault (used by the watchdog in cmd/ftw). The registry
    lifecycle hook now calls Holds.Clear.
  • The send error is still answered while the per-driver lock is held, as
    before. Send takes a done callback for that reason, so the status read
    that explains a 404 or 409 cannot race a lifecycle change.
  • As the owner asked in answer 3 on docs(adr): propose heat control from a target temperature and a learned twin #1994, the unused thermal policy draft
    is removed: DecideIntent, DecisionInput, Intent, IntentKind,
    TemperatureBand, MarginalPrice and the synthetic_price strategy.
    Nothing outside the package used them. The declaration vocabulary that feat(drivers): let a control declare what it does to heat #1995
    uses (AssetKind, InfluenceStrategy, ValidAssetKind) stays.

Scope and safety

No behaviour change is intended, and none is visible to a user.

Verification

Run on a Raspberry Pi 4 (linux/arm64), Go 1.26.10:

go vet ./internal/drivercontrol ./internal/thermal ./internal/drivers ./internal/api ./cmd/ftw   clean
go test ./internal/drivercontrol ./internal/thermal ./internal/drivers -count=1                  ok
go test ./internal/api -count=1                                                                    ok
go test ./cmd/ftw -count=1   (umask 022)                                                           ok

With the Pi's default umask 002, seven cmd/ftw nova key tests fail because
the temporary key directory is group-writable. That is the environment, and
the same on master. The race detector does not run on this Pi's 39-bit VMA;
CI covers it.

Checklist

  • The change follows VISION.md and one selected scope.
  • I checked overlapping PRs and coordinated shared files/contracts.
  • Relevant checks cover the changed behaviour and failure paths.
  • A human reviewed changed UI in a browser, or no UI changed.
  • A Changeset is included, or the change is exempt.
  • Every commit has a DCO sign-off.

🤖 Generated with Claude Code

hannesb90 and others added 3 commits November 10, 2026 09:51
A control may now declare asset, strategy, zone and write timings in the
thermal contract's vocabulary, and a driver may list the metrics that are
a heat store's temperature. /api/drivers/{name} exposes both. Nothing
plans for or sends a heat command because of them; this is the
declaration step of the proposal in srcfl#1994.

A string control may declare a closed set of options, which the control
API now enforces. A misdeclared thermal field drops the control, as an
unrenderable declaration already does. nestedBlock now requires the whole
field name, so temp_sensors is not read as sensors.

Refs srcfl#520

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: hannesb90 <bruhn90@gmail.com>
The owner's answer on srcfl#1994: planned control needs the device to fall
back on its own through a device-side timed hold; otherwise planned
values stay a small step from the device default. A control may now
declare device_hold_max_s, and a number input its default, so Core can
own that lease rule. A zero or non-numeric hold, or a default outside
min..max or on a non-number input, drops the control.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: hannesb90 <bruhn90@gmail.com>
The per-driver lock, hold, expiry and default path move unchanged from
internal/api into internal/drivercontrol, so a planned value can later go
through the same lock and expiry as an operator's (ADR 0012). The API
keeps its routes, responses and SendDriverDefault; the registry lifecycle
hook now clears holds through the package.

As asked on srcfl#1994, the unused thermal policy draft goes with it:
DecideIntent, DecisionInput, Intent, TemperatureBand, MarginalPrice and
the synthetic_price strategy. The declaration vocabulary stays.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: hannesb90 <bruhn90@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant