From 1998ee3e20cd88dbf460fb8bfed24c308586e0b1 Mon Sep 17 00:00:00 2001 From: Joseph Doherty Date: Mon, 19 Oct 2026 19:45:39 -0400 Subject: [PATCH] docs: address Task 12 review (supersede stale ack steps, outcome rows) Claude-Session: https://claude.ai/code/session_016J6isfcgqy4SB2RK3ZvFvT --- docs/AlarmClientDiscovery.md | 9 ++++++--- docs/AlarmProbeFindings.md | 11 ++++++----- docs/Authorization.md | 2 +- docs/Grpc.md | 2 +- 4 files changed, 14 insertions(+), 10 deletions(-) diff --git a/docs/AlarmClientDiscovery.md b/docs/AlarmClientDiscovery.md index 51090de..ad40a4b 100644 --- a/docs/AlarmClientDiscovery.md +++ b/docs/AlarmClientDiscovery.md @@ -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: `\\\Galaxy!` (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 diff --git a/docs/AlarmProbeFindings.md b/docs/AlarmProbeFindings.md index 6ef466d..f7d9f59 100644 --- a/docs/AlarmProbeFindings.md +++ b/docs/AlarmProbeFindings.md @@ -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. diff --git a/docs/Authorization.md b/docs/Authorization.md index 21e60e7..7d02679 100644 --- a/docs/Authorization.md +++ b/docs/Authorization.md @@ -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: diff --git a/docs/Grpc.md b/docs/Grpc.md index 11a2edd..93333bd 100644 --- a/docs/Grpc.md +++ b/docs/Grpc.md @@ -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).