diff --git a/mxaccesscli/docs/usage.md b/mxaccesscli/docs/usage.md index f4b81b0..689b84d 100644 --- a/mxaccesscli/docs/usage.md +++ b/mxaccesscli/docs/usage.md @@ -135,6 +135,22 @@ The human output appends `(as , userId=N)` to the success line so t > ⚠️ Verified behavior on the test galaxy used during development: `AuthenticateUser` returned `userId=1` for both the correct password and intentionally bad credentials (incl. an unknown username). This is consistent with a galaxy configured in `Free Access` mode where security checks are effectively disabled — the CLI's auth path is wired correctly, the galaxy just isn't strict. To exercise real authentication, target a galaxy with `galaxyAuthenticationMode` enabled and attribute-level security classifications above `Free Access`. +### Advise variant — operator vs supervisory + +`write` picks how it subscribes to the destination attribute (the briefly-active subscription used for type resolution before the Write call) based on whether you supplied credentials: + +| `--username` supplied? | Advise variant used | Audit-trail intent | +| --- | --- | --- | +| Yes | `LMXProxyServer.Advise` | Operator action — attribute the Write to the authenticated Galaxy user. | +| No (anonymous) | `LMXProxyServer.AdviseSupervisory` | Supervisory action — attribute the Write to the hosting client (no Galaxy user claimed). | + +This affects how System Platform records the action in the alarm/event subsystem and the Historian's `Events` table. On a strict galaxy with `galaxyAuthenticationMode` and real user records: + +- Authenticated + `Advise` → `User_Name = `, `User_Account = \`. +- Anonymous + `AdviseSupervisory` → `User_Name` typically NULL or the supervisory client identity. + +On a permissive galaxy (the development config used here), every action maps to `DefaultUser` regardless of advise variant — the mechanism is wired correctly but can't be differentiated until galaxy security is configured with real users. See [`Authentication`](#authentication) above. + ### Reusing an already-resolved `userId` `AuthenticateUser` may be expensive (involves SQL Server lookup + Windows cred check). For batch scripts that issue many writes, call `AuthenticateUser` once via a manual call, capture the `userId`, then pass it directly via `--user-id ` to subsequent `write` invocations. This skips the per-call auth round-trip. diff --git a/mxaccesscli/src/MxAccess.Cli/Commands/WriteCommand.cs b/mxaccesscli/src/MxAccess.Cli/Commands/WriteCommand.cs index 72cbe48..38a82f1 100644 --- a/mxaccesscli/src/MxAccess.Cli/Commands/WriteCommand.cs +++ b/mxaccesscli/src/MxAccess.Cli/Commands/WriteCommand.cs @@ -122,11 +122,21 @@ namespace MxAccess.Cli.Commands // within the expected range" because the proxy doesn't yet // know the destination type. // + // Pick the advise variant based on whether a user was supplied: + // - --username given → Advise (operator action, attribute + // the write to the authenticated Galaxy user in the audit + // trail). + // - anonymous → AdviseSupervisory (the write is on + // behalf of the hosting client itself; the audit trail + // records it as a supervisory action without trying to + // attribute it to a specific user). + // // Caveat: a bare-array reference (no brackets) will return // MxCategoryCommunicationError, Detail=1003 here — same as on // a `read` of that form. Tag the user-facing error so the // failure mode is recognizable. - item.Advise(); + if (verifyUser != null) item.Advise(); + else item.AdviseSupervisory(); var resolveTimeout = TimeSpan.FromSeconds(TimeoutSeconds); if (!session.WaitForUpdate( u => u.Kind == MxUpdateKind.DataChange && u.ItemHandle == item.Handle, diff --git a/mxaccesscli/src/MxAccess.Cli/Mx/MxItem.cs b/mxaccesscli/src/MxAccess.Cli/Mx/MxItem.cs index ce9ac7d..214a598 100644 --- a/mxaccesscli/src/MxAccess.Cli/Mx/MxItem.cs +++ b/mxaccesscli/src/MxAccess.Cli/Mx/MxItem.cs @@ -32,6 +32,18 @@ namespace MxAccess.Cli.Mx _advised = true; } + /// Subscribe in supervisory mode. Use this for actions that should + /// be attributed to the hosting client (rather than to a Galaxy user) + /// in the alarm/event audit trail — e.g. anonymous bulk operators, + /// integration scripts, or automation acting on its own authority. + /// Pairs with the same UnAdvise / RemoveItem teardown as Advise(). + public void AdviseSupervisory() + { + if (_advised) return; + _proxy.AdviseSupervisory(_hServer, Handle); + _advised = true; + } + public void UnAdvise() { if (!_advised) return;