zara-home: add production Home Assistant transport with bounded auth/refresh/failure semantics #51

Open
opened 2026-09-08 02:02:52 +00:00 by nsaspy · 0 comments
Owner

Problem

zara-home currently has a provider-neutral domain and a bounded HomeAssistantAdapter, but production URL/authentication/timeouts are deliberately delegated to an injected transport. There is no production transport in the current plugin source, so Home Assistant support is still fixture-only despite the adapter being present.

Current Home Assistant APIs make the missing boundary concrete:

  • REST requests require bearer-token authentication and return explicit 401/4xx failures;
  • the supported auth flow can issue short-lived access tokens plus refresh tokens, and invalid refresh means the account must be treated as disconnected/re-auth required;
  • WebSocket auth is a distinct handshake and is the supported event/state-change stream;
  • service-call acknowledgement is not observed state: state changes must still be independently observed/re-read before Zara reports mutation success.

Bounded slice

Implement the production HTTP transport/auth seam only for HomeAssistantAdapter; do not add SmartThings or a second assistant/runtime abstraction.

Required behavior:

  • explicit configured Home Assistant base URL; reject malformed/unsupported URL schemes and prevent request-path/base-URL escape;
  • bearer-token injection without exposing credentials in return values/errors/log strings;
  • bounded connect/read timeout and cancellation/failure propagation;
  • exact structured handling for 400/401/403/404/429/5xx and malformed/non-JSON responses;
  • support a refresh callback/provider seam for expiring OAuth access tokens; one bounded refresh+retry on 401, never an unbounded retry loop;
  • failed refresh/revoked credentials return explicit unavailable/re-auth-required state rather than falling back to anonymous/local execution;
  • retain HomeService post-write read-back as the only source of verified=true; HTTP 2xx/service acknowledgement alone is never verification;
  • no committed credentials and no secrets in deterministic fixtures.

Event/WebSocket subscription can follow in a separate slice after the HTTP/auth boundary is proven.

Tests first

Use a deterministic local fake HTTP server and cover:

  • bearer header present while secret is absent from errors/evidence;
  • 401 -> one refresh -> retry success;
  • refresh rejection/revocation -> explicit failure with no second retry;
  • timeout/disconnect/malformed JSON/429/5xx;
  • base URL/path escape rejection;
  • service POST acknowledgement followed by mismatched read-back remains verified=false;
  • successful read-back matching intended value is verified=true;
  • no real Home Assistant instance/network credential required.

Then run exact-head:

  • python3 -m unittest discover -s plugins/zara-home/test -t plugins/zara-home/test
  • python3 scripts/validate-registry.py
  • repository compatibility/plugin gates relevant to zara-home
  • nix flake check

Dependencies / boundaries

  • No Zara Core change required for this slice.
  • SmartThings server credential/provider ownership remains Zara Core #202; do not import or recreate it here.
  • Keep credentials outside plugin metadata/Nix store.

Research basis

Home Assistant Developer Docs, current September 2026:

  • REST API: bearer auth and HTTP failure semantics.
  • Authentication API: OAuth authorization-code/access/refresh lifecycle and 401 refresh behavior.
  • WebSocket API: explicit auth handshake, service-call completion distinct from state_changed observation.
## Problem `zara-home` currently has a provider-neutral domain and a bounded `HomeAssistantAdapter`, but production URL/authentication/timeouts are deliberately delegated to an injected `transport`. There is no production transport in the current plugin source, so Home Assistant support is still fixture-only despite the adapter being present. Current Home Assistant APIs make the missing boundary concrete: - REST requests require bearer-token authentication and return explicit 401/4xx failures; - the supported auth flow can issue short-lived access tokens plus refresh tokens, and invalid refresh means the account must be treated as disconnected/re-auth required; - WebSocket auth is a distinct handshake and is the supported event/state-change stream; - service-call acknowledgement is not observed state: state changes must still be independently observed/re-read before Zara reports mutation success. ## Bounded slice Implement the production **HTTP transport/auth seam only** for `HomeAssistantAdapter`; do not add SmartThings or a second assistant/runtime abstraction. Required behavior: - explicit configured Home Assistant base URL; reject malformed/unsupported URL schemes and prevent request-path/base-URL escape; - bearer-token injection without exposing credentials in return values/errors/log strings; - bounded connect/read timeout and cancellation/failure propagation; - exact structured handling for 400/401/403/404/429/5xx and malformed/non-JSON responses; - support a refresh callback/provider seam for expiring OAuth access tokens; one bounded refresh+retry on 401, never an unbounded retry loop; - failed refresh/revoked credentials return explicit unavailable/re-auth-required state rather than falling back to anonymous/local execution; - retain `HomeService` post-write read-back as the only source of `verified=true`; HTTP 2xx/service acknowledgement alone is never verification; - no committed credentials and no secrets in deterministic fixtures. Event/WebSocket subscription can follow in a separate slice after the HTTP/auth boundary is proven. ## Tests first Use a deterministic local fake HTTP server and cover: - bearer header present while secret is absent from errors/evidence; - 401 -> one refresh -> retry success; - refresh rejection/revocation -> explicit failure with no second retry; - timeout/disconnect/malformed JSON/429/5xx; - base URL/path escape rejection; - service POST acknowledgement followed by mismatched read-back remains `verified=false`; - successful read-back matching intended value is `verified=true`; - no real Home Assistant instance/network credential required. Then run exact-head: - `python3 -m unittest discover -s plugins/zara-home/test -t plugins/zara-home/test` - `python3 scripts/validate-registry.py` - repository compatibility/plugin gates relevant to `zara-home` - `nix flake check` ## Dependencies / boundaries - No Zara Core change required for this slice. - SmartThings server credential/provider ownership remains Zara Core #202; do not import or recreate it here. - Keep credentials outside plugin metadata/Nix store. ## Research basis Home Assistant Developer Docs, current September 2026: - REST API: bearer auth and HTTP failure semantics. - Authentication API: OAuth authorization-code/access/refresh lifecycle and 401 refresh behavior. - WebSocket API: explicit auth handshake, service-call completion distinct from `state_changed` observation.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
nsaspy/zara-plugins#51
No description provided.