B3.1 + B3.2 + B3.3: ChildRouter fan-out + commandRegistry 'none' + UnitPolicy dual-shape
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>
This commit is contained in:
@@ -10,7 +10,7 @@
|
||||
// JSON-Schema. Anything richer belongs in the handler itself, which has
|
||||
// access to logger via ctx.
|
||||
|
||||
const SCALAR_TYPES = new Set(['string', 'number', 'boolean', 'object', 'any']);
|
||||
const SCALAR_TYPES = new Set(['string', 'number', 'boolean', 'object', 'any', 'none']);
|
||||
|
||||
class CommandRegistry {
|
||||
constructor(commands, options = {}) {
|
||||
@@ -49,6 +49,7 @@ class CommandRegistry {
|
||||
topic: cmd.topic,
|
||||
aliases,
|
||||
payloadSchema: cmd.payloadSchema || null,
|
||||
description: typeof cmd.description === 'string' ? cmd.description : null,
|
||||
handler: cmd.handler,
|
||||
};
|
||||
this._byKey.set(cmd.topic, descriptor);
|
||||
@@ -75,6 +76,7 @@ class CommandRegistry {
|
||||
topic: d.topic,
|
||||
aliases: d.aliases.slice(),
|
||||
payloadSchema: d.payloadSchema,
|
||||
description: d.description,
|
||||
}));
|
||||
}
|
||||
|
||||
@@ -119,6 +121,12 @@ class CommandRegistry {
|
||||
return true;
|
||||
}
|
||||
if (type === 'any') return true;
|
||||
if (type === 'none') {
|
||||
if (payload !== undefined && payload !== null) {
|
||||
log.warn?.(`${descriptor.topic}: payload ignored — this is a trigger-only topic`);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
// typeof null === 'object' — explicit null fails an object schema.
|
||||
if (type === 'object') {
|
||||
if (payload === null || typeof payload !== 'object') {
|
||||
|
||||
Reference in New Issue
Block a user