25 unmappable tags on SITE-A's sa-modbus2 closed both gaps in one setup: chip 25/26 SKIPPED, sample capped at exactly MaxSample=20, tooltip '(+5 more not shown)'. Trimming to 20 skipped gave 20/21 with NO truncation note, pinning the off-by-one boundary. SITE-A is the discriminator for the proto path. Phase 6 partitioned it into its own Akka mesh -- central's MEMBERS panel lists only central-1/central-2, and DistributedPubSub does not federate across meshes. The single site-a reference in central's log is the ClusterClient receptionist, a point-to-point command path, not gossip. So the report's only route to the page is TelemetryProtoMapNode -> gRPC -> TelemetryProtoMapCentral, and central's view tracking a change made solely on the site node is conclusive. Also corrects round one's claim that the fused central pair meant telemetry never crossed gRPC -- wrong about this rig. All six ClusterNode rows carry GrpcPort 4056 and Telemetry__Mode=Grpc; the two 'Telemetry stream to node' lines that suggested a two-node dial set were startup failure warnings. Rig restored: test tags deleted, SITE-A redeployed, sa-modbus2 back to 1 tag.
7.4 KiB
Live gate — skipped-tag visibility (Gitea #541)
Date: 2026-08-05
Rig: local docker-dev/ (OrbStack), MAIN pair central-1 / central-2, AdminUI http://localhost:9200
Build under test: fix/541-skipped-tag-visibility
Result: PASS. The gate found one defect unit tests did not (§4). Round two on 2026-08-06 closed
the two items round one left unproven and corrected a wrong claim about the rig's topology (§6).
What needed proving
The unit suites pin the ledger semantics, the publish fingerprint, the proto round-trip and the
view-model. None of them prove the thing an operator actually depends on: that the Tags column on
/hosts renders the right cell, in the right colour, with actionable detail, against a driver that is
genuinely skipping tags on a real deployment.
Method
The rig already carried eleven driver instances. Three of the thirteen tags on the pymodbus Modbus
driver had their TagConfig.region rewritten to NoSuchRegion — the realistic shape of a bad blob —
then MAIN was redeployed via POST /api/deployments. Backup of the original thirteen blobs taken
first; all three restored and redeployed at the end (§5).
Results
Driver logs confirmed the skips reached the driver:
[WRN] Modbus tag config did not map to a definition; skipping. Driver=MAIN-modbus RawPath=pymodbus/plc/Probe3
[WRN] Modbus tag config did not map to a definition; skipping. Driver=MAIN-modbus RawPath=pymodbus/plc/Probe4
[WRN] Modbus tag config did not map to a definition; skipping. Driver=MAIN-modbus RawPath=pymodbus/plc/Probe5
/hosts then rendered — note every other column on the pymodbus row reads perfectly healthy, which
is the entire point:
| Driver | Status | Errors/5 min | Last error | Last read | Tags | Chip |
|---|---|---|---|---|---|---|
pymodbus |
HEALTHY | 0 | — | advancing | 3 / 13 SKIPPED | chip-warn (amber) |
sql-oracle-fixture |
HEALTHY | 0 | — | advancing | 1 / 1 SKIPPED | chip-bad (red) |
sql-postgres-fixture |
HEALTHY | 0 | — | advancing | 1 / 1 SKIPPED | chip-bad (red) |
gate-modbus-renamed |
HEALTHY | 0 | — | advancing | 1 / 1 SKIPPED | chip-bad (red) |
abcip-browse |
HEALTHY | 0 | — | advancing | 5 tags | none |
adsfa (Calculation) |
HEALTHY | 0 | — | — | 0 tags | none |
opcua1 |
FAULTED | 1 | (config error) | — | — | none |
rtu-gate |
FAULTED | 1 | Connection refused | — | — | none |
Tooltip on the amber chip, verbatim:
3 of 13 authored tag(s) are not being polled. They report BadNodeIdUnknown and the driver's state does not reflect them. pymodbus/plc/Probe3: TagConfig did not map to a definition · pymodbus/plc/Probe4: … · pymodbus/plc/Probe5: …
and on a red one:
This driver is polling NONE of its 1 authored tag(s). …
Three of the four skipping drivers were not planted. sql-oracle-fixture, sql-postgres-fixture
and gate-modbus-renamed were already misconfigured on this rig and had been silently skipping their
tags for an unknown length of time, each reporting Healthy with zero errors. The column surfaced them
on its first run — an unplanned but fairly direct demonstration of the defect being real rather than
theoretical.
Restoring the three blobs and redeploying returned pymodbus to 13 tags with no chip, which also
exercises the clear-on-clean-rebuild path live.
4. Defect the gate found — 0 tags on a driver that never built a table
The first pass rendered 0 tags for opcua1 and rtu-gate. Both are FAULTED, both have two
authored tags in the ConfigDb, and neither ever completed InitializeAsync — so their ledgers had
never committed and Snapshot() returned SkippedTagReport.Empty.
A zero report is a positive claim — "this driver skipped nothing" — and making it on behalf of a driver that never got as far as looking is precisely the quiet inaccuracy #541 exists to remove. Not caught by unit tests because every one of them drove a driver through a completed build first.
Fixed by making ISkippedTagReporter.GetSkippedTags() and SkippedTagLedger.Snapshot() return
null until the first committed build, so the column shows the same em-dash it already used for a
driver that cannot report at all. Both are no answer; neither is nothing skipped. Pinned by three
new SkippedTagLedgerTests cases plus a corrected FOCAS assertion. Re-gated on a rebuilt image: both
faulted drivers now render —, while adsfa — which genuinely has zero authored tags and did
build — correctly still renders 0 tags. That pair is the discriminating case.
5. Rig left as found
The three pymodbus blobs were restored from the pre-change backup and MAIN redeployed; /hosts
shows 13 tags. The three pre-existing skipping drivers were left exactly as they were — they are the
rig's own configuration, not this gate's.
6. Round two (2026-08-06) — truncation note + the central proto path
Both items round one left unproven were closed by one setup, chosen so that a single observation
settles both. One correction first: round one recorded "the rig's central pair is fused
admin+driver, so the snapshot never crossed the Phase 5 gRPC telemetry stream." That was wrong about
the rig. All six ClusterNode rows carry GrpcPort 4056, every node runs Telemetry__Mode=Grpc, and
central dials all six — the two Telemetry stream to node … lines seen in round one were startup
failure warnings, not the dial set.
Setup. 25 tags with region: NoSuchRegion added to sa-modbus2 on SITE-A, joining its one
good tag (26 total), then SITE-A deployed. SITE-A is the discriminator: Phase 6 partitioned it into
its own Akka mesh, so central's MEMBERS panel lists only central-1:4053 and central-2:4053.
DistributedPubSub fans out within one mesh's mediator and does not federate, and the single
akka.tcp://otopcua@site-a-1 reference in central's log is the ClusterClient receptionist — a
point-to-point command path, not gossip. So a SITE-A driver's health can reach central's /hosts by
exactly one route: TelemetryProtoMapNode → gRPC TelemetryStreamService/Subscribe →
TelemetryProtoMapCentral.
Result — 25 skipped of 26 authored:
| Fact | Observed |
|---|---|
| Chip | 25 / 26 SKIPPED, chip-warn |
| Driver status | HEALTHY, 0 errors (as ever) |
| Sample entries in tooltip | 20 (= SkippedTagLedger.MaxSample) |
| Truncation note | (+5 more not shown) |
Skips logged on site-a-1 |
25 |
That proves the truncation note, and simultaneously proves the proto path: the counts and all 20 sample entries with their RawPath + reason survived the wire.
Positive control, doubling as the cap boundary. Five of the 25 broken tags were deleted and SITE-A
redeployed. Central's page tracked the change made only on the site node — 20 / 21 SKIPPED, sample
still 20, and no truncation note, correct at exactly MaxSample (the off-by-one that would have
read "+0 more not shown"). A live-updating view of a node with no gossip path is conclusive.
Rig restored: all 25 test tags deleted, SITE-A redeployed, sa-modbus2 back to 1 tag; the
deployment revision hash returned to the pre-test 6178a8f3….
Still not proven live
Nothing outstanding from the original two. Remaining unit-only coverage is the ledger's concurrency guard (a torn read under a real init/publish interleaving) — inherently hard to force on a rig, and the lock makes it a code-inspection matter rather than an observational one.