You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
fix(bulk): narrow client: Any to HomeAssistantClient, pin deep-copy and example-value fixes
1. Narrow the untyped client seam the deleted malformed-states test used
to protect. ServiceTools.__init__, _reject_operations_group_member_conflicts,
register_service_tools, resolve_bulk_selector, and _load_topology now take
HomeAssistantClient instead of Any -- so a states-shaped response from the
wrong client (e.g. the websocket client's get_states() -> dict[str, Any],
vs. the REST client's list[dict[str, Any]]) is a static error again, not
a silent fail-open on the group-safety gate.
This did cascade one step, as flagged as a possibility: registry.py's
ToolsRegistry.__init__(server: Any, ...) was the remaining break in the
chain (self.client = server.client inferred Any from it). Closed with a
small Protocol (_ServerLike, matching the existing CustomRouteServer
pattern in browser_landing.py) instead of importing the concrete
HomeAssistantSmartMCPServer class, since server.py's own lazy import of
ToolsRegistry exists specifically to avoid that circular import.
Narrowing surfaced two real (independent, pre-existing) issues along the
way, both fixed: _capture_initial_state called get_entity_state(entity_id)
where entity_id was str | None -- safe today only because the one call
site's should_wait guard embeds an entity_id is not None check the type
checker can't see through, now made explicit. And the first Protocol
draft declared plain (implicitly settable) attributes, which
HomeAssistantSmartMCPServer's read-only client/device_tools @Property
definitions don't satisfy -- fixed by declaring them as @Property in the
Protocol too. Validated with the exact CI mypy command (mypy src/
custom_components/ homeassistant-addon/ scripts/, 255 files) -- zero new
errors anywhere else in the codebase.
2. Add test_parameters_copies_are_genuinely_deep_not_shallow: the existing
parameters-isolation test only reassigned a top-level scalar key, which
a shallow dict(...) copy already protects against. This mutates a nested
list value (rgb_color) both externally (caller's dict, after the call)
and on one already-returned row, checking the resolution's stored copy
and a fresh .operations re-read. Verified load-bearing by temporarily
reverting each of the two copy.deepcopy call sites to dict() independently
and confirming the test fails both times before restoring the fix.
3. Fix the selector-only-parameter example's placeholder rendering as a
quoted string for every field, including timeout_seconds (a float).
New _PER_ROW_PARAMETER_EXAMPLE_VALUES gives each field a
correctly-typed sample (5 for timeout_seconds, {"brightness_pct": 30}
for parameters, etc.) instead of a bare "...". Pinned by
test_selector_only_parameter_example_uses_a_correctly_typed_sample.
Full unit suite: 10725 passed (up from 10722 by the tests added here); the
same 26 pre-existing Windows-platform-only failures as every prior baseline
this session remain, unrelated to this change.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
0 commit comments