fix(focas): disambiguate duplicate sibling axis/spindle names in the fixed tree (#548)
A CNC can report duplicate sibling names — the wonder Makino returns two
axes both called AA — which made the Axes/ subtree untrustworthy: the
browser offered two identical folders the commit gate had to refuse, and
the poll loop's name-keyed snapshot cache let the second axis silently
overwrite the first. Spindles and servo loads had the same collision in
their own keying, plus a resolve-to-first-match on the spindle read path.
Scheme: every member of a collision set gets a 1-based occurrence-ordinal
suffix in slot order (AA_1, AA_2); unique names stay bare. Chosen over the
alternatives deliberately:
- Names, not slot numbers, stay the primary identity — the segment map is
re-derived from the live device each session, so a name-keyed path
follows the physical axis across config changes, where a slot-qualified
path would silently rebind on any axis-list shift and rename every axis
on every machine for one pathological CNC.
- The WHOLE collision set is suffixed: bare AA ceases to exist when
ambiguous, so a tag authored against it reads BadNodeIdUnknown rather
than one arbitrary axis's data — the #534/#540 silent-wrongness class.
- Occurrence ordinal, not absolute slot: inserting/removing an unrelated
axis never renames an existing duplicate pair. If the collision-set
membership itself changes, segments change on the next browse and stale
authored segments read Bad, never another axis's data.
One pure helper (FixedTreeNames.Disambiguate, collision-proof against
genuine names by suffix escalation) feeds FocasFixedTreeCache.AxisSegments
/ SpindleSegments, and the emitter, axis snapshot keys, servo-load keys
(cnc_rdsvmeter answers in slot order, so the k-th AA load belongs to the
k-th AA axis) and the spindle read resolution all consume those — they can
no longer disagree. The nameless-spindle S{n} fallback applies before
disambiguation so a fallback name colliding with a real one is handled
like any other duplicate.
Tests are red-first on the pre-fix driver (all five driver-level tests
fail on master's code): browse emits AA_1/AA_2 and no bare AA, each
duplicate reads its own position and servo load, duplicate spindles
resolve per slot, bare AA reads Bad. The wire half runs live against
focas-mock (duplicate names survive the managed wire parse and publish as
suffixed segments); the mock's dynamic store is name-keyed so the
distinct-values half stays with the offline tests. FOCAS 327/327,
integration 11/11, FOCAS.Cli 52, AdminUI 1113, ControlPlane 231.
Closes #548.
Claude-Session: https://claude.ai/code/session_014WNM4vjoVksyyBraTXSZE1
This commit is contained in:
+12
-5
@@ -187,11 +187,18 @@ fixed-tree paths are discovered from the machine, not composed from parts.
|
||||
|
||||
Two caveats worth knowing before you browse:
|
||||
|
||||
- **A real CNC can report duplicate sibling names.** One observed Makino returns
|
||||
two axis folders both called `AA`. Committing both is refused at commit time
|
||||
(they would be the same RawPath), and the runtime cache is keyed by the same
|
||||
name — so the `Axes/` subtree on such a machine cannot currently distinguish
|
||||
them. Prefer the non-axis subtrees there until this is resolved.
|
||||
- **A real CNC can report duplicate sibling names, and they are disambiguated
|
||||
automatically** (#548). One observed Makino returns two axes both called `AA`;
|
||||
every member of such a collision set gets a 1-based occurrence suffix in slot
|
||||
order — the tree offers `Axes/AA_1/` and `Axes/AA_2/`, each reading its own
|
||||
axis — while unique names stay bare (`Axes/Y/`). The bare name of a duplicate
|
||||
set deliberately does not resolve: a tag authored against `Axes/AA/…` on such
|
||||
a machine reads `BadNodeIdUnknown` rather than one arbitrary axis's data. The
|
||||
suffix counts occurrences of the *same* name, so adding an unrelated axis
|
||||
never renames an existing pair — but if the collision-set membership itself
|
||||
changes (a third `AA` appears, or one of two is removed), the segments change
|
||||
on the next browse, and previously committed tags on the old segments read Bad
|
||||
until re-committed. The same rule covers spindles.
|
||||
- **Values are dated from the last answered poll, not from the read.** If a CNC
|
||||
stops answering, its fixed-tree values go `BadCommunicationError` rather than
|
||||
continuing to serve the last snapshot as `Good`; the budget is eight poll
|
||||
|
||||
Reference in New Issue
Block a user