mxaccesscli: route write through Advise vs AdviseSupervisory by user
WriteCommand now picks the LMXProxyServer advise variant based on
whether credentials were supplied:
--username given -> Advise (operator action; the write
is attributed to the
authenticated Galaxy user
in the alarm/event audit
trail)
no --username -> AdviseSupervisory (supervisory action; the
write is attributed to the
hosting client itself, no
Galaxy user claimed)
MxItem grows AdviseSupervisory() alongside Advise() and shares the
same UnAdvise / RemoveItem teardown.
Verified live with the trigger / ack-as-dohertj2 / clear sequence on
TestMachine_001.TestAlarm002. The Set (anonymous, supervisory) and
Clear (anonymous, supervisory) rows pair with the Acknowledged row
(authenticated, Advise) under one Alarm_ID. On this development
galaxy every action still maps to User_Name=DefaultUser regardless
of advise variant — that's a galaxy-security configuration trait,
not a CLI bug. The routing is in place and will differentiate
correctly on a strict galaxy with real user records.
docs/usage.md gains an "Advise variant" section explaining the rule
and the expected User_Name population on strict vs permissive
galaxies.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -135,6 +135,22 @@ The human output appends `(as <verify-user>, 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 = <galaxy user>`, `User_Account = <galaxy domain>\<user>`.
|
||||
- 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 <N>` to subsequent `write` invocations. This skips the per-call auth round-trip.
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user