Merge R2-07 Surgical pure-adds (arch-review round 2) [PR #436]
v2-ci / build (push) Successful in 3m19s
v2-ci / unit-tests (push) Failing after 8m13s

Finding 03/P1: surgical in-place address-space adds/removals skip full rebuilds
(AddressSpaceChangeClassifier default-closed to Rebuild). 3 new
ISurgicalAddressSpaceSink remove members, guard-first + forwarded through
DeferredAddressSpaceSink (F10b inertness net green). T5/T6/T12/T14 over-the-wire +
docker-dev gates deferred. Auto-merged OtOpcUaNodeManager.cs with R2-08; verified
OpcUaServer.Tests 341/341 + forwarding guard 3/3. Build clean.
This commit is contained in:
Joseph Doherty
2026-07-13 12:27:59 -04:00
22 changed files with 2465 additions and 199 deletions
@@ -357,6 +357,16 @@ public sealed class OpcUaPublishActor : ReceiveActor, IWithTimers
// values once the first dependency update arrives; until then variables show BadWaitingForInitialData.
failedNodes += _applier.MaterialiseEquipmentVirtualTags(composition);
// R2-07 T4b — on a NON-rebuild apply (PureAdd / AttributeOnly), the Materialise passes above
// just created exactly the added nodes WITHOUT tearing anything down, so announce them with a
// Part 3 GeneralModelChangeEvent(NodeAdded) per affected parent — model-aware clients then
// re-browse and pick up the new nodes while every existing MonitoredItem stays alive. Ordering
// is correct by construction: Apply returned before the passes ran, and this announce runs after
// them, so the nodes exist when clients re-browse. After a full rebuild the announcement is moot
// (subscriptions are dead anyway) — the guard skips it. AnnounceAddedNodes is Safe-wrapped
// internally, so a faulting announce can never break the deploy.
if (!outcome.RebuildCalled) _applier.AnnounceAddedNodes(plan);
OtOpcUaTelemetry.OpcUaSinkWrite.Add(1, new KeyValuePair<string, object?>("kind", "rebuild"));
if (outcome.RebuildFailed || failedNodes > 0)
{