# Requirements — fix mutation-path COM lifecycle Authored 2026-04-29; updated 2026-04-30 after retest with the typelib fix in place. The original `TYPE_E_LIBNOTREGISTERED` error has been **resolved** — `objects checkout` now succeeds. A second mutation-path defect has been uncovered in the same retest. This doc covers both; the typelib regression is preserved as historical context (✅ shipped) and the new defect is the active blocker. ## ✅ Already fixed (2026-04-30) | Command | Mode | Status | |---|---|---| | `objects checkout` | session + one-shot | ✅ `CheckOut: True` | | `objects checkin` | session + one-shot | ✅ `CheckIn: True` | | `object uda add` (first call) | session + one-shot | ✅ `Add UDA: OK` (but see active defect below) | The publisher-policy + typed-call fix used for `object snapshot` is now applied to the mutation surface. Thank you. ## 🔴 Active defect — `object uda add` doesn't `Save` + `CheckIn` ### Symptom After a successful `object uda add`, the template ends up in a phantom checked-out state that persists across CLI processes and across helper-process kills: ```text $cli session start --node localhost --galaxy ZB $cli template derive --galaxy ZB --name '$UserDefined' --new-name '$X' --confirm --confirm-target '$UserDefined' # CreateTemplate: OK $cli objects checkout --galaxy ZB --type template --name '$X' --confirm --confirm-target '$X' # CheckOut: True $cli object uda add --galaxy ZB --type template --name '$X' --uda A --data-type MxFloat --category MxCategoryWriteable_USC --security MxSecurityOperate --confirm --confirm-target '$X' # Add UDA: OK ← lies; nothing persists $cli object uda add --galaxy ZB --type template --name '$X' --uda B --data-type MxFloat --category MxCategoryWriteable_USC --security MxSecurityTune --confirm --confirm-target '$X' # Error: Add UDA failed: ID=cmdObjectIsCheckedOutToSomeoneElse (302) ``` ### Two consequences 1. **The "Add UDA: OK" return is a lie.** Subsequent `object snapshot --llm-json` on the same template shows `"AttributeValues": []` and `"CheckoutStatus": "notCheckedOut"`. The first AddUDA's mutation never persisted in the export-package fallback the snapshot reads from. 2. **The phantom checkout survives process death.** Killing all `GRAccessApp.exe` helpers (12+ accumulated over an hour of testing) does not release the lock. `object snapshot` still says `notCheckedOut` while AddUDA still says `cmdObjectIsCheckedOutToSomeoneElse`. The two views are inconsistent — likely the GR repo's checkout table holds a row that the AddUDA path checks but the snapshot path doesn't. ### Root cause hypothesis `GRAccessCommandDispatcher.cs:507-515` (the `case "add":` arm of `ExecuteUda`) calls `obj.AddUDA(...)` directly: ```csharp case "add": obj.AddUDA( Arg(args, "uda"), EnumValue(Arg(args, "data-type")), UdaCategory(Arg(args, "category")), EnumValue(Arg(args, "security")), BoolArg(args, "is-array"), IntArg(args, "array-count", 0)); return CommandSummary(obj, "Add UDA"); ``` There is no `obj.Save()` and no implicit checkin. The GRAccess mutation lifecycle is: ``` CheckOut → AddUDA / DeleteUDA / etc. → Save → CheckIn ``` Skipping `Save` leaves the change in the helper process's COM-memory only — when that process dies, the change vanishes. Skipping `CheckIn` keeps the GR repo's checkout row alive past the process death, which is what blocks every subsequent mutation. The same defect almost certainly affects: - `object uda delete`, `object uda rename`, `object uda update` - `object extension add`, `object extension delete`, `object extension rename` - `object attribute set`, `object attribute security`, `object attribute lock`, `object attribute buffer` - `object scripts set` - Anything else under `ExecuteUda` / `ExecuteAttribute` / `ExecuteExtension` / `ExecuteScript` that mutates without an explicit Save+CheckIn. ### Recommended fix Two options, in order of preference: #### Option A — wrap each mutation in atomic CheckOut + Save + CheckIn The dispatcher takes responsibility for the lifecycle: ```csharp case "add": obj.CheckOut(); try { obj.AddUDA( Arg(args, "uda"), EnumValue(Arg(args, "data-type")), UdaCategory(Arg(args, "category")), EnumValue(Arg(args, "security")), BoolArg(args, "is-array"), IntArg(args, "array-count", 0)); obj.Save(); obj.CheckIn(string.Empty); } catch { try { obj.UndoCheckOut(); } catch { /* best effort */ } throw; } return CommandSummary(obj, "Add UDA"); ``` This makes each `object uda add` self-contained — no `objects checkout` required from the caller. Mirrors the GUI's Edit→Save→Check-in flow. **Downside:** breaks the "do many edits, then one CheckIn" batch pattern that the existing `objects checkout` + `objects checkin` commands suggest is supported. #### Option B — keep the batch model, add Save only The dispatcher keeps the explicit-CheckOut-and-CheckIn workflow but calls `Save()` after each mutation so the change is persisted even within an open checkout: ```csharp case "add": obj.AddUDA(...); obj.Save(); return CommandSummary(obj, "Add UDA"); ``` Caller is still expected to do `objects checkout` once, run all the mutations, then `objects checkin`. The phantom-checkout symptom goes away on process exit because the explicit `objects checkin` always runs the `CheckIn`. **Downside:** the "first AddUDA OK then second fails" pattern means `obj.Save()` mid-checkout might itself release the checkout — needs verification against the live GRAccess. I recommend **Option A** for `object uda add` and the other single-shot mutation commands, and keeping the explicit `objects checkout` / `objects checkin` for callers that genuinely want the batch model. That way `graccess object uda add ...` "just works" in a script without a checkout dance. ### Acceptance criteria (re-stated) The script in the original "Acceptance criteria" section below must run end-to-end without phantom-checkout failures. Repeat-runs (delete the sandbox, re-derive, re-add UDAs) must produce identical results. A `object snapshot --llm-json` after the AddUDA must include the new UDA in `AttributeValues` (or wherever the dispatcher surfaces them in its snapshot). ## ✅ Default-value fixes (2026-04-30) The two bad-default complaints from the 2026-04-29 doc are still worth tracking — please verify these in the same fix: ### `--category` default for `object uda add` Currently `MxCategoryWriteable_C`, which is **not** in the `MxAttributeCategory` enum. Valid members: ``` MxCategoryWriteable_U MxCategoryWriteable_S MxCategoryWriteable_US MxCategoryWriteable_UC MxCategoryWriteable_USC ← recommended new default MxCategoryWriteable_UC_Lockable MxCategoryWriteable_USC_Lockable MxCategoryWriteable_C_Lockable ``` ### `--force-option` default for `template delete` Currently `galaxy_DeleteIfNoInstances`, which is not in the `EForceDeleteTemplateOption` enum. Valid members: ``` dontForceTemplateDelete ← recommended new default cascadeDeleteDontUndeploy ``` ## 🚿 Helper process leak (lower priority) Each CLI invocation spawns a `GRAccessApp.exe` helper that survives the CLI process. After 12+ invocations during a single rig-setup session there were 12 stale helpers consuming memory. They don't appear to cause bugs (the phantom checkout persists even after they are killed), but they do leak. The CLI should `Marshal.ReleaseComObject` / `Marshal.FinalReleaseComObject` the GRAccess COM root on exit, which should let the helper terminate. ## Acceptance script Repeated from the 2026-04-29 doc — this script must run end-to-end on this dev box's live `ZB` galaxy after both the typelib + lifecycle fixes are in: ```powershell $cli = "C:\Users\dohertj2\Desktop\graccess\graccess_cli\src\ZB.MOM.WW.GRAccess.Cli\bin\x86\Debug\net48\ZB.MOM.WW.GRAccess.Cli.exe" # 1. Derive sandbox template from $UserDefined & $cli template derive --node localhost --galaxy ZB ` --name '$UserDefined' --new-name '$OtOpcUaParityTest' ` --confirm --confirm-target '$UserDefined' # 2. Add four UDAs covering the parity matrix's write classifications # (Option A: each call is self-contained — no separate checkout/checkin) & $cli object uda add --node localhost --galaxy ZB ` --type template --name '$OtOpcUaParityTest' ` --uda OperateValue --data-type MxFloat ` --category MxCategoryWriteable_USC --security MxSecurityOperate ` --confirm --confirm-target '$OtOpcUaParityTest' & $cli object uda add --node localhost --galaxy ZB ` --type template --name '$OtOpcUaParityTest' ` --uda TuneValue --data-type MxFloat ` --category MxCategoryWriteable_USC --security MxSecurityTune ` --confirm --confirm-target '$OtOpcUaParityTest' & $cli object uda add --node localhost --galaxy ZB ` --type template --name '$OtOpcUaParityTest' ` --uda ConfigValue --data-type MxFloat ` --category MxCategoryWriteable_C_Lockable --security MxSecurityConfigure ` --confirm --confirm-target '$OtOpcUaParityTest' & $cli object uda add --node localhost --galaxy ZB ` --type template --name '$OtOpcUaParityTest' ` --uda Counter --data-type MxInteger ` --category MxCategoryWriteable_USC --security MxSecurityFreeAccess ` --confirm --confirm-target '$OtOpcUaParityTest' # 3. Add an analog-limit alarm extension on Counter and a history extension # on OperateValue & $cli object extension add --node localhost --galaxy ZB ` --type template --name '$OtOpcUaParityTest' ` --extension-type AnalogLimitAlarm --primitive Counter ` --object-extension ` --confirm --confirm-target '$OtOpcUaParityTest' & $cli object extension add --node localhost --galaxy ZB ` --type template --name '$OtOpcUaParityTest' ` --extension-type HistoryExtension --primitive OperateValue ` --object-extension ` --confirm --confirm-target '$OtOpcUaParityTest' # 4. Verify state — must show all 4 UDAs and both extensions & $cli object snapshot --node localhost --galaxy ZB ` --name '$OtOpcUaParityTest' --llm-json # 5. Instantiate, assign, deploy & $cli template instantiate --node localhost --galaxy ZB ` --name '$OtOpcUaParityTest' --new-name 'OtOpcUaParityTest_001' ` --confirm --confirm-target '$OtOpcUaParityTest' & $cli instance assign-area --node localhost --galaxy ZB ` --name 'OtOpcUaParityTest_001' --area 'DEV' ` --confirm --confirm-target 'OtOpcUaParityTest_001' & $cli instance assign-engine --node localhost --galaxy ZB ` --name 'OtOpcUaParityTest_001' --engine 'DevAppEngine' ` --confirm --confirm-target 'OtOpcUaParityTest_001' & $cli instance deploy --node localhost --galaxy ZB ` --name 'OtOpcUaParityTest_001' ` --confirm --confirm-target 'OtOpcUaParityTest_001' # 6. Cleanup (verify the delete path too) & $cli instance delete --node localhost --galaxy ZB ` --name 'OtOpcUaParityTest_001' ` --confirm --confirm-target 'OtOpcUaParityTest_001' & $cli template delete --node localhost --galaxy ZB ` --name '$OtOpcUaParityTest' ` --force-option dontForceTemplateDelete ` --confirm --confirm-target '$OtOpcUaParityTest' ``` Pass criteria: 1. Every command exits with status 0. 2. Step 4's snapshot shows `OperateValue`, `TuneValue`, `ConfigValue`, `Counter` in `AttributeValues`, and the two extensions in `Extensions` (or wherever the dispatcher surfaces them). 3. Step 5's deploy succeeds and the instance is reachable from MxAccess (verifiable via the lmxopcua parity tests, or a manual `aaWindowViewer` browse). 4. Repeat the script — second run after cleanup must produce the same outputs (idempotency). ## Verification environment - Windows 10, dev box `DESKTOP-6JL3KKO` - ArchestrA System Platform installed with live `ZB` galaxy (single deployed `$WinPlatform` named `DevPlatform`, engine `DevAppEngine`, area `DEV`) - `OtOpcUaGalaxyHost` Windows service running. Stop it before deploys if MxAccess sessions hold the platform; otherwise leave it running. - graccess-cli x86 build at the path above. ## Consumer of this fix The lmxopcua repo's parity rig (`docs/v2/Galaxy.ParityRig.md` in the lmxopcua repo, branch `v2-mxgw-integration`) wants to provision the sandbox from a script so the seven scenario classes in `Driver.Galaxy.ParityTests` exercise real attributes instead of skipping. The 17-test parity matrix is the gate for retiring the legacy Galaxy.Host backend. Fixing the mutation lifecycle here unblocks that workflow on every parity rig (dev box + customer rigs) without requiring GUI clicks.