Files
wwtools/graccesscli/analysis/package-manager-deep-dive.md
T
Joseph Doherty c52d8d0171 graccesscli: correct script-edit docs to TN-537 truth (writes DO persist)
I was wrong. AVEVA Tech Note 537 ("Creating an Application Object Script
Using GRAccess", April 2008) documents the supported pattern:
ConfigurableAttributes[<script>.<field>].SetValue(MxValue) inside a
CheckOut/Save/CheckIn cycle. graccesscli's existing
FindAttributeForMutation already follows this — writes to MxCategoryPackageOnly_Lockable
script-text fields persist correctly.

The earlier "writeback gap" diagnosis was a phantom caused by a reader-side
issue. `object attribute value get` against a script body returns
"Supported: False / Attribute value is not exposed" because
MxValueDetails uses a case-sensitive `ReadProperty(attr, "Value")` lookup
plus an accessor probe (GetBoolean -> GetInteger -> GetFloat -> GetDouble
-> GetString) that can fall through silently for some MxValue shapes. The
COM-side property is exposed as `value` (lowercase), readable as
`attr.value.GetString()` -- which the live probe at
`analysis/ide-edit-investigation/probe_setvalue/` does and confirms the
post-write content matches the marker exactly.

Live verification on $TestMachine.UpdateTestChangingInt.DeclarationsText
and $DelmiaReceiver.ProcessRecipe.{ExecuteText,DeclarationsText}:

  === verdict ===
    marker landed on same-proxy   ConfigurableAttributes: True
    marker landed on same-proxy   Attributes            : True
    marker landed on fresh-proxy  ConfigurableAttributes: True
    marker landed on fresh-proxy  Attributes            : True

The probe also confirmed that two earlier graccesscli `object scripts set`
invocations (which I had wrongly believed failed) had persisted -- the
marker text I wrote previously was still on disk in
ProcessRecipe.{ExecuteText,DeclarationsText} when read directly via
attr.value.GetString(). The probe restored both fields to their original
values.

This commit:

- Updates the misleading [Command(...)] / [CommandOption(...)]
  descriptions in GRAccessSurfaceCommands.cs back to honest versions
  citing TN-537.
- Restores the --file-using examples for `object scripts set` and
  `object scripts create` across script-editing.md, llm-integration.md,
  usage.md, and zb-testmachine.md.
- Removes the test that asserted the (wrong) EnsureMutableViaSetValue
  guard. Re-aims ScriptCommandDescriptions_… at the corrected wording.
- Removes two leftover EnsureMutableViaSetValue calls in the trigger-period
  / trigger-type write paths (both targeted MxCategoryWriteable_C_Lockable
  attributes; would never have fired even if the helper still existed).
- Adds analysis/ide-edit-investigation/REPORT.md (replacing the earlier
  wrong report) plus the probe sources under probe_setvalue/.

The MxValueDetails reader gap (case-sensitive ReadProperty + accessor
probe) is a real follow-up: `object attribute value get` should
case-insensitively read `value` and try GetString first when the
underlying MxValue.DataType is MxString. Out of scope here -- that's a
separate, smaller fix.

Test count delta: 67 -> 66 (-2 wrong tests, +1 corrected description test).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-05 21:33:51 -04:00

297 lines
13 KiB
Markdown

# Package Manager Deep Dive
Date: 2026-05-05
## Scope
This investigation focused on the local System Platform package manager path that sits below GRAccess and appears to be the missing write path for object-level script body fields and some settings.
Tools used:
- `TlbImp.exe` against registered COM/type libraries.
- .NET reflection over generated interop assemblies.
- Registry inspection of `HKCR\TypeLib`.
- Binary string extraction from package/runtime DLLs.
- `dumpbin.exe` on native package/runtime DLLs.
- Existing decompiled IDE wrapper notes under `analysis/ide-edit-investigation`.
Ghidra was available at `C:\Users\dohertj2\Desktop\focas\tools\ghidra_12.0.4_PUBLIC\support\analyzeHeadless.bat`, but the registered type libraries and embedded primitive XML exposed the actionable surface without needing a full native decompile.
## Registered components
### Galaxy package server
- Type library: `xxGalaxyPackageServer 1.0 Type Library`
- TypeLib GUID: `{74B9B2CA-05E6-4BC4-81C3-1CAFF652035D}`
- Binary: `C:\Program Files (x86)\ArchestrA\Framework\Bin\xxGalaxyPackageServer.dll`
- ProgID: `XxGalaxyPackageServer.GalaxyPackage`
- CLSID: `{F6C78E0D-8EA2-48B8-9592-6D9FB507DAD3}`
- Threading model: `Apartment`
### Configuration access component
- Type library: `ConfigurationAccessComponent 1.0 Type Library`
- TypeLib GUID: `{2539619B-EA9B-4035-9AE6-71421DA6C8FD}`
- Binary: `C:\Program Files (x86)\ArchestrA\Framework\Bin\ConfigurationAccessComponent.dll`
- Main COM server: `ConfigurationAccessComponent.ConfigurationAccessServer`
- CLSID from interop: `{55414847-A533-4642-8E92-76B191B24B87}`
### Script package
- Type library: `ScriptPackage`
- TypeLib GUID: `{8DF25C4D-C021-4080-849D-74A81D97D548}`
- Binary: `C:\Program Files (x86)\ArchestrA\Framework\Bin\ScriptPackage.dll`
- Package CLSID: `{A261929D-90AB-4217-9234-667A83756FFB}`
- Managed compiler helper: `C:\Program Files (x86)\ArchestrA\Framework\Bin\ScriptPackage.Net.dll`
### Script runtime primitive definition
The `ScriptExtension` primitive definition is embedded in:
- `C:\Program Files (x86)\ArchestrA\Framework\Bin\ScriptRuntime.dll`
Important embedded metadata:
- `DefaultInternalName`: `ScriptExtension`
- `Package_CLSID`: `{A261929D-90AB-4217-9234-667A83756FFB}`
- `Runtime_CLSID`: `{2B2616D6-98A0-4e94-ABAD-C75BB022E2D6}`
- Default `ExecutionGroup`: `Custom 1`
## Package manager surface
The important public COM interfaces are already in the local package server type library.
### `IGalaxyConfiguration` / `IGalaxyConfigurationV30`
This is the high-level object lifecycle and package access surface. Important methods:
- `GetIDFromObjectName(Namespace nSpace, string objectName) -> int`
- `GetGObjectInfoFromName(string name, Namespace nSpace)`
- `CheckOutObjects(int[] gObjectIds, int hint, IGObjectOperationStatus callback)`
- `CheckinObjects(int[] gObjectIds, string comment, int hint, IGObjectOperationStatus callback)`
- `UndoCheckoutObjects(int[] gObjectIds, int hint, bool override, IGObjectOperationStatus callback)`
- `GetObject(int objectId, ECODEMODULES codeModules, out object package, out ERRORCODE status, out string message, EPACKAGEPROP prop)`
- `GetObjectInternal(int objectId, ECODEMODULES code, bool editCheckRequired, out object package, out ERRORCODE status, out string message, EPACKAGEPROP prop)`
- `GetGalaxyConfigurationNet() -> object`
- `GetIGalaxy() -> object`
### `IPackageManager`
This is broader and includes lower-level attribute APIs. Important methods:
- `GetObject(...)`
- `GetObjectInternal(...)`
- `GetFullObjectByName(string name) -> object`
- `GetIDFromName(string name) -> int`
- `ReleaseFsObjectEditsession(int fsObjectId)`
- `GetObjectAttribute(Namespace, objectName, attributeName, EATTRIBUTEPROPERTY, MxValue, out result)`
- `InternalSetObjectAttributesEx(Namespace, objectName, EPACKAGETYPE, string[] attributeNames, EATTRIBUTEPROPERTY[] properties, MxValue[] values, bool saveObjectEvenErrorOccurs, out status, out statusMsg)`
- `InternalGetObjectAttributesEx(...)`
The `InternalSetObjectAttributesEx` method is a promising direct write path because it accepts object name, package type, attribute names, property ids, and MxValues without needing the IDE configuration access component.
### `IConfigurationEditorSite2`
This is the direct package editor site returned by package access calls. Important methods:
- `GetAttributeCookie(string attributeFullName) -> int`
- `GetAttribute(int cookie, short propertyId, MxValue value)`
- `SetAttribute(int cookie, short propertyId, MxValue value) -> bool`
- `Validate()`
- `Commit(out string errorString) -> EPACKAGEOPERATIONSTATUS`
- `IsReadOnly()`
- `SetSubscriberInfo(object subscription)`
- `AddExtensionPrimitive(...)`
- `DeleteExtensionPrimitive(...)`
- `RenameExtensionPrimitive(...)`
- `AddUDA(...)`
- `DeleteUDA(...)`
- `RenameUDA(...)`
- `UpdateUDA(...)`
For normal value writes, property id `10` is `EATTRIBUTEPROPERTY.idxAttribPropValue`.
### `IPrimitivePackageSite7`
The script package uses the generic primitive package site contract:
- `GetAttributeHandle(string attributeFullName) -> AttributeHandle`
- `GetAllCategoryAttributeHandle(string attributeFullName) -> AttributeHandle`
- `GetAttribute(AttributeHandle, MxValue)`
- `SetAttribute(AttributeHandle, MxValue)`
- `GetAttributeHandleEx(...)`
- `GetPrimitiveIdByInternalNameEx(...)`
- `GetPrimitiveAttributeIds(...)`
- dynamic attribute operations.
The script package itself exposes:
- `CoScriptPackageClass.Initialize(short primitiveId, IPrimitivePackageSite site)`
- `SetHandler(ref AttributeHandle, MxValue) -> string`
- `Validate() -> EPACKAGESTATUS`
There is no script-specific "SetScriptText" API in the script package typelib. It is generic attribute/handler based.
## ScriptExtension attribute facts
`ScriptRuntime.dll` embeds the actual script primitive attributes. The important ones:
- `ExecuteText`: `MxBigString`, `PackageOnly_Lockable`, `CfgSethandler=True`
- `DeclarationsText`: `MxBigString`, `PackageOnly_Lockable`, `CfgSethandler=True`
- `StartupText`: `MxBigString`, `PackageOnly_Lockable`, `CfgSethandler=True`
- `OnScanText`: `MxBigString`, `PackageOnly_Lockable`, `CfgSethandler=True`
- `OffScanText`: `MxBigString`, `PackageOnly_Lockable`, `CfgSethandler=True`
- `ShutdownText`: `MxBigString`, `PackageOnly_Lockable`, `CfgSethandler=True`
- `Expression`: `MxBigString`, `PackageOnly_Lockable`, `CfgSethandler=True`
- `TriggerType`: `MxCustomEnum`, `Writeable_C_Lockable`, `CfgSethandler=True`
- `_TriggerTypeEnum`: values `WhileTrue`, `WhileFalse`, `OnTrue`, `OnFalse`, `DataChange`, `Periodic`
- `TriggerPeriod`: `MxElapsedTime`, `Writeable_C_Lockable`, `CfgSethandler=True`
- `DataChangeDeadband`: `MxDouble`, `Writeable_UC_Lockable`, `CfgSethandler=True`
- `RunsAsync`: `MxBoolean`, `Writeable_C_Lockable`, `CfgSethandler=True`
- `ExecuteTimeout.Limit`: `MxInteger`, `Writeable_C_Lockable`, `CfgSethandler=True`
- `ScriptExecutionGroup`: `MxCustomEnum`, `PackageOnly_Lockable`, `CfgSethandler=False`
- `ScriptOrder`: `MxInteger`, `PackageOnly_Lockable`, `CfgSethandler=False`
This confirms the current GRAccess `IAttribute.SetValue` path is not enough for script bodies. The body fields are package-only and cfg-sethandler driven.
## IDE wrapper behavior
The decompiled IDE wrapper creates `ConfigurationAccessComponent.ConfigurationAccessServer`, casts it to `IConfigurationEditor`, and initializes it with the object package site:
1. `configurationEditor.Initialize("", inObjectManage, this, readOnly)`
2. `inObjectManage.SetSubscriberInfo(configurationEditor)`
3. `inObjectManage.SetSubscriberInfo(this)`
4. Subscribes to `DConfigurationAccessEvents`.
5. Sets CAS data:
- `ww:SupportedLocales`
- `ww:IPackageManager`
- `ww:Tagname`
- `ww:ContainedName`
- `ww:DerivedFrom`
- `ww:KeepCheckedOut`
This is a full IDE editor-host path. It is probably more complete than direct attribute writes, but it requires implementing enough editor-site/event plumbing in the CLI.
## Feasible edit paths
### Path A: direct package editor site
Likely flow:
1. Use existing GRAccess login to obtain `IGalaxy`.
2. Call `IGalaxy.GetGalaxyConfiguration()` and cast the returned object to `IGalaxyConfiguration` or `IGalaxyConfigurationV30`.
3. Resolve object id with `GetIDFromObjectName(Namespace.AutomationObject, tagName)`.
4. Check out the object via existing GRAccess or `CheckOutObjects`.
5. Get the editable package with `GetObjectInternal(id, eEditorAndPackageCodeModules, true, out package, ...)` or `GetObject(...)`.
6. Cast the package object to `IConfigurationEditorSite2`.
7. Resolve `scriptName.ExecuteText` with `GetAttributeCookie`.
8. Create `MxValueClass` and call `PutString(newScriptBody)`.
9. Call `SetAttribute(cookie, idxAttribPropValue, mxValue)`.
10. Call `Validate()`.
11. Call `Commit(out error)`.
12. Check in or undo checkout and release edit session on failure.
Pros:
- Uses typed COM APIs.
- Avoids the full IDE configuration access server.
- Directly targets the package-only fields.
Risks:
- Needs live validation that the object returned by `GetObjectInternal` supports `IConfigurationEditorSite2` in this context.
- Needs confirmation that `SetAttribute` invokes the script package cfg set handler and recompiles `_Binary`.
- Needs correct custom enum MxValue creation for `TriggerType`.
### Path B: package manager internal batch set
Likely flow:
1. Get `IPackageManager` from the same object returned by `IGalaxy.GetGalaxyConfiguration()` or via package-manager support.
2. Check out object.
3. Call `InternalSetObjectAttributesEx` with:
- `Namespace.AutomationObject`
- object tagname
- likely `EPACKAGETYPE.eBeingEditedPackage`
- attribute names such as `UpdateTestChangingInt.ExecuteText`
- property ids `[idxAttribPropValue]`
- MxValues for the new data
- `saveObjectEvenErrorOccurs=false` initially
4. Check status and status message.
5. Check in or undo checkout.
Pros:
- Smallest API surface.
- No editor host required.
- Best candidate for a CLI-first package write helper.
Risks:
- It is explicitly named `Internal`.
- It may require exact package type and checkout state.
- Unknown whether it invokes all cfg-sethandler compile side effects for script bodies.
### Path C: configuration access component
Likely flow:
1. Get editable object package as `IConfigurationEditorSite2`.
2. Instantiate `ConfigurationAccessComponent.ConfigurationAccessServer`.
3. Implement minimal `IBaseEditorSite` and event sink in the CLI.
4. Call `IConfigurationEditor.Initialize`.
5. Set the IDE wrapper's `ww:*` data entries.
6. Set `Data("ScriptName.ExecuteText", "Value") = body`.
7. Apply/commit through the editor site.
Pros:
- Closest to the IDE behavior.
- Most likely to run all validation/dirty/event plumbing.
Risks:
- Largest implementation.
- Requires COM connection-point/event hosting.
- More fragile in a headless CLI.
## Current CLI implication
The current `object scripts set` path writes through `IAttribute.SetValue`. That cannot persist the package-only body fields shown above.
The current `object scripts settings set --trigger-period-ms` should be more viable because `TriggerPeriod` is `Writeable_C_Lockable`, but it still goes through generic GRAccess `IAttribute.SetValue`.
The current `object scripts settings set --trigger-type` is suspicious because `TriggerType` is `MxCustomEnum`; the CLI currently creates an `MxValue` string for non-explicit data types. The package metadata says the valid values are backed by `_TriggerTypeEnum`, so a package-manager implementation should set `TriggerType` as a custom enum or verify that package `SetAttribute` accepts the string form.
## Recommended next implementation
Do not add more typelibs first. The useful typelibs already exist locally:
- `ArchestrA.GRAccess.dll`
- `xxGalaxyPackageServer.dll`
- `ConfigurationAccessComponent.dll`
- `ScriptPackage.dll`
The next improvement should add a small, isolated package-manager interop layer and a probe/edit helper:
1. Add minimal `[ComImport]` declarations for only the needed package-manager interfaces, or add a checked-in interop reference generated from `xxGalaxyPackageServer.dll`.
2. Bridge from `IGalaxy.GetGalaxyConfiguration()` to `IGalaxyConfigurationV30` / `IPackageManager`.
3. Implement a read-only probe first:
- object id resolution
- `GetObjectInternal`
- cast checks for `IConfigurationEditorSite2`, `IObjectManage`, `IPackageManager`
- `GetAttributeCookie` for script fields/settings
- `GetAttribute` for `idxAttribPropValue`
4. Implement one guarded write path for `ExecuteText`.
5. Verify whether `_Binary`, `_ErrorMessage`, `_ErrorLine`, and `_ErrorColumn` update after commit. If they do not, switch from Path A/B to the CAS path.
6. Only then wire it into `object scripts set` and package-only script settings.
## Live validation blocker
Previous live GRAccess validation against the `ZB` galaxy failed because the local license was unavailable:
`cmdLicenseUnavailable (105); Feature 'wspdevstudio-iocount'`
The package-manager write path needs live validation after license access is restored because static metadata cannot prove whether script cfg-sethandler side effects fire on direct package writes.