Since #516 a DriverConfig change is stop + respawn, so DriverSpawnPlan.ToApplyDelta was always empty and the in-place path had no producer. #663 and #664 were both latent bugs in it. This deletes it: - host: ApplyChildDelta, _pendingDelta, HandleApplyResult and HandleDeltaApplied - plan: DriverSpawnPlan.ToApplyDelta - actor: the ApplyDelta, ApplyResult and DeltaApplied messages, the Connected reinit handler and the mid-connect AdoptConfigDuringInit - tests: the actor tests that drove the seam IDriver.ReinitializeAsync and each driver's implementation and unit tests stay. Comments and docs that cited AdoptConfigDuringInit as the reason a driver must release prior state at init now cite the IDriver contract and overlapping retries, which still apply. Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
5.1 KiB
Incremental Sync
How a change reaches the running server's address space in v3. There is exactly one path that
changes the served address space — the deploy artifact — and one advisory signal
(IRediscoverable) that never changes it.
The deploy-artifact path
- Compose.
ConfigComposer(src/Server/ZB.MOM.WW.OtOpcUa.ControlPlane/AdminOperations/ConfigComposer.cs) snapshots the whole Config DB into one gzip-compressed JSON blob (Deployment.ArtifactBlob) whoseRevisionHashis the SHA-256 of those exact bytes. A deployment is fleet-wide; each node self-scopes the artifact (DeploymentArtifact.ResolveClusterScope). - Dispatch.
ConfigPublishCoordinator(.../ControlPlane/Coordinators/) sends each enabledClusterNodea payload-free dispatch and waits for acks until the apply deadline. A node obtains the bytes either straight from central SQL (ConfigSource:Mode=Direct) or over gRPC with a SHA-256 check (FetchAndCache) — seedocs/Configuration.md§ConfigSource. - Apply.
DriverHostActor(src/Server/ZB.MOM.WW.OtOpcUa.Runtime/Drivers/) opens the artifact and reconciles in two halves:- Drivers —
DriverSpawnPlanner.ComputeyieldsToStop/ToSpawn. A changed driver config (DriverConfig, DriverType or ResilienceConfig) is a stop + respawn (#516) — there is no in-place path (#665 deleted the unreachableApplyDeltaseam); a removed driver is stopped. An unreadable or empty blob is "no answer": the apply fails and the running children are left alone (never an empty configuration). - Address space —
AddressSpaceComposerbuilds anAddressSpaceCompositionfrom the artifact;AddressSpacePlandiffs it against the last applied composition;AddressSpaceChangeClassifierpicks the delta class;AddressSpaceApplier(src/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer/) mutatesOtOpcUaNodeManagerthrough the sink.
- Drivers —
- Classes (
AddressSpaceChangeKind):Empty,AttributeOnly,PureAdd,PureRemove,AddRemoveMix— all applied surgically, so unrelated MonitoredItems survive — andRebuild, the default-closed fallback for anything the classifier does not whitelist. Any surgicalfalse/throw also ratchets to a safe rebuild, and a degraded non-rebuild apply does not advance the last-applied composition (the next deploy re-diffs and re-ensures). The whitelist, the UNS-mirror repair rule and the add/remove announcements are described in the "surgical deploys" paragraph ofCLAUDE.mdanddocs/plans/2026-08-15-surgical-deploys.md.
Virtual tags and scripted alarms ride the same artifact and the same plan; a script edit is just another
changed record in the composition (see docs/VirtualTags.md, docs/ScriptedAlarms.md).
Driver-backend rediscovery is advisory
Drivers whose backend signals a tag-set change implement IRediscoverable
(src/Core/ZB.MOM.WW.OtOpcUa.Core.Abstractions/IRediscoverable.cs; OnRediscoveryNeeded carries a
Reason and an optional ScopeHint). Raisers today: Galaxy (DeployWatcher, new
time_of_last_deploy), TwinCAT (ADS symbol-version-changed), MQTT/Sparkplug and MTConnect.
AbCip, AbLegacy, FOCAS, Modbus, S7 and the rest raise nothing.
DriverInstanceActor is the only production consumer: it subscribes in PreStart, detaches in
PostStop, and folds the raise into the driver-health snapshot
(DriverHealthChanged.RediscoveryNeededUtc / .RediscoveryReason), which the AdminUI /hosts page shows
as a re-browse chip. It never rebuilds or edits the served address space — v3 raw tags are authored
explicitly in /raw, so an operator re-browses the device and commits, and the result reaches the
server as an ordinary deployment. ScopeHint is not read by the actor. (The older runtime
injection path was removed, #507 closed as superseded.)
Not part of the v3 path
sp_PublishGeneration/sp_ComputeGenerationDiffare stubs thatRAISERRORin the v3 baseline migration; there is no generation-publish flow and noDraftRevisionToken.GenericDriverNodeManager(Core/OpcUa/) is retained scaffolding with no production caller; the diff / teardown / subscription-replay "rebuild flow" described by earlier versions of this page does not exist insrc/.
Key source files
src/Server/ZB.MOM.WW.OtOpcUa.ControlPlane/AdminOperations/ConfigComposer.cs— artifact composition + hashsrc/Server/ZB.MOM.WW.OtOpcUa.ControlPlane/Coordinators/ConfigPublishCoordinator.cs— dispatch + ack setsrc/Server/ZB.MOM.WW.OtOpcUa.Runtime/Drivers/DriverHostActor.cs— apply + driver reconcilesrc/Server/ZB.MOM.WW.OtOpcUa.Runtime/Drivers/DeploymentArtifact.cs— node-side artifact parsingsrc/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer/AddressSpaceComposer.cs,AddressSpacePlan.cs,AddressSpaceChangeClassifier.cs,AddressSpaceApplier.cs— compose, diff, classify, applysrc/Server/ZB.MOM.WW.OtOpcUa.Runtime/Drivers/DriverInstanceActor.cs—IRediscoverableconsumersrc/Core/ZB.MOM.WW.OtOpcUa.Commons/OpcUa/ISurgicalAddressSpaceSink.cs— in-place update seam