Files
wwtools/graccesscli/requirements-mutation-typelib-fix.md
Joseph Doherty 32f26272ae Initial commit: Wonderware / System Platform tools and reference
Five tools under one repo, all docs organized per DOCS-GUIDE.md:

- aalogcli: .NET 4.8 / x86 CliFx CLI for reading System Platform binary
  logs (*.aaLGX) for LLM debugging, built on aaOpenSource/aaLog. Commands:
  last, tail, range, unread, fields. Stable JSON envelope under --llm-json.
  Build template under lib/build/ for rebuilding aaLogReader.dll.

- aot: ArchestrA Object Toolkit 2014 v4.0 reference material. Dev guide
  (Markdown converted from CHM), API reference for the ArchestrA.Toolkit
  namespace, and the Monitor / Watchdog VS sample solutions.

- graccesscli: .NET 4.8 / x86 CliFx CLI that automates Galaxy
  configuration via the ArchestrA GRAccess COM interop. Includes session
  daemon, IPC protocol, and llm-json envelope contract.

- grdb: SQL/DDL exploration of the Galaxy Repository database. DDL
  captures, reusable queries, hierarchy / contained-name <-> tag-name
  translation notes.

- histdb: LLM-oriented reference for AVEVA Historian retrieval. INSQL
  linked-server, extension tables, every wwXxx time-domain extension,
  every retrieval mode, alarm/event SQL recipes, REST API. Distilled
  from the 243-page Historian Retrieval Guide.

Root contains:
- CLAUDE.md: thin index pointing into each tool's README.
- DOCS-GUIDE.md: doctrine for organizing docs for LLM consumption.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 18:22:20 -04:00

13 KiB

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 resolvedobjects 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:

$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:

case "add":
    obj.AddUDA(
        Arg(args, "uda"),
        EnumValue<MxDataType>(Arg(args, "data-type")),
        UdaCategory(Arg(args, "category")),
        EnumValue<MxSecurityClassification>(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.

Two options, in order of preference:

Option A — wrap each mutation in atomic CheckOut + Save + CheckIn

The dispatcher takes responsibility for the lifecycle:

case "add":
    obj.CheckOut();
    try
    {
        obj.AddUDA(
            Arg(args, "uda"),
            EnumValue<MxDataType>(Arg(args, "data-type")),
            UdaCategory(Arg(args, "category")),
            EnumValue<MxSecurityClassification>(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:

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:

$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.