docs: address Task 12 review (supersede stale ack steps, outcome rows)
ci / nightly-windev (push) Has been skipped
ci / java (push) Successful in 2m10s
ci / windows-x86 (push) Successful in 2m11s
ci / portable (push) Failing after 2m18s

Claude-Session: https://claude.ai/code/session_016J6isfcgqy4SB2RK3ZvFvT
This commit is contained in:
Joseph Doherty
2026-10-19 19:45:39 -04:00
parent 027cc7393d
commit 1998ee3e20
4 changed files with 14 additions and 10 deletions
+6 -3
View File
@@ -661,7 +661,9 @@ alarm-consumer surface unblocks A.2 fully. Outline:
events for added/changed/removed records.
- `AlarmAckByGUID(VBGUID, comment, oprName, node, domain,
fullName)` for client-driven acknowledgements (matches
PR A.5's `AlarmAckCommand` payload).
PR A.5's `AlarmAckCommand` payload). *Ack step superseded
2026-10-19: acks write `AckMsg` as the authenticated operator
(see "Acknowledge in subtag mode" and `AlarmProbeFindings.md`).*
- Lifecycle teardown: `DeregisterConsumer` +
`UninitializeConsumer` + `Marshal.FinalReleaseComObject`.
3. **Conversion layer:** map XML record fields to
@@ -702,8 +704,9 @@ alarm-consumer surface unblocks A.2 fully. Outline:
`bRetainHiddenAlarms` parameter at all.
- Subscription expression format: `\\<machine>\Galaxy!<area>`
(literal `Galaxy` provider) for both libraries.
- Native ack: `AlarmAckByGUID(VBGUID guid, comment, oprName,
node, domain, fullName)` on the v2 surface; ID 5-arg
- Native ack surface (not used by the gateway; superseded 2026-10-19 by
the authenticated `AckMsg` write): `AlarmAckByGUID(VBGUID guid,
comment, oprName, node, domain, fullName)` on the v2 surface; ID 5-arg
variant on the legacy `IwwAlarmConsumer` interface.
These findings retire the open follow-up probes from the
+6 -5
View File
@@ -24,14 +24,15 @@ 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; 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 |
| GUID stable across `UNACK → ACK` | Blocker resolved 2026-10-19 — the rig is driven to `ACK_ALM` by an `AckMsg` write, and the `MxSecurityOperate` classification was not the cause (see "Fourth attempt"); GUID survival across that transition has not been recorded yet |
| Acknowledge an alarm headless | Answered 2026-10-19 — **an `AckMsg` write as the authenticated operator**; every alarm-client ack is inert (see "Fourth attempt") |
| `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" and diagnosed in "Third attempt".
different, described in "Second attempt" and diagnosed in "Third attempt"
(resolved in the Fourth attempt, 2026-10-19).
## First attempt (2026-08-17): plain `Write`
@@ -450,8 +451,8 @@ snapshot parser reads them for Acknowledge transitions. An unauthenticated write
therefore acknowledges but leaves no operator in the Historian, which is why the
gateway requires `operator_user` and authenticates it.
Another actor's `AckMsg` writes on the same day (authenticated as `dohertj2`)
logged `Operator=dohertj2`, `Domain="."` and that machine's name as the node,
Another actor's `AckMsg` writes on the same day (authenticated as another
operator account) logged that account as `Operator`, `Domain="."` and that machine's name as the node,
which matches the client-name rule. By this date unauthenticated writes to
`MxSecurityOperate` attributes were also accepted Galaxy-wide on the rig, where
August refused them with 1008.
@@ -466,7 +467,7 @@ nor the Historian:
- wnwrap `IwwAlarmConsumer2` on a sole consumer with `SetXmlAlarmQuery` applied
(list populated, `GetAlarm(0)` returns the row): `AlarmAck` with ids
0/1/2/3/-1/65536, and `Select(0)` + `AlarmAckSelected`, with operators
`TestOperator`, `Administrator` and `dohertj2`. `SelectByGUID` is `E_NOTIMPL`.
`TestOperator`, `Administrator` and a third account. `SelectByGUID` is `E_NOTIMPL`.
- The same calls with `TestAlarm001` temporarily reclassified to
`MxSecurityFreeAccess` (template and instance, restored afterwards), so the
security classification is not the cause.
+1 -1
View File
@@ -125,7 +125,7 @@ public string ResolveRequiredScope(object request)
}
```
The `_ => GatewayScopes.Admin` fallback is intentional: any future request type that the resolver does not recognize fails closed, requiring the strongest scope until the resolver is updated. `AcknowledgeAlarm` is treated as a write — it mutates alarm state, mirroring `MxCommandKind.Write*` — and stays `invoke:write` rather than `invoke:secure` because the request carries the operator's own Galaxy credential, which the worker authenticates before the `AckMsg` write, so the credential, not the API-key scope, authorizes the identity the Historian records — and `StreamAlarms` and `QueryActiveAlarms` share the alarm/event surface with `StreamEvents` and `MxCommandKind.DrainEvents`, so they carry `events:read` (the active-alarm snapshot is the same data reachable through the event surface). All three alarm RPCs are session-less: the scope check is the only authorization gate, since there is no per-session ownership to enforce.
The `_ => GatewayScopes.Admin` fallback is intentional: any future request type that the resolver does not recognize fails closed, requiring the strongest scope until the resolver is updated. `AcknowledgeAlarm` is treated as a write — it mutates alarm state, mirroring `MxCommandKind.Write*`. It stays `invoke:write` rather than `invoke:secure` because the request carries the operator's own Galaxy credential, which the worker authenticates before the `AckMsg` write; the credential, not the API-key scope, authorizes the identity the Historian records. `StreamAlarms` and `QueryActiveAlarms` share the alarm/event surface with `StreamEvents` and `MxCommandKind.DrainEvents`, so they carry `events:read` (the active-alarm snapshot is the same data reachable through the event surface). All three alarm RPCs are session-less: the scope check is the only authorization gate, since there is no per-session ownership to enforce.
`MxCommandRequest` is special because it multiplexes many MxAccess operations through a single RPC. The resolver inspects the embedded `MxCommandKind` so each operation gets its own scope:
+1 -1
View File
@@ -111,7 +111,7 @@ The worker calls `AuthenticateUser` as the operator, then writes `operator_comme
| Write applied, or sent but no `OnWriteComplete` within the wait (unconfirmed, with a diagnostic) | `OK` | `0` |
| Unknown alarm, or no ack target (non-Galaxy provider, no ack-comment subtag) | `INVALID_REQUEST` | `-1` |
| `AuthenticateUser` failed | `MXACCESS_FAILURE` | the `AuthenticateUser` HRESULT |
| Write rejected (`OnWriteComplete` non-OK) | `MXACCESS_FAILURE` | the status `detail` |
| Write rejected (`OnWriteComplete` non-OK) | `MXACCESS_FAILURE` | the `detail` of the `OnWriteComplete` `MXSTATUS_PROXY` |
`reply.Statuses` carries the `OnWriteComplete` `MxStatusProxy` rows when they were observed. Because an unconfirmed write also returns `OK`, a client that must be certain should watch for the resulting transition on `StreamAlarms`. `AcknowledgeAlarm` stays under the `invoke:write` scope; see [Authorization](./Authorization.md).