From 59feb40d033f3a6a7caead348d5035d6ea6c82a3 Mon Sep 17 00:00:00 2001 From: Michael Thompson Date: Mon, 22 Jun 2026 11:24:24 -0600 Subject: [PATCH 1/3] Add cross-repo Rsms engine map for compatibility-bug work Document that custom-platform scripts authored here are executed by the Rsms scriptable engine in Kevin-Andrew/PangaeaAppliance, and map every authored construct (operations, Do-block verbs, parameter types, reserved variables, task-log/status contracts, validation entry points) to the authoritative source file:line in that repo. - Add docs/agent-reference/rsms-engine-map.md (source-cited engine map) - Add 'Cross-repo: the Rsms execution engine' section to AGENTS.md - Index the new doc in docs/agent-reference/README.md - Correct stale Hercules\\Source\\* citations (the subtree was relocated under src\\) in tools/README.md and the task-log-analysis and safeguard-ps-operations skills Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../skills/safeguard-ps-operations/SKILL.md | 2 +- .agents/skills/task-log-analysis/SKILL.md | 2 +- AGENTS.md | 13 + docs/agent-reference/README.md | 1 + docs/agent-reference/rsms-engine-map.md | 335 ++++++++++++++++++ tools/README.md | 11 +- 6 files changed, 359 insertions(+), 5 deletions(-) create mode 100644 docs/agent-reference/rsms-engine-map.md diff --git a/.agents/skills/safeguard-ps-operations/SKILL.md b/.agents/skills/safeguard-ps-operations/SKILL.md index 555728c..57cdd8d 100644 --- a/.agents/skills/safeguard-ps-operations/SKILL.md +++ b/.agents/skills/safeguard-ps-operations/SKILL.md @@ -245,7 +245,7 @@ The dev-loop wrapper distinguishes three failure shapes: ## Secret handling - Do not write secret parameter values into evidence, status messages, or operator-visible output. -- SPP server-side already redacts known credential parameters as the literal string `**secret**` in returned task logs (constant `Hercules\Source\Hercules.DevKit\Constants\ParameterConstants.cs:5`, cited in [`tools/README.md`](../../../tools/README.md), "Secret handling"). Do not attempt to recover real values from these markers. +- SPP server-side already redacts known credential parameters as the literal string `**secret**` in returned task logs (constant `PangaeaAppliance\src\Platform\Platform.Rsms\Constants\ParameterConstants.cs:5`, cited in [`tools/README.md`](../../../tools/README.md), "Secret handling"; cross-repo map in [`docs/agent-reference/rsms-engine-map.md`](../../../docs/agent-reference/rsms-engine-map.md)). Do not attempt to recover real values from these markers. - Custom-script authors who add new secret parameters must declare them with `Type: "Secret"` so the same redaction applies — see [`script-authoring`](../script-authoring/SKILL.md). ## Failing closed diff --git a/.agents/skills/task-log-analysis/SKILL.md b/.agents/skills/task-log-analysis/SKILL.md index 39db692..4b29a37 100644 --- a/.agents/skills/task-log-analysis/SKILL.md +++ b/.agents/skills/task-log-analysis/SKILL.md @@ -40,7 +40,7 @@ The skill consumes the JSON document produced by [`tools/Invoke-PlatformDevLoop. Two layers of evidence matter: - **`phases[2].data.taskLog`** — the structured array (`Timestamp`, `Status`, `Message`) that `safeguard-ps` attaches to `Ex.SafeguardLongRunningTaskException` on a trigger failure. The first non-`Queued`/`Running` `Status` value usually pins the failure phase; the last entry is the user-visible summary. -- **`phases[3].data.log`** — the per-named-log entries (`Recorded`, `Level`, `Event`) from `Get-SafeguardTaskLog`. Sections are separated by synthetic `--- ---` entries inserted by safeguard-ps. The two log names produced by SPP for platform tasks are stable string constants `Operation` and `SshCommunication` (cited in [`tools/README.md`](../../../tools/README.md) "phases[3] data"; defined in `Hercules\Source\Rsms.Public\Constants\Logging.cs:14-15`). +- **`phases[3].data.log`** — the per-named-log entries (`Recorded`, `Level`, `Event`) from `Get-SafeguardTaskLog`. Sections are separated by synthetic `--- ---` entries inserted by safeguard-ps. The two log names produced by SPP for platform tasks are stable string constants `Operation` and `SshCommunication` (cited in [`tools/README.md`](../../../tools/README.md) "phases[3] data"; defined in `PangaeaAppliance\src\Common\Common.Rsms\Rsms.Public\Constants\Logging.cs:14-15`, mapped in [`docs/agent-reference/rsms-engine-map.md`](../../../docs/agent-reference/rsms-engine-map.md)). Read both. The `Operation` log shows what the platform script intended; `SshCommunication` (when present) shows the raw frames so a `Send`/`Receive` mismatch becomes diagnosable. diff --git a/AGENTS.md b/AGENTS.md index 7b69df1..d580958 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -22,6 +22,19 @@ SPP runs the script against an asset and an account: the asset supplies network Authoring a custom platform is iterative: draft the JSON, validate it against the schema, import it into a test appliance, trigger an operation with extended logging, read the task log, fix the script, repeat. The agent skill system is built around making this loop fast and grounded in real evidence. +## Cross-repo: the Rsms execution engine + +The scripts authored here do not run in this repo. They are parsed, validated, and executed by the **Rsms** scriptable engine that ships inside the appliance, whose source lives in a separate repository: [`Kevin-Andrew/PangaeaAppliance`](https://github.com/Kevin-Andrew/PangaeaAppliance). Several contracts this repo depends on — the task-log shape, the status enum, the log-name constants, and the secret-redaction sentinel — are *defined there*, not here. + +Two consequences shape how an agent should reason about compatibility: + +- **The engine is authoritative; this repo's `schema/custom-platform-script.schema.json` is a convenience mirror that can drift.** When the local schema and the engine disagree, the engine wins. A clean local schema validation does not prove the engine will accept the script (see "`SchemaOnly` is not a correctness signal" below). The usual fix for a disagreement is to correct this repo's schema or sample, not the script under test. +- **A "compatibility bug" is almost always drift between the two repos** — the engine added, moved, renamed, or tightened a construct, and this repo's schema, samples, docs, or citations lagged. Resolving one means checking what the engine *actually* does at the relevant source location. + +When a compatibility question arises — "is this verb real?", "does the engine accept this parameter type?", "why does the appliance reject a script that passes local schema validation?" — consult [`docs/agent-reference/rsms-engine-map.md`](docs/agent-reference/rsms-engine-map.md). It maps every authored construct (operations, `Do`-block verbs, parameter types, reserved variables, task-log/status contracts, the validation entry points) to the authoritative PangaeaAppliance source file and line. Its "Script validation" section is the highest-yield place to look for "schema accepts / engine rejects" classes of bug. Note that engine source paths move (the `Hercules\Source\*` tree was relocated under `src\`); the engine-map records the current paths and re-confirming against the live PangaeaAppliance tree before a high-stakes change is expected. + +If a change in PangaeaAppliance is what broke the contract, the fix belongs in both places: update the engine-map citation here, and — when reachable — leave a note in the PangaeaAppliance Rsms project pointing back at this repo so the next engine change prompts a heads-up. + ## Operating modes The agent declares the active mode at the start of every session. Each skill declares the modes it supports and **fails closed** when invoked outside them. diff --git a/docs/agent-reference/README.md b/docs/agent-reference/README.md index 1f9fc94..3b4e1f7 100644 --- a/docs/agent-reference/README.md +++ b/docs/agent-reference/README.md @@ -15,6 +15,7 @@ Human-facing documentation lives in `docs/concepts/`, `docs/guides/`, `docs/tuto | [`failure-patterns.md`](failure-patterns.md) | Error-signature → likely cause → fix catalog used by `task-log-analysis`. | **Initially empty.** Rows are populated from real extended task logs as failures are encountered. Invented rows are not acceptable. | | [`script-authoring-deep-dives.md`](script-authoring-deep-dives.md) | Long-form reference for topics extracted out of the `script-authoring` skill (diagnostics rules, sample-mining loop, function-call signatures, Linux `CheckPassword` pattern, `Catch`-block logging). | Hand-maintained. | | [`vendor-doc-search-recipes.md`](vendor-doc-search-recipes.md) | Query templates for fetching vendor docs and a normalization recipe for pasted vendor-doc excerpts. | Hand-maintained. | +| [`rsms-engine-map.md`](rsms-engine-map.md) | Cross-repo map: each authored construct (operations, `Do`-block verbs, parameter types, reserved variables, task-log/status contracts, validation entry points) → the authoritative source `file:line` in the `Kevin-Andrew/PangaeaAppliance` repo, where the Rsms engine that executes these scripts lives. Backs cross-repo compatibility-bug work. | Hand-maintained from a source-cited analysis of the PangaeaAppliance tree. Re-verify citations against the current engine ref; paths move. | ## Related contracts diff --git a/docs/agent-reference/rsms-engine-map.md b/docs/agent-reference/rsms-engine-map.md new file mode 100644 index 0000000..5e66005 --- /dev/null +++ b/docs/agent-reference/rsms-engine-map.md @@ -0,0 +1,335 @@ +[← Agent reference](README.md) + +# Rsms engine map + +The custom-platform JSON authored in this repo is **not** executed here. It is +parsed, validated, and run by the **Rsms** scriptable engine that ships inside +the appliance, whose source lives in a *different* repository: +[`Kevin-Andrew/PangaeaAppliance`](https://github.com/Kevin-Andrew/PangaeaAppliance). + +This file maps each construct an author writes (operations, `Do`-block verbs, +parameter types, reserved variables, task-log/status contracts) to the +**authoritative Rsms source location** that defines it. It exists so an agent +fixing a compatibility bug between the two repos can answer "what does the +engine *actually* accept?" with a one-hop lookup instead of guessing from this +repo's `schema/` — which is a convenience mirror that can drift. + +## How to read this file + +- **All `path\to\file.cs:line` citations are repo-root-relative paths inside + `PangaeaAppliance`, not this repo.** Windows separators, matching that repo. +- **The engine is authoritative; `schema/custom-platform-script.schema.json` is + not.** When the local JSON Schema and the engine disagree, the engine wins. + A `SchemaOnly` green is necessary but not sufficient (see `AGENTS.md`, + "`SchemaOnly` is not a correctness signal"). The fix for a disagreement is + almost always to correct this repo's schema/sample, not the script under test. +- **Verify before trusting.** These citations are a snapshot. Re-confirm against + the current PangaeaAppliance tree before acting on a high-stakes change — file + moves happen (see "Path-drift warning"). The quick index at the bottom is the + minimal set of files to re-check. + +## Provenance + +Source-cited by a cross-repo analysis of the PangaeaAppliance tree (see commit +ref in the quick index). Every claim below carries a `file:line` citation into +that repo. When a path here stops resolving, treat it as drift and re-locate the +symbol by name — do not assume the contract changed. + +## Path-drift warning (read this first) + +The PangaeaAppliance **`Hercules\Source\Rsms.*` and `Hercules.DevKit` paths that +older citations in this repo use no longer exist.** The Hercules subtree was +dissolved and relocated into PangaeaAppliance's standard `src\` layout. The C# +**namespaces are still `Hercules.*` / `Rsms.Public.*`**, but the files now live +under `src\Service\Rsms`, `src\Platform\Platform.Rsms*`, and +`src\Common\Common.Rsms`. Concretely: + +| Older (stale) path cited in this repo | Current path in PangaeaAppliance | +| --- | --- | +| `Hercules\Source\Rsms.Public\Constants\Logging.cs` | `src\Common\Common.Rsms\Rsms.Public\Constants\Logging.cs` | +| `Hercules\Source\Hercules.DevKit\Constants\ParameterConstants.cs` | `src\Platform\Platform.Rsms\Constants\ParameterConstants.cs` | +| `src\Data\Transfer\V2\PlatformTasks\TaskLog.cs` / `TaskStatus.cs` | unchanged — still correct | + +This drift is itself the canonical example of why this file exists: a contract +moved in the engine repo and the citations here went stale without any +functional change. The stale references are corrected in +[`tools/README.md`](../../tools/README.md) and +[`.agents/skills/task-log-analysis/SKILL.md`](../../.agents/skills/task-log-analysis/SKILL.md). + +## 1. Engine entry point + +The Scriptable backend is the **`Service.Rsms`** service (assembly `Rsms`, +`src\Service\Rsms\Rsms.csproj`, TFM `net10.0-windows`). The interpreter lives +under `src\Service\Rsms\Modules\Modules\Scriptable\`. + +| Role | Class | Source | +| --- | --- | --- | +| Backend / operation host | `ScriptableModule` | `src\Service\Rsms\Modules\Modules\Scriptable\ScriptableModule.cs:32` (registered `[ModuleBackendDefinition(BackendNames.ScriptableBackendName, …)]` at `:31`) | +| Interpreter / `Do`-loop | `Runner` | `Runner.cs:17` — `ExecuteOperationBlock` walks the `Do` list `:181-197`; `ExecuteOperation` runs each verb `:204-213`; function call + scoping `:89-161` | +| Verb registry / resolver | `ComponentManager` | `ComponentManager.cs:13` — auto-registers every `IModuleComponent` keyed by `[ComponentName]` `:45-66`; resolves by name `:33-36`. Components must be stateless `:56-57` | +| JSON → object-model parser | `ModelOperationBlockConverter` | `Abstractions\ModelOperationBlockConverter.cs` — each `Do` step is a single-property object `{ "Verb": {…} }`; `CreateOperation` `:245-260`; verb→type lookup `GetComponentTypeMatchingComponentName` `:317-328` (throws `No component found matching name …` on unknown verb `:324`) | +| Platform-definition model | `ScriptablePlatformDefinition` | `Model\ScriptablePlatformDefinition.cs:16` — binds top-level keys `Imports` `:19`, `Meta` `:22`, `Functions` `:25`; `GetFunctionBlock(OperationType)` `:37-45` | + +Caller chain from the appliance Core API into the engine: +`PlatformLogic.ValidatePlatformScriptAsync` → +`HerculesClient.ValidateScriptAsync` +(`src\Platform\Platform.Rsms.Client\HerculesClient.cs:273`) → Rsms +`PlatformsController` (`src\Service\Rsms\Controllers\v1\PlatformsController.cs`). + +## 2. `Do`-block verbs (case-sensitive) + +Verb names are string constants in `Hercules.Common.Constants.ComponentNames` +(`src\Platform\Platform.Rsms\Constants\ComponentNames.cs`). The parser matches a +`Do` step's single property name **exactly, ordinal/case-sensitive**, against +`ComponentNameAttribute.Name` (`ModelOperationBlockConverter.cs:320-321`). One +component may register several names (`Attributes\ComponentNameAttribute.cs:5-6`). + +All component files are under +`src\Service\Rsms\Modules\Modules\Scriptable\Components\`. + +| Verb | Dispatched at (under `…\Components\`) | +| --- | --- | +| `Connect` | `Connect\ConnectComponent.cs:22` | +| `Disconnect` | `Disconnect\DisconnectComponent.cs:12` | +| `Send` | `Send\SendComponent.cs:15` | +| `Receive` | `Receive\ReceiveComponent.cs:18` | +| `ExecuteCommand` | `ExecuteCommand\ExecuteCommandComponent.cs:22` | +| `ExecuteDependentCommand` | `ExecuteDependentCommand\ExecuteDependentCommandComponent.cs:21` | +| `Request` | `Request\RequestComponent.cs:26` | +| `NewHttpRequest` | `NewRequest\NewHttpRequestComponent.cs:9` | +| `BaseAddress` | `BaseAddress\BaseAddressComponent.cs:9` | +| `Headers` | `Headers\HeaderComponent.cs:12` | +| `HttpAuth` | `HttpAuth\HttpAuthComponent.cs:17` | +| `ExtractJsonObject` | `ExtractJsonObject\ExtractJsonObjectComponent.cs:14` | +| `ExtractFormData` | `ExtractFormData\ExtractFormDataComponent.cs:12` | +| `GetFormValue` / `GetFormData` | `GetFormValue\GetFormValueComponent.cs:10-11` | +| `SetFormValue` / `SetFormData` | `SetFormValue\SetFormValueComponent.cs:11-12` | +| `WriteResponseObject` | `WriteResponseObject\WriteResponseObjectComponent.cs:12` | +| `GetCookie` | `GetCookie\GetCookieComponent.cs:12` | +| `SetCookie` | `SetCookie\SetCookieComponent.cs:11` | +| `ClearCookie` | `ClearCookie\ClearCookieComponent.cs:13` | +| `Split` | `Split\SplitComponent.cs:10` | +| `Eval` | `Eval\EvalComponent.cs:9` | +| `Log` | `Log\LogComponent.cs:9` | +| `Status` | `Status\StatusComponent.cs:38` | +| `Wait` | `Wait\WaitComponent.cs:10` | +| `Comment` | `Comment\CommentComponent.cs:8` | +| `SmaAccountName` | `SmaAccountName\SmaAccountNameComponent.cs:10` | +| `DiscoverSshHostKey` | `DiscoverSshHostKey\DiscoverSshHostKeyComponent.cs:21` | +| `WriteDiscoveredAccount` | `WriteDiscoveredAccount\WriteDiscoveredAccountComponent.cs:19` | +| `WriteDiscoveredService` | `WriteDiscoveredService\WriteDiscoveredServiceComponent.cs:18` | +| `WriteDiscoveredSshKey` | `WriteDiscoveredSshKey\WriteDiscoveredSshKeyComponent.cs:14` | +| `WriteDiscoveredAsset` | `WriteDiscoveredAsset\WriteDiscoveredAssetComponent.cs:19` | +| `Condition` | `Condition\ConditionComponent.cs:10` | +| `Switch` | `Switch\SwitchComponent.cs:14` | +| `For` | `For\ForComponent.cs:10` | +| `ForEach` | `ForEach\ForEachComponent.cs:12` | +| `Try` | `Try\TryComponent.cs:13` | +| `Throw` | `Throw\ThrowComponent.cs:9` | +| `Function` (call) | `Function\FunctionComponent.cs:11` | +| `Return` / `Break` | `Return\ReturnComponent.cs:12-13` | +| `SetItem` / `Declare` | `SetItem\SetItemComponent.cs:8-9` | + +**Lesser-known verbs the engine supports** (rarely in samples, easy to miss): +`AwsFunction` (`AwsFunction\AwsComponent.cs:13`); `VmwareSdkConnect` / +`VmwareSdkDisconnect` / `VmwareSdkDiscoverAssets` +(`VmwareSdkFunction\VmwareSdkComponent.cs:21-23`); `ComparePasswordHash` / +`CompareShadowHash` / `CompareMacOsPasswordHash` / `CompareUnixPasswordHash` +(one component, `ComparePasswordHash\ComparePasswordHashComponent.cs:12-15`); +`CryptMd5` (`Encrypt\EncryptComponent.cs:11`); `UrlEncode` / `UrlDecode` +(`EncodeDecode\EncodeDecodeComponent.cs:11-12`). Useful aliases: +`Declare`==`SetItem`, `Break`==`Return`, `GetFormData`==`GetFormValue`, +`SetFormData`==`SetFormValue`. + +**Not executable `Do` verbs** even though they appear in `ComponentNames.cs`: +`Import`, `Functions`, `Meta` are **top-level definition keys**, and `Define` / +`NewObject` are structural — none register a component. Emitting them as a `Do` +step throws `No component found matching name …` +(`ModelOperationBlockConverter.cs:324`). + +## 3. Operations + +Public operation names are the `Rsms.Public.Definitions.OperationType` enum — +`src\Common\Common.Rsms\Rsms.Public\Definitions\OperationType.cs:3-30`. Full set +(enum order): + +`Unknown, CheckSystem, CheckPassword, ChangePassword, ChangeSshKey, +UpdateDependentSystem, DiscoverAccounts, DiscoverServices, DiscoverSshHostKey, +EnableAccount, DisableAccount, DiscoverAuthorizedKeys, CheckSshKey, CheckHostKey, +DiscoverAssets, RemoveAuthorizedKey, RetrieveSshHostKey, CheckApiKey, +ChangeApiKey, DiscoverApiKeys, CreateAdminUser, ElevateAccount, DemoteAccount, +CheckFile, ChangeFile` + +A script "supports" an operation iff its `Functions` contains a function whose +`Name` matches the `OperationType` name (case-insensitive: +`ScriptablePlatformDefinition.GetFunctionBlock` `Model\ScriptablePlatformDefinition.cs:42-45`; +gating `ScriptableOperationAttribute.IsSupportedForPlatformDefinition` +`Attributes\ScriptableOperationAttribute.cs:14-37`). There are **no runtime +feature flags** toggling operations — support is per-platform-definition. + +Operations the engine actually **implements** (`[ScriptableOperation]` methods in +`ScriptableModule.cs`): `CheckSystem:112`, `CheckPassword:181`, +`ChangePassword:227`, `CreateAdminUser:264`, `ChangeSshKey:303`, +`CheckApiKey:326`, `ChangeApiKey:349`, `UpdateDependentSystem:372`, +`DiscoverAccounts:520`, `DiscoverServices:558`, `DiscoverSshHostKey:602`, +`EnableAccount:634`, `DisableAccount:672`, `DemoteAccount:710`, +`ElevateAccount:737`, `DiscoverAuthorizedKeys:764`, `CheckSshKey:810`, +`DiscoverAssets:861`, `RemoveAuthorizedKey:924`, `CheckFile:971`, +`ChangeFile:1022`, `RetrieveSshHostKey:1080`. + +> **Compatibility gap:** `OperationType` defines `DiscoverApiKeys` and +> `CheckHostKey`, but neither has a `[ScriptableOperation]` method in +> `ScriptableModule.cs` on the analyzed tree — they are valid enum values that +> are **not executable** on this engine version. A script that defines a +> `DiscoverApiKeys` function will not be driven by the engine. Re-verify against +> the target appliance build before relying on either. + +## 4. Parameter types & reserved variables + +Two type enums exist — **do not conflate them**: + +| Enum | Values | Source | Used for | +| --- | --- | --- | --- | +| `Rsms.Public.Definitions.DataType` | `Null, Boolean, Integer, Float, String, Array, Object, Secret, Email` | `src\Common\Common.Rsms\Rsms.Public\Definitions\DataType.cs:3-14` | engine-internal typing; function-parameter and reserved-variable types | +| `CustomScriptParameterType` (`Pangaea.Data.Transfer.V2.PlatformTasks`) | `String=0, Integer=1, Boolean=2, Secret=3, Array=4, Object=5, Float=6` | `src\Data\Transfer\V2\PlatformTasks\CustomScriptParameterType.cs:6-42` | the `Type` an author sets on a custom platform parameter | + +`CustomScriptParameterType` has **no `Email`/`Null`**; map between the two by +name, not by numeric value. + +**Reserved/built-in variables** the engine injects are the +`Rsms.Public.Definitions.ReservedParameterName` enum (105 names) — +`src\Common\Common.Rsms\Rsms.Public\Definitions\ReservedParameterDefinitions.cs:7-106`; +canonical `DataType`s in `ReservedParameterDefinitionObjects` `:126-225`. Matching +is **case-insensitive** (`IsReservedParameterName` uses `Enum.TryParse(…, true, …)` +`:230-231`). A custom platform **cannot redefine a reserved name with a different +type** — enforced in +`src\Platform\Platform.Rsms\Extensions\PlatformExtensions.cs:85-87`. + +High-use reserved variables: + +| Variable | Type | Notes | +| --- | --- | --- | +| `Address` | String | `ReservedParameterDefinitions.cs:133` | +| `Port` / `SshPort` | Integer | `:172` / `:202` | +| `AssetName` | String | `:139`; defaults to `"Custom Asset"` when absent (`ScriptableModule.cs:38, 83-87`) | +| `AccountUserName` / `AccountId` / `AccountDn` / `AccountNamespace` | String | `:131` / `:129` / `:128` / `:132` | +| `AccountPassword` | **Secret** | `:130` | +| `NewPassword` | **Secret** | `:164` | +| `UseSsl` / `SkipServerCertValidation` | Boolean | — | +| `Timeout` / `Interval` | Integer | — | +| `DependentUsername` / `DependentPassword`(Secret) / `DependentCommand` / `CommandArguments` / `StdinArguments`(Array) | mixed | dependent-system flow | +| `HttpProxyUri` / `HttpProxyPort` / `HttpProxyUserName` / `HttpProxyPassword`(Secret) | mixed | HTTP proxy flow | + +## 5. Task-log & status contracts + +These back [`task-log-analysis`](../../.agents/skills/task-log-analysis/SKILL.md) +and the `phases[2]/phases[3]` payloads documented in +[`tools/README.md`](../../tools/README.md). + +- **`TaskLog`** — `src\Data\Transfer\V2\PlatformTasks\TaskLog.cs:7`. Fields (all + `[ReadOnly(true)]`): `DateTimeOffset Timestamp` `:13`, `TaskStatus Status` + `:25`, `string Message` `:31`. +- **`TaskStatus`** enum (25 values, 0–24) — + `src\Data\Transfer\V2\PlatformTasks\TaskStatus.cs:3`: + `Unknown=0, Success=1, Failure=2, Connecting=3, Checking=4, Changing=5, + PasswordMismatch=6, SshHostKeyMismatch=7, ServiceChangePasswordSuccess=8, + ServiceChangePasswordFailure=9, ServiceRestartSuccess=10, + ServiceRestartFailure=11, TaskChangePasswordSuccess=12, + TaskChangePasswordFailure=13, Running=14, Queued=15, Finalizing=16, Saving=17, + SshKeyMismatch=18, Discovering=19, Submitted=20, Cancelled=21, + ApiKeyMismatch=22, Skipped=23, FileMismatch=24`. +- **Log-name constants** — `Hercules.Common.Constants.Logging`, + `src\Common\Common.Rsms\Rsms.Public\Constants\Logging.cs`: `Operation = + "Operation"` `:14`, `SshCommunication = "SshCommunication"` `:15` (plus derived + `Operation.log` `:21`, `SshCommunication.log` `:23`, `PlatformTasks` `:32`, + `Failure.log` `:34`, default log path `:12`; Tn3270/Telnet/Odbc/Open3270 names + `:16-19`). These two names are the sections `Get-SafeguardTaskLog` emits. +- **Secret redaction sentinel** — + `Hercules.Common.Constants.ParameterConstants.Secret = "**secret**"`, + `src\Platform\Platform.Rsms\Constants\ParameterConstants.cs:5`. SPP replaces + known secret parameter values with this literal before returning a log. Do not + attempt to recover real values from it. Authors introducing new secret + parameters should declare them `Type: "Secret"` so the same redaction applies. + +## 6. Script validation (where rejection happens) + +The appliance Core API surface this repo hits: +`src\Service\Core\Controllers\V3\Partitions\PlatformsController.cs` +— `POST .../ValidateScript` (base64 body) `:235-264` and +`POST .../ValidateScript/Raw` (octet-stream/file body) `:271-294`. Both require +`AssetAdmin`, are primary-only, and return a `V3.Partitions.Platform`. They call +`PlatformLogic.ValidatePlatformScriptAsync` +(`src\Data\Middleware\Core\V2\System\PlatformLogic.cs:823, 895`) → +`HerculesClient.ValidateScriptAsync` +(`src\Platform\Platform.Rsms.Client\HerculesClient.cs:273`). + +Engine-side validation (real rejection): +`PlatformsController.CreateOrUpdateCustomPlatformDefinition(validateOnly:true)` +(`src\Service\Rsms\Controllers\v1\PlatformsController.cs:230-265`) → +`PlatformDefinitionFileSource.ValidateCustomPlatformDefinition` +(`src\Service\Rsms\Modules\Modules\Registry\PlatformDefinitionFileSource.cs:312`) +→ `ScriptableModuleCustomPlatformDefinitionValidator` +(`…\Scriptable\Validation\ScriptableModuleCustomPlatformDefinitionValidator.cs:11`) +and the static analyzer `ScriptableModuleStaticAnalyzer.Analyze` +(`…\Scriptable\Validation\ScriptableModuleStaticAnalyzer.cs:16`). + +**Hard rejections (errors) the local JSON Schema does not mirror:** + +| Engine rejects | Where | Schema mirrors it? | +| --- | --- | --- | +| Malformed JSON / wrong content → `E10017_PlatformRequestBadContent` | `Controllers\v1\PlatformsController.cs:245,252` | partial | +| **Duplicate function names** (across file + imports) → `DuplicateFunctionException` | `ScriptableModuleCustomPlatformDefinitionValidator.cs:24-30` | no | +| **Unknown `Do` verb** → `No component found matching name {name}` | `ModelOperationBlockConverter.cs:324` | no (schema is permissive on verb set) | +| **Reserved-parameter type/shape violations** | `ScriptableModuleStaticAnalyzer.cs:27-30` (+ `PlatformExtensions.cs:85-87`) | no | +| **Assignment to an undefined key** → `Key "{x}" was not found, the following keys exist: […]` | `ControlFlowExtensions.cs:141` | no | + +**Warnings (do NOT reject):** uncalled functions +(`ScriptableModuleStaticAnalyzer.cs:37-42`); `GLOBAL` prefix usage +(`ControlFlowExtensions.cs:152`); use of an unpassed global-scope variable +(`:200-201`); possible infinite function recursion (`:345-346`); top-level +`Return` not returning a boolean (`:611`). + +> **This section is the highest-yield place to look for "schema accepts / engine +> rejects" compatibility bugs.** The engine enforces unique function names, +> exact-case verb names, single-property-per-`Do`-step shape, and reserved-name +> typing — none of which a permissive JSON Schema necessarily catches. When a +> script passes `-SchemaOnly` but fails `Test-SafeguardCustomPlatformScript`, +> classify the appliance error against this table first. + +## 7. Versioning & compatibility signals + +- **No engine semantic version or changelog.** `Rsms.csproj` sets only + `TargetFrameworks=net10.0-windows` (`src\Service\Rsms\Rsms.csproj:3`), no + ``. You cannot read a feature-introduction version off the assembly. +- The engine's HTTP API is **v1 only** + (`src\Service\Rsms\Controllers\v1\…`). +- **The practical compatibility oracle is the appliance build version**, not an + engine version. Rsms ships and versions with the appliance ISO (the + `version.json` written at bundle time). To decide "does build *X* support + construct *Y*," map *Y* to the presence of (a) a `ComponentNames` constant + + a registered `[ComponentName]` component (verbs), or (b) a `[ScriptableOperation]` + method (operations) in the PangaeaAppliance tree at that build's ref. + +## Quick contract index + +Re-check these files first when verifying a compatibility claim. Paths are +PangaeaAppliance-relative. + +| Contract | File | +| --- | --- | +| Verb name strings | `src\Platform\Platform.Rsms\Constants\ComponentNames.cs` | +| Verb → impl dispatch | `[ComponentName]` on each `…\Scriptable\Components\*\*Component.cs` | +| Verb resolution rule (exact-case) | `…\Scriptable\Abstractions\ModelOperationBlockConverter.cs:317-328` | +| Operation names | `src\Common\Common.Rsms\Rsms.Public\Definitions\OperationType.cs` | +| Operation impls | `src\Service\Rsms\Modules\Modules\Scriptable\ScriptableModule.cs` | +| Engine type system | `src\Common\Common.Rsms\Rsms.Public\Definitions\DataType.cs` | +| Author param types | `src\Data\Transfer\V2\PlatformTasks\CustomScriptParameterType.cs` | +| Reserved variables | `src\Common\Common.Rsms\Rsms.Public\Definitions\ReservedParameterDefinitions.cs` | +| TaskLog / TaskStatus | `src\Data\Transfer\V2\PlatformTasks\TaskLog.cs`, `TaskStatus.cs` | +| Log names / secret sentinel | `src\Common\Common.Rsms\Rsms.Public\Constants\Logging.cs`, `src\Platform\Platform.Rsms\Constants\ParameterConstants.cs` | +| Validation entry | `…\Scriptable\Validation\ScriptableModuleStaticAnalyzer.cs`, `ScriptableModuleCustomPlatformDefinitionValidator.cs` | +| Public validate endpoints | `src\Service\Core\Controllers\V3\Partitions\PlatformsController.cs:235,271` | + +> **Snapshot ref:** analyzed against PangaeaAppliance commit `9be615a13d` +> ("TFS 709205 … net10 upgrade"). Confirm the current ref before relying on +> line numbers; symbol names are more stable than lines. diff --git a/tools/README.md b/tools/README.md index 466d677..062f28c 100644 --- a/tools/README.md +++ b/tools/README.md @@ -249,7 +249,10 @@ constants: * `SshCommunication` — raw SSH transport-level frames (when applicable) Both are defined in -`Hercules\Source\Rsms.Public\Constants\Logging.cs:14-15`. +`PangaeaAppliance\src\Common\Common.Rsms\Rsms.Public\Constants\Logging.cs:14-15` +(see [`docs/agent-reference/rsms-engine-map.md`](../docs/agent-reference/rsms-engine-map.md) +for the full cross-repo contract map — these constants moved out of the old +`Hercules\Source\Rsms.Public\…` path). Real entry shapes: @@ -269,8 +272,10 @@ Real entry shapes: **Secret handling.** SPP server-side redacts known credential parameters as the literal string `**secret**` before returning the log. The redaction constant is defined in -`Hercules\Source\Hercules.DevKit\Constants\ParameterConstants.cs:5` -(`public const string Secret = "**secret**"`). +`PangaeaAppliance\src\Platform\Platform.Rsms\Constants\ParameterConstants.cs:5` +(`public const string Secret = "**secret**"`; moved out of the old +`Hercules\Source\Hercules.DevKit\…` path — see +[`docs/agent-reference/rsms-engine-map.md`](../docs/agent-reference/rsms-engine-map.md)). Agents should NOT attempt to recover real values from these markers. Custom-script authors who introduce new secret parameters should declare them with `Type: "Secret"` so SPP applies the same redaction. From abaf57cdc5dd721df1012f33a58d8614f41365e4 Mon Sep 17 00:00:00 2001 From: Michael Thompson Date: Mon, 22 Jun 2026 11:25:47 -0600 Subject: [PATCH 2/3] Pin verifiable PangaeaAppliance snapshot ref in Rsms engine map Record snapshot commit fb5ae4e0fe1bc438c8035dbe9b7086b77b40115b as the verifiable whole-tree ref (9be615a13d was only the last commit touching the Scriptable engine dir), and note that the log-name constants in Common.Rsms\\Rsms.Public\\Constants\\Logging.cs are the single canonical definition. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- docs/agent-reference/rsms-engine-map.md | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/docs/agent-reference/rsms-engine-map.md b/docs/agent-reference/rsms-engine-map.md index 5e66005..46b7003 100644 --- a/docs/agent-reference/rsms-engine-map.md +++ b/docs/agent-reference/rsms-engine-map.md @@ -239,7 +239,10 @@ and the `phases[2]/phases[3]` payloads documented in SshKeyMismatch=18, Discovering=19, Submitted=20, Cancelled=21, ApiKeyMismatch=22, Skipped=23, FileMismatch=24`. - **Log-name constants** — `Hercules.Common.Constants.Logging`, - `src\Common\Common.Rsms\Rsms.Public\Constants\Logging.cs`: `Operation = + `src\Common\Common.Rsms\Rsms.Public\Constants\Logging.cs` (the **single + canonical definition** — other `Logging.cs` / `class Logging` files in + PangaeaAppliance, e.g. `src\Service\Rsts\HttpService\Logging.cs`, are unrelated + and do not define these members): `Operation = "Operation"` `:14`, `SshCommunication = "SshCommunication"` `:15` (plus derived `Operation.log` `:21`, `SshCommunication.log` `:23`, `PlatformTasks` `:32`, `Failure.log` `:34`, default log path `:12`; Tn3270/Telnet/Odbc/Open3270 names @@ -330,6 +333,10 @@ PangaeaAppliance-relative. | Validation entry | `…\Scriptable\Validation\ScriptableModuleStaticAnalyzer.cs`, `ScriptableModuleCustomPlatformDefinitionValidator.cs` | | Public validate endpoints | `src\Service\Core\Controllers\V3\Partitions\PlatformsController.cs:235,271` | -> **Snapshot ref:** analyzed against PangaeaAppliance commit `9be615a13d` +> **Snapshot ref:** analyzed against PangaeaAppliance commit +> `fb5ae4e0fe1bc438c8035dbe9b7086b77b40115b` (branch +> `features/f_696160_ISO_InitialSetupWebsite`; tip dated 2026-06-22). The last +> commit touching the Scriptable engine dir +> (`src\Service\Rsms\Modules\Modules\Scriptable`) specifically was `9be615a13d` > ("TFS 709205 … net10 upgrade"). Confirm the current ref before relying on > line numbers; symbol names are more stable than lines. From 506a0def2427942f37237cc9ccd211f4b4b82f6e Mon Sep 17 00:00:00 2001 From: Michael Thompson Date: Mon, 22 Jun 2026 11:31:30 -0600 Subject: [PATCH 3/3] Fix drifted PasswordHash citation in failure-patterns catalog The shadow-entry hash-check logic moved with the Hercules->src\ relocation: Source\Hercules.WebService\Common\Crypt\PasswordHash.cs is now src\Service\Rsms\Common\Crypt\PasswordHash.cs:96 (CheckPasswordAgainstShadowEntry, splits the shadow line on ':' at :98). Update the failure-patterns row and add the helper location to the engine map. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- docs/agent-reference/failure-patterns.md | 2 +- docs/agent-reference/rsms-engine-map.md | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/docs/agent-reference/failure-patterns.md b/docs/agent-reference/failure-patterns.md index 84a18c4..fa6f6b2 100644 --- a/docs/agent-reference/failure-patterns.md +++ b/docs/agent-reference/failure-patterns.md @@ -49,7 +49,7 @@ Each row is grounded in a real cmdlet response captured during authoring or onbo | --- | --- | --- | --- | | `An error was thrown in the try block: "An interactive session is required"` (immediately after first `Send` on a successfully-opened SSH connection) | operation | The `Connect` command opened the SSH session without requesting a PTY, but the very first `Send` (or any subsequent `sudo` invocation against a target with `Defaults use_pty` in sudoers) requires an interactive shell. The Ubuntu 24.04 default sudoers sets `Defaults use_pty`, so any `sudo` will fail with this signature even if the initial bash session would have tolerated `Send` without a PTY. | Add `"RequestTerminal": "%RequestTerminal%"` (or `"RequestTerminal": true`) to every `Connect` command in the script, and declare a `RequestTerminal` parameter (`Type: Boolean`, `DefaultValue: true`) on every operation that opens a connection. The PTY allocation is a per-Connect setting, not a per-platform one. | | `Sorry, try again.\r\nsudo: no password was provided\r\nsudo: 2 incorrect password attempts` (in a `Receive` buffer after a `(printf ... ) \| sudo -S ...` pipeline; the `Send` completed cleanly and `Connect` succeeded) | operation | Piping a password through a bash one-liner into `sudo -S` is brittle inside a PTY-allocated shell: the parent shell may strip or echo the password line in ways that defeat sudo's stdin read, and the only diagnostic ever printed is a generic "Sorry, try again". The pattern is also fragile to passwords containing shell metacharacters. The proven SSH password-rotation pattern in the repo never pipes the password — it sends `sudo passwd ` as a normal command, then walks through sudo's password prompt, the new-password prompt, and the retype-new-password prompt **as separate `Send`/`Receive` pairs**, with the password buffers marked `"ContainsSecret": true`. | Replace any `printf ... \| sudo -S ...` construct with the prompt-driven pattern from [`samples/ssh/generic-linux/GenericLinux.json`](../../samples/ssh/generic-linux/GenericLinux.json) lines 281–340 (`ChangeUserPassword`): `Send "sudo passwd ; echo CHGPASS=$?\n"`, then `Receive` the sudo prompt, `Send` `%FuncPassword%` (`ContainsSecret: true`, no surrounding quotes, no trailing `\n` — the appliance terminates secret sends itself), `Receive` `[Nn]ew.*[Pp]assword:`, `Send` `%NewPassword%`, `Receive` retype/new prompt, `Send` `%NewPassword%` again, `Receive` `CHGPASS=[0-9]+`. Capture the final buffer with `WriteResponseObject` so the task log shows the success/failure marker on the very next iteration. | -| `Error in component 'SetItem': invalid expression: Ambiguous match found for: 'Split'` (raised by Z.Expressions when evaluating any `%{ ... .Split(':') ... }%` interpolation) | parse | The script-engine's expression evaluator (Z.Expressions) is bound against a modern .NET base class library where `string.Split(...)` has multiple overloads (`Split(char)`, `Split(char[])`, `Split(string)`, etc.). When the operand is a single-character literal like `':'`, the evaluator cannot pick between `Split(char)` and `Split(char[])` and throws `Ambiguous match found for: 'Split'`. The result is that **any** ssh-batch script using `someString.Split(':')[N]` to slice a colon-delimited shadow line, `passwd` entry, or fstab field will fail at the first `SetItem` that interpolates it — even though the same expression is legal C# in isolation. | **First ask whether you need to split at all.** If the value is going into `CompareShadowHash.SaltedHash`, do **not** pre-split — the component handler splits the shadow line on `:` internally (verified in Hercules `Source/Hercules.WebService/Common/Crypt/PasswordHash.cs` `CheckPasswordAgainstShadowEntry`, and demonstrated by [`samples/ssh/generic-linux/GenericLinux.json`](../../samples/ssh/generic-linux/GenericLinux.json) line 236 which passes `%AccountEntry%` whole). When a split is genuinely required, replace `.Split('')[]` with a `Regex.Match`-based extraction: `%{ Regex.Match(, "^[^:]*:([^:]*):").Groups[1].Value }%` for the second colon-delimited field, parameterizing the pattern for the field index needed. `Regex.Match` has a single overload at the appliance and is unambiguous. The same applies to any other ambiguous `string` method (`Replace`, `IndexOf`) when called with a single-character literal. | +| `Error in component 'SetItem': invalid expression: Ambiguous match found for: 'Split'` (raised by Z.Expressions when evaluating any `%{ ... .Split(':') ... }%` interpolation) | parse | The script-engine's expression evaluator (Z.Expressions) is bound against a modern .NET base class library where `string.Split(...)` has multiple overloads (`Split(char)`, `Split(char[])`, `Split(string)`, etc.). When the operand is a single-character literal like `':'`, the evaluator cannot pick between `Split(char)` and `Split(char[])` and throws `Ambiguous match found for: 'Split'`. The result is that **any** ssh-batch script using `someString.Split(':')[N]` to slice a colon-delimited shadow line, `passwd` entry, or fstab field will fail at the first `SetItem` that interpolates it — even though the same expression is legal C# in isolation. | **First ask whether you need to split at all.** If the value is going into `CompareShadowHash.SaltedHash`, do **not** pre-split — the component handler splits the shadow line on `:` internally (verified in `PangaeaAppliance\src\Service\Rsms\Common\Crypt\PasswordHash.cs:96` `CheckPasswordAgainstShadowEntry`, which splits the `/etc/shadow` line itself at `:98`; the `CompareShadowHash` component delegates to it and passes the whole line — formerly at the old-layout path `Source\Hercules.WebService\Common\Crypt\PasswordHash.cs`, see [`docs/agent-reference/rsms-engine-map.md`](rsms-engine-map.md)), and demonstrated by [`samples/ssh/generic-linux/GenericLinux.json`](../../samples/ssh/generic-linux/GenericLinux.json) line 236 which passes `%AccountEntry%` whole). When a split is genuinely required, replace `.Split('')[]` with a `Regex.Match`-based extraction: `%{ Regex.Match(, "^[^:]*:([^:]*):").Groups[1].Value }%` for the second colon-delimited field, parameterizing the pattern for the field index needed. `Regex.Match` has a single overload at the appliance and is unambiguous. The same applies to any other ambiguous `string` method (`Replace`, `IndexOf`) when called with a single-character literal. | | Operation returns a clean verdict (e.g., `CheckResult: false`, `PasswordMismatch`) against a yescrypt-format `/etc/shadow` entry (`$y$j9T$...`) **after** a `Try`/`Catch` fired earlier in the operation. The catch's exception text — typically a Z.Expressions `Ambiguous match found for: 'Split'` from a `SetItem` that pre-extracted a hash field — is logged but the verdict surfaces unannotated. | operation (misdiagnosed) | The verdict is the **catch's fallback value, not the target's answer**. `CompareShadowHash` supports yescrypt (`$y$` prefix) via the `Yescrypt.IsYescrypt`/`Yescrypt.CheckPassword` branch in `PasswordHash.CheckPasswordAgainstHash`; the upstream `LinuxSshFunctions.json` import uses it on yescrypt-default Ubuntu/Debian systems. The actual root cause is a script-side bug in the path that runs **before** `CompareShadowHash` — most often pre-splitting the shadow line in a `SetItem` expression that hits the Split-overload-ambiguity row above, then a `Try`/`Catch` falls back to `auth-by-login` or returns a sentinel mismatch. Without reading the caught exception, the verdict looks like a target-side hash incompatibility. | Read the **caught exception text** from the operation log before drawing target-side conclusions — `script-authoring`'s "Catch blocks must log before falling back" rule applies. Then fix the real bug: pass `%AccountEntry%` (the whole shadow line, captured via `getent shadow `) directly to `CompareShadowHash.SaltedHash`. Do not pre-split. Mirror [`samples/ssh/generic-linux/GenericLinux.json`](../../samples/ssh/generic-linux/GenericLinux.json) lines 220–245: capture, compare whole, condition on `PasswordHashMatched`, and only fall back to a login-as-test pattern in a `Catch` block (and only when `getent` is genuinely unavailable, e.g., locked-down sudo). yescrypt is not the problem. | | `HTTP Request threw an exception ... AuthenticationException: The remote certificate is invalid according to the validation procedure: RemoteCertificateNameMismatch, RemoteCertificateChainErrors` (on the very first `Request` component log line `Sending request [...] [Skip SSL Validation=False]`; subsequent `Block returned error state` followed by the operation's `Catch` returning a sentinel like `false`, which SPP surfaces as `PasswordMismatch` even though the script never reached the auth step) | connect | The `Request` component does not skip server certificate validation by default — its log emits `[Skip SSL Validation=False]` when the flag is unset. Targets with self-signed or hostname-mismatched certificates fail the TLS handshake before any HTTP traffic. If the operation's outer `Try` catches all exceptions and returns `false`, SPP sees a clean `CheckPassword` verdict of "mismatch" with no auth attempt — the cert error is buried mid-log. | Declare the **reserved parameter** `SkipServerCertValidation` (`Type: Boolean`, `DefaultValue: false`) on every operation and on any function that issues a `Request`, then set `"IgnoreServerCertAuthentication": "%{SkipServerCertValidation}%"` on every `Request` block — including token-refresh/login calls inside helper functions. SPP auto-sources the parameter from the asset's `VerifySslCertificate` flag (asset-level toggle via `Edit-SafeguardAsset -VerifyServerSslCertificate $false` for self-signed labs), so no `-CustomScriptParameters` plumbing is needed at onboarding. See [`docs/reference/reserved-parameters.md`](../reference/reserved-parameters.md) line 128 and [`docs/guides/http-platforms.md`](../guides/http-platforms.md) lines 638–651. **Do not invent a custom parameter name** — reserved names get the auto-population for free; custom names require the operator to remember `-CustomScriptParameters` on every asset. Also audit `Catch` blocks: a `Catch` that swallows all exceptions and returns a domain sentinel (`false` for `CheckPassword`, `true` for `CheckSystem`) hides connect-phase failures as operation-phase verdicts — the `script-authoring` "Catch blocks must log before falling back" rule applies. | | `Response status: BadRequest` against a target that accepts the **identical** body when sent manually with `Invoke-RestMethod` or `curl`; the log shows `Sending request [...] [Skip SSL Validation=True]` followed by a 400 response from an endpoint that expects form fields containing `@` or `%`. The `Request` block uses `Content: { "Value": "%PreBuiltBody%", "ContentType": "application/x-www-form-urlencoded" }` where `%PreBuiltBody%` is a string built by `SetItem` with `UrlEncode`-ed fields concatenated by hand. | parse | `Request.Content.Value` is **not a documented field** ([`docs/reference/commands/request.md`](../reference/commands/request.md) lines 51–55 only define `Content.ContentObjectName` and `Content.ContentType`; line 158 explicitly states "`ContentType` is only applied when `ContentObjectName` is present"). When a script supplies `Content.Value` with form ContentType, the engine's behavior is undefined — observed cases include re-encoding `%40` to `%2540` (double-encoding) and silently dropping the body. The target then sees corrupted or absent form fields and returns 400 "Parameter verification failed" or similar — fooling diagnosis into thinking the credential is wrong. | Build a form object with one `SetFormValue` per field (the form is created on first use; default `CreateForm: "CreateIfNotFound"` is correct), then reference it from `Request.Content.ContentObjectName`. The engine URL-encodes each field exactly once. Mirror [`samples/http/twitter/CustomTwitter.json`](../../samples/http/twitter/CustomTwitter.json) lines 133–148 (CheckPassword) and 225–242 (ChangePassword). Do **not** pre-`UrlEncode` field values when feeding them through `SetFormValue` — the component encodes during serialization. Reserve `UrlEncode` + `SetItem` for URL **path** components substituted into `Request.Url` (where `Request.Content` is not involved). | diff --git a/docs/agent-reference/rsms-engine-map.md b/docs/agent-reference/rsms-engine-map.md index 46b7003..6c952aa 100644 --- a/docs/agent-reference/rsms-engine-map.md +++ b/docs/agent-reference/rsms-engine-map.md @@ -135,7 +135,11 @@ All component files are under `VmwareSdkDisconnect` / `VmwareSdkDiscoverAssets` (`VmwareSdkFunction\VmwareSdkComponent.cs:21-23`); `ComparePasswordHash` / `CompareShadowHash` / `CompareMacOsPasswordHash` / `CompareUnixPasswordHash` -(one component, `ComparePasswordHash\ComparePasswordHashComponent.cs:12-15`); +(one component, `ComparePasswordHash\ComparePasswordHashComponent.cs:12-15`; for +`CompareShadowHash` it delegates to the `IPasswordHash` helper +`src\Service\Rsms\Common\Crypt\PasswordHash.cs:96` `CheckPasswordAgainstShadowEntry`, +which splits the `/etc/shadow` line on `:` itself at `:98` — pass the whole line, +do not pre-split); `CryptMd5` (`Encrypt\EncryptComponent.cs:11`); `UrlEncode` / `UrlDecode` (`EncodeDecode\EncodeDecodeComponent.cs:11-12`). Useful aliases: `Declare`==`SetItem`, `Break`==`Return`, `GetFormData`==`GetFormValue`,