feat(drivers): host.http_patch write verb for device REST writes (#537) - #716
Conversation
…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
left a comment
There was a problem hiding this comment.
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.
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>
What
Adds
host.http_patch(url, body, headers)to the Lua host — the verb RESTdevice 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_patchis deliberately stricter thanhttp_post. Beyond the plaincapabilities.httpgrant it requires a new explicitcapabilities.http.allow_write: true— so granting HTTP for telemetrynever implicitly grants the ability to mutate a device (the HTTP twin of a
read-only Modbus driver vs. one allowed to write registers).
http_getstays a read;http_poststays under the plain grant, unchanged (existing drivers POST to query-style APIs). No existing HTTP driver changes behaviour.allowWrite/recordWriteEvidence).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, sohttp_postredirect behaviour is untouched.Wiring
HTTPCapability.AllowWrite(config) →HostEnv.HTTPAllowWrite(viaWithHTTPAllowWritein the registry) → the gate in thehttp_patchclosure.Relationship to the managed control-v2 model
Managed control-v2 packages still only carry Modbus permissions
(
RuntimePolicy.validaterejects non-Modbus write permissions), sohttp_patchis — likehttp_post— an unmanaged-driver verb: forRuntimePolicy == nildrivers,allowWriteis a no-op and the effective gateis
capabilities.http+capabilities.http.allow_write. A driver on an oldercore without
http_patchdetects its absence and stays read-only.Tests
go/internal/drivers/lua_http_patch_test.gopins both gates and the wireformat:
PATCH), body and headers;allow_write— denied without it, and the server is never reached (hit counter asserts the request never left the host);httpcapability — same;allowed_hostsfence.Build + vet clean;
configpackage green. (The driver-dependent suites needmake driversto materialise the bundled snapshot, unrelated to this change.)Companion PR
Driver + write path: srcfl/device-drivers#46. Tracks #537.
🤖 Generated with Claude Code