revert(drivers): remove the periodic 5-minute device re-browse (#523)

Removes the tag-set drift detector shipped in e77c8a35. The design was sound
-- the tautology drivers were correctly gated out, and the structurally
undetectable "vanished" direction was declared rather than faked -- but none of
that is the objection. The feature browsed real plant equipment on a recurring
timer, at a frequency no operator could configure (the interval was a
constructor parameter with no appsettings key), and that outbound traffic was
never once observed against a real PLC, CNC or MTConnect Agent.

Removed: TagSetDrift/TagSetDriftDetector; DriverInstanceActor's drift timer,
tick message, Props/ctor parameter, gate, handler and leaf-collector;
ITagDiscovery.AuthoredDiscoveryRefs + .DiscoveryStreamIncludesAuthoredTags and
their four implementations; 14 tests.

Kept: ITagDiscovery.SupportsOnlineDiscovery (it predates this and drives the
universal browse picker), and the whole IRediscoverable consumption from
09a401b8 -- the driver tells us, we do not poll.

CONSEQUENCE, deliberately accepted: AbCip and FOCAS browse a live backend but
implement no IRediscoverable, so they now have NO tag-change signal at all -- a
PLC re-download is invisible until someone re-browses by hand. Recorded in
CLAUDE.md so the next reader does not have to rediscover it, along with the
shape to reach for if that coverage is wanted back (event-driven, or triggered
by a reconnect/deploy, not a wall-clock timer).

Runtime.Tests 476 pass / 13 skipped (skip set unchanged); FOCAS 275, TwinCAT
192, MTConnect 491, AbCip 342, Core.Abstractions 266 all green.

Claude-Session: https://claude.ai/code/session_015p7wGqy3YpZNCpDzTpGMKo
This commit is contained in:
Joseph Doherty
2026-07-28 13:59:06 -04:00
parent 16752032e5
commit 5fff597324
11 changed files with 59 additions and 743 deletions
+15 -31
View File
@@ -188,38 +188,22 @@ line and injected nothing. Gone with it: `HandleDiscoveredNodes`, `PartitionDisc
browse picker drives it through `Commons/Browsing/DiscoveryDriverBrowser`, which never went through
the actor.
**Tag-set drift detection is slated for REMOVAL (Gitea #523).** It is in `master` today and the
description below is accurate, but the periodic device re-browse is being taken back out: it is
recurring, unconfigurable (interval is a constructor parameter, no appsettings key) and never
live-gated traffic to real PLCs/CNCs/Agents. After removal, **AbCip and FOCAS have no tag-change signal
at all** (they implement no `IRediscoverable`); TwinCAT and MTConnect keep their native ones. The
`IRediscoverable` consumption above is unaffected.
**`IRediscoverable` is the ONLY tag-change signal, and four drivers do not raise it.** A periodic
tag-set drift check briefly existed (shipped `e77c8a35`, **removed** by Gitea **#523**): every
`SupportsOnlineDiscovery` driver was re-browsed on a 5-minute timer and the result diffed against its
authored tags. It was taken back out because it generated recurring, **unconfigurable** (interval was a
constructor parameter with no appsettings key), **never-live-gated** outbound traffic to real
PLCs/CNCs/Agents — how often to browse someone's plant equipment is an operational decision, and the
design treated it as an implementation detail.
**Tag-set drift detection (2026-07-28) — the second signal, and it is GATED.** `DriverInstanceActor`
re-browses on a slow timer (`DefaultDriftCheckInterval`, 5 min) and compares what the remote offers
against what an operator authored, raising the same re-browse prompt. This matters most for **AbCip and
FOCAS**, which browse a live backend but implement no `IRediscoverable`, so a periodic compare is their
only way to notice a PLC re-download.
⚠️ **It runs ONLY for a driver declaring `ITagDiscovery.SupportsOnlineDiscovery` AND reporting
`AuthoredDiscoveryRefs` (nullable, default null = opt out).** Modbus, S7, MQTT, AbLegacy and Sql *echo
authored config* from `DiscoverAsync` rather than browsing the device, so the diff for them is a
tautology that would report "no drift" forever and read as coverage. A check that cannot fail is worse
than no check. Note `AuthoredDiscoveryRefs` is **nullable, not empty-by-default** — an empty collection
legitimately means "nothing authored, so everything discovered is new".
⚠️ **One direction is structurally undetectable for most drivers.** FOCAS, TwinCAT and AbCip re-emit
their authored tags into the same `DiscoverAsync` stream as device-derived ones (so the browse picker
shows existing tags), which means discovered ⊇ authored **always** and a *vanished* tag can never appear
missing. Those drivers declare `DiscoveryStreamIncludesAuthoredTags => true`, and the detector then
reports only what it can see and **says so** in the operator message rather than implying a clean bill of
health. **MTConnect is currently the only driver where both directions are detectable** — its stream is
built purely from the Agent's probe model.
Other properties worth knowing: a **failed browse is not drift** (an unreachable device would otherwise
report every authored tag as vanished); a **truncated** capture is skipped for the same reason; an
**unchanged** drift is reported once rather than re-stamping its timestamp every 5 min; and drift that
resolves **clears** the prompt. The timer starts on Connected entry and is cancelled on every exit.
⚠️ **Consequence to know before you rely on rediscovery:** **AbCip and FOCAS browse a live backend but
implement no `IRediscoverable`, so they have no tag-change signal at all** — a PLC program re-download
or a CNC option install is invisible until someone re-browses the device by hand. Galaxy, TwinCAT,
MTConnect and MQTT raise natively and are unaffected. If that coverage matters later, reach for an
event-driven signal, or one that runs when something *else* already indicates change (a reconnect, a
deploy) — not a wall-clock timer. Removed with the feature: `TagSetDrift`/`TagSetDriftDetector`,
`DriverInstanceActor`'s drift timer, and `ITagDiscovery.AuthoredDiscoveryRefs` /
`DiscoveryStreamIncludesAuthoredTags`.
⚠️ **`IHostConnectivityProbe` is still unconsumed.** `GetHostStatuses()` has no production call site and
nothing ever writes a `DriverHostStatus` row (the table was re-created in the v3 initial migration, so