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:
@@ -34,6 +34,53 @@ test('declare returns a policy whose canonical/output match the input', () => {
|
||||
assert.equal(policy.curve('control'), '%');
|
||||
});
|
||||
|
||||
test('canonical/output/curve are also frozen property bags (dot access)', () => {
|
||||
const policy = UnitPolicy.declare(baseSpec);
|
||||
// Property-access form — equivalent to the method-call form above.
|
||||
assert.equal(policy.canonical.flow, 'm3/s');
|
||||
assert.equal(policy.canonical.pressure, 'Pa');
|
||||
assert.equal(policy.output.flow, 'm3/h');
|
||||
assert.equal(policy.output.temperature, 'C');
|
||||
assert.equal(policy.curve.flow, 'm3/h');
|
||||
assert.equal(policy.curve.control, '%');
|
||||
// Method-call form keeps working alongside it.
|
||||
assert.equal(policy.canonical('flow'), 'm3/s');
|
||||
assert.equal(policy.output('power'), 'kW');
|
||||
});
|
||||
|
||||
test('canonical/output/curve property bags are frozen — no assignment / delete / redefine', () => {
|
||||
'use strict';
|
||||
const policy = UnitPolicy.declare(baseSpec);
|
||||
// Existing own-properties are non-writable.
|
||||
assert.throws(() => { policy.canonical.flow = 'tampered'; }, TypeError);
|
||||
// Existing own-properties are non-configurable: delete throws.
|
||||
assert.throws(() => { delete policy.canonical.pressure; }, TypeError);
|
||||
// Redefining an existing prop throws.
|
||||
assert.throws(
|
||||
() => Object.defineProperty(policy.canonical, 'flow', { value: 'tampered' }),
|
||||
TypeError
|
||||
);
|
||||
// Object.isFrozen reports the accessor as frozen.
|
||||
assert.equal(Object.isFrozen(policy.canonical), true);
|
||||
assert.equal(Object.isFrozen(policy.output), true);
|
||||
assert.equal(Object.isFrozen(policy.curve), true);
|
||||
// Original values survive the failed attempts.
|
||||
assert.equal(policy.canonical.flow, 'm3/s');
|
||||
assert.equal(policy.canonical.pressure, 'Pa');
|
||||
});
|
||||
|
||||
test('curve property bag is present (empty) even when no curve was declared', () => {
|
||||
const policy = UnitPolicy.declare({
|
||||
canonical: baseSpec.canonical,
|
||||
output: baseSpec.output,
|
||||
});
|
||||
// Method form returns null for unknown types.
|
||||
assert.equal(policy.curve('flow'), null);
|
||||
// Property form is an empty frozen function — accessing missing keys is undefined.
|
||||
assert.equal(policy.curve.flow, undefined);
|
||||
assert.equal(Object.isFrozen(policy.curve), true);
|
||||
});
|
||||
|
||||
test('declare throws when canonical or output is missing', () => {
|
||||
assert.throws(() => UnitPolicy.declare({ output: {} }), /canonical/);
|
||||
assert.throws(() => UnitPolicy.declare({ canonical: {} }), /output/);
|
||||
|
||||
Reference in New Issue
Block a user