Skip to content

feat(drivers): host.http_patch write verb for device REST writes (#537) - #716

Merged
frahlg merged 1 commit into
srcfl:masterfrom
HuggeK:ftw-http-patch
Jul 30, 2026
Merged

feat(drivers): host.http_patch write verb for device REST writes (#537)#716
frahlg merged 1 commit into
srcfl:masterfrom
HuggeK:ftw-http-patch

Conversation

@HuggeK

@HuggeK HuggeK commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

What

Adds host.http_patch(url, body, headers) to the Lua host — the verb REST
device APIs use for state-changing writes. It is the core enabling change
for the NIBE Solar PV surplus feed (#537), whose driver lives in
srcfl/device-drivers and
PATCHes the pump's "available power" point.

Security posture

http_patch is deliberately stricter than http_post. Beyond the plain
capabilities.http grant it requires a new explicit
capabilities.http.allow_write: true — so granting HTTP for telemetry
never implicitly grants the ability to mutate a device (the HTTP twin of a
read-only Modbus driver vs. one allowed to write registers).

  • Scope is exactly PATCH. http_get stays a read; http_post stays under the plain grant, unchanged (existing drivers POST to query-style APIs). No existing HTTP driver changes behaviour.
  • Without the grant the driver gets an error string and the request never leaves the host.
  • Reuses the existing allowlist, TLS-pinning, 1 MB response cap and the managed-write accounting (allowWrite / recordWriteEvidence).
  • Refuses to follow redirects — Go re-issues a redirected PATCH (301/302/303) as a body-less GET, which would otherwise report success for a device write that never landed. The refusal is scoped to the PATCH method, so http_post redirect behaviour is untouched.

Wiring

HTTPCapability.AllowWrite (config) → HostEnv.HTTPAllowWrite (via
WithHTTPAllowWrite in the registry) → the gate in the http_patch closure.

Relationship to the managed control-v2 model

Managed control-v2 packages still only carry Modbus permissions
(RuntimePolicy.validate rejects non-Modbus write permissions), so
http_patch is — like http_post — an unmanaged-driver verb: for
RuntimePolicy == nil drivers, allowWrite is a no-op and the effective gate
is capabilities.http + capabilities.http.allow_write. A driver on an older
core without http_patch detects its absence and stays read-only.

Tests

go/internal/drivers/lua_http_patch_test.go pins both gates and the wire
format:

  • forwards method (PATCH), body and headers;
  • requires allow_write — denied without it, and the server is never reached (hit counter asserts the request never left the host);
  • requires the http capability — same;
  • refuses redirects — a redirected PATCH surfaces as an error and the redirect target is never reached;
  • respects the allowed_hosts fence.

Build + vet clean; config package green. (The driver-dependent suites need
make drivers to materialise the bundled snapshot, unrelated to this change.)

Companion PR

Driver + write path: srcfl/device-drivers#46. Tracks #537.


🤖 Generated with Claude Code

…ttp.allow_write

The Lua host gains `host.http_patch(url, body, headers)` — the verb REST
device APIs use for state-changing writes. It is the enabling core change for
the NIBE Solar PV surplus feed (srcfl#537), whose driver lives in
srcfl/device-drivers and PATCHes the pump's "available power" point.

http_patch is deliberately stricter than http_post. Beyond the plain
`capabilities.http` grant it requires a new, explicit
`capabilities.http.allow_write: true`, so granting HTTP for telemetry never
implicitly grants the ability to mutate a device — the HTTP twin of a
read-only Modbus driver versus one allowed to write registers. Scope is
exactly PATCH: `http_get` stays a read and `http_post` stays under the plain
grant unchanged (existing drivers POST to query-style APIs), so no existing
HTTP driver changes behaviour. Without the grant a driver gets an error
string and the request never leaves the host.

The verb reuses the existing allowlist, TLS-pinning, 1 MB response cap and the
managed-write accounting (allowWrite / recordWriteEvidence) of the other
verbs. Unlike http_post it refuses to follow redirects: Go re-issues a
redirected PATCH (301/302/303) as a body-less GET, which would otherwise
report success for a device write that never landed — a write must reach the
host it was checked against or fail loudly. The refusal is scoped to the PATCH
method, so http_post's redirect behaviour is untouched.

Wiring: `HTTPCapability.AllowWrite` (config) → `HostEnv.HTTPAllowWrite`
(via WithHTTPAllowWrite in registry) → the gate in the http_patch closure.
Managed control-v2 packages still only carry Modbus permissions, so http_patch
is, like http_post, an unmanaged-driver verb; the driver detects an older core
without it and stays read-only.

Signed-off-by: Hugo Karlsson <48095810+HuggeK@users.noreply.github.com>
Co-authored-by: HuggeK <48095810+HuggeK@users.noreply.github.com>

@frahlg frahlg left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CODEOWNERS review for `go/internal/drivers/`. Approving.

The security model is right. Two independent gates (`capabilities.http` and `capabilities.http.allow_write`), each tested by asserting the request never reaches the server rather than only that an error came back — a hit counter catches a deleted gate, a dead port would not. Scope is exactly PATCH, so no existing HTTP driver changes behaviour, and the verb inherits the allowlist, TLS pin, 1 MB cap and managed-write accounting.

Refusing to follow a redirected PATCH is the part I would have asked for. Go re-issues 301/302/303 as a body-less GET, so without that guard a device write that never landed would report success — exactly the failure a write path must not have. The guard keys on `via[0].Method`, so GET and POST chains are untouched.

Also fixes a real documentation error: the pump-side write gate is the installer read-only/read-write choice in menu 7.5.15, not aid mode.

@frahlg
frahlg merged commit a342532 into srcfl:master Jul 30, 2026
13 checks passed
HuggeK added a commit to HuggeK/ftw that referenced this pull request Jul 30, 2026
Branch was cut from a stale fork master. Two conflicts in registry.go,
both additive:

- Registry struct: keep upstream's new SerialFactory field alongside the
  corrected ARPLookup doc comment.
- HTTP capability branch: keep upstream's AllowWrite wiring (srcfl#716) and
  append the ARP lookup after it.

Co-authored-by: HuggeK <48095810+HuggeK@users.noreply.github.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.

3 participants