B3.1 ChildRouter per-listener fan-out (drop emit monkey-patch):
Partial-filter subscriptions enumerate every concrete
<type>.measured.<position> event name (cartesian product over
the canonical POSITIONS list + 19 KNOWN_TYPES) and register a
plain emitter.on() per combo. Multi-parent semantics are trivial:
each ChildRouter's listeners are independent. Drop the wrap/unwrap
bookkeeping in tearDown. ChildRouter.js 184→164 lines.
B3.2 commandRegistry 'none' + description:
Add 'none' to payloadSchema.type — handler still fires; logs warn
if msg.payload is non-empty (catches accidental passes). Add
optional `description` field per descriptor; surfaced via .list()
so wikiGen can render per-topic effect text.
commandRegistry.js 157→164 lines. 23/23 tests pass.
B3.3 UnitPolicy dual-shape:
policy.canonical/output/curve are now BOTH callable methods AND
frozen property bags. policy.canonical('flow') === 'm3/s' and
policy.canonical.flow === 'm3/s' both work. Property bags are
frozen (assign/delete/redefine throw in strict). Drops the
_unitView workaround in MGC + rotatingMachine specificClass.
UnitPolicy.js 149→163 lines, 15/15 tests pass.
CONTRACTS.md §4 + §6 updated.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds platform infrastructure used by the upcoming refactor of
nodeClass / specificClass across all 12 nodes:
- src/domain/UnitPolicy.js — extracted from rotatingMachine/MGC
- src/domain/ChildRouter.js — declarative event routing on top of childRegistrationUtils
- src/domain/LatestWinsGate.js — extracted from MGC dispatch gate
- src/domain/HealthStatus.js — standardised {level, flags, message, source}
- src/nodered/statusBadge.js — compose / error / idle / byState / text helpers
- src/stats/index.js — mean / stdDev / median / mad / lerp
All additive — no existing exports change shape.
56 unit tests pass under node:test.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>