MeasurementContainer.get: strict-resolve explicit .child(name)

A read chain `.child(name).getCurrentValue()` previously fell through
silently to the implicit-default child or the first available sibling
when the named child did not exist. Caller asked for X, got Y, no
warning. Surfaced via pumpingStation spillPrev: a fresh basin's
.child('overflow').getCurrentValue() returned the value of
'manual-qout' (the only existing child at that position).

Split the resolution into two strictness levels:
  _currentChildId (per-chain .child(name)) → STRICT, missing = null.
  this.childId    (persistent setChildId)  → HINT, falls back to
                                              'default' then first.

The persistent path is what registered children (rotatingMachine etc.)
rely on: they write under composed ids ('up-<id>') but expect reads
without explicit .child() to still resolve.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Rene De Ren
2026-05-06 17:17:58 +02:00
parent 4b6250cc42
commit a516c2b2b6

View File

@@ -425,16 +425,34 @@ class MeasurementContainer {
// Legacy single measurement
if (posBucket?.getCurrentValue) return posBucket;
// Child-aware: pick requested child, otherwise fall back to default, otherwise first available
// Child-aware lookup. Two separate sources of "child-id" on the
// container, with DIFFERENT strictness:
//
// _currentChildId : transient, set by .child(name) inside a chain.
// Explicit per-call. STRICT — if the named child
// does not exist, return null. Silent fall-through
// to a sibling would mask a missing-stream read
// as a wrong-stream read (see pumpingStation
// spillPrev bug, 2026-05-06).
//
// this.childId : persistent, set by setChildId(id). HINT only —
// try it first, then fall back to 'default' then
// first available. Containers registered with a
// persistent id (rotatingMachine, etc.) write
// under composed child ids (e.g. 'up-<id>') that
// don't equal the persistent id, and reads must
// still resolve to those writes.
if (posBucket && typeof posBucket === 'object') {
const requestedKey = this._currentChildId || this.childId;
const keys = Object.keys(posBucket);
if (!keys.length) return null;
const measurement =
(requestedKey && posBucket[requestedKey]) ||
if (this._currentChildId) {
return posBucket[this._currentChildId] || null;
}
return (this.childId && posBucket[this.childId]) ||
posBucket.default ||
posBucket[keys[0]];
return measurement || null;
posBucket[keys[0]] ||
null;
}
return null;