Commit Graph

69 Commits

Author SHA1 Message Date
Joseph Doherty a833d1b4aa fix(alarms): address code review — accurate reconnect comment + SubscribeAlarms drop handlers
- Correct the misleading DetachAlarmSource comment: a session-less feed (Galaxy) is NOT
  torn down on an in-place reconnect, so re-subscribe is additive (harmless; gate reads [0]).
- Add trace-only SubscribeAlarms drop handlers in Connecting/Reconnecting (symmetry with
  NativeAlarmRaised) so a self-tell overtaken by a queued disconnect doesn't dead-letter.
- Document the deliberate no-unsubscribe-on-empty asymmetry vs the value path.
Behavior-neutral for the un-gate path. Minor handle-accumulation leak tracked as follow-up.
2026-06-15 00:49:19 -04:00
Joseph Doherty 7f313df7a6 fix(alarms): subscribe native alarms to un-gate the IAlarmSource feed
Phase B native alarms never fired end-to-end: GalaxyDriver suppresses OnAlarmEvent until
an alarm subscription exists (_alarmSubscriptions.Count > 0), but the runtime only attached
the OnAlarmEvent handler and never called SubscribeAlarmsAsync — so the central feed stayed
gated and no transition reached the Part 9 condition / /alerts. Unit tests passed because
they inject through the IAlarmSource seam directly; the deferred live /run surfaced it.

DriverHostActor computes per-driver alarm refs (alarm-bearing tags' FullNames) and hands them
via SetDesiredSubscriptions; DriverInstanceActor calls SubscribeAlarmsAsync for IAlarmSource
drivers on Connected entry and whenever alarm refs are pushed while Connected (the deploy path),
idempotent via a cached handle reset on detach so reconnect re-subscribes.
2026-06-15 00:42:43 -04:00
Joseph Doherty 50e1141fc2 test(historian): assert Deferred sink forwards historianTagname + doc nits
I1: DeferredAddressSpaceSinkTests.RecordingSink now captures HistorianTagname
per EnsureVariable call (HistorianQueue/HistorianCalls, matching the
Phase7ApplierTests pattern); new test EnsureVariable_forwards_historianTagname_to_inner_sink
asserts the arg is forwarded unchanged through DeferredAddressSpaceSink.

M1: OtOpcUaNodeManager.EnsureVariable doc-comment notes that a changed
historize intent on an already-registered node is silently ignored until
a RebuildAddressSpace (rebuild precondition for Task 3 implementers).

N2: DeploymentArtifact.ExtractTagHistorize doc wording: "The live-edit
side" → "The live-edit composer side".
2026-06-14 19:16:23 -04:00
Joseph Doherty c35c1d3734 refactor(historian): single-parse ExtractTagHistorize + review-nit tests/docs
Stop parsing TagConfig twice per tag on the deploy hot path: Phase7Composer's
equipment-tag Select lambda is now block-bodied (captures isHistorized/historianTagname
once), and DeploymentArtifact.BuildEquipmentTagPlans captures locals before result.Add.
Add wrong-type-historianTagname InlineData to ExtractTagHistorizeTests. Extend the
parity round-trip fixture with a 4th tag (isHistorized:false + JSON-null tagname)
exercising the artifact-side private guard path. Align DeploymentArtifact's
ExtractTagHistorize doc-comment with the composer-side phrasing (ExtractTagFullName /
ExtractTagAlarm cross-reference).
2026-06-14 19:02:02 -04:00
Joseph Doherty 440929c82a feat(historian): carry isHistorized + historianTagname through EquipmentTagPlan (byte-parity) 2026-06-14 18:55:04 -04:00
Joseph Doherty 7ffe8939e7 style(drivers): clarify Reconnecting InitializeFailed no-op is generation-agnostic (code-review nit) 2026-06-14 17:19:34 -04:00
Joseph Doherty 751786ec8c fix(drivers): adopt corrected config via ApplyDelta while (re)connecting (#7)
A DriverInstanceActor stuck Reconnecting/Connecting now adopts a config delivered via ApplyDelta and
re-initialises with it, instead of dead-lettering and retrying the stale config forever. A monotonic
init generation supersedes the in-flight init so the corrected config always wins.
2026-06-14 17:15:28 -04:00
Joseph Doherty f9be38430c fix(alarms): route native alarms by ConditionId (dotted FullName), not bare SourceNodeId (integration review)
v2-ci / build (push) Failing after 46s
v2-ci / unit-tests (tests/Core/ZB.MOM.WW.OtOpcUa.Cluster.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.ControlPlane.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.Security.Tests) (push) Has been skipped
v2-ci / integration (tests/Server/ZB.MOM.WW.OtOpcUa.Host.IntegrationTests) (push) Has been skipped
v2-ci / integration (tests/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer.IntegrationTests) (push) Has been skipped
2026-06-14 04:09:01 -04:00
Joseph Doherty 7e86fa7099 fix(alarms): normalise native TransitionKind to canonical EmissionKind vocabulary (review) 2026-06-14 03:58:46 -04:00
Joseph Doherty 8736fcc37c feat(alarms): Primary-gated AlarmTransitionEvent fan-out for native alarms (Phase B WS-5) 2026-06-14 03:48:41 -04:00
Joseph Doherty 4c56a1719b feat(alarms): DriverHostActor routes native alarm transitions to Part 9 conditions (Phase B WS-4c) 2026-06-14 03:34:25 -04:00
Joseph Doherty 422e5b7db2 refactor(alarms): harden ExtractTagAlarm severity parse (TryGetInt32) + trim projector prior-state (review nits) 2026-06-14 03:27:03 -04:00
Joseph Doherty 25c3bd16ba feat(alarms): DriverInstanceActor forwards native OnAlarmEvent to parent (Phase B WS-4b) 2026-06-14 03:24:24 -04:00
Joseph Doherty c1aeafaaf3 feat(alarms): NativeAlarmProjector maps transitions to condition snapshots (Phase B WS-4a) 2026-06-14 03:16:44 -04:00
Joseph Doherty e1ccd99ea2 feat(alarms): EquipmentTagPlan.Alarm parsed byte-parity from TagConfig (Phase B WS-2) 2026-06-14 03:12:48 -04:00
Joseph Doherty 590e497872 fix(runtime): narrow ActorNodeWriteGateway catch + drop vacuous no-actor assertion 2026-06-14 01:32:34 -04:00
Joseph Doherty 526ddb6a57 feat(runtime): ActorNodeWriteGateway — Asks RouteNodeWrite, returns NodeWriteOutcome 2026-06-14 01:23:43 -04:00
Joseph Doherty 7e405e949b fix(runtime): swallow self SubscriptionFailed too (symmetric to SubscriptionEstablished) 2026-06-14 00:42:31 -04:00
Joseph Doherty 42b4a923fd fix(runtime): fast-fail writes in degraded driver states + swallow self SubscriptionEstablished 2026-06-14 00:34:37 -04:00
Joseph Doherty 4cda275b8d fix(runtime): fast-fail RouteNodeWrite while Stale + micro-opts + raw-blob routing test 2026-06-14 00:16:47 -04:00
Joseph Doherty a23fb2b82e feat(server): equipment-tag node writability from Tag.AccessLevel (parity-safe, no migration) 2026-06-13 11:46:00 -04:00
Joseph Doherty f8f1027287 feat(runtime): NodeId->driver reverse routing + primary-gated RouteNodeWrite 2026-06-13 11:44:26 -04:00
Joseph Doherty c4435e4fd6 feat(runtime): route driver values to folder-scoped equipment NodeIds (live-value delivery)
v2-ci / build (push) Failing after 44s
v2-ci / unit-tests (tests/Core/ZB.MOM.WW.OtOpcUa.Cluster.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.ControlPlane.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.Security.Tests) (push) Has been skipped
v2-ci / integration (tests/Server/ZB.MOM.WW.OtOpcUa.Host.IntegrationTests) (push) Has been skipped
v2-ci / integration (tests/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer.IntegrationTests) (push) Has been skipped
2026-06-13 06:32:38 -04:00
Joseph Doherty da1accceff feat(runtime): carry DriverInstanceId on AttributeValuePublished (live-value routing key) 2026-06-13 06:27:52 -04:00
Joseph Doherty 7d25480fee docs(galaxy): neutralize remaining stale SystemPlatform/alias terminology in comments + a test name
Replace "SystemPlatform mirror tag", "Galaxy alias", and "SystemPlatform-kind" in doc-comments and
test names with neutral accurate wording ("FolderPath-scoped tag", "EquipmentId == null", etc.).
No code, logic, or test bodies changed — comments and one test method name only.
2026-06-12 22:30:50 -04:00
Joseph Doherty 5edea52bd7 docs(galaxy): fix stale SystemPlatform/alias/Galaxy doc comments (review follow-up)
Resolves the code-review notes on 95be607a + the AdminUI bundle: the
EnsureVariable docs (IOpcUaAddressSpaceSink, OtOpcUaNodeManager) and the Tag
entity doc no longer say 'Galaxy / SystemPlatform / alias'; the DriverHostActor
ForwardToMux comment now states the real equipment-tag value-routing gap (the
FullName→NodeId 'live values' milestone) instead of claiming Galaxy values map
straight through.
2026-06-12 22:00:52 -04:00
Joseph Doherty 95be607a07 feat(opcua): remove SystemPlatform-mirror GalaxyTags contract end-to-end (composer+applier+artifact, byte-parity) 2026-06-12 21:45:19 -04:00
Joseph Doherty bc9e83ed9f feat(composer): admit GalaxyMxGateway-backed equipment alias tags (+byte-parity) 2026-06-11 21:10:21 -04:00
Joseph Doherty 06c415598c feat(redundancy): gate scripted-alarm alerts publish on Primary (A1) 2026-06-11 08:44:44 -04:00
Joseph Doherty 5256761368 feat(scripted-alarms): spawn + apply ScriptedAlarmHostActor in DriverHostActor (T10) 2026-06-10 15:17:29 -04:00
Joseph Doherty c9590c03d0 fix(scripted-alarms): harden artifact boolean decode + direct helper tests (T6 review)
Default HistorizeToAveva/Retain/Enabled to the entity defaults (true) when a
field is absent/null/non-boolean so a partial blob decodes identically to the
composer's view of a default-constructed ScriptedAlarm (byte-parity), and only
call GetBoolean for a genuine true/false token. Add direct ExtractAlarmDependencyRefs
unit tests (overlap dedup + reserved {{equip}} exclusion).
2026-06-10 14:47:24 -04:00
Joseph Doherty 8e8ca9efe8 feat(scripted-alarms): DeploymentArtifact byte-parity for the alarm plan (T6) 2026-06-10 14:41:46 -04:00
Joseph Doherty 66ea9c56f6 feat(runtime): DeploymentArtifact substitutes {{equip}} (parity with composer) 2026-06-10 07:53:20 -04:00
Joseph Doherty d909a8e4f6 docs+test(deploy): clarify driver-less attribution docs + no-line exclusion test (Task 2 review) 2026-06-08 07:02:25 -04:00
Joseph Doherty c688899134 fix(deploy): cluster-attribute driver-less equipment via its UNS line area (BuildClusterSets) 2026-06-08 06:53:41 -04:00
Joseph Doherty 6b36eff2d3 refactor(runtime): capture-first in HandleWriteAsync; assert no handler leak on resubscribe; fix stale comment 2026-06-07 10:31:20 -04:00
Joseph Doherty 98259ab026 fix(runtime): capture Sender before await in DriverInstanceActor subscribe (no-ActorContext race) 2026-06-07 10:26:17 -04:00
Joseph Doherty 4c221ce2b3 merge: equipment-namespace live values (VirtualTag route)
v2-ci / build (push) Failing after 36s
v2-ci / unit-tests (tests/Core/ZB.MOM.WW.OtOpcUa.Cluster.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.ControlPlane.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.Security.Tests) (push) Has been skipped
v2-ci / integration (tests/Server/ZB.MOM.WW.OtOpcUa.Host.IntegrationTests) (push) Has been skipped
v2-ci / integration (tests/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer.IntegrationTests) (push) Has been skipped
2026-06-07 09:33:21 -04:00
Joseph Doherty 1c579410cd fix(runtime): flag cross-cluster orphan-equipment bindings on rebuild
v2-ci / build (push) Failing after 42s
v2-ci / unit-tests (tests/Core/ZB.MOM.WW.OtOpcUa.Cluster.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.ControlPlane.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.Security.Tests) (push) Has been skipped
v2-ci / integration (tests/Server/ZB.MOM.WW.OtOpcUa.Host.IntegrationTests) (push) Has been skipped
v2-ci / integration (tests/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer.IntegrationTests) (push) Has been skipped
ParseComposition(blob, nodeId, onInconsistency?) detects a kept equipment whose
UNS line belongs to another cluster (a same-cluster-invariant violation that
would orphan the equipment folder) and reports it via an optional callback,
wired to OpcUaPublishActor's logger. Detection-only; the upstream draft
validator remains the authority. Adds two unit tests.
2026-06-07 08:24:11 -04:00
Joseph Doherty 2bfe18abcf chore(runtime): warn on missing VirtualTag evaluator; document Stale-recovery VirtualTag behaviour
Log a WARNING on startup when IVirtualTagEvaluator is not registered so a DI misconfig on a
driver-role node is visible in logs instead of silently evaluating all VirtualTags to NoChange.
Add a comment in PushDesiredSubscriptions noting that TryRecoverFromStale does not call this
method, so VirtualTags remain empty after a Stale recovery until the next deployment dispatch
(intentional, consistent with driver recovery).
2026-06-07 05:46:24 -04:00
Joseph Doherty 397f9b783a feat(runtime): spawn+apply VirtualTagHostActor on deploy apply and restore 2026-06-07 05:41:04 -04:00
Joseph Doherty c7661d0510 feat(opcua): parse Equipment VirtualTag plans from the deployment artifact 2026-06-07 05:09:53 -04:00
Joseph Doherty 1b7f995aea feat(runtime): DriverHost spawns + subscribes only its own ClusterId's drivers 2026-06-07 03:19:22 -04:00
Joseph Doherty 4fca4e1aca feat(runtime): node-scoped ParseComposition filters address space by ClusterId 2026-06-07 03:15:46 -04:00
Joseph Doherty 7b2f64fdb8 refactor(runtime): case-insensitive ClusterId/NodeId match + suppress short-circuit + edge tests (review) 2026-06-07 03:12:09 -04:00
Joseph Doherty 24796f2c12 feat(runtime): ClusterId scope resolution + node-scoped driver-spec parse 2026-06-07 03:05:02 -04:00
Joseph Doherty aaf869145a fix(opcua): equipment-tag planner diff + folder-scoped NodeIds (review findings)
Two bundle-review fixes + idempotency coverage:
- CRITICAL: the planner ignored EquipmentTags, so an incremental deploy changing only
  equipment tags produced an empty plan and HandleRebuild short-circuited before
  materialising them. Add TagId to EquipmentTagPlan + Added/Removed/ChangedEquipmentTags
  to Phase7Plan (diffed by TagId, in IsEmpty, driving Apply's needsRebuild) — mirroring
  the GalaxyTags treatment.
- IMPORTANT: equipment variable NodeId was the raw driver FullName, which collides across
  identical machines (e.g. two PLCs both exposing register 40001) — the second variable
  was silently dropped. NodeId is now folder-scoped (parent/Name); FullName stays on
  EquipmentTagPlan for the later values-routing milestone.
- Task 4: SDK-backed idempotency test (double-apply -> single variable); restart-safety
  confirmed (RestoreApplied reuses the same RebuildAddressSpace -> HandleRebuild path).
- Minor: align composer equipment-tag sort with the artifact decoder (coalesce FolderPath).
2026-06-06 15:02:50 -04:00
Joseph Doherty 08cddfe128 fix(opcua): UNS equipment folders browse by friendly Name, NodeId stays the logical Id
Equipment folder DisplayName was the colloquial MachineCode; the live rebuild (artifact
ReadEquipmentNode) + composer now use the UNS level-5 Name segment, matching Area/Line
folders + EquipmentNodeWalker. NodeId stays the logical EquipmentId so browse-path
resolution + ACLs are unaffected.
2026-06-06 14:51:12 -04:00
Joseph Doherty febe462750 feat(opcua): carry Equipment-namespace tags through the deployment composition
Add EquipmentTagPlan + an init-only EquipmentTags member on Phase7CompositionResult
(mirror of GalaxyTags). Populate it compose-side (Tag.EquipmentId != null AND owning
namespace Kind == Equipment) and artifact-decode-side via BuildEquipmentTagPlans, with
FullName extracted from Tag.TagConfig. Init-only member (not a 7th positional param) so
existing convenience constructors + call sites are untouched.
2026-06-06 14:42:38 -04:00
Joseph Doherty b1b3f3ff23 fix(runtime): materialise from applied artifact + restore served state on bootstrap
v2-ci / build (push) Failing after 47s
v2-ci / unit-tests (tests/Core/ZB.MOM.WW.OtOpcUa.Cluster.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.ControlPlane.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.Security.Tests) (push) Has been skipped
v2-ci / integration (tests/Server/ZB.MOM.WW.OtOpcUa.Host.IntegrationTests) (push) Has been skipped
v2-ci / integration (tests/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer.IntegrationTests) (push) Has been skipped
Two ordering/lifecycle gaps surfaced once tag values began streaming:

1. OpcUaPublishActor.HandleRebuild loaded the latest *Sealed* artifact, but the
   rebuild fires at apply time — before this deployment seals — so it materialised
   the PREVIOUS revision while SubscribeBulk subscribed to the applied one. The two
   disagreed (4 variables materialised vs 396 subscribed) and every config needed
   two deploys. RebuildAddressSpace now carries the applied DeploymentId and the
   rebuild loads that exact artifact.

2. On restart a node recovered its revision from NodeDeploymentState but left the
   driver children + address space empty (and an identical-config redeploy no-ops on
   the unchanged revision), so a rebuilt node served nothing until a config change.
   Bootstrap now calls RestoreApplied: re-spawn drivers, rebuild from the applied
   artifact, re-push SubscribeBulk — no re-ack.

Verified live: recreating the driver nodes auto-restores all 396 galaxy mirror
tags across 40 machines with Good live values, no deploy required.
2026-06-06 12:53:38 -04:00