# User-attribution investigation — pick-up notes **Started:** 2026-05-03 · **Last updated:** 2026-05-04 · **Status:** open ## Goal Get the Historian's `Events.User_Name` / `Events.User_Account` columns to reflect the **authenticated user** (`dohertj2`) on `Alarm.Acknowledged` rows when an ack is issued via `mxa write .AckMsg`. Anonymous writes (no `--username`) should remain NULL. ## What's been tried Each row's `Alarm.Acknowledged` `User_Name`/`User_Account` field is the result we cared about. | # | Galaxy security | Advise variant on ack | Write variant on ack | `auth_user_id` | `User_Name` | Notes | | --- | --- | --- | --- | --- | --- | --- | | 1 | `eNone` | `Advise` | `Write` | 1 | `DefaultUser` | Galaxy in Free-Access mode mapped every action onto `DefaultUser`. Even bad password / unknown user resolved to `userId=1`. | | 2 | `eNone` | `Advise` (explicit, after wiring `AdviseSupervisory` for anonymous) | `Write` | 1 | `DefaultUser` | Advise-variant routing landed but galaxy still mapped to `DefaultUser`. | | 3 | `eOSUserBased` (no role) | `Advise` | `Write` | 1 | NULL | Mode flip removed the `DefaultUser` over-attribution. `dohertj2` not yet attributed because user wasn't in any role. | | 4 | `eOSUserBased` + `ArchestraUsers` role | `Advise` | `Write` | 1 | NULL | After role assignment. `Auth` proven to validate (bad password throws `ArgumentException` — fixed in `MxSession.Authenticate` to surface as `auth_user_id=0`). Still no `User_Name`. | | 5 | same | `Advise` | `Write` (with `--client `) | 1 | NULL | `User_NodeName` flowed from `--client` (now `DESKTOP-6JL3KKO`); confirms `--client` is just a workstation tag, not a security primitive. | | 6 | same | `Advise` | **`WriteSecured`** (`--secured`, single-user) | 1 | NULL | `WriteSecured(currentUserId=verifierUserId=1, value)` succeeded with `MxCategoryOk` but did **not** change attribution. Conclusion: User_Name population is not gated on Write vs WriteSecured. | ## Current state of the code - `mxa write` accepts `--username/--domain/--password` → `AuthenticateUser` → userId for `Write` / `WriteSecured`. - `mxa write` accepts `--secured` (single-user Secured) and `--verifier-username/--verifier-domain/--verifier-password` (two-user Verified Write). - Advise variant routes by user: `Advise` when authenticated, `AdviseSupervisory` when anonymous. See [`docs/usage.md`](docs/usage.md#advise-variant--operator-vs-supervisory). - `MxSession.Authenticate` catches `ArgumentException` from the proxy and normalizes to `userId=0` → CLI emits clean `authentication-failed` envelope, exit 1. ## Current state of the galaxy (`ZB`) | Setting | Value | | --- | --- | | `MxSecurityModelType` | `eOSUserBased` | | `IGalaxyUsers.Count` | 0 (read-only collection; doesn't reflect runtime logins via MxAccess) | | `IGalaxyRoles.Count` | 0 (same) | | `IGalaxyGroups.Count` | 0 (same) | | Configured users (per IDE) | `DESKTOP-6JL3KKO\dohertj2` assigned to `ArchestraUsers` | | `DevPlatform`, `DevAppEngine` | **deployed before** the security mode change | ## Leading hypothesis (not yet tested) **The running aaEngine processes are still operating under the original `eNone` security context** because galaxy security is compiled into the platform/engine deployment. New auth rules don't take effect for already-running engines until they are redeployed (or `aaBootstrap` is restarted). This is consistent with: - `auth_user_id` returning `1` on every successful authentication regardless of mode/user — the engine's runtime still treats authenticated calls as the legacy default operator. - `User_Name=NULL` rather than `dohertj2` — the engine has no way to map our session's userId onto its (stale) user table. - `IGalaxyUsers` not populating from MxAccess logins — that's the design (population requires IDE-driven login activity), but it also means we can't verify enrollment from the CLI side. ## Next step — pick up here 1. **Undeploy + redeploy `DevPlatform` and below**, then rerun the alarm sequence. Driving this through `graccesscli`: ```powershell $CLI="c:/Users/dohertj2/Desktop/wwtools/graccesscli/src/ZB.MOM.WW.GRAccess.Cli/bin/x86/Debug/net48/ZB.MOM.WW.GRAccess.Cli.exe" $HOST=$env:COMPUTERNAME # Top-down undeploy (objects -> areas -> engine -> platform) & $CLI objects undeploy --galaxy ZB --node $HOST --names TestMachine_001 # cascades, see graccesscli docs & $CLI objects undeploy --galaxy ZB --node $HOST --names DevAppEngine & $CLI objects undeploy --galaxy ZB --node $HOST --names DevPlatform # Bottom-up redeploy & $CLI objects deploy --galaxy ZB --node $HOST --names DevPlatform & $CLI objects deploy --galaxy ZB --node $HOST --names DevAppEngine & $CLI objects deploy --galaxy ZB --node $HOST --names TestArea & $CLI objects deploy --galaxy ZB --node $HOST --names TestMachine_001 ``` *(Confirm exact command surface via `graccesscli objects deploy --help` before running — undeploying a running engine drops live-data feeds.)* 2. **Re-run the alarm-attribution test** (the same sequence used in run #6): ```powershell $MXA="c:/Users/dohertj2/Desktop/wwtools/mxaccesscli/src/MxAccess.Cli/bin/x86/Release/net48/mxa.exe" $HOST=$env:COMPUTERNAME & $MXA write TestMachine_001.TestAlarm002 true --type bool --client $HOST -t 10 & $MXA write TestMachine_001.TestAlarm002.AckMsg "post-redeploy ack" ` --username dohertj2 --domain $HOST --password Sonamu89 ` --secured --client $HOST -t 12 --llm-json & $MXA write TestMachine_001.TestAlarm002 false --type bool --client $HOST -t 10 ``` 3. **Query the Historian** (template): ```sql DECLARE @start datetime2 = DATEADD(minute, -3, GETDATE()); DECLARE @end datetime2 = GETDATE(); SELECT EventTime, Type, Alarm_State, Comment, User_Name, User_Account, User_NodeName, LEFT(CAST(Alarm_ID AS varchar(40)), 8) AS id_short FROM Events WHERE Source_ConditionVariable = 'TestMachine_001.TestAlarm002' AND EventTime BETWEEN @start AND @end ORDER BY EventTime ASC; ``` 4. **Compare** — does `User_Name = dohertj2` (or `User_Account = DESKTOP-6JL3KKO\dohertj2`) on the `Alarm.Acknowledged` row? ## Fallbacks if redeploy doesn't change anything - **Restart `aaBootstrap`.** Heavier-handed but forces the entire ArchestrA stack to reload galaxy config. (Will interrupt the test alarm cycle on `TestAlarm001`.) - **Try a Verified Write** (two-person), supplying both `--username dohertj2` and `--verifier-username dohertj2` (or two different OS users). The audit subsystem may only fully attribute Verified Writes. - **Check the attribute's security classification.** `TestAlarm002.AckMsg` may be `Free Access`, in which case the engine doesn't need to verify the user and may discard the identity. Set the security classification to `Operate` (or stricter) on the template, redeploy, retest. Per `aot/dev-guide/appendix-e-security-classifications.md`, only stricter classifications enforce user identity in the audit. - **Sample a different attribute** that we know other tools (Object Viewer, InTouch) record `User_Name` on. If those clients also produce NULL, the issue is system-wide (galaxy still misconfigured); if they record a user, the issue is specific to the MxAccess Write path. ## Files touched in this investigation - [`src/MxAccess.Cli/Mx/MxSession.cs`](src/MxAccess.Cli/Mx/MxSession.cs) — `Authenticate` catches `ArgumentException`. - [`src/MxAccess.Cli/Mx/MxItem.cs`](src/MxAccess.Cli/Mx/MxItem.cs) — `WriteSecured` method. - [`src/MxAccess.Cli/Commands/WriteCommand.cs`](src/MxAccess.Cli/Commands/WriteCommand.cs) — `--username/--domain/--password`, advise routing, `--secured/--verifier-*`. - [`docs/usage.md`](docs/usage.md) — *Authentication*, *Advise variant*, *userId is session-scoped* sections. ## Galaxy state Alarm `TestMachine_001.TestAlarm002` is **cleared** (`ACK_RTN`) at the close of run #6. No lingering active alarms. Other test attributes restored to their original values.