# 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.