Skip to content

FAC: Add members package (step A) — mediated membership container#234

Closed
fumitoh wants to merge 7 commits into
mainfrom
claude/refactor-modelx-core-TyfDX
Closed

FAC: Add members package (step A) — mediated membership container#234
fumitoh wants to merge 7 commits into
mainfrom
claude/refactor-modelx-core-TyfDX

Conversation

@fumitoh

@fumitoh fumitoh commented May 18, 2026

Copy link
Copy Markdown
Owner

Introduce modelx.core.members as the keystone of the separation-of-concerns refactor. MemberContainer replaces the raw cells/own_refs/named_spaces dicts on BaseSpaceImpl with a single observable, defined/derived-aware surface so composition, inheritance and namespace assembly stop sharing raw dicts and faking notifications. NamespaceSource/NamespaceComposer realize the already-documented-but-dead "subjects in priority order" design in NamespaceServer, enabling step B. Not yet wired into space.py — this is the reviewable interface artifact that steps B and C hang off.

https://claude.ai/code/session_01WiPQBiJXuU4DRgCicANTd7

claude added 7 commits May 18, 2026 14:30
Introduce modelx.core.members as the keystone of the separation-of-concerns
refactor. MemberContainer replaces the raw cells/own_refs/named_spaces dicts
on BaseSpaceImpl with a single observable, defined/derived-aware surface so
composition, inheritance and namespace assembly stop sharing raw dicts and
faking notifications. NamespaceSource/NamespaceComposer realize the
already-documented-but-dead "subjects in priority order" design in
NamespaceServer, enabling step B. Not yet wired into space.py — this is the
reviewable interface artifact that steps B and C hang off.

https://claude.ai/code/session_01WiPQBiJXuU4DRgCicANTd7
Swap the three raw member dicts on BaseSpaceImpl and ModelImpl for
MemberContainer. MemberContainer now subclasses dict so this change is
byte-for-byte behavior-preserving: every existing call site (item access,
del, pop, dict.__setitem__ in _rename_item, sort_dict, use as a
CustomChainMap layer, the file serializers, pickling of the space<->member
reference cycle) keeps working unchanged. The notifying mutation API
(set/remove/move_to_end/sort/rename/flush) and Subject nature remain
additive for steps B/C; notification still flows through the existing
on_notify path in step A.

Verified: 746 core+serialize and 164 io/managers tests pass; inheritance
propagation and file-serializer round-trip exercised end to end.

https://claude.ai/code/session_01WiPQBiJXuU4DRgCicANTd7
Replace the hardcoded three-phase assembly in BaseSpaceImpl.on_update_ns
(named_spaces / refs isinstance-ladder / cells) with a one-line delegate to
members.fill_space_namespace. The ordering, per-kind value projection, and
the refs isinstance ladder now live in members/source.py expressed via the
NamespaceSource abstraction, realizing the "subjects in priority order"
design NamespaceServer already documented. The refs chainmap is passed
directly so its collapsed .items() is byte-for-byte the old loop; the
existing on_notify invalidation path is unchanged (persistent
composer/observer wiring is deferred to step C).

Verified: 910 core/serialize/io/managers tests pass; nested-space
namespace, inherited refs, and the ItemSpace 5-layer refs chainmap
projection exercised end to end.

https://claude.ai/code/session_01WiPQBiJXuU4DRgCicANTd7
…r API

Untangle the namespace<->inheritance coupling at its core: the space now
observes its own cells/own_refs MemberContainers, so the notifying
container API (set/remove) drives the existing NamespaceServer.on_notify
invalidation. on_del_cells/on_create_ref/on_del_ref (and on_change_ref via
the latter two) drop their manual ``self.on_notify(self.cells)`` /
``on_notify(self.own_refs)`` calls and mutate through .remove()/.set().
Behavior is preserved: the observer edge triggers exactly the same
on_notify path the explicit calls did, at the same point, with the same
notification count. on_rename is left raw (its pop+reinsert moves the entry
to the end, unlike MemberContainer.rename which is position-preserving).
__setstate__ re-establishes the space->container observation that
MemberContainer.__setstate__ resets, mirroring the dynamic_cache pattern.

Verified: 910 core/serialize/io/managers tests pass; live ref-change and
cell-redefine invalidation and a full file-serializer round-trip (restored
model still invalidates correctly) exercised end to end.

https://claude.ai/code/session_01WiPQBiJXuU4DRgCicANTd7
…tance pkg

Move the body of UserSpaceImpl.on_inherit into modelx.core.inheritance.
inherit_members; on_inherit is now a one-line delegate. The algorithm
(diff member container vs bases, create derived placeholders, reorder
base-before-defined, recurse into derived members, drop stale derived) is
relocated verbatim and operates on the space's MemberContainers. This is a
behavior-preserving move: the container mutations stay the original raw
dict operations (MemberContainer is a dict subclass, so notification timing
is unchanged); converting them to the notifying API is deliberately
deferred. space.on_del_cells/on_del_ref remain on the space and are invoked
from the extracted function exactly as before. No import cycle:
inheritance imports cells/reference/chainmap, none of which import space.

Verified: 910 core/serialize/io/managers tests and 121 inheritance-focused
tests pass; multi-level derivation, defined-overrides-derived, base-member
add/remove propagation exercised end to end.

https://claude.ai/code/session_01WiPQBiJXuU4DRgCicANTd7
…tance pkg

Move the node-path helpers and the seven engine classes (SpaceGraph,
Instruction, InstructionList, SharedSpaceOperations, SpaceManager,
SpaceUpdater, ReferenceManager) verbatim from the tail of model.py into
modelx.core.inheritance.manager. Logic is untouched -- only the location
changes. The model<->engine import cycle is broken by referencing the
model class lazily as _model.ModelImpl (runtime attribute access on the
partially-initialized model module; the engine only needs it inside method
bodies). model.py re-exports all moved names so modelx.core.model.SpaceManager
etc. remain valid (identity preserved). inheritance/__init__ deliberately
does NOT import manager, so space.py -> inheritance -> updater stays
cycle-free.

Verified: 910 core/serialize/io/managers + 138 inheritance/copy/graph/mro
tests pass; add_bases/remove_bases/copy_space/MRO/nested derivation and a
full file-serializer round-trip (spmgr and _graph reconstructed) exercised
end to end.

https://claude.ai/code/session_01WiPQBiJXuU4DRgCicANTd7
…dependency

Introduce modelx.core.execution.invalidation, an execution-owned port
(invalidate_object / invalidate_attr_referrers). The inheritance/
composition hooks that used to reach directly into the TraceManager
(self.model.clear_obj / clear_attr_referrers) now call this port instead:
on_del_cells, on_sort_cells, on_change_ref, on_del_ref, on_rename
(space.py), CellsImpl.on_inherit (cells.py), ReferenceImpl.on_inherit
(reference.py). The knowledge of how trace invalidation maps onto
TraceManager calls now lives in the execution package, mirroring how
members.fill_space_namespace owns the namespace rule.

Dispatch is synchronous and the underlying calls and their timing are
byte-for-byte unchanged -- only the direction of the dependency is
inverted. An async publish/subscribe variant was deliberately not done: it
would move when invalidation happens relative to member detachment,
risking silently stale dependency graphs. Out-of-scope execution-internal
self-invalidation (Cells.reload/set_formula/on_set_property) is left as-is.

Verified: 910 core/serialize/io/managers + 178 inheritance/ref tests pass;
derived-cell formula change, derived-ref change, cell deletion and ref
change all invalidate correctly through the new port end to end.

https://claude.ai/code/session_01WiPQBiJXuU4DRgCicANTd7
@fumitoh fumitoh closed this May 19, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants