docs(#615): G6 PASS, end to end against the live windev Galaxy; evidence + readiness record
v2-ci / unit-tests (push) Has been cancelled
v2-ci / unit-tests (push) Has been cancelled
The run covered browse (raw + UNS), read, subscribe, write and readback, and a native alarm through Part 9 to an ack confirmed in Galaxy and a clear. The run found #895 (fixed in c971b171) and #896 (open). DeployWatcher was not exercised: there is no headless Galaxy redeploy. Claude-Session: https://claude.ai/code/session_01GT67ACmpMR7b9LGRPLijMi
This commit is contained in:
@@ -226,6 +226,7 @@ Every (re)open of `StreamAlarms` replays the gateway's active-alarm snapshot. `G
|
||||
⚠️ This cannot currently fire: the sentinel is only emitted on a stream opened with `after_worker_sequence > 0`, and the driver opens with `0`, which the gateway treats as "fresh stream, not a resume". Enabling resume is a bigger change than it looks — the sequence is only meaningful **within one gateway session**, and the reconnect path recreates the session, so a remembered sequence replayed against a fresh session would set the server's live watermark *above* every event it is about to send, silencing the stream rather than recovering it.
|
||||
- **Undecodable values** (`galaxy.events.undecodable`): a value kind this driver has no CLR representation for is published as **`BadTypeMismatch` with a null value**, and the first occurrence of each distinct kind is logged. It is *not* published as a null carrying the event's own quality: an MXAccess event can be Good-quality while the payload is one we cannot map, and a good null is indistinguishable from a genuine null reading — a client would consume a value it never received as a real one, with the failure visible only in a log. Non-zero here means the gateway is sending a wire type `MxValueDecoder` has no mapping for and those readings are being lost; the fix is a decoder mapping, not a quality change.
|
||||
- **Initial values can overtake the subscribe reply (#859)** (`galaxy.events.early_parked` / `early_replayed` / `early_overflow`): mxaccessgw gives no reply-before-event ordering — the worker frames the SubscribeBulk reply on a thread-pool continuation while the STA is already pumping the OnDataChange that Advise queued, and the reply (unary Invoke) and the event (StreamEvents) are separate HTTP/2 streams anyway. An event for an unregistered handle is therefore only "stale after unsubscribe" when no subscribe is in flight. Every SubscribeBulk call site (`SubscribeAsync`, the subscribe-once `ReadAsync` — which waits only for the initial value and would otherwise read `BadTimeout` — the reconnect/replay-gap `ResubscribeAllAsync` (one window per subscription, not across the whole replay), `PerPlatformProbeWatcher`) opens `SubscriptionRegistry.BeginPendingSubscribe()` before the RPC and closes it right after registering, never later; inside the window unknown-handle events are parked, promoted by the `Register`/`Rebind` that binds the handle — map writes and promotion are one critical section, so no later event can resolve in between — and replayed on the pump's dispatch thread before any later event for that handle. Held events (parked + promoted-but-unreplayed) are bounded at 100 000 together. Unclaimed parked events are trimmed by **window sequence**, not only when every window has closed (concurrent reads keep some window open almost permanently): each window gets a monotonically increasing sequence when it opens, each parked event is stamped with the newest sequence, and once every window that opened at or before that stamp has closed the event is discarded — outside any window the stale drop is unchanged. **Sessions (#861):** item handles are per gateway session and the next session reuses the numbers. On reconnect `ReopenAsync` calls `SubscriptionRegistry.StartNewSession()` both before and after the gateway session swap (`ReplayAsync` does it only when no reopen ran), and the replay re-subscribes only subscriptions still holding the previous session's handles (one registered after the swap is already live). The pump is always restarted, even with nothing to replay. `StartNewSession()` discards everything held, **invalidates the reverse map** (subscriptions are kept; new-session events park until the replay's per-subscription `Rebind` promotes them, instead of resolving to the tag that owned the number before), and bumps a session number. Registration and rebind are refused for a stale session, so a `SubscribeAsync` whose reply straddled the reconnect is **re-issued on the new session** (up to 3 attempts), a subscribe-once read returns `BadCommunicationError`, and dead old-session handle numbers are never sent to `UnsubscribeBulk` (a read rebound by the replay releases the handles the registry holds, not its original ones); an old session's pump can no longer resolve through the map. Reads listen on the driver, not on a pump instance, so a read survives the reconnect's pump restart. `PerPlatformProbeWatcher` records the session with each probe handle: only current-session handles are published (fenced `TryUpsert`), unsubscribed or purged, and a seed reply that straddled a reconnect is re-issued. Purges are fenced by the session captured before the unsubscribe RPC. **Interim (I-3):** a completed `UnsubscribeBulk` purges anything parked for its handles, so a trailing event of a removed item is not promoted onto a reused handle number within one session. It cannot catch an event still in flight after the unsubscribe, and it also drops a genuine early value for a number reused that fast (a timeout rather than another tag's value). The complete fix needs a worker sequence on the SubscribeBulk reply so the driver can tell pre-subscribe events from post-subscribe ones — an optional cross-repo change in mxaccessgw.
|
||||
- **Native-alarm reference forms (#895)**: the gateway alarm feed names an alarm `Galaxy!<Area>.<tag>.<Attr>`, with `SourceObjectReference = <tag>.<Attr>`. An alarm tag may carry either form as its `attributeRef`: the plain `<tag>.<Attr>` the AdminUI Galaxy picker authors, or the provider-qualified form. The driver matches a transition on the full reference, then the source reference, then the full reference with its `Provider!Area.` prefix stripped. It remembers the full reference so that an ack dials what `AcknowledgeAlarm` accepts, since the gateway refuses a non-provider reference. Before #895, a picker-authored alarm never went active (found in the G6 live gate, `docs/drivers/evidence/galaxy-g6-e2e-2026-11-12.md`).
|
||||
- **Duplicate `attributeRef` (#605 / 06-S-13)**: two authored RawPaths dialling the same Galaxy attribute are both dialled, but the reverse map is first-wins, so value fan-out and alarm `ConditionId` surface under the first RawPath only. The loser is logged at Warning and recorded in the skipped-tag ledger with the winner path in the reason.
|
||||
- **Subscribe-once reads are bounded (#605 / 06-S-3)**: `ReadAsync` on the production path (SubscribeBulk, first event, Unsubscribe) carries an internal 30 s deadline in addition to the caller token; slots that never publish come back `BadTimeout`, so `CancellationToken.None` can no longer hang on a silent tag.
|
||||
- **Supervisory advise is retried after a thrown failure (#605 / 06-S-12)**: the writer marks an item handle advised before the call; a thrown advise (not just a non-OK status) now unmarks it, fails the write closed and re-advises on the next write.
|
||||
|
||||
@@ -0,0 +1,252 @@
|
||||
# Galaxy G6 (#615): end to end against a live Galaxy, 2026-11-12
|
||||
|
||||
Gitea #615 gate G6 (`docs/plans/v2-ga-lab-gates-plan.md` §G6): "at least one end-to-end
|
||||
integration run against live Galaxy succeeds".
|
||||
|
||||
**Result: PASS.** Every G6 pass criterion was met, plus write and native-alarm checks that the plan
|
||||
does not require:
|
||||
|
||||
| G6 criterion (plan §G6) | Result |
|
||||
|---|---|
|
||||
| Browse returns a non-empty tree that mirrors the Galaxy hierarchy | PASS. Both subtrees: raw `galaxy-windev/dev/TestArea/TestMachine_015/…`, `…/TestArea3/TestMachine_036/…`; UNS `testarea/line-1/testmachine-015/…`, `testarea3/line-1/testmachine-036/…` |
|
||||
| Read returns `Good` with a non-null value | PASS. Every authored value tag, through both NodeIds |
|
||||
| Subscribe receives at least one data change within 5 s | PASS. First notification inside the first second, then one per second (1 s publishing) through both NodeIds |
|
||||
| No `BadNoCommunication` / `BadTimeout` in the server log | PASS, with one exception. The only `BadNoCommunication` line is the planned driver respawn during a redeploy (`driver g6gal disconnected — 10 value node(s) marked BadNoCommunication (value retained)`), and the driver reconnected 10 s later. There are no `BadTimeout` lines, and no `ERR`/`FTL` lines in either run |
|
||||
|
||||
The extra checks:
|
||||
|
||||
| Extra step | Result |
|
||||
|---|---|
|
||||
| Write a TestMachine attribute, then read it back | PASS through raw and UNS, cross-checked through an independent gateway session. The anonymous negative control was refused `BadUserAccessDenied` |
|
||||
| Native alarm becomes a Part 9 condition, then Ack, then Clear | PASS, with the ack confirmed in Galaxy (`.Acked=True`, `.AckMsg` = the comment) |
|
||||
| Native alarm authored the way the AdminUI picker authors it | **FAIL on master `00a5be26`** (the condition never went active). Fixed in `e113faf5` (#895) and **PASS live** after the fix |
|
||||
| DeployWatcher on a Galaxy redeploy | **NOT RUN.** See below |
|
||||
|
||||
Two defects were filed: **#895** (fixed in this branch) and **#896** (open).
|
||||
|
||||
## Target and versions
|
||||
|
||||
| | |
|
||||
|---|---|
|
||||
| Galaxy | windev `DESKTOP-6JL3KKO` test Galaxy `DEV` (GR DB `ZB`). AVEVA System Platform 2020 / Application Server 2020 (20.0.000). **129 deployed instances**, including all 40 `$TestMachine` objects (`TestMachine_001..040`) under areas `TestArea`, `TestArea2` and `TestArea3`. Last Galaxy deploy 2026-10-19T15:58:30Z (`GetLastDeployTime`) |
|
||||
| Gateway | mxaccessgw main `447070a5`, NSSM `MxAccessGw`, gRPC h2c `http://10.100.0.48:5120`. Not restarted or reconfigured. `http://10.100.0.48:5130/health/ready` returned 200 before and after the run |
|
||||
| Gateway key | temporary `g6-otopcua-e2e` (`session:open,session:close,invoke:read,invoke:write,events:read,metadata:read`, 1-day expiry), minted with the gateway admin CLI and revoked afterwards |
|
||||
| OtOpcUa | `origin/master` `00a5be26` for the main run. The #895 re-check ran the same tree plus the fix (committed as `e113faf5`) |
|
||||
| OPC UA client | `otopcua-cli` (`src/Client/ZB.MOM.WW.OtOpcUa.Client.CLI`), security None |
|
||||
|
||||
**Clock note.** windev's clock is about **9 min 12 s behind** the Mac (04:07:57 against 04:17:09 UTC,
|
||||
checked during the run). Galaxy `SourceTimestamp`s therefore read about 9 min behind `ServerTimestamp`
|
||||
throughout. This is box skew and not a defect: the gap stayed constant while values changed every second.
|
||||
|
||||
## Setup (local, temporary, removed afterwards)
|
||||
|
||||
The docker-dev rig was in use by other agents and its image commit could not be identified, so a single
|
||||
OtOpcUa Host ran on the Mac from this worktree:
|
||||
|
||||
- Fused `admin,driver` single node, Akka self-seeded on `localhost:4063`.
|
||||
- OPC UA on `opc.tcp://localhost:4850`, AdminUI and REST on `http://127.0.0.1:9310`.
|
||||
- Its own ConfigDb `OtOpcUaG6` on the rig's SQL container (`localhost:14330`), migrated with `dotnet ef database update`.
|
||||
- Cluster `G6`: `NodeCount=1`, `RedundancyMode=None`, one `ClusterNode localhost:4063`.
|
||||
- Committed docker-dev dev secrets (KEK, pepper, JWT, LDAP); `DisableLogin=true`; `Security:Acl:Enabled=false`.
|
||||
|
||||
Authored config, seeded into the ConfigDb and deployed with `POST /api/deployments` (`X-Api-Key`):
|
||||
|
||||
| Row | Value |
|
||||
|---|---|
|
||||
| DriverInstance | `g6gal` "galaxy-windev", `GalaxyMxGateway`, `Gateway.Endpoint=http://10.100.0.48:5120`, `UseTls=false`, `ApiKeySecretRef=env:OTOPCUA_GALAXY_APIKEY`, `MxAccess.ClientName=OtOpcUa-G6-gate`, `Repository.WatchDeployEvents=true`, `AlarmAck.FallbackUser=Administrator` with `FallbackPasswordSecretRef=dev:` (empty password, the windev rig's documented identity, mxaccessgw `docs/AlarmProbeFindings.md`) |
|
||||
| Device / groups | device `dev`; TagGroups `TestArea/TestMachine_015` and `TestArea3/TestMachine_036` (mirroring Galaxy area → object) |
|
||||
| Tags | `TestMachine_015`: `TestChangingInt` (Int32 R), `TestDouble` (Double RW), `TestAlarm002` (Boolean RW, value), `TestAlarm002Condition` (`attributeRef "Galaxy!TestArea.TestMachine_015.TestAlarm002"` + `alarm` AlarmCondition 500); `TestMachine_036`: `TestChangingInt`, `TestHistoryValue` (Int32 R). Later added: `TestAlarm003` (`attributeRef "TestMachine_015.TestAlarm003"` + `alarm` OffNormalAlarm 700, the picker's form) |
|
||||
| UNS | areas `testarea` / `testarea3` → `line-1` → equipment `testmachine-015` (`EQ-a6f600000000`) / `testmachine-036` (`EQ-b6f600000000`), with all six tags referenced |
|
||||
| Data-plane key | `g6-gate` (principal `g6gate`, 8 h), `NodeAcl` Cluster grant 927 (Browse/Read/Subscribe/HistoryRead/WriteOperate/AlarmRead/AlarmAck/AlarmConfirm) |
|
||||
|
||||
Deploys `8ee64aa9…` (driver), `0646dee1…` (key and ACL), `20affbc8…` (TestAlarm003). Each one was
|
||||
`Sealed`, with node `localhost:4063` `Applied`. A first key deploy was correctly refused
|
||||
`422 NodeAclScopeIdUnexpected`: a Cluster grant must carry no ScopeId.
|
||||
|
||||
Driver start:
|
||||
|
||||
```
|
||||
DriverHost localhost:4063: spawned GalaxyMxGateway driver g6gal (stub=False)
|
||||
GalaxyMxSession connected — clientName=OtOpcUa-G6-gate serverHandle=1
|
||||
GalaxyDriver g6gal initialized — endpoint=http://10.100.0.48:5120 clientName=OtOpcUa-G6-gate
|
||||
DriverInstance g6gal: subscribed to 5 refs (galaxy-sub-1)
|
||||
DriverInstance g6gal: native-alarm subscription established for 1 alarm ref(s)
|
||||
```
|
||||
|
||||
## Step 1: browse both subtrees (PASS)
|
||||
|
||||
`browse -r -d 8` (excerpt; condition children elided):
|
||||
|
||||
```
|
||||
[Object] OtOpcUa (NodeId: ns=2;s=OtOpcUa)
|
||||
[Object] testarea (NodeId: ns=3;s=g6-area-test)
|
||||
[Object] line-1 (NodeId: ns=3;s=g6-line-a)
|
||||
[Object] testmachine-015 (NodeId: ns=3;s=EQ-a6f600000000)
|
||||
[Variable] TestChangingInt (NodeId: ns=3;s=testarea/line-1/testmachine-015/TestChangingInt)
|
||||
[Variable] TestDouble (NodeId: ns=3;s=testarea/line-1/testmachine-015/TestDouble)
|
||||
[Variable] TestAlarm002 (NodeId: ns=3;s=testarea/line-1/testmachine-015/TestAlarm002)
|
||||
[Variable] TestAlarm002Condition (NodeId: ns=3;s=testarea/line-1/testmachine-015/TestAlarm002Condition) <- #896
|
||||
[Object] TestAlarm002Condition (NodeId: ns=2;s=galaxy-windev/dev/TestArea/TestMachine_015/TestAlarm002Condition)
|
||||
[Object] testarea3 (NodeId: ns=3;s=g6-area-test3)
|
||||
[Object] line-1 (NodeId: ns=3;s=g6-line-b)
|
||||
[Object] testmachine-036 (NodeId: ns=3;s=EQ-b6f600000000)
|
||||
[Variable] TestChangingInt (NodeId: ns=3;s=testarea3/line-1/testmachine-036/TestChangingInt)
|
||||
[Variable] TestHistoryValue (NodeId: ns=3;s=testarea3/line-1/testmachine-036/TestHistoryValue)
|
||||
[Object] galaxy-windev (NodeId: ns=2;s=galaxy-windev)
|
||||
[Object] dev (NodeId: ns=2;s=galaxy-windev/dev)
|
||||
[Object] TestArea (NodeId: ns=2;s=galaxy-windev/dev/TestArea)
|
||||
[Object] TestMachine_015 (NodeId: ns=2;s=galaxy-windev/dev/TestArea/TestMachine_015)
|
||||
[Variable] TestAlarm002 (…/TestMachine_015/TestAlarm002)
|
||||
[Object] TestAlarm002Condition (…/TestMachine_015/TestAlarm002Condition) (+ the full Part 9 child set: ActiveState, AckedState, Acknowledge, Confirm, ShelvingState, …)
|
||||
[Variable] TestChangingInt (…/TestMachine_015/TestChangingInt)
|
||||
[Variable] TestDouble (…/TestMachine_015/TestDouble)
|
||||
[Object] TestArea3 (NodeId: ns=2;s=galaxy-windev/dev/TestArea3)
|
||||
[Object] TestMachine_036 (NodeId: ns=2;s=galaxy-windev/dev/TestArea3/TestMachine_036)
|
||||
[Variable] TestChangingInt (…/TestMachine_036/TestChangingInt)
|
||||
[Variable] TestHistoryValue (…/TestMachine_036/TestHistoryValue)
|
||||
```
|
||||
|
||||
The raw subtree uses `ns=2;s=<RawPath>` and the UNS subtree uses `ns=3;s=<Area>/<Line>/<Equipment>/<Signal>`.
|
||||
The Part 9 condition appears under both the raw TagGroup and the referencing equipment folder (notifier fan-out).
|
||||
|
||||
## Step 2: read through both NodeIds (PASS)
|
||||
|
||||
| NodeId | Value | Status |
|
||||
|---|---|---|
|
||||
| raw `…/TestMachine_015/TestChangingInt` | 841 | Good |
|
||||
| raw `…/TestMachine_015/TestDouble` | 0 | Good |
|
||||
| raw `…/TestMachine_015/TestAlarm002` | False | Good |
|
||||
| raw `…/TestMachine_036/TestChangingInt` | 479 | Good |
|
||||
| raw `…/TestMachine_036/TestHistoryValue` | 0 | Good |
|
||||
| uns `testarea/line-1/testmachine-015/TestChangingInt` | 904 | Good |
|
||||
| uns `testarea/line-1/testmachine-015/TestDouble` | 0 | Good |
|
||||
| uns `testarea3/line-1/testmachine-036/TestChangingInt` | 330 | Good |
|
||||
| `Server.ServiceLevel` (i=2267) | 250 | Good |
|
||||
| uns `…/testmachine-015/TestAlarm002Condition` | (none) | **BadWaitingForInitialData**, permanently. #896 |
|
||||
|
||||
## Step 3: subscribe (PASS)
|
||||
|
||||
Raw `…/TestMachine_015/TestChangingInt`, 1000 ms, 10 s: 9 Good notifications, the first inside the first
|
||||
second (`49, 474, 899, 325, 750, 176, 601, 27, 452`). UNS equipment `EQ-b6f600000000` recursive: 2 variables,
|
||||
8 notifications in 8 s (`TestChangingInt` every second; `TestHistoryValue` initial value only, since the
|
||||
attribute is static). Summaries: `Ever went BAD during window: 0`, `No update received at all: 0`.
|
||||
|
||||
## Step 4: write and read back (PASS)
|
||||
|
||||
```
|
||||
anonymous write raw TestDouble = 42.125 -> Write failed: BadUserAccessDenied (negative control; anonymous has no WriteOperate)
|
||||
apikey g6gate write raw TestDouble = 42.125 -> Write successful
|
||||
read raw …/TestMachine_015/TestDouble -> 42.125 Good
|
||||
read uns testarea/line-1/testmachine-015/TestDouble -> 42.125 Good
|
||||
apikey g6gate write uns TestDouble = 17.5 -> Write successful
|
||||
read raw …/TestMachine_015/TestDouble -> 17.5 Good
|
||||
```
|
||||
|
||||
Independent readback through a separate gateway session (mxgw-dotnet `smoke --item TestMachine_015.TestDouble`):
|
||||
`OnDataChange doubleValue: 17.5, quality 192`. TestDouble was then written back to its original 0.
|
||||
|
||||
Before the key deploy, a write with the new key was refused (`AclGate: … key … is no longer enabled or has
|
||||
expired in generation 1`). That is expected: data-plane key usability follows the deployed generation, so a
|
||||
key inserted after a deploy needs a redeploy.
|
||||
|
||||
## Step 5: native alarm to Part 9, ack and clear (PASS)
|
||||
|
||||
The alarm was raised through OPC UA: `write raw …/TestMachine_015/TestAlarm002 = True` (key `g6gate`). The live
|
||||
`alarms --refresh` subscriber (key `g6gate`) saw:
|
||||
|
||||
```
|
||||
[2026-11-13T04:11:05.2390000Z] ALARM galaxy-windev/dev/TestArea/TestMachine_015/TestAlarm002Condition
|
||||
State: Active, Unacknowledged
|
||||
Severity: 750
|
||||
Retain: True
|
||||
EventId: 9503F17B7A8F82498FBCEDD868C4AD98
|
||||
```
|
||||
|
||||
```
|
||||
otopcua-cli ack -n "ns=2;s=…/TestAlarm002Condition" -e 9503F17B… -c "OtOpcUa #615 G6 gate" -U apikey -P <key>
|
||||
Acknowledge successful
|
||||
[2026-11-13T04:20:34.4620500Z] … State: Active, Acknowledged Retain: True
|
||||
```
|
||||
|
||||
Driver log:
|
||||
|
||||
```
|
||||
Galaxy driver g6gal: acknowledging Galaxy!TestArea.TestMachine_015.TestAlarm002 as Galaxy user Administrator (Fallback) for OPC UA principal g6gate (key 8e142bf2a2b70df8)
|
||||
DriverInstance g6gal: acknowledged native condition galaxy-windev/dev/TestArea/TestMachine_015/TestAlarm002Condition by g6gate
|
||||
```
|
||||
|
||||
Galaxy side, read through a separate gateway session after the ack:
|
||||
|
||||
| Attribute | Value |
|
||||
|---|---|
|
||||
| `TestMachine_015.TestAlarm002.Acked` | **True** (q 192) |
|
||||
| `TestMachine_015.TestAlarm002.AckMsg` | **`OtOpcUa #615 G6 gate [OPC UA: g6gate, key 8e142bf2a2b70df8]`** |
|
||||
| `TestMachine_015.TestAlarm002.InAlarm` | True |
|
||||
| gateway `QueryActiveAlarms` | `ALARM_CONDITION_STATE_ACTIVE_ACKED`, `operatorUser: Administrator`, the same comment |
|
||||
|
||||
This is the leg H6 could not get on vd03 (`galaxy-h6-ack-2026-11-11.md`): the ack reached Galaxy.
|
||||
|
||||
The alarm was cleared through the UNS NodeId: `write uns testarea/line-1/testmachine-015/TestAlarm002 = False`.
|
||||
Galaxy `TestAlarm002`/`.InAlarm` went False at once. The gateway feed's return-to-normal arrived between 40 s
|
||||
and 100 s later; the gateway `ReconcileIntervalSeconds` is 30. The condition then went
|
||||
`Inactive, Acknowledged`, `Retain: False`. The clear event's `Time` carried the earlier transition timestamp,
|
||||
not the return-to-normal time. That latency and timestamp are gateway-side behaviour, so they are recorded
|
||||
here but not filed against OtOpcUa.
|
||||
|
||||
**Severity (#894).** The gateway reported `severity 500` (AVEVA priority 500, High band 251–500), and the
|
||||
condition read **750**. The authored `alarm.severity` was 500 for TestAlarm002 and 700 for TestAlarm003; the
|
||||
live feed value overrode both. This is the #887 band mapping working live (750, as #894 expects), on another
|
||||
alarm and another gateway than #894's vd03 target.
|
||||
|
||||
## Step 6: picker-form native alarm (FAIL on master, PASS with #895)
|
||||
|
||||
`TestAlarm003` was authored as the AdminUI Galaxy picker would author it: `attributeRef "TestMachine_015.TestAlarm003"`
|
||||
plus the seeded `{"alarmType":"OffNormalAlarm","severity":700}`. It was raised by a supervisory write through a
|
||||
separate gateway session. A plain `Write` is refused `SecurityError 1008`, because `TestAlarm003` is `MxSecurityOperate`.
|
||||
|
||||
- **Master `00a5be26`:** the gateway feed reported `Galaxy!TestArea.TestMachine_015.TestAlarm003` as
|
||||
`ALARM_CONDITION_STATE_ACTIVE`. The OPC UA condition stayed `Inactive, Acknowledged`. Cause:
|
||||
`OnAlarmFeedTransition` mapped only the feed's full reference to the authored attributeRef. The
|
||||
`sourceObjectReference` the feed also sends (`TestMachine_015.TestAlarm003`) was ignored, so the transition got
|
||||
a ConditionId matching no condition. → **#895**.
|
||||
- **With the fix (`e113faf5`):** after a host restart on the same deployment, which the host restored from
|
||||
LocalDb on bootstrap, ConditionRefresh returned
|
||||
`TestAlarm003 State: Active, Unacknowledged Severity: 750 Retain: True`. The OPC UA `Acknowledge` dialled
|
||||
`Galaxy!TestArea.TestMachine_015.TestAlarm003` (log line as above), and the condition went `Active, Acknowledged`.
|
||||
Galaxy then read `.Acked = True` and `.AckMsg = "OtOpcUa #615 G6 gate picker-form (#895) [OPC UA: g6gate, key 8e142bf2a2b70df8]"`.
|
||||
After the supervisory clear the condition went `Inactive, Acknowledged`, `Retain: False`, and the gateway
|
||||
showed no active alarms.
|
||||
|
||||
## Not run: DeployWatcher on a Galaxy redeploy
|
||||
|
||||
This step was not run for two reasons:
|
||||
|
||||
- **No way to trigger a redeploy.** A Galaxy redeploy needs the ArchestrA IDE or a GRAccess script on windev.
|
||||
No GRAccess assembly was found in the ArchestrA Framework bin directory.
|
||||
- **Risk to another agent.** The test Galaxy was serving another agent's MXAccess captures, and redeploying
|
||||
its objects would have disturbed them.
|
||||
|
||||
Expected behaviour if it is run later: `docs/drivers/Galaxy.md` says the `IRediscoverable` / `DeployWatcher`
|
||||
path raises a "re-browse" prompt on `/hosts` and does **not** rebuild the address space (#518). The live check
|
||||
would be a `/hosts` chip, not a tree rebuild. `Repository.WatchDeployEvents=true` was set and the driver started
|
||||
cleanly with it.
|
||||
|
||||
## Defects
|
||||
|
||||
- **#895 (fixed here, `e113faf5`).** A native alarm authored by the AdminUI picker (`attributeRef tag.Attr`)
|
||||
never went active, and its ack would dial a reference the gateway refuses. Three unit tests were added to
|
||||
`GalaxyDriverRawPathKeyingTests`. The main one fails with the fix reverted, which was checked by reverting
|
||||
the fix and re-running it.
|
||||
- **#896 (open).** The UNS mirror of an alarm-bearing raw tag gets a value Variable that stays
|
||||
`BadWaitingForInitialData` forever, against the documented "condition instead of value variable" contract.
|
||||
It was not fixed here because the composer test fixtures assert that mirror.
|
||||
|
||||
## Cleanup
|
||||
|
||||
- **windev:** gateway key `g6-otopcua-e2e` revoked. No other key was touched. `MxAccessGw` was never stopped,
|
||||
restarted or reconfigured, and `/health/ready` returned 200 throughout. Temporary `.ps1` files in
|
||||
`C:\Users\Public` were deleted.
|
||||
- **Galaxy:** TestDouble restored to 0. TestAlarm002 and TestAlarm003 are both back to False, and the gateway
|
||||
shows no active alarms. Their raise/ack/clear rows stay in the Galaxy's own alarm history.
|
||||
- **Mac:** local Host stopped. Database `OtOpcUaG6` dropped from the rig SQL container. The docker-dev rig
|
||||
containers and their `OtOpcUa` DB were untouched. Scratch key files deleted.
|
||||
@@ -18,7 +18,7 @@ From `docs/v2/v2-release-readiness.md` §"Release-readiness exit criteria":
|
||||
| G3 | Release blockers closed | Audit | Already closed (code-path) |
|
||||
| G4 | Phase 5 driver complement shipped | Audit | Already closed |
|
||||
| G5 | Production deployment checklist signed off by Fleet Admin | Operator | No — separate doc, human signoff |
|
||||
| G6 | At least one end-to-end integration run against live Galaxy succeeds | Dev rig | No — requires AVEVA platform |
|
||||
| G6 | At least one end-to-end integration run against live Galaxy succeeds | Dev rig | ✅ **PASS 2026-11-12** (#615) — windev test Galaxy via mxaccessgw `447070a5`; see `docs/drivers/evidence/galaxy-g6-e2e-2026-11-12.md` |
|
||||
| G7 | FOCAS live-CNC wire-level smoke (#54) passes against a real FANUC control | Lab hardware | ✅ **RESOLVED 2026-06-25** *(truth-sweep note 2026-08-07)* — live-validated on a real 31i-B (`10.201.31.5`): version gate, timer, PMC range, servo-meter, alarms, probe all verified live; caveat: `cnc_rdparam` found unsupported on that control. See `2026-06-25-focas-pdu-v3-30i-b-support.md` |
|
||||
| G8 | OPC UA CTT / UA Compliance Test Tool passes against the live endpoint | Operator tool | No — requires CTT binary + live endpoint |
|
||||
| G9 | Non-transparent redundancy cutover validated with >= 1 production client | Lab | No — see `docs/plans/phase-6-3-redundancy-interop-plan.md` |
|
||||
|
||||
@@ -136,13 +136,14 @@ v2 GA requires all of the following:
|
||||
- [x] Release blockers listed above all closed.
|
||||
- [x] Phase 5 driver complement shipped (Galaxy, Modbus, S7, OpcUaClient, AbCip, AbLegacy, TwinCAT, FOCAS).
|
||||
- [ ] Production deployment checklist (separate doc) signed off by Fleet Admin.
|
||||
- [ ] At least one end-to-end integration run against the live Galaxy on the dev box succeeds.
|
||||
- [x] At least one end-to-end integration run against the live Galaxy on the dev box succeeds. **G6 PASS 2026-11-12** (#615): windev test Galaxy `DEV` through mxaccessgw `447070a5`; browse (raw + UNS), read, subscribe, write and readback, and native alarm → Part 9 → ack (confirmed in Galaxy) → clear. Evidence: `docs/drivers/evidence/galaxy-g6-e2e-2026-11-12.md`. Found #895 (fixed, `e113faf5`) and #896 (open). DeployWatcher not exercised (no headless Galaxy redeploy).
|
||||
- [ ] FOCAS live-CNC wire-level smoke (#54) runs clean against a real FANUC control.
|
||||
- [ ] OPC UA conformance test (CTT or UA Compliance Test Tool) passes against the live endpoint.
|
||||
- [ ] Non-transparent redundancy cutover validated with at least one production client (Ignition 8.3 recommended — see decision #85).
|
||||
|
||||
## Change log
|
||||
|
||||
- **2026-11-12** — **G6 (#615) PASS: end to end against a live Galaxy.** A local fused admin+driver Host (OtOpcUa `00a5be26`) ran the `GalaxyMxGateway` driver against the windev test Galaxy `DEV` (System Platform 2020, 129 deployed objects) through mxaccessgw `447070a5`. Every plan §G6 criterion was met through both the raw (`ns=2;s=<RawPath>`) and UNS (`ns=3;s=Area/Line/Equipment/Signal`) NodeIds: non-empty browse mirroring area → object, `Good` reads, subscribe notifications inside 1 s, no `BadTimeout`. The only `BadNoCommunication` was a planned driver respawn on redeploy. Extra steps also passed: write and readback (also verified through an independent gateway session; anonymous negative control `BadUserAccessDenied`), and a Galaxy alarm raised by an OPC UA write surfacing as a Part 9 condition. That condition was acked over OPC UA and the ack was confirmed in Galaxy (`.Acked=True`, `.AckMsg` = the comment), then cleared. Severity 750 for AVEVA priority 500 (#887 band; data point for #894). The live run found **#895**: a native alarm authored the AdminUI-picker way (`attributeRef tag.Attr`) never went active, because the feed reports `Galaxy!Area.tag.Attr`. Fixed in `e113faf5` and re-verified live. It also found **#896** (open): the UNS mirror of an alarm-bearing tag is a dead `BadWaitingForInitialData` variable. DeployWatcher on a Galaxy redeploy was not run: no IDE/GRAccess on windev, and the Galaxy was shared with another agent. Evidence: `docs/drivers/evidence/galaxy-g6-e2e-2026-11-12.md`.
|
||||
- **2026-05-26** — Gap-closeout pass. `OpcUaApplicationHost.PopulateServerArray` populates `Server.ServerArray` (NodeId `i=2254`) with self + `OpcUaApplicationHostOptions.PeerApplicationUris`, giving non-transparent peer URI visibility through the standard discovery surface. New `tests/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer.IntegrationTests/` IT project (`DualEndpointTests`) validates with two real `OpcUaApplicationHost` instances on loopback + a live OPCFoundation client `Session` read. CI `v2-ci.yml` `integration:` job converted to a matrix across `Host.IntegrationTests` + `OpcUaServer.IntegrationTests`. Per-role appsettings overlays shipped (`appsettings.admin.json` / `appsettings.driver.json` / `appsettings.admin-driver.json`) — `Program.cs:33-35` loads by alphabetical-joined role suffix. `FailoverScenarioTests` → `FailoverDuringDeployTests` rename. Stale empty `src/Server/{Server,Admin}` + `tests/Server/{Server.Tests,Admin.Tests,Admin.E2ETests}` directories deleted (no source, absent from `.slnx`).
|
||||
- **2026-04-24** — Phase 5 driver complement closed (task #120 CLOSED). AB CIP, AB Legacy, TwinCAT, FOCAS all shipped. FOCAS migration: retired the Tier-C split (`Driver.FOCAS.Host` + `Driver.FOCAS.Shared` + `FwlibNative` + shim DLL deleted) in favour of a pure-managed in-process `FocasWireClient` inlined into `Driver.FOCAS`; driver is now read-only against the CNC by design. Integration test matrix grew to cover Browse / Subscribe / IAlarmSource / Probe end-to-end.
|
||||
- **2026-04-23** — Phase 6.4 audit close-out. IdentificationFolderBuilder + OPC 40010 Identification folder verified against the shipped code.
|
||||
|
||||
Reference in New Issue
Block a user