Clarify script mutation limits

This commit is contained in:
Joseph Doherty
2026-05-05 16:40:55 -04:00
parent c12fbc5988
commit 87c0124174
7 changed files with 76 additions and 21 deletions
+2 -2
View File
@@ -60,7 +60,7 @@ graccess object scripts get --galaxy ZB --name TestMachine --type template --scr
The CLI tries direct GRAccess reads first. If direct GRAccess does not expose inheritance, attribute values, or script bodies, read commands may export the target object to a temporary `.aaPKG`, parse textual/package entries, recurse into nested package archives, parse binary UTF-16 script extension records, and delete the temp files. SQL Server repository reads are not part of normal CLI behavior and should only be used for development verification/debugging.
Script body access is adapter-dependent. For `ScriptExtension` objects, a bare script name maps to `.ExecuteText`; `object scripts set` writes the matching script body attribute through GRAccess. Mutating script and attribute commands prefer `ConfigurableAttributes[...]` before `Attributes[...]`, because script extension settings such as `TriggerPeriod`, `TriggerType`, `Expression`, and `ExecuteText` are configuration attributes in common GRAccess builds. If neither direct GRAccess nor the package fallback exposes body text, script read commands return structured unavailable details instead of pretending success.
Script body access is adapter-dependent. For `ScriptExtension` objects, a bare script name maps to `.ExecuteText`. Mutating script and attribute commands prefer `ConfigurableAttributes[...]` before `Attributes[...]`. On the local GRAccess build, body fields such as `ExecuteText` and `Expression` are package-only and are not persisted by `IAttribute.SetValue`, so the CLI fails fast for those writes instead of returning a false success. Mutable settings such as `TriggerPeriod` and `TriggerType` remain available through `object scripts settings set`. If neither direct GRAccess nor the package fallback exposes body text, script read commands return structured unavailable details instead of pretending success.
Use the script settings wrapper for IDE-style script configuration:
@@ -71,7 +71,7 @@ graccess object scripts settings set --galaxy ZB --name '$TestMachine' --type te
Use the create wrapper for new object-level script extensions:
```powershell
graccess object scripts create --galaxy ZB --name '$MyTemplate' --type template --script OnScan --file OnScan.txt --trigger-type Periodic --trigger-period-ms 1000 --confirm --confirm-target '$MyTemplate' --llm-json
graccess object scripts create --galaxy ZB --name '$MyTemplate' --type template --script OnScan --trigger-type Periodic --trigger-period-ms 1000 --confirm --confirm-target '$MyTemplate' --llm-json
```
## Inheritance And Embedded Objects
+13 -10
View File
@@ -17,7 +17,7 @@ For LLM-driven script work, read script metadata and validate guarded edits with
```powershell
graccess object scripts list --galaxy ZB --name TestMachine --type template --llm-json
graccess object scripts get --galaxy ZB --name TestMachine --type template --script UpdateTestChangingInt --llm-json
graccess object scripts set --galaxy ZB --name TestMachine --type template --script UpdateTestChangingInt --file .\UpdateTestChangingInt.txt --confirm --confirm-target TestMachine --dry-run --llm-json
graccess object scripts settings set --galaxy ZB --name TestMachine --type template --script UpdateTestChangingInt --trigger-period-ms 500 --confirm --confirm-target TestMachine --dry-run --llm-json
```
## What The CLI Can Edit Today
@@ -28,7 +28,8 @@ graccess object scripts set --galaxy ZB --name TestMachine --type template --scr
| Script library export | `script-library export` |
| Script library import/add | `script-library import`, `script-library add`, `galaxy import-script-library` |
| Object script metadata | `object scripts list`, `object scripts get` |
| Object script body read/write | `object scripts get`, `object scripts set` for script body attributes such as `ExecuteText` |
| Object script body read | `object scripts get` package fallback for script body attributes such as `ExecuteText` |
| Object script mutable settings | `object scripts settings set --trigger-type`, `--trigger-period-ms` |
| Script-like attributes | `object attribute set`, lock, security, buffer |
| Extension primitives | `object extension add/delete/rename` |
| Full object package script payloads | Use `objects export` and `galaxy import-objects` |
@@ -90,13 +91,15 @@ $scripts = (@($attrs) + @($extended)) | Where-Object {
$scripts | Select-Object Name, DataType, Category, Locked
```
If the script-like setting is a scalar string attribute, edit it through the normal template edit flow. Script extension fields should be written through `ConfigurableAttributes`; the CLI does this automatically for `object scripts set`, `object scripts settings set`, and mutating `object attribute` commands. `object scripts set` is the convenience wrapper for script body attributes. A bare script name maps to `.ExecuteText`; explicit fields such as `.StartupText`, `.OnScanText`, or `.Expression` are preserved.
If the script-like setting is a scalar writable attribute, edit it through the normal template edit flow. Script extension attributes are looked up through `ConfigurableAttributes` first, then `Attributes`.
For local `ScriptExtension` objects, body fields such as `ExecuteText`, `StartupText`, `OnScanText`, and `Expression` are package-only. GRAccess `IAttribute.SetValue` can return success without persisting package-only fields, so `object scripts set` and `object scripts settings set --expression` fail fast when the attribute category starts with `MxCategoryPackageOnly`. Use the IDE or a future package-rewrite path for those fields.
```powershell
graccess object checkout --galaxy ZB --name TestMachine --type template --confirm --confirm-target TestMachine
graccess object scripts set --galaxy ZB --name TestMachine --type template --script UpdateTestChangingInt --file .\UpdateTestChangingInt.txt --confirm --confirm-target TestMachine
graccess object attribute value set --galaxy ZB --name TestMachine --type template --attribute SomeWritableScriptSetting --value Updated --data-type string --confirm --confirm-target TestMachine
graccess object save --galaxy ZB --name TestMachine --type template --confirm --confirm-target TestMachine
graccess object checkin --galaxy ZB --name TestMachine --type template --comment 'Update script text' --confirm --confirm-target TestMachine
graccess object checkin --galaxy ZB --name TestMachine --type template --comment 'Update writable script setting' --confirm --confirm-target TestMachine
```
Update periodic script settings and lock the interval for deployment inheritance:
@@ -137,16 +140,16 @@ Delete:
graccess object extension delete --galaxy ZB --name TestMachine --type template --extension-type ScriptExtension --primitive OnScan2 --object-extension --confirm --confirm-target TestMachine
```
These commands manage primitive structure. The IDE-oriented wrapper below creates the same `ScriptExtension` primitive and can initialize the body and common settings:
These commands manage primitive structure. The wrapper below creates the same `ScriptExtension` primitive and can initialize mutable settings:
```powershell
graccess object checkout --galaxy ZB --name '$MyTemplate' --type template --confirm --confirm-target '$MyTemplate'
graccess object scripts create --galaxy ZB --name '$MyTemplate' --type template --script OnScan --file .\OnScan.txt --trigger-type Periodic --trigger-period-ms 1000 --confirm --confirm-target '$MyTemplate'
graccess object scripts create --galaxy ZB --name '$MyTemplate' --type template --script OnScan --trigger-type Periodic --trigger-period-ms 1000 --confirm --confirm-target '$MyTemplate'
graccess object save --galaxy ZB --name '$MyTemplate' --type template --confirm --confirm-target '$MyTemplate'
graccess object checkin --galaxy ZB --name '$MyTemplate' --type template --comment 'Add OnScan script' --confirm --confirm-target '$MyTemplate'
```
After adding a `ScriptExtension` primitive, set its body with `object scripts set --script <primitiveName>` or `object scripts set --script <primitiveName>.ExecuteText`.
After adding a `ScriptExtension` primitive, body text still needs the IDE/package path when the projected body attributes are package-only.
## Full-Fidelity Object Script Edits
@@ -200,7 +203,7 @@ graccess instance deploy --galaxy ZB --name TestMachine_ScriptTest_001 --type in
```text
graccess object scripts list --galaxy ZB --name TestMachine --type template --json
graccess object scripts get --galaxy ZB --name TestMachine --type template --script UpdateTestChangingInt --llm-json
graccess object scripts set --galaxy ZB --name TestMachine --type template --script UpdateTestChangingInt --file .\UpdateTestChangingInt.txt --confirm --confirm-target TestMachine --llm-json
graccess object scripts settings set --galaxy ZB --name TestMachine --type template --script UpdateTestChangingInt --trigger-period-ms 500 --confirm --confirm-target TestMachine --llm-json
```
The local GRAccess examples sometimes show `Template.Scripts[index].ScriptString`. This repository's installed interop exposes the same practical content through script extension attributes such as `UpdateTestChangingInt.ExecuteText`; the CLI writes those attributes directly via `IAttribute.SetValue`.
The local GRAccess examples sometimes show `Template.Scripts[index].ScriptString`, but this repository's installed `ArchestrA.GRAccess.dll` does not expose a public object script collection. It exposes script extension projections as attributes and package records. The CLI can read body text through exported package parsing and can write mutable settings through `IAttribute.SetValue`; package-only body/expression fields are not persisted by that GRAccess setter.
+4 -3
View File
@@ -242,14 +242,15 @@ Scalar `string`, `bool`, `int`, `float`, and `double` writes are supported first
graccess object scripts list --galaxy ZB --name TestMachine --type template --llm-json
graccess object scripts get --galaxy ZB --name TestMachine --type template --script UpdateTestChangingInt --llm-json
graccess object scripts get --galaxy ZB --name TestMachine --type template --script UpdateTestChangingInt.ExecuteText --llm-json
graccess object scripts set --galaxy ZB --name TestMachine --type template --script UpdateTestChangingInt --file UpdateTestChangingInt.txt --confirm --confirm-target TestMachine --llm-json
graccess object scripts settings set --galaxy ZB --name '$TestMachine' --type template --script UpdateTestChangingInt --trigger-period-ms 500 --lock-trigger-period --confirm --confirm-target '$TestMachine' --llm-json
graccess object scripts create --galaxy ZB --name '$MyTemplate' --type template --script OnScan --file OnScan.txt --trigger-type Periodic --trigger-period-ms 1000 --confirm --confirm-target '$MyTemplate' --llm-json
graccess object scripts create --galaxy ZB --name '$MyTemplate' --type template --script OnScan --trigger-type Periodic --trigger-period-ms 1000 --confirm --confirm-target '$MyTemplate' --llm-json
```
Direct object script body access depends on the local GRAccess object model. Reads inspect the exported package fallback for script extension bodies and script text fields such as `ExecuteText`, `DeclarationsText`, `StartupText`, `ShutdownText`, `OnScanText`, `OffScanText`, and `Expression`.
For writes, the CLI follows the GRAccess pattern used by `ScriptExtension` objects: script body and setting mutations prefer `IgObject.ConfigurableAttributes[...]`, then fall back to `Attributes[...]` only if the configurable collection does not expose the requested field. `object scripts set` writes the matching script body attribute; a bare script name maps to `.ExecuteText`. `object scripts settings set` writes common script settings such as `TriggerPeriod`, `TriggerType`, and `Expression`; `--lock-trigger-period` applies `MxLockedInMe` so derived instances receive the interval on deploy. `object scripts create` calls `AddExtensionPrimitive("ScriptExtension", <script>, true)` and can initialize the body/settings in the same checkout flow.
For writes, the CLI uses the public GRAccess path exposed by `IgObject.ConfigurableAttributes[...]` / `Attributes[...]` and `IAttribute.SetValue`. This is valid for mutable settings such as `TriggerPeriod` and `TriggerType`. `MxElapsedTime` values are stored in 100 ns increments, so `--trigger-period-ms 500` writes `5,000,000`.
Script body fields and `Expression` on the local `ScriptExtension` projection are package-only (`MxCategoryPackageOnly*`). GRAccess `IAttribute.SetValue` can report success for those fields without persisting the value, so the CLI now fails fast when it sees a package-only category. `object scripts create` can add the `ScriptExtension` primitive and initialize mutable settings, but it cannot initialize package-only body text through GRAccess. Use the IDE or a future package-rewrite path for body/expression edits.
### Area, engine, assignment, and I/O wrappers
+1 -1
View File
@@ -316,5 +316,5 @@ The parent instance shape matches `$TestMachine` by count and script metadata. `
- Edit embedded child objects by targeting the child instance tagname, such as `DelmiaReceiver_001`, or its hierarchical name when a command supports hierarchical lookup.
- Do not mutate `$TestMachine` until the existing checkout state is understood; the template was `checkedOutToMe` during capture.
- Direct scalar value readback for many template attributes is not exposed by the current generic attribute value path.
- Direct script body readback is not exposed by the current generic value path; use `object scripts get --llm-json` package fallback for reads and `object scripts set` for script body attribute writes.
- Direct script body readback is not exposed by the current generic value path; use `object scripts get --llm-json` package fallback for reads. Package-only script body/expression fields are not persisted by GRAccess `IAttribute.SetValue`; use the IDE/package path for those edits.
- Extended attributes failed because the local COM type library was not registered.
@@ -426,7 +426,7 @@ namespace ZB.MOM.WW.GRAccess.Cli.Commands
public override Dictionary<string, object> Args() { var args = base.Args(); args["script"] = Script; return args; }
}
[Command("object scripts set", Description = "Set an object script body when supported by the local adapter")]
[Command("object scripts set", Description = "Attempt to set object script text through GRAccess; package-only ScriptExtension text fails fast")]
public sealed class ObjectScriptsSetCommand : ObjectScriptsGetCommand
{
public override string Subcommand => "scripts-set";
@@ -434,7 +434,7 @@ namespace ZB.MOM.WW.GRAccess.Cli.Commands
[CommandOption("file", Description = "Script source file", IsRequired = true)]
public string File { get; init; }
[CommandOption("field", Description = "Script-text field to write: ExecuteText (default), DeclarationsText, StartupText, ShutdownText, OnScanText, OffScanText, Expression. Pass to target a non-body field; omit to default to ExecuteText.")]
[CommandOption("field", Description = "Script-text field to write: ExecuteText (default), DeclarationsText, StartupText, ShutdownText, OnScanText, OffScanText, Expression. Package-only fields fail fast instead of silently no-oping.")]
public string Field { get; init; } = "";
public override Dictionary<string, object> Args() { var args = base.Args(); args["file"] = File; args["field"] = Field; return args; }
@@ -454,7 +454,7 @@ namespace ZB.MOM.WW.GRAccess.Cli.Commands
[CommandOption("trigger-type", Description = "Script trigger type value")]
public string TriggerType { get; init; } = "";
[CommandOption("expression", Description = "Script expression value")]
[CommandOption("expression", Description = "Script expression text. On local ScriptExtension builds this is package-only and fails fast.")]
public string Expression { get; init; } = "";
[CommandOption("lock-trigger-period", Description = "Lock TriggerPeriod in this object after setting it")]
@@ -475,7 +475,7 @@ namespace ZB.MOM.WW.GRAccess.Cli.Commands
}
}
[Command("object scripts create", Description = "Create a ScriptExtension primitive and optionally initialize its body/settings")]
[Command("object scripts create", Description = "Create a ScriptExtension primitive and optionally initialize mutable settings")]
public sealed class ObjectScriptsCreateCommand : ObjectScriptSettingsCommandBase
{
public override string Subcommand => "scripts-create";
@@ -486,7 +486,7 @@ namespace ZB.MOM.WW.GRAccess.Cli.Commands
public override Dictionary<string, object> Args() { var args = base.Args(); args["file"] = File; return args; }
}
[Command("object scripts settings set", Description = "Set ScriptExtension settings through ConfigurableAttributes")]
[Command("object scripts settings set", Description = "Set mutable ScriptExtension settings through ConfigurableAttributes")]
public sealed class ObjectScriptsSettingsSetCommand : ObjectScriptSettingsCommandBase
{
public override string Subcommand => "scripts-settings-set";
@@ -143,6 +143,7 @@ namespace ZB.MOM.WW.GRAccess.Cli.GRAccess
RequireConfirm(args, Arg(args, "name"));
return AtomicObjectEdit(FindSingleObject(galaxy, Kind(args), Arg(args, "name")), obj => ObjectScriptCreate(galaxy, obj, args));
case "scripts-delete":
RequireConfirm(args, Arg(args, "name"));
return AtomicObjectEdit(FindSingleObject(galaxy, Kind(args), Arg(args, "name")), obj =>
{
var scriptName = Arg(args, "script");
@@ -1693,6 +1694,7 @@ namespace ZB.MOM.WW.GRAccess.Cli.GRAccess
if (!string.IsNullOrWhiteSpace(triggerPeriodMs))
{
var attr = FindAttributeForMutation(obj, scriptName + ".TriggerPeriod");
EnsureMutableViaSetValue(attr, scriptName + ".TriggerPeriod");
attr.SetValue(CreateMxValue(triggerPeriodMs, "elapsed-ms"));
yield return CommandSummary(attr, $"Set script {scriptName}.TriggerPeriod");
@@ -1707,6 +1709,7 @@ namespace ZB.MOM.WW.GRAccess.Cli.GRAccess
if (!string.IsNullOrWhiteSpace(triggerType))
{
var attr = FindAttributeForMutation(obj, scriptName + ".TriggerType");
EnsureMutableViaSetValue(attr, scriptName + ".TriggerType");
attr.SetValue(CreateMxValue(triggerType, "string"));
yield return CommandSummary(attr, $"Set script {scriptName}.TriggerType");
@@ -164,6 +164,54 @@ namespace ZB.MOM.WW.GRAccess.Cli.Tests.Commands
source.ShouldContain("return AtomicObjectEdit(FindSingleObject(galaxy, Kind(args), Arg(args, \"name\")), obj => ObjectScriptSettingsSet");
}
[Fact]
public void DispatcherScriptDeleteMutation_RequiresConfirmation()
{
var source = DispatcherSource();
var branch = source.Substring(
source.IndexOf("case \"scripts-delete\":", StringComparison.Ordinal),
source.IndexOf("case \"scripts-set\":", StringComparison.Ordinal) - source.IndexOf("case \"scripts-delete\":", StringComparison.Ordinal));
branch.ShouldContain("RequireConfirm(args, Arg(args, \"name\"));");
}
[Fact]
public void DispatcherScriptSettings_GuardsPackageOnlyNoOpsBeforeSetValue()
{
var source = DispatcherSource();
source.ShouldContain("EnsureMutableViaSetValue(attr, scriptName + \".TriggerPeriod\");");
source.ShouldContain("EnsureMutableViaSetValue(attr, scriptName + \".TriggerType\");");
source.ShouldContain("EnsureMutableViaSetValue(attr, scriptName + \".Expression\");");
}
[Fact]
public void DispatcherElapsedMilliseconds_UseDocumentedHundredNanosecondUnits()
{
var source = DispatcherSource();
source.ShouldContain("var elapsedMs = Int64ToElapsedTime(Convert.ToInt64(milliseconds * 10000d));");
source.ShouldContain("mxValue.PutElapsedTime(ref elapsedMs);");
}
[Fact]
public void ScriptCommandDescriptions_CallOutPackageOnlyLimit()
{
var set = (CommandAttribute)Attribute.GetCustomAttribute(
typeof(ObjectScriptsSetCommand),
typeof(CommandAttribute));
var settings = (CommandAttribute)Attribute.GetCustomAttribute(
typeof(ObjectScriptsSettingsSetCommand),
typeof(CommandAttribute));
set.ShouldNotBeNull();
settings.ShouldNotBeNull();
set.Description.ShouldNotBeNull();
settings.Description.ShouldNotBeNull();
set.Description!.ShouldContain("package-only");
settings.Description!.ShouldContain("mutable");
}
private static string DispatcherSource()
{
var directory = new DirectoryInfo(Directory.GetCurrentDirectory());