docs(probe): ack-leg third attempt — unavailable by configuration, GUID leg still assumed

This commit is contained in:
Joseph Doherty
2026-08-18 06:56:58 -04:00
parent f57a6ae5ff
commit d1ae43d0d3
+141 -2
View File
@@ -24,13 +24,13 @@ the blocker rather than rediscovering it.
|---|---|
| GUID stable across polls and `ALM → RTN` | Answered — yes (2026-05-01 capture in `AlarmClientDiscovery.md`, reconfirmed 2026-08-18) |
| GUID stable across clear-then-re-raise | Answered 2026-08-18 — **no**, a re-raise mints a new GUID |
| GUID stable across `UNACK → ACK` | **Open** — the rig cannot be driven into an acknowledged state at all |
| GUID stable across `UNACK → ACK` | **Open** — the rig cannot be driven into an acknowledged state; 2026-08-18 (third attempt) identifies the reason as the test attribute's `MxSecurityOperate` classification, which no non-interactive ack surface on the rig can satisfy |
| `COUNT` = total active vs records-in-reply under a capped fetch | Answered 2026-08-18 — **records in the reply** |
The 2026-08-17 run below is kept because it is the record of the wrong-verb blocker. The
2026-08-18 run cleared that blocker with `AuthenticateUser` + `WriteSecured` and answered
two of the three questions; the acknowledge leg is now blocked on something narrower and
different, described in "Second attempt".
different, described in "Second attempt" and diagnosed in "Third attempt".
## First attempt (2026-08-17): plain `Write`
@@ -264,8 +264,147 @@ MXAccess-side entry point and the wnwrap-side entry point is inert.
spurious Clear plus a spurious Raise, which is the same shape the now-observed re-raise
behaviour produces and which `ComputeTransitions` already handles as two instances.
## Third attempt (2026-08-18): why the acknowledge is refused
The second attempt left two candidate explanations for `AlarmAckByName` returning `rc=0`
and changing nothing: the rig enforces an acknowledgement security requirement the wnwrap
consumer cannot meet, or wnwrap's ack is simply broken here. This attempt was read-only —
no writes, no alarms raised, no configuration touched — and settles the first question:
the requirement is **enforced**, and the alarm attribute's security classification is what
enforces it.
### Method
Read-only inspection of the `ZB` Galaxy Repository over `sqlcmd -S localhost -d ZB -E`,
plus the already-built `mxa` CLI (`C:\Users\dohertj2\Desktop\wwtools\mxaccesscli\src\MxAccess.Cli\bin\x86\Release\net48\mxa.exe`)
for runtime reads. Nothing was written and nothing was installed. The `lmxopcua\gr` schema
notes referenced elsewhere in this repo are **not present on this box** (`Test-Path` is
`False`), so the schema was located by querying `sys.tables` / `sys.columns` directly.
### The test attribute is classified `MxSecurityOperate`
UDA security classification lives in `dynamic_attribute.security_classification`, keyed by
`gobject_id` — for the `$TestMachine` template that is `1055`:
```
attribute_name | security_classification | mx_attribute_category
ProtectedValue | 2 | 10
ProtectedValue1 | 3 | 10
TestAlarm001 | 1 | 10
TestAlarm002 | 1 | 10
TestAlarm003 | 1 | 10
TestChangingInt | 1 | 10
```
The enum is pinned by two independent sources rather than assumed. `ProtectedValue` and
`ProtectedValue1` are the mxaccess analysis project's documented *secured-write* and
*verified-write* fixtures (`C:\Users\dohertj2\Desktop\mxaccess\docs\galaxy-test-fixtures.md`),
and `docs/NMX-COM-Contracts.md` in the same project records "Galaxy security classification
(`2` for `SecuredWrite`, `3` for VerifiedWrite)" — so `2` and `3` land on exactly the two
attributes that are supposed to carry them. `galaxy-test-fixtures.md` also records the
provisioning verb used for every UDA in that inventory: `--security MxSecurityOperate`.
`TestAlarm001` therefore reads `1` = **`MxSecurityOperate`**.
That is the missing piece from 2026-08-17. `Operate` is not free access: it requires an
authenticated galaxy identity holding Operate permission on the object's security group.
An unauthenticated `Write` is refused with `SecurityError` `1008`, and the same write after
`AuthenticateUser` succeeds — which is precisely the pair of results both prior attempts
recorded. Galaxy security is live on this rig, not disabled.
`wwAlarmConsumerClass.AlarmAckByName` carries an operator *name* string and a comment. It
carries no authenticated user id, no credential, and no token — there is no parameter on
either the 6-arg or the 8-arg overload that could convey one. A consumer calling it cannot
satisfy an `Operate` classification, and `rc=0` followed by no state change is what an ack
dropped downstream of an accepted call looks like.
The one step this stops short of is a direct experiment: the rig's security configuration
is out of scope for a read-only probe, so "an `Operate` alarm cannot be acknowledged
without an authenticated identity" is inferred from the classification plus the observed
`1008`/`rc=0` pattern rather than observed by relaxing the classification and watching the
ack start working. The human step below is exactly that experiment.
### There is no writeable `.Ack` attribute — the second attempt targeted the only one there is
A plausible reading of the second attempt was that it wrote the wrong sub-attribute:
ArchestrA alarm extensions are commonly described as exposing a writeable `.Ack` alongside
the read-only `.Acked`. On this galaxy they do not. Every `Ack`-named attribute the alarm
primitives define:
```
attribute_name | security_classification | mx_attribute_category
Acked | -1 | 2
AckMsg | 0 | 6
Bad.Acked | -1 | 2
Bad.AckMsg | 0 | 6
TimeAlarmAcked | -1 | 2
AlarmAckCnt | -1 | 2
AlarmAckErrorsCnt | -1 | 2
AlarmMostUrgentAcked | -1 | 2
AlarmUnAckedCnt | -1 | 2
```
No `Ack`. `Acked` carries `security_classification = -1` — no classification at all, which
is what a non-writeable attribute carries, as distinct from the `1` on the writeable
`TestAlarm001`. That is the configuration-side counterpart of the `detail=1007`
operational refusal the second attempt got when it wrote `.Acked`: not a permission
refusal, but an attribute that has no write path to refuse. The second attempt had already
found the only MXAccess-side candidate, and it is read-only by definition.
### No non-interactive acknowledge surface is installed
The `wwtools` collection on the box (`aalogcli`, `aot`, `graccesscli`, `grdb`, `histdb`,
`mbproxy`, `mxaccesscli`, `secrets`) is the most likely home for a scriptable ack. There
isn't one. `mxa --help` lists `diag`, `info`, `read`, `read-batch`, `subscribe`,
`subscribe-batch`, `write`, `write-batch` — a tag data-plane only, with no alarm surface.
`graccesscli` is a Galaxy Repository configuration tool (`object uda add`, `instance
deploy`), which acts at configure/deploy time and not on live alarm state. Reading the
galaxy's own authentication mode at runtime is also unavailable: `Galaxy.AuthenticationMode`
does not resolve over MXAccess (`Category=4 Detail=6`), and the value is not in
`dynamic_attribute` — only the attribute *definition* names `AuthenticationMode` and
`_AuthenticationModeEnum` exist in `attribute_definition`.
So every remaining acknowledge surface on this rig is interactive: the IDE's alarm client,
InTouch, or an ArchestrA graphic bound to the alarm. Driving those is out of scope.
### Status of the acknowledge leg
**Unavailable by configuration, and the GUID question stays assumed.** The two are separate
statements and both matter:
- The wnwrap ack path is unavailable on this rig as configured, for an identified reason
rather than an unknown one. This is a real finding for the gateway: `AcknowledgeByName`
will behave the same way — accepted, inert — on any customer galaxy whose alarmed
attributes carry a non-free-access security classification. It is worth noting in the
alarm client's documentation that a silent `rc=0` is not proof of acknowledgement.
- Whether wnwrap re-mints the record GUID on `UNACK_ALM → ACK_ALM` is still unobserved, and
after three attempts it stays assumed. As the second attempt already noted, this remains
a documentation gap rather than a correctness one: a re-minted GUID produces a spurious
Clear plus a spurious Raise, the same shape the observed re-raise behaviour produces, and
`ComputeTransitions` already handles that correctly as two instances.
#### What a human would need to do
Either of these answers it; the second is cheaper and also confirms or refutes the
classification hypothesis above, which the read-only probe could only infer.
1. Acknowledge `TestMachine_001.TestAlarm001` from an interactive System Platform client
(IDE alarm client, InTouch, or an ArchestrA graphic) while a wnwrap probe polls
`GetXmlCurrentAlarms2` against `\\DESKTOP-6JL3KKO\Galaxy!TestArea`, and record whether
`STATE` reaches `ACK_ALM` and whether `GUID` survives the transition. Raise the alarm
first with `AuthenticateUser` + `WriteSecured` as the second attempt did.
2. Reclassify `TestAlarm001` on the `$TestMachine` template to free access and redeploy —
`graccesscli object uda ... --security MxSecurityFreeAccess` against `$TestMachine`,
then `instance deploy TestMachine_001` — and re-run the second attempt's ack probe
unchanged. If `AlarmAckByName` then moves `STATE` to `ACK_ALM`, the classification is
confirmed as the blocker and the GUID question is answered in the same run. Restore the
classification to `MxSecurityOperate` afterwards, since the secured-write fixtures in
`WorkerLiveMxAccessSmokeTests` depend on the alarm UDAs being secured.
### Rig state left behind
The third attempt changed nothing. `TestMachine_001.TestAlarm001` and its `.Acked` subtag
both read `false` at the end of the session, matching the state the second attempt left.
The three `TestMachine_00{1,2,3}.TestAlarm001` UDAs are back to `false` and their
`.InAlarm` subtags read `false`, but each leaves a `UNACK_RTN` record in the wnwrap
snapshot, since nothing can acknowledge them away. `SnapshotActiveAlarms` counts only