Strength: Worth exploring
Category: in-process, structural
⚠️ Touches ADR-0005 and ADR-0009
Both ADRs record that path-changed events are skipped during import/rebuild. This refactor preserves the decision but moves it inside a named module instead of being threaded through three handler variants and a special-case in ReplayEvent. Worth flagging for ADR review — the ADRs may want a small note pointing at the new module.
Problem
Three handler variants of handleEntityReparented* all walk descendants — once to emit path-changed events, once to skip them (replay), once to preview them (import). The descendant-walk logic is duplicated. The replay-vs-dispatch decision lives outside the handlers in ReplayEvent, not in the handlers themselves.
Located:
internal/eventbus/handlers.go — handleEntityReparented, handleEntityReparentedProjectionOnlyTx, handleEntityReparentedComputePayloadsTx
internal/eventbus/bus.go — ReplayEvent special-cases EntityReparentedEvent to choose a variant
Solution
Introduce PathPropagator with three named modes:
.Apply(tx, reparent) — apply reparent + emit path-changed events
.ApplyProjectionOnly(tx, reparent) — write projection rows only (replay path)
.ComputePayloads(tx, reparent) — return prospective payloads (import path)
The descendant walk lives in one private method; the three modes differ only in what they emit. handleEntityReparented shrinks to one call into PathPropagator. ReplayEvent's branch becomes a method dispatch on the propagator.
Wins
- Locality: descendant walk in one place
- Leverage: ADRs enforced by module shape, not by convention
- Interface names the three replay modes
- Removes special-case branch from
ReplayEvent
- Tests target propagation directly
- Composes cleanly with the event-registry refactor
Composes with
Source
Architecture review 2026-05-31, candidate #4 (Worth exploring). Distinct from #204 (registry collapse) — this issue addresses the descendant-walk duplication, not the event-type enumeration.
Strength: Worth exploring
Category: in-process, structural
Both ADRs record that path-changed events are skipped during import/rebuild. This refactor preserves the decision but moves it inside a named module instead of being threaded through three handler variants and a special-case in
ReplayEvent. Worth flagging for ADR review — the ADRs may want a small note pointing at the new module.Problem
Three handler variants of
handleEntityReparented*all walk descendants — once to emit path-changed events, once to skip them (replay), once to preview them (import). The descendant-walk logic is duplicated. The replay-vs-dispatch decision lives outside the handlers inReplayEvent, not in the handlers themselves.Located:
internal/eventbus/handlers.go—handleEntityReparented,handleEntityReparentedProjectionOnlyTx,handleEntityReparentedComputePayloadsTxinternal/eventbus/bus.go—ReplayEventspecial-casesEntityReparentedEventto choose a variantSolution
Introduce
PathPropagatorwith three named modes:.Apply(tx, reparent)— apply reparent + emit path-changed events.ApplyProjectionOnly(tx, reparent)— write projection rows only (replay path).ComputePayloads(tx, reparent)— return prospective payloads (import path)The descendant walk lives in one private method; the three modes differ only in what they emit.
handleEntityReparentedshrinks to one call intoPathPropagator.ReplayEvent's branch becomes a method dispatch on the propagator.Wins
ReplayEventComposes with
Source
Architecture review 2026-05-31, candidate #4 (Worth exploring). Distinct from #204 (registry collapse) — this issue addresses the descendant-walk duplication, not the event-type enumeration.