docs: state two parts of the DeviceTreeBuilder contract the API did not convey - #65
Merged
Conversation
…ot convey Both reported by a consumer reconciling an existing multi-device builder against it, and both were places the types alone could not answer the question. 1. Model ownership. DeviceTreeBuilder takes a GroupedPropertyDict it never creates, which leaves open whether a producer should ADAPT its own model to that type or OWN one of that type. It is the second: the observable-model pattern building-a-proxy.md prescribes, where acquisition code writes into the model and publishing is a reactive side effect of the bindings. It accepts rather than creates so one model can span a whole tree, and so a producer already holding one can hand it over. 2. The limit of add()'s ordering. add() orders late-bound ids and builds an unbuilt parent it was handed, but DeviceSpec is frozen and parent is a direct reference, so a child spec cannot be constructed before its parent spec exists. A caller deriving specs from a declarative source that names parents indirectly (by class, by type, by key) still owns that dependency ordering. add() reads as though ordering is handled generally; it is handled for ids. Documentation only. No behavior change. Refs #49 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
dcj
added a commit
that referenced
this pull request
Aug 21, 2026
…n) (#71) Bumps __version__ to 0.22.0 (single source of truth), promotes Unreleased to [0.22.0], and adds the tag link definition. #64 node_id callable, so one device can carry two instances of a capability #65 DeviceTreeBuilder contract: model ownership, and the limit of add()'s ordering #66 the builder replaced properties in a model it does not own, discarding live values, callbacks and entity_setters. A producer that wired inbound control lost the actuator while $description kept advertising settable: true. Data loss in 0.21.0's headline feature. #67 a child could silently shadow an ancestor's id, or a sibling's, putting two devices on the same topics. Plus the feature it was filed for: add_root_capabilities(), so a root can carry its own capabilities rather than only parenting children. #68 extend(), so a device that already exists can grow a capability. The previous workaround dropped properties from $description while leaving their retained topics on the broker. Minor rather than patch: three new public methods. #66's fix is the reason not to sit on it, since 0.21.0 is live with that data loss. Materialization is now idempotent at three levels (node, property, and the state transition itself), which is what makes a re-fired incremental lifecycle a genuine no-op. The third matters most: an empty state_transition still emits init -> ready, and that edge forces every controller on the bus to resync. Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Refs #49. Documentation only, no behavior change.
Two parts of
DeviceTreeBuilder's contract that the API alone did not convey, both reported by a consumer reconciling an existing multi-device builder against it. Both are places where reading the types could not answer the question.1. Model ownership
The builder takes a
GroupedPropertyDictit never creates, which leaves open whether a producer should adapt its own model to that type or own one of that type.It is the second: the observable-model pattern
doc/building-a-proxy.mdprescribes, where acquisition code writes values into the model and publishing is a reactive side effect of the bindings. It accepts rather than creates one so a single model can span a whole tree, and so a producer that already holds one (populated before any Homie tree exists) can hand it over. It is not an adapter seam for a foreign model type.2. The limit of
add()'s orderingadd()orders late-bound ids, and builds an unbuilt parent it was handed. It does not order the construction of the specs themselves.DeviceSpecis frozen andparentis a direct reference, so a child spec cannot be constructed before its parent spec exists. A caller deriving specs from a declarative source that names parents indirectly (by class, by type, by key) still owns that dependency ordering, one step earlier than before.The consumer's phrasing is the reason this is worth writing down:
add()"reads as though ordering is handled and it is only handled for ids, not for parent resolution."Verification
658 tests pass,
ruff check/ruff format --checkclean, markdownlint clean onCHANGELOG.mdanddoc/building-a-proxy.md.🤖 Generated with Claude Code