Repository navigation
3.0.2: resolved values follow the states registered above a state - #54
Merged
Merged
Conversation
A state resolved its inherited fields once, from the ancestors registered when it was built, and nothing compared that with later registration: register and unregister told no descendant. A step built before its plate was registered kept the outer value until something else invalidated it. Each resolved snapshot now records the registered ancestors it was computed from (ObjectStateRegistry.registered_ancestors, the one definition of that chain). Every resolved read compares them with today's and resolves both snapshots again when they differ. register, unregister and unregister_scope_and_descendants ask the registered states below the changed scope to make that comparison at once, so subscribers are told without a read. Three related cases stay open as strict xfail tests (#53). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Owner decision: the MRO is the outer loop, scopes the inner one. The order is stated with a Leaf/Base, plate/global example in context_system.rst; the resolver docstring and configuration_framework.rst refer to it. The sentence in configuration_framework.rst that described scope first is corrected. The crossed-pair tests are the policy now, not a characterisation; assertions are unchanged, and so is the resolver. Co-Authored-By: Claude Opus 5.5 <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.
Defect
An
ObjectStatebuilt before its ancestor state was registered kept the resolved values it computed at build time (the outer default) after the ancestor registered. The same held for unregistering an ancestor, replacing it at its scope, and clearing the registry.Cause
A state resolves its inherited fields from the states registered at its ancestor scopes, read from
ObjectStateRegistrywhen the snapshot is computed. Nothing tied the snapshot to that set:register,unregisterandunregister_scope_and_descendantstold no descendant, and the snapshot did not record what it was computed from. Only edits of an ancestor's values reached descendants (invalidate_by_type_and_scope).Fix
ObjectStateRegistry.registered_ancestors(scope_id)is the one definition of the chain;get_ancestor_objectsandget_ancestor_objects_with_scopesderive from it (two copies of the loop removed).ObjectState._ancestors_resolved_from, written in_compute_resolved_snapshot)._ensure_live_resolvedand the saved reads compare them with today's and resolve live and saved again in full when they differ, then update dirty fields and notify.register,unregisterandunregister_scope_and_descendantsask registered states below the changed scope to make that comparison at once (_resolve_again_below), soon_resolved_changedsubscribers hear without a read. A state whose ancestors did not change does no work; registering a state does not resolve it again.No caller invalidates anything and there is no refresh method to remember. The per-read cost is one walk of the scope's prefixes.
Tests
tests/test_resolved_values_follow_registered_ancestors.py: 30 of its 31 passing tests fail on main. Read kinds are asserted separately: stored value, raw field of the built object, uncached attribute access,state.fields, live and saved resolved values and objects, source(scope, class), dirty fields.Mutations covered: ancestor registered late (descendant never registered / registered before / after it), unregistered (
unregister, cascade,clear), registered again with another value, replaced at its scope, registered with unsaved edits (live and saved differ), edit / save / restore of the ancestor, descendantscope_idmoved under another ancestor, a context entered after the build, a field invalidation pending together with an ancestor change, notification at registration, no work when ancestors are unchanged.Open, as strict
xfailwith reasons (#53): unregistered descendant after an ancestor edit; global config replaced through the setters; a state resolved inside an entered context. Widening the comparison to the ancestors' objects would fix the first two at about 4x the cost per descendant per ancestor edit (measured, in #53).Precedence order (owner decision: class first)
context_system.rststates the order once, with aLeaf/Base, plate/global example; the resolver docstring andconfiguration_framework.rstrefer to it. The sentence inconfiguration_framework.rstthat described scope first is corrected.Release
3.0.2. Full suite in a fresh venv (Python 3.12):
343 passed, 5 xfailed.🤖 Generated with Claude Code